Access Modifiers and Packages
public, protected, private, and the package-private one nobody knows the name of. What each does, when to use them, and how packages organise the chaos.
Four levels of visibility
Java has exactly four levels of access. You see three keywords (public, protected, private) plus one that has no keyword at all — the **package-private** or "default" access level. Most Java developers can name three of them. The fourth catches everyone occasionally.
| Modifier | Visible to | Common use |
|---|---|---|
| public | Everyone | API surfaces, constants intended for callers |
| protected | Same package + subclasses anywhere | Methods designed for subclass extension |
| (none) — "package-private" | Same package only | Internal helpers and types |
| private | Same class only | All instance fields, internal helper methods |
The omission is the tricky one. When you write:
class User {
String name;
void greet() { ... }
}
The class and members have **no modifier**, which means **package-private** — visible within the same package, invisible everywhere else. Many developers assume "no modifier" means public. It doesn't.
This chapter walks through each level, when to use it, and how packages fit in.
private
private restricts access to the same class. Anything marked private can be touched only from code inside the same .java file (actually the same top-level class — inner classes can still see private members of their enclosing class).
public class User {
private String password; // only User itself can read/write this
public boolean checkPassword(String input) {
return Objects.equals(password, input); // fine — same class
}
}
public class Hacker {
public void steal(User u) {
String stolen = u.password; // compile error — password is private
}
}
**Almost every instance field should be private.** Field access is the most common encapsulation leak. Making fields private and exposing only carefully designed methods is the single most important habit in writing maintainable Java.
private also applies to methods that are internal helpers:
public class OrderProcessor {
public void process(Order order) {
validate(order);
chargePayment(order);
ship(order);
}
private void validate(Order o) { ... } // internal step
private void chargePayment(Order o) { ... }
private void ship(Order o) { ... }
}
These three methods exist only to break the public process() into readable steps. They're not part of the class's public contract. Marking them private means:
- Callers can't accidentally depend on them.
- You can rename, reorder, merge, or split them freely without breaking anyone.
- Tests of OrderProcessor go through the public API, not through private methods.
private is the default you should start from. Loosen access only when you have a specific reason.
Package-private (no modifier)
When you write a member with no access modifier — neither public, protected, nor private — it's package-private. Visible to any code in the same package, invisible to anything outside.
// File: com/example/billing/Invoice.java
package com.example.billing;
class Invoice { // package-private class
String customerId; // package-private field
void issue() { ... } // package-private method
}
// File: com/example/billing/InvoiceService.java
package com.example.billing; // same package
public class InvoiceService {
public void process() {
Invoice inv = new Invoice(); // fine — same package
inv.issue(); // fine
}
}
// File: com/example/orders/OrderService.java
package com.example.orders; // DIFFERENT package
import com.example.billing.Invoice; // compile error — Invoice not visible
public class OrderService {
public void process() {
Invoice inv = new Invoice(); // not allowed
}
}
Package-private is **the underused gem of Java access control**. Most developers reach for public or private and forget the middle option exists. But package-private is perfect for:
- **Internal types** that several classes in the same package use, but should not leak outside the package.
- **Methods used between collaborating classes** in the same package.
- **Tests in the same package** can see package-private classes and methods — useful for testing internals without making them globally accessible.
Real-world example: a payment module might have a package com.example.payment containing PaymentService (public — the API surface), plus PaymentValidator, PaymentSerialiser, TransactionState, etc. (all package-private — internal collaborators). External code uses only PaymentService; the rest can change freely.
**A useful convention for tests:** put your unit tests in the same package as the class under test. That way the tests can poke at package-private internals when needed. The tests typically live in src/test/java/... mirroring the package structure of src/main/java/..., and the build tool merges them at compile time.
protected
protected is package-private plus visibility to subclasses, regardless of which package they live in.
// File: com/example/animals/Animal.java
package com.example.animals;
public class Animal {
protected String name; // visible to subclasses anywhere
protected void log(String msg) { // visible to subclasses anywhere
System.out.println("[" + name + "] " + msg);
}
}
// File: com/example/pets/Dog.java
package com.example.pets;
import com.example.animals.Animal;
public class Dog extends Animal {
public void bark() {
log("Woof!"); // fine — protected, and Dog extends Animal
}
}
// File: com/example/sneaky/Hacker.java
package com.example.sneaky;
public class Hacker {
public void steal(Animal a) {
a.log("breach"); // compile error — Hacker doesn't extend Animal
}
}
protected is the right access level for:
- **Hooks designed for subclass extension.** A template-method pattern where a base class provides the skeleton and subclasses fill in steps via protected methods.
- **State that subclasses legitimately need to see and possibly modify.**
The honest take: protected is used less often than people think. Most "I need to share this between this class and a subclass" cases are better handled by passing values explicitly through constructors, or by package-private access if the parent and child live in the same package.
A common anti-pattern: making every field protected "in case someone wants to subclass." Don't. That leaks implementation to all subclasses, prevents you from refactoring the field, and is rarely needed.
public
public makes a member visible to all code, anywhere. Use it for the *intentional* public API of a class — the methods and types you want callers to depend on.
public class UserService {
public User findById(long id) { ... }
public void save(User user) { ... }
public void delete(long id) { ... }
// Everything else — fields, helper methods — should be private or package-private
}
**The single most common access-modifier mistake: making everything public.** Every public method is a contract you've signed. Once callers depend on it, you can't change it without coordination. Public API is forever (or at least, until a major version bump).
**Practical guidance:**
- **Public** belongs on: the few methods that constitute the class's intended API, plus public constants (
public static final). - **Private** is the default for: every field, every helper method.
- **Package-private** for: types and methods used between collaborators in the same package.
- **Protected** for: deliberate extension points in a class hierarchy.
When in doubt, start with private. Tighten down beats loosening up — narrowing access later breaks callers; widening it doesn't break anything.
One thing many developers don't realise: **a Java source file can have at most one public top-level class**, and that class must be named the same as the file. Other top-level classes in the file must be package-private (no modifier).
// File: User.java
public class User { ... } // OK — public, matches filename
class UserHelper { ... } // OK — package-private (no modifier)
public class Address { ... } // compile error — second public class in same file
Packages — Java's namespacing system
A **package** is a namespace for related classes. The Java standard library has packages like java.util, java.io, java.net. Your own code lives in packages too — typically following the convention of reversed domain name plus your project structure:
com.electrominds.blog.models
com.electrominds.blog.services
com.electrominds.blog.controllers
The package declaration is the first non-comment line in a .java file:
package com.electrominds.blog.models;
public class User { ... }
The file User.java for this class must live in a directory matching the package — com/electrominds/blog/models/User.java relative to the source root. The build tool (Maven, Gradle) and the JVM rely on this convention.
**Why packages exist:**
- **Namespacing.** You can have com.example.User and com.acme.User without conflict.
- **Access control.** Package-private visibility lets you have internal types that don't leak.
- **Organisation.** Grouping related classes makes the codebase navigable.
**Importing.** To use a class from a different package, you import it:
package com.example.app;
import java.util.List;
import java.util.ArrayList;
import com.example.shared.User;
public class Main {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
}
}
java.lang.* is imported automatically — that's why you don't need to import String, Object, Math, System. Everything else requires an explicit import or a fully qualified name.
**Package naming conventions:**
- All lowercase. com.example.foo, not com.Example.Foo.
- Reversed domain name. com.electrominds.* for electrominds.com.
- Then nested by responsibility: com.electrominds.blog.payment.refund.*.
A common project layout in 2026:
src/main/java/com/yourorg/yourapp/
Application.java ← entrypoint
config/ ← configuration classes
controllers/ ← HTTP layer
services/ ← business logic
repositories/ ← data access
models/ or domain/ ← entities, records
util/ ← shared helpers
This is the Spring Boot convention. Other frameworks use slight variations. The point is to group code by responsibility, not by random file count.
Modules — Java 9's addition
Java 9 (2017) added the **module system** (Project Jigsaw) — a layer above packages for very large codebases. A module is a named, versionable group of packages with explicit "exports" (what's visible to other modules) and "requires" (what it depends on).
A module-info.java file at the root of your module declares:
module com.electrominds.blog {
requires java.base;
requires com.fasterxml.jackson.databind;
exports com.electrominds.blog.api;
exports com.electrominds.blog.models;
// com.electrominds.blog.internal.* is NOT exported — invisible to other modules
}
This makes encapsulation stricter than packages alone provide: an exported package's public types are visible outside; a non-exported package's public types are invisible to other modules, even though they're still public within the module.
**The honest truth about modules in 2026:** most Java applications don't use them. They're useful for very large codebases (the JDK itself was modularised), for security-sensitive code that needs strong boundaries, and for library authors who want to enforce visibility. Most application code just uses packages and lives in a classpath like Java pre-9.
If you're starting a new project, you can ignore modules entirely. If you join a project that uses them, the rules are predictable — read its module-info.java files to understand the boundaries.
For most readers, the package level of access control is the relevant one. Modules are a layer above that you'll touch only in specific contexts.
Practical rules of thumb
A short list of habits that pay off.
**Default to the most restrictive access.** Start every field as private. Tighten methods by default — public only when callers genuinely need them.
**Don't expose mutable internal state.**
public class User {
private final List<String> roles = new ArrayList<>();
public List<String> getRoles() {
return roles; // callers can mutate our internal list!
}
}
Either return an unmodifiable view, return a copy, or return only the data callers need:
public List<String> getRoles() {
return List.copyOf(roles); // immutable snapshot
}
**Avoid public fields.** Even public static final constants are mostly fine — primitive and immutable types are safe. But public static final List<String> is a trap because the LIST is final, not its contents. Use List.of(...) to get a truly immutable list.
**Use package-private boundaries.** Group collaborating classes into packages, mark internals package-private, and only the intended API stays public. This is the strongest encapsulation Java offers without going to modules.
**Tests in the same package can see package-private members.** Useful for testing internals without making them globally visible. Most build tools (Maven, Gradle) merge src/test/java and src/main/java so the test classes can sit alongside their targets.
**Be deliberate about protected.** It's a contract with every future subclass. Only use it for hooks you genuinely want subclasses to override or call.
**Read your import statements.** If a class needs imports from 10 different packages, it might be doing too much. Refactor toward more focused classes with fewer dependencies.
⁂ Back to all modules