Access modifiers in Java control where a class, constructor, method, or variable can be accessed. They are one of the main tools Java provides for protecting implementation details and defining clear boundaries between different parts of an application.
A useful way to think about access modifiers is to imagine an office building. Some rooms are open to everyone, some are available only to employees, some are restricted to a particular department, and some are private rooms accessible only from inside. Java provides similar visibility levels for program elements.
Why Do We Need Access Modifiers?
Consider a bank account. The account balance is important internal state. If every part of an application could freely change it, the object could easily become invalid.
class BankAccount {
public double balance;
}
With a public field, external code could do this:
BankAccount account = new BankAccount(); account.balance = -50000;
That may violate the business rules of the application. A better design restricts direct access and provides controlled operations.
class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
}
Now the class controls how its internal state changes. This is one of the practical foundations of encapsulation.
Remember: Access modifiers are not just about hiding code. They define the visibility boundary of your classes and members.
The Four Access Levels
Java provides four practical access levels: private, package-private, protected, and public.
| Modifier | Same Class | Same Package | Subclass in Other Package | Other Package |
|---|---|---|---|---|
| private | Yes | No | No | No |
| package-private | Yes | Yes | No | No |
| protected | Yes | Yes | Yes, through inheritance rules | No direct general access |
| public | Yes | Yes | Yes | Yes |
The exact behavior of protected across packages has an important inheritance-related rule, so it is worth studying separately rather than reducing it to simply “visible to subclasses.”
private Access Modifier
The private modifier provides the most restrictive member-level access. A private member can be accessed directly only from within the class that declares it.
class Employee {
private double salary;
private void calculateBonus() {
System.out.println("Calculating bonus");
}
void displaySalary() {
System.out.println(salary);
calculateBonus();
}
}
Inside the Employee class, both the private field and private method can be accessed normally.
Code outside the class cannot directly access them.
Employee employee = new Employee(); employee.salary = 50000;
The code above does not compile because salary is private.
Why private Is So Important
In professional Java applications, fields are frequently declared private so that a class can control how its internal state is read or modified.
class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount <= 0) {
return;
}
balance += amount;
}
public double getBalance() {
return balance;
}
}
External code cannot arbitrarily assign a value to balance. Instead, it uses the public operations provided by the class.
Important: Making a field private does not make the data inaccessible forever. It means direct access is restricted. The class can expose controlled public methods when access is appropriate.
Package-Private Access
If no access modifier is specified, Java uses package-private access, sometimes called default access.
class Employee {
String name;
void display() {
System.out.println(name);
}
}
Here, name and display() can be accessed by other classes in the same package, but not by unrelated code in another package.
Package-private access is useful when several classes in the same package are designed to cooperate closely while keeping implementation details hidden from other packages.
protected Access Modifier
The protected modifier allows access within the same package and also provides specific access to subclasses outside the package.
package com.example.vehicle;
public class Vehicle {
protected int speed;
}
A class in the same package can access speed. A subclass in another package can also access the protected member through the inherited context of the subclass.
package com.example.car;
import com.example.vehicle.Vehicle;
public class Car extends Vehicle {
void accelerate() {
speed = 100;
}
}
The Car subclass can access the inherited protected member.
The Important protected Rule Across Packages
The protected rule becomes more restrictive when a subclass is in a different package. The subclass has protected access through inheritance, but arbitrary code in that other package does not automatically gain access.
This distinction is frequently tested in Java interviews because simply remembering “protected means subclass access” is incomplete.
public Access Modifier
The public modifier provides the broadest access. A public class or member can generally be accessed from code in other packages, subject to the normal rules of Java and the enclosing type.
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
Code from another package can create and use the class when the class is accessible and available on the class path or module path.
Access Modifiers for Classes
Access rules for top-level classes are slightly different from member access rules. A top-level class can be declared public or have package-private access.
public class Employee {
}
A public top-level class can be accessed from other packages when the class is available to them.
A top-level class without an access modifier is package-private.
class Employee {
}
It can be accessed only from code in the same package.
Important: Top-level classes cannot be declared private or protected. Those modifiers can be used for nested classes and members, but not for ordinary top-level classes.
Access Modifiers for Members
Fields, methods, constructors, and nested classes can use access modifiers according to Java's language rules.
| Member Type | private | Package-Private | protected | public |
|---|---|---|---|---|
| Field | Yes | Yes | Yes | Yes |
| Method | Yes | Yes | Yes | Yes |
| Constructor | Yes | Yes | Yes | Yes |
| Nested class | Yes | Yes | Yes | Yes |
Access Modifier Example
class Employee {
private int id;
String name;
protected double salary;
public String company;
private void privateMethod() {
System.out.println("Private");
}
void packageMethod() {
System.out.println("Package-private");
}
protected void protectedMethod() {
System.out.println("Protected");
}
public void publicMethod() {
System.out.println("Public");
}
}
This class demonstrates all four member-level visibility choices. The correct modifier depends on who should be allowed to interact with each part of the class.
Access Modifiers and Encapsulation
Access modifiers are closely connected to encapsulation. A well-designed class often keeps its internal state private and exposes only the operations that other parts of the application actually need.
class Temperature {
private double celsius;
public void setCelsius(double celsius) {
if (celsius >= -273.15) {
this.celsius = celsius;
}
}
public double getCelsius() {
return celsius;
}
}
The class prevents callers from directly assigning a physically impossible temperature below absolute zero. The access boundary supports the class's responsibility for protecting its own state.
Getters and Setters
A common beginner pattern is to make every field private and automatically create a getter and setter for everything. That is not always the best design.
A getter or setter should exist because the class has a meaningful reason to expose that operation.
class BankAccount {
private double balance;
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
}
Notice that there is no unrestricted setBalance(). The class controls balance changes through a meaningful business operation.
Access Modifiers and Inheritance
Access modifiers affect what a subclass can use from its parent class.
class Animal {
private String name;
protected int age;
public void eat() {
System.out.println("Eating");
}
}
class Dog extends Animal {
void display() {
System.out.println(age);
eat();
}
}
The subclass can directly access the protected age and public eat(). It cannot directly access the private name field.
Access Modifiers and APIs
In a large application or library, public members effectively become part of the API that other developers can depend on.
Changing a private implementation detail is usually easier than changing a widely used public method. This is why experienced developers think carefully before making something public.
A useful design principle is simple: expose what callers need and hide what they do not need to know.
Common Beginner Mistakes
- Assuming package-private means the same thing as public.
- Thinking protected means unrestricted access from every subclass location.
- Trying to declare a top-level class as private or protected.
- Making every field public for convenience.
- Assuming private members are directly accessible from subclasses.
- Creating getters and setters without considering whether the operation should actually be exposed.
Best Practices
- Start with the narrowest reasonable visibility and widen it only when required.
- Keep important internal state private whenever practical.
- Expose meaningful operations rather than unrestricted access to internal data.
- Use package-private access when classes within the same package need to cooperate internally.
- Use protected deliberately when inheritance is genuinely part of the design.
- Treat public APIs as long-term contracts that other code may depend on.
Interview Insights
A common interview question is: “Which access modifier provides the most restrictive access?” The answer is private.
Another common question is: “What is default access in Java?” When no access modifier is specified, the member or top-level class has package-private access.
Interviewers also frequently test the difference between protected and package-private access. Protected members are available within the same package and also have specific visibility to subclasses outside the package, whereas package-private members are restricted to the same package.
Quick Learning Check
Before moving to encapsulation, make sure you can answer these questions:
- What are the four Java access levels?
- Which access level is the most restrictive?
- What happens when no access modifier is specified?
- Why is private commonly used for instance variables?
- How does protected behave inside and outside the package?
- Which access modifiers can be used with top-level classes?
Final Takeaway
Access modifiers define the visibility boundaries of Java types and members. private provides the strongest restriction, package-private keeps access within the package, protected combines package access with specific inheritance access, and public provides broad access. Good Java design generally exposes only what other code needs and keeps implementation details hidden, making systems easier to maintain, change, and understand.
