Access modifiers: private, package-private, protected and public
Access modifiers decide which code can use a class, field, method or constructor. They're how a class protects its rules: outside code can only touch what you deliberately expose.
- private: only inside the same class. Use it for fields almost always.
- package-private (no keyword): any class in the same package.
- protected: the same package, plus subclasses in other packages (through inheritance).
- public: any code anywhere.
Rules of thumb:
- Make fields private and expose behaviour through methods. That's encapsulation.
- Start with the most restrictive level that works, and widen it only when needed. Narrowing it later breaks other people's code.
- A top-level class can only be public or package-private; nested classes can use all four.
- An overriding method can keep or widen the access level, never narrow it.
Side by side
| Who can access it | Typical use | |
|---|---|---|
| private | The same class | Fields, helper methods |
| (none) package-private | + every class in the same package | Implementation classes inside a feature package |
| protected | + subclasses in other packages | Extension points of a class designed to be extended |
| public | + everyone (in exported packages, with modules) | The API other code is meant to use |
Example
package com.shop.model;
public class Product { // public: usable from any package
private final String sku; // private: only Product can touch it
private long pricePaise;
String category; // package-private: classes in com.shop.model only
protected int stock; // protected: package + subclasses elsewhere
public Product(String sku, long pricePaise) {
this.sku = sku;
setPrice(pricePaise);
}
public long price() { return pricePaise; } // read access through a method
public void setPrice(long pricePaise) { // the class enforces its own rule
if (pricePaise < 0) throw new IllegalArgumentException("Price can't be negative");
this.pricePaise = pricePaise;
}
private String normalise(String s) { return s.trim().toUpperCase(); } // internal helper
}package com.shop.digital;
import com.shop.model.Product;
public class Ebook extends Product {
public Ebook(String sku) { super(sku, 49900); }
void restock(Ebook other, Product plain) {
this.stock = 100; // OK: inherited, accessed through this subclass
other.stock = 5; // OK: through an Ebook reference
// plain.stock = 5; // compile error: a different package may only use protected members
// through its own subclass type, not through any Product
}
}Common mistake
Making fields public "to save writing getters". Every caller can then put the object into an invalid state, and you can never add validation later without breaking them.
Under the hood
Java 9 modules add a layer above all of this: a public class is only visible outside its module if its package is exported in module-info.java. Interfaces are public by design: their abstract, default and static methods are implicitly public, and since Java 9 they can have private helper methods. Reflection can bypass access checks with setAccessible(true), but strong encapsulation of the JDK (Java 16+) blocks that for JDK internals.
Check yourself
A field has no access modifier. Who can access it?
How this connects
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Part of OOP in practice.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.