Java packages do more than organize classes. They also play an important role in access control. When classes are placed in different packages, Java's access modifiers determine which classes and members can be accessed across those package boundaries.
This is one of the places where packages become especially powerful. They allow developers to define which parts of an application are publicly available, which parts are protected for inheritance, and which implementation details should remain accessible only within the same package.
Why Does Package Access Matter?
Imagine an application containing dozens of classes. Some classes are intended to be used throughout the application, while others are implementation details that should only be used by closely related classes.
Without access control, every class could potentially depend on every implementation detail. Over time, this creates tightly coupled code that becomes harder to change.
Java's access modifiers help establish clear boundaries between classes and packages.
Java Access Modifiers
| Modifier | Same Class | Same Package | Different Package | Subclass in Different Package |
|---|---|---|---|---|
| private | Yes | No | No | No |
| default | Yes | Yes | No | No |
| protected | Yes | Yes | Limited access through inheritance | Yes, under protected rules |
| public | Yes | Yes | Yes | Yes |
The most important package-specific rule is the behavior of the default, or package-private, access level. When no access modifier is written, the member or top-level type is accessible within the same package.
Default Access
If no access modifier is specified, Java gives the member package-private access.
package com.example.school;
class Student {
String name;
void display() {
System.out.println(name);
}
}
Here, both the Student class and its members use default access. Other classes in the same package can access them, but classes in unrelated packages cannot directly access them.
Access Within the Same Package
Consider two classes in the same package.
package com.example.school;
class Student {
String name = "Rahul";
}
Another class in the same package can access the package-private field:
package com.example.school;
public class SchoolApp {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name);
}
}
This works because both classes belong to com.example.school.
Remember: Package-private access is based on package membership, not on whether one class is physically located next to another file.
Access from a Different Package
Now place the application class in a different package.
package com.example.app;
public class SchoolApp {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name);
}
}
If Student and name have package-private access in com.example.school, this code will not compile because SchoolApp belongs to a different package.
Public Access Across Packages
A public class can be accessed from another package when the required class is available through the appropriate classpath or module configuration.
package com.example.school;
public class Student {
public String name = "Rahul";
public void display() {
System.out.println(name);
}
}
A class in another package can then use it:
package com.example.app;
import com.example.school.Student;
public class Main {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name);
student.display();
}
}
Both the class and the members being accessed are public, so the package boundary does not prevent access.
Private Access and Packages
The private modifier is stricter than package-private access. A private member can be accessed only within the class that declares it.
package com.example.school;
public class Student {
private String name = "Rahul";
public void display() {
System.out.println(name);
}
}
Even another class in the same package cannot directly access name.
package com.example.school;
public class SchoolApp {
public static void main(String[] args) {
Student student = new Student();
// Compilation error
System.out.println(student.name);
}
}
The package does not override the private access rule. Private members remain restricted to their declaring class.
Protected Access and Packages
The protected modifier has special behavior involving both packages and inheritance.
A protected member is accessible to classes in the same package. It can also be accessed by subclasses in different packages, subject to Java's protected-access rules.
package com.example.school;
public class Student {
protected String name = "Rahul";
}
A class in the same package can access the protected member directly:
package com.example.school;
public class SchoolApp {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name);
}
}
The situation becomes more restrictive when a subclass exists in a different package. The subclass can access the protected member through inheritance, but protected access is not equivalent to unrestricted public access.
Protected Access Across Packages
Consider a parent class:
package com.example.school;
public class Student {
protected String name = "Rahul";
}
A subclass in another package can access the protected member through the inherited context.
package com.example.app;
import com.example.school.Student;
public class GraduateStudent extends Student {
public void display() {
System.out.println(name);
}
}
However, code in the different package should not treat a protected member as if it were public and freely access it through arbitrary parent-class instances.
Important: Protected access across packages is primarily an inheritance feature. Remembering this prevents one of the most common misunderstandings about the protected modifier.
Top-Level Classes and Package Access
A top-level class can use either public or package-private access. It cannot be declared private or protected.
package com.example.school;
class Student {
}
This class is package-private and can be accessed only by code in the same package.
To make the class available from other packages, declare it public:
package com.example.school;
public class Student {
}
Package Access and Encapsulation
Package-level access can be useful for designing encapsulated components. A package can contain implementation classes that are intentionally hidden from code outside the package, while exposing only the public classes that form the package's external API.
For example, an application might expose a public service while keeping helper classes package-private.
package com.example.payment;
public class PaymentService {
public void processPayment() {
PaymentValidator validator = new PaymentValidator();
validator.validate();
}
}
class PaymentValidator {
void validate() {
System.out.println("Payment validated");
}
}
Here, PaymentValidator is package-private. Other packages can use PaymentService without directly depending on the validator implementation.
Real-World Design Insight
This pattern appears frequently in professional software. Developers often expose a small public API while keeping supporting classes package-private. This reduces the number of types that other parts of the system can depend on.
Fewer external dependencies generally mean fewer places that need to change when an internal implementation evolves.
Common Beginner Mistakes
- Assuming package-private members are accessible from every package after using an import statement.
- Thinking that protected always means accessible everywhere.
- Assuming private members become accessible simply because two classes belong to the same package.
- Forgetting that a top-level class cannot be declared private or protected.
- Making every class and member public without considering encapsulation.
Best Practices
- Use the most restrictive access level that satisfies the design.
- Keep implementation details package-private when they do not need to be exposed externally.
- Use private members to protect class-level implementation details.
- Expose public classes and methods intentionally as part of the application's usable API.
- Use protected access primarily when inheritance or same-package collaboration genuinely requires it.
Interview Insight
A common interview question is, "Can a class in one package access a default-access member of a class in another package?" The answer is no. Default, or package-private, access allows access only within the same package.
Interview Tip: If asked to compare package-private and protected access, remember the key difference: package-private works only within the same package, while protected also allows access through inheritance from a subclass in another package, subject to protected-access rules.
Quick Revision
| Modifier | Same Package | Different Package | Main Idea |
|---|---|---|---|
| private | No, except declaring class | No | Class-level encapsulation |
| default | Yes | No | Package-level access |
| protected | Yes | Yes, through permitted inheritance access | Package access plus inheritance |
| public | Yes | Yes | Broad access |
Package access is an important part of Java's encapsulation model. Packages define a natural boundary where closely related classes can collaborate without exposing every implementation detail to the rest of the application. Once this boundary is understood, choosing appropriate package names becomes the next important step in keeping large Java projects consistent and maintainable.
