Java Encapsulation
Bundle data and methods together in Java classes and hide implementation details using private fields and public accessors.
Encapsulation is the first of the four pillars of OOP, and the easiest one to actually do: keep a class's data private, and expose behavior through methods. The class becomes responsible for its own state — no outside code can put it in a bad shape — and the rest of the program talks to it through an interface you control.
The mechanism is private fields plus public methods. The discipline is "don't expose state you can't control."
The pattern
A non-encapsulated class is just a bag of public fields:
public class Account {
public int balance;
}
Account a = new Account();
a.balance = 100;
a.balance = -50; // nothing stops thisThe encapsulated version hides the field and exposes deliberate operations:
public class Account {
private int balance;
public int balance() { return balance; }
public void deposit(int amount) {
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
public boolean withdraw(int amount) {
if (amount <= 0) throw new IllegalArgumentException();
if (amount > balance) return false;
balance -= amount;
return true;
}
}Now there's no way for outside code to set balance to a negative number, overwrite it without going through validation, or check it without going through balance().
What you gain
Invariants you can trust. The Account class enforces "balance ≥ 0 after every public operation." Because nothing outside can touch balance, the rule is enforceable from one file, not the whole codebase.
Freedom to change. With balance private, you can later switch its type from int to long, change the unit from dollars to cents, store it in a BigDecimal, or move it to a database — without touching a single caller. With a public field, the type and storage are baked into every call site.
A smaller, clearer API. Callers see only deposit, withdraw, balance — not the dozen private helpers that make them work. The class advertises what it does, not how.
Locality of reasoning. When something goes wrong with balance, the bug is in one of three methods, not in any of the thousand places that could otherwise write the field.
Encapsulation ≠ getters and setters for everything
A common anti-pattern is to mechanically generate a getter and a setter for every field:
public class Account {
private int balance;
public int getBalance() { return balance; }
public void setBalance(int v) { this.balance = v; } // same as public field, with extra steps
}This is technically "encapsulated" in the textbook sense but achieves none of encapsulation's actual benefits — anyone can still put the object in any state they like. Real encapsulation expresses operations, not raw field access:
deposit(amount)instead ofsetBalance(balance + amount)withdraw(amount)returning success/failure instead ofsetBalance(balance - amount)balance()(a read-only accessor) without a correspondingsetBalance
The next chapter on getters and setters goes through the conventions for when each is appropriate.
Defensive copies
If a field's type is mutable (an array, a list, a Date), returning it directly leaks control:
public class Order {
private final List<String> items = new ArrayList<>();
public List<String> items() { return items; } // leak!
}
Order o = new Order();
o.items().add("apple"); // outside code mutated the orderThe fix is to return an unmodifiable view or a defensive copy:
public List<String> items() {
return List.copyOf(items); // immutable snapshot
}Same care applies to setters that take mutable values — copy them on the way in:
public Order(List<String> items) {
this.items = new ArrayList<>(items);
}The immutable classes chapter goes deeper.
Encapsulation in design
Past a certain size, encapsulation becomes a design tool, not just a coding rule. Each class draws a boundary around a chunk of state and the operations on it; the rest of the system talks to it through that boundary. Most architectural patterns — layered, hexagonal, MVC, Clean — are arguments about where to draw the boundaries.
Practical guidelines:
- Fields are private until proven otherwise. Start there; widen only with a reason.
- Expose verbs, not nouns.
cancel(),pay(),ship()beatsetStatus(...). - Don't return mutable internals raw. Wrap, copy, or use an immutable view.
- Validate on the way in. Reject impossible state at the boundary so internal code can assume it's valid.
A worked example
What's next
The mechanical part of encapsulation — the private fields with public methods that read or write them — has its own conventions worth knowing. Continue to getters and setters.
Practice
Why does mechanically generating a public getter and public setter for every private field defeat the purpose of encapsulation?