Access Modifiers
Access modifiers are the keywords you place before a class, field, method, or constructor to control who can see and use it. They are the primary tool Java gives you to enforce encapsulation — drawing clear boundary lines between the parts of your code that are internal implementation details and the parts that form a public API.
The Four Access Levels
Java has exactly four access levels. Three have an explicit keyword; the fourth is the default when you write no keyword at all.
| Modifier | Keyword | Visible to |
|---|---|---|
| Private | private | Only the declaring class |
| Package-private | (none) | Any class in the same package |
| Protected | protected | Same package + subclasses (any package) |
| Public | public | Everyone |
Think of them as progressively wider circles of visibility.
Tip: The golden rule: start with the most restrictive access level you can get away with (
private), and only widen it when you have a concrete reason.
private — Class-Level Encapsulation
private members are visible only inside the class that declares them. Not to subclasses, not to classes in the same package — only to the class itself.
public class Counter {
private int count = 0; // hidden from all outside code
public void increment() {
count++; // OK — same class
}
public int getCount() {
return count; // controlled read access
}
}
class App {
public static void main(String[] args) {
Counter c = new Counter();
c.increment();
System.out.println(c.getCount()); // OK
// c.count = 99; // Compile error: count has private access in Counter
}
}
Output:
1
Use private for all fields by default, and for helper methods that are pure implementation details. See the Encapsulation page for the full pattern of pairing private fields with public getters and setters.
Package-Private (Default) — Package-Level Collaboration
When you write no modifier at all, the member is package-private — visible to all classes in the same package, but invisible outside it.
package com.example.billing;
class Invoice { // package-private class
double amount; // package-private field
void print() { // package-private method
System.out.println("Invoice: $" + amount);
}
}
package com.example.billing;
class InvoiceProcessor { // same package — can access Invoice freely
void process() {
Invoice inv = new Invoice();
inv.amount = 250.00;
inv.print();
}
}
package com.example.shipping;
// import com.example.billing.Invoice; // Compile error: Invoice is not public
Package-private is useful for classes and helpers that are internal to a module but need to cooperate with several classes in the same package. Packages explains how to organize your code into packages to take full advantage of this.
Note: Top-level classes (not nested) can only be
publicor package-private — you cannot declare a top-level classprivateorprotected.
protected — Inheritance Access
protected members are visible to:
- All classes in the same package (just like package-private).
- Any subclass, even if it lives in a different package.
package com.example.shapes;
public class Shape {
protected String color; // subclasses can read/write this
protected void describe() {
System.out.println("Shape color: " + color);
}
}
package com.example.drawing; // different package!
import com.example.shapes.Shape;
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
this.color = color; // OK: inherited protected field
this.radius = radius;
}
public void draw() {
describe(); // OK: inherited protected method
System.out.println("Circle radius: " + radius);
}
}
public class Main {
public static void main(String[] args) {
Circle c = new Circle("red", 5.0);
c.draw();
// c.color = "blue"; // Compile error: not accessible from unrelated class
}
}
Output:
Shape color: red
Circle radius: 5.0
Warning: A common misconception —
protectedis not accessible from an arbitrary class in another package just because it’s a “weaker” public. An unrelated class in another package still cannot touch aprotectedmember. Only subclasses (and same-package classes) get access.
protected is the right choice when you are designing a class for inheritance and want subclasses to be able to customize or read internal state, without opening it up to everyone.
public — Open API
public members are accessible from any class anywhere, as long as the class itself is also reachable (i.e., the class must also be public for cross-package access to work).
package com.example.math;
public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
public static double sqrt(double x) {
return Math.sqrt(x);
}
}
import com.example.math.MathUtils;
public class App {
public static void main(String[] args) {
System.out.println(MathUtils.add(3, 4)); // 7
System.out.println(MathUtils.sqrt(16)); // 4.0
}
}
Output:
7
4.0
Mark methods and classes public when they are intentionally part of the API you want others to use. Avoid making fields public — this bypasses encapsulation entirely, leaving you no control over how they are read or modified.
Quick Visual Reference
Here is a complete at-a-glance table. “Y” means accessible, “N” means not accessible.
| Location | private | package-private | protected | public |
|---|---|---|---|---|
| Same class | Y | Y | Y | Y |
| Same package (different class) | N | Y | Y | Y |
| Subclass (different package) | N | N | Y | Y |
| Unrelated class (different package) | N | N | N | Y |
Applying Modifiers to Classes, Constructors, and Methods
Access modifiers apply consistently to all member types, but there are a few nuances worth knowing:
Top-Level Classes
Only public or package-private (no modifier). Making a class public means it must live in a file with the same name.
Constructors
A private constructor prevents instantiation from outside the class — the basis of the Singleton pattern and factory methods.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {} // nobody can call new Singleton() from outside
public static Singleton getInstance() {
return INSTANCE;
}
}
Nested / Inner Classes
Inner classes can use all four access modifiers, including private (common for internal helper classes that should be completely hidden from outside code) and protected (for helper classes meant for subclasses).
Under the Hood
Access control in Java operates at two levels: compile time and runtime.
At compile time, javac checks every member access against the declared access flags and rejects illegal access with a compile error — this is where you normally encounter these rules.
In the bytecode, each class, field, and method carries an access_flags bitmask in the .class file. Flags like ACC_PUBLIC (0x0001), ACC_PRIVATE (0x0002), and ACC_PROTECTED (0x0004) are recorded there. You can inspect these with the javap tool:
javap -p -verbose Counter.class
At runtime, the JVM verifier re-checks access flags when loading and linking classes. Violations throw IllegalAccessError — a subclass of Error, not Exception. This protects against classes that were compiled correctly but whose bytecode was later tampered with.
The Reflection API can bypass access modifiers by calling setAccessible(true) on a Field or Method. This works for legitimate use cases (testing, frameworks), but Java 9’s module system (JPMS) added an additional layer of enforcement — strong encapsulation that even reflection cannot always breach without explicit module exports. See Java 9: Modules for details.
Note: Performance-wise,
privatemethods have a small advantage in HotSpot: the JIT compiler knows they cannot be overridden and will eagerly inline them. Non-private, non-finalvirtual methods require vtable dispatch — see vtable & Dynamic Dispatch for a deeper look.
Common Mistakes
- Making fields
publicfor convenience. This is the most common encapsulation mistake. Always useprivatefields with getters/setters instead. - Confusing
protectedwith “accessible to anyone in other packages.” Remember: only subclasses (and same-package classes) can touchprotectedmembers. - Forgetting that package-private is a real boundary. Code in a different package without subclassing gets zero access to package-private members — very useful for internal APIs.
- Using
publicon everything. This is the “no encapsulation” approach and leads to tightly coupled, hard-to-change code.
Related Topics
- Encapsulation — The OOP principle that access modifiers enforce; the bigger picture.
- Packages — How to organize classes into packages, which determines what “same package” means for access rules.
- Inheritance — Where
protectedaccess becomes essential for designing extensible class hierarchies. - Inner Classes — Nested classes can be
private, making them completely invisible outside the enclosing class. - Reflection API — The advanced mechanism that can bypass access modifiers at runtime.
- Java 9: Modules — JPMS adds a stronger layer of encapsulation on top of access modifiers for large-scale applications.