Access Specifiers — Junior¶
What? Access specifiers (also called access modifiers) are keywords that decide who can see a class, field, method, or constructor. Java has four levels:
public,protected, package-private (no keyword), andprivate. How? Place the keyword (or omit it for package-private) before the declaration. The compiler enforces the rules at compile time.
1. The four levels at a glance¶
| Modifier | Same class | Same package | Subclass (different package) | Anywhere |
|---|---|---|---|---|
private | ✓ | ✗ | ✗ | ✗ |
| package-private (no keyword) | ✓ | ✓ | ✗ | ✗ |
protected | ✓ | ✓ | ✓ | ✗ |
public | ✓ | ✓ | ✓ | ✓ |
The order from most-restrictive to least-restrictive: private → package-private → protected → public.
2. The default — package-private¶
If you write nothing, you get package-private (also called "default" access):
class Helper { // package-private — only visible inside this package
int compute(int x) { ... }
String name = "helper";
}
Other classes in the same package can see and use Helper. Classes in other packages cannot — they don't see the class at all.
This is the default, but most beginners write public everywhere out of habit. Don't. Pick the narrowest access that lets your code work.
3. private — visible only within the same class¶
public class BankAccount {
private long balanceCents; // only methods of BankAccount can read/write
public void deposit(long cents) { balanceCents += cents; }
public long getBalance() { return balanceCents; }
}
Outside BankAccount:
private is the default you should reach for first. Loosen it only when you have a reason. Most field declarations should be private.
A small subtlety: private is per-class, not per-instance. One BankAccount instance can read another's private fields:
public class BankAccount {
private long balanceCents;
public boolean richerThan(BankAccount other) {
return this.balanceCents > other.balanceCents; // ✓ legal
}
}
This is by design — equals, compareTo, etc., would be impossible otherwise.
4. public — visible everywhere¶
public class Mathx {
public static int add(int a, int b) { return a + b; }
}
// Anywhere else:
import com.example.Mathx;
int s = Mathx.add(2, 3);
public is a commitment. Every public member becomes part of your class's API. Once shipped, callers depend on it; renaming or removing it is a breaking change.
Use public when:
- The member is genuinely part of the class's API (the methods callers should use).
- The class is a library/framework type meant for outside use.
Don't use public "just in case." Tighten access first, loosen later if needed.
5. protected — same package + subclasses¶
package com.example.shapes;
public class Shape {
protected double area; // visible to subclasses, even in other packages
protected double computeArea() { ... }
}
// Different package, same hierarchy:
package com.example.fancy;
import com.example.shapes.Shape;
public class Circle extends Shape {
public double doubleArea() {
return area * 2; // ✓ — protected access via subclass
}
}
protected is the right choice when:
- A subclass legitimately needs the field/method to extend the class.
- You're designing a template method pattern (the parent calls protected hooks the subclass implements).
Subtle rule: protected access from a subclass in a different package only works through the subclass's own type, not through arbitrary references. (You can read subclass.protectedField but not someOtherShape.protectedField from outside the package.)
6. Package-private — same package only¶
This is the right level for internal helpers — code that supports your public API but isn't itself part of it. Use it generously:
- Helper classes with no callers outside the package.
- Utility methods that the public surface uses internally.
- Test-friendly hooks (a package-private method can be tested by a test class in the same package).
Many JDK internals use package-private heavily. It's a discipline worth adopting.
7. Top-level classes: public or package-private only¶
A top-level class (one not nested inside another) can be only public or package-private:
public class Foo { ... } // ✓
class Bar { ... } // ✓ (package-private)
private class Baz { ... } // ❌ compile error
protected class Qux { ... } // ❌ compile error
private and protected apply only to nested members (classes, fields, methods, constructors).
A .java file can contain at most one public class, and its name must match the file name. The other top-level classes (if any) must be package-private.
8. Constructors have access too¶
public class Singleton {
private Singleton() { } // private constructor — only this class can call
public static final Singleton INSTANCE = new Singleton();
}
new Singleton(); // ❌ compile error
Singleton.INSTANCE; // ✓
Common patterns:
privateconstructor for singletons, utility classes (no instances), or static-factory-only classes.- Package-private constructor when you want the package's classes to instantiate freely but block outsiders.
protectedconstructor when only subclasses (and same-package) should construct.
If you make a class non-instantiable (utility class), declare private Singleton() { throw new AssertionError(); } so reflection + a stray new can't sneak through.
9. Why bother? — the value of access control¶
Access modifiers are a tool for encapsulation. They let you say:
- "These fields are mine — outsiders can't read or write them, so I can change them later without breaking callers." (
private) - "These methods are the API — clients depend on them; I commit to keeping them stable." (
public) - "These hooks are for subclasses — they may extend or override; outsiders shouldn't touch them." (
protected) - "These helpers are internal to my module — feel free to use them within this package." (package-private)
Without access control, every refactor risks breaking unknown callers. With it, you have a shrink-wrapped boundary.
10. Read access vs write access¶
The same modifier governs both reading and writing. If you want, e.g., everyone to read a field but only the owning class to write it, expose a getter and keep the field private:
public class Config {
private String url; // hidden — only the class writes it
public String url() { return url; } // public read access
}
This is the textbook reason for getters: asymmetric access (read public, write private) without exposing the field.
11. Order matters in a declaration¶
The conventional order (and what most style guides require):
The compiler doesn't enforce this order, but tools and humans both expect it.
12. Quick rule of thumb (the pragmatic default)¶
When in doubt, follow this default order:
- Fields:
private. Always. Loosen only with a real reason. - Methods:
privateif it's an internal helper;publicif it's the API; package-private otherwise. - Classes: package-private unless something outside the package needs the class. Then
public. - Constructors:
publicfor instantiable classes;privatefor singletons/utilities; package-private for "controlled" construction.
Tighten first. You can always loosen later. Tightening later is much harder — it breaks every existing caller.
13. Examples putting it together¶
package com.example.banking;
public class BankAccount { // public API class
private final long id; // hidden state
private long balanceCents; // hidden state, mutable
public BankAccount(long id) { // public constructor
this.id = id;
}
public long getBalance() { // public reader
return balanceCents;
}
public void deposit(long cents) { // public mutator
if (cents <= 0) throw new IllegalArgumentException();
balanceCents = Math.addExact(balanceCents, cents);
}
void debit(long cents) { // package-private — used by InternalTransfer
balanceCents = Math.subtractExact(balanceCents, cents);
}
private void log(String msg) { // private helper
System.out.println(id + ": " + msg);
}
}
class InternalTransfer { // package-private support class
static void move(BankAccount from, BankAccount to, long cents) {
from.debit(cents); // ✓ package-private access
to.deposit(cents);
}
}
Each modifier is doing real work — outsiders see a tight BankAccount API, the package collaborates internally without exposing transfer logic, and log stays inside.
14. Common beginner mistakes¶
| Mistake | Symptom | Fix |
|---|---|---|
Making everything public | Refactors break callers everywhere | Tighten access by default |
Keeping fields public | Invariants get bypassed | private + getter/setter |
Using protected to "share with friends" | Subclasses become required for access | Use package-private instead |
| Hidden static field defaults to package | Surprised that another package can't see it | Decide intentionally — public/private/none |
Compiler-rejected private on top-level class | Doesn't work for top-level | Use package-private or public |
15. Cheat sheet¶
private → only this class
(none) → only this package
protected → this package + subclasses (incl. other packages)
public → everyone
Default for fields: private. Default for methods: as narrow as possible. Default for classes: package-private unless externally needed. Default for constructors: public for normal use; private for singletons/utility.
That's the entire vocabulary of access control. Master the four levels and you've fortified the boundary that all the other OOP rules rely on.