Back to Blog
Java

Java Package-Private: Default Access in Practice

java package private: Learn how Java's package-private access works, when to use it, and how it differs from public, protected, and private.

Javaaccess modifiersencapsulationpackage-privatedefault access
Diagram showing Java package-private access boundaries between classes in the same package

java package private requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

In Java, every class, method, and field has an access level that controls which code can use it. The default access level, often called package-private, applies when you omit an explicit modifier. This article explains what package-private means, how it behaves, and where it fits in real Java code.

What Does Package-Private Mean in Java?

Package-private is the default access modifier in Java. When you declare a class, method, or field without public, protected, or private, it becomes accessible only to classes in the same package. This is not a separate keyword; it is the absence of a modifier. For example:

class PackagePrivateClass { int count; // package-private field void increment() { // package-private method count++; } }

Here, PackagePrivateClass and its members are accessible only from other classes in the same package. A class in a different package cannot reference them, even if that class is a subclass. This behavior is defined by the Java Language Specification and is consistent across all Java versions.

The package-private level sits between private and protected in terms of visibility. It is more restrictive than protected (which allows access to subclasses outside the package) but less restrictive than private (which restricts access to the enclosing class only).

How Package-Private Compares to Other Access Levels

Understanding the full access hierarchy helps you decide when to use package-private. The following table maps each modifier to its visibility scope:

ModifierSame classSame packageSubclass (different package)Any class
privateYesNoNoNo
package-privateYesYesNoNo
protectedYesYesYesNo
publicYesYesYesYes

Package-private is the only level that does not have a keyword. It is the default when no modifier is written. This often surprises developers coming from languages like C# where internal is explicit. In Java, forgetting to add public to a class you intended to expose will silently make it package-private, which can cause compilation errors in other packages.

Declaring Package-Private Members

You can apply package-private to top-level classes, nested classes, methods, and fields. The syntax is simply to omit the modifier. Here is a more complete example:

package com.example.util; class StringHelper { static String trimAndLower(String input) { return input.trim().toLowerCase(); } } public class TextProcessor { StringHelper helper = new StringHelper(); // package-private field public String process(String raw) { return StringHelper.trimAndLower(raw); } }

In this code, StringHelper is package-private, so it is only visible inside com.example.util. The helper field in TextProcessor is also package-private. A class in another package cannot create a StringHelper instance or access the helper field directly. This allows you to hide implementation details that are only relevant within a package.

One important detail: a package-private top-level class cannot be referenced from outside its package, even if it implements a public interface or extends a public class. The reference itself is inaccessible. This is a common source of confusion when refactoring code.

When Package-Private Is the Right Choice

Package-private is useful when you have a group of classes that cooperate closely within the same package but should not expose their internals to the rest of the application. Common scenarios include:

  • Internal helper classes: A package may contain a public facade class that delegates to several package-private support classes. Those helpers do not need to be part of the public API.
  • Package-level cohesion: When multiple classes in a package share a common data structure or utility, making those members package-private keeps the coupling explicit and contained.
  • Testing hooks: Unit tests placed in the same package can access package-private members, which allows you to test internal behavior without making it public.

For example, consider a package that implements a custom collection. The main public class might be OrderedList, but the internal node class and the comparison logic can be package-private. This keeps the public surface small and reduces the risk of misuse by external code.

Package-Private and Testing

A practical benefit of package-private is that test classes in the same package can access the members directly. This is often used to test internal logic without exposing it through public APIs. Suppose you have a package-private method that performs a complex calculation. A test class in the same package can call it directly:

package com.example.calc; public class CalculatorTest { @Test void testInternalCalculation() { Calculator calc = new Calculator(); int result = calc.internalAdd(2, 3); // package-private method assertEquals(5, result); } }

If internalAdd were private, this test would not compile. By keeping it package-private, you allow white-box testing while still hiding the method from external consumers. This is a common pattern in library development, where the public API remains minimal but internal correctness is verified.

However, this also means that any class in the same package can call the method, not just tests. If your package contains untrusted or unrelated classes, package-private does not provide strong isolation. It is a design choice, not a security boundary.

Package-Private in Java Modules

Since Java 9, the module system adds another layer of access control. A package that is not exported from a module is inaccessible to code outside the module, regardless of whether its classes are public or package-private. This means that package-private becomes even more restrictive when modules are used.

Within a module, package-private members are visible only to classes in the same package, as usual. But if the package itself is not exported, even public classes are inaccessible outside the module. This can simplify your design: you can make a class public within the module but keep it hidden from the outside world by not exporting the package. In that scenario, package-private is often redundant for external hiding, but it still enforces internal package boundaries.

When migrating an existing application to modules, you may need to decide which packages to export. Package-private members will not affect exports, but they do affect how classes within the module interact. This is worth reviewing during modularization to avoid accidentally exposing internals through exported packages.

Common Pitfalls and Maintenance Concerns

The main risk with package-private is accidental omission. If you forget to write public on a class that should be reachable from other packages, you get a compile-time error. The fix is straightforward, but the error message can be confusing if the class is referenced from a different package. The compiler reports that the class is not visible, which often leads developers to search for a missing import rather than a missing modifier.

Another pitfall is relying on package-private for cross-package access. If you later move a class to a different package, you must update all references or change the access level. This coupling is intentional but can be fragile. When you design a package, think about which members truly need to be shared within the package and which should be private to the class. Overusing package-private can create hidden dependencies that are hard to trace.

From a maintainability perspective, package-private is most effective when the package is cohesive and stable. If your package changes frequently, the internal access boundaries will need to be adjusted often. In contrast, private gives you the freedom to refactor a class without affecting the rest of the package. Use package-private only when you explicitly want to share state or behavior among a fixed set of classes.

One more consideration: package-private constructors can be used to enforce factory patterns within a package. If a class's constructor is package-private, only classes in the same package can instantiate it directly. This is useful for controlling object creation without exposing the constructor publicly, which is a common pattern in dependency injection frameworks and builder implementations.

java package private: Practical Usage and Code Examples | RYUSLOG DEV