Java Default Access Modifier: Package-Private Explained
java default access modifier: Learn how Java's default access modifier (package-private) controls visibility within packages, when to use it, and how it differs from p...
java default access modifier requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
Java's default access modifier applies when you declare a class member without public, private, or protected. The member receives package-private visibility: it is accessible from any class in the same package and invisible to classes in other packages.
package com.example.warehouse; public class StockItem { int quantity; // package-private field void adjustQuantity(int delta) { // package-private method quantity += delta; } }
The quantity field and adjustQuantity method are usable by other classes in com.example.warehouse, but a class in com.example.shipping cannot reference them directly.
Visibility Rules at a Glance
Package-private sits between private and protected in the access hierarchy. The exact visibility depends on where the accessing code lives.
| Access level | Same class | Same package | Subclass, other package | Any class |
|---|---|---|---|---|
private | Yes | No | No | No |
| default | Yes | Yes | No | No |
protected | Yes | Yes | Yes | No |
public | Yes | Yes | Yes | Yes |
The critical difference from protected is the third column. A subclass in a different package can access a protected member but cannot access a package-private member. This distinction matters when you design class hierarchies that span packages.
Where Package-Private Access Is Useful
Package-private members are a natural fit for internal collaboration within a package. When several classes in the same package cooperate closely, they often need to share state or helper methods that external callers should not see.
package com.example.warehouse; class InventoryCalculator { int computeAvailable(StockItem item) { return item.quantity - item.reserved; } }
Here InventoryCalculator reads the package-private quantity and reserved fields directly. The fields stay hidden from code outside the package, so the warehouse package can change its internal representation without breaking external consumers.
This pattern appears throughout the Java standard library. Many utility classes expose package-private methods that other classes in the same package rely on, while keeping the public API surface small.
Comparing Default Access with Private and Protected
Choosing between private, default, and protected depends on how much you want to expose.
private is the right choice when a member is used only by the declaring class itself. It gives you the strongest encapsulation and the most freedom to refactor.
Default access is appropriate when a member is shared among classes in the same package but should not leak outside. This is common for:
- helper methods used by multiple classes in a package
- internal state that collaborating classes read or update
- constructors that should only be invoked from within the package
protected is for members that subclasses in other packages need to access. If you are designing a framework where users extend your classes from their own packages, protected is often necessary. If you are not supporting external subclassing, default access keeps the API tighter.
A useful rule of thumb: start with private, widen to default when a package collaborator needs access, and reach for protected only when cross-package subclassing requires it.
Common Misconceptions
One recurring misconception is that "default" means "no restriction." It does not. A package-private member is still restricted, just to package scope. Code in another package cannot see it, even if that code is in a subclass.
Another confusion involves the default value of fields. A field declared without an initializer, such as int count;, receives the default value 0. That behavior is unrelated to the access level. The word "default" appears in both contexts, but they describe different mechanisms.
A third mistake is assuming that package-private members are visible to subclasses in the same package. They are, but that visibility comes from being in the same package, not from the subclass relationship. A subclass in the same package sees the member because of package membership.
Maintainability and Refactoring Impact
Package-private members create implicit coupling within a package. When you rename or change a package-private member, every class in the package that references it must be updated. The compiler enforces this, so the breakage is visible at build time, but the blast radius is still the entire package.
Moving a class to a different package can silently break access to package-private members. The compiler will report the errors, but the fix may require widening access to protected or public, which changes the API surface. Before moving a class, check which package-private members it relies on and whether the target package can access them.
Package-private access also affects testing. Test classes in the same package can access package-private members directly, which is convenient for unit tests that need to inspect internal state. Tests in a different package cannot, which forces you to either widen access or test through the public API. Many teams keep test classes in the same package specifically to leverage this.
Package Design and Encapsulation Boundaries
The default access modifier is the primary tool for package-level encapsulation. A well-designed package exposes a small public API and hides the rest. Package-private members let collaborating classes share implementation details without making them part of the public contract.
package com.example.warehouse; public class Warehouse { private final List<StockItem> items = new ArrayList<>(); void registerItem(StockItem item) { // package-private items.add(item); } }
The registerItem method is callable from other classes in the warehouse package, such as an importer or a validator, but it is not part of the public API. External code must use the public methods of Warehouse instead.
This boundary is especially valuable in libraries. Public API is a long-term commitment; package-private members are not. You can change, remove, or rename them in any release without breaking downstream users. Keeping internal helpers package-private rather than public reduces the maintenance burden of your library.
When you design a package, decide which classes are part of the public contract and which are internal collaborators. Mark the public contract with public and the internal collaborators with default access. This makes the package's boundaries explicit and keeps the API surface minimal.