Java Encapsulation
Bundle data with the methods that operate on it. Hide the rest.
Why encapsulate?
Without encapsulation, every field of every object is public — anyone can change it from anywhere. That seems convenient, until your class has 50 callers and one of them sets person.age = -5. Encapsulation lets you:
- Validate values before they're set
- Change internal representation without breaking callers
- Make some fields read-only
- Trace where mutations come from
Access modifiers
| Modifier | Same class | Same package | Subclass | Anywhere |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
| (default) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
Default visibility (no modifier) is sometimes called "package-private".
The classic pattern
Make fields private. Provide public getters and setters with optional validation.
public class BankAccount { private String owner; private double balance; public BankAccount(String owner) { this.owner = owner; this.balance = 0; } // Getters public String getOwner() { return owner; } public double getBalance() { return balance; } // No public setter for balance — only deposit/withdraw, with validation public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException("Amount must be positive"); } balance += amount; } public void withdraw(double amount) { if (amount <= 0) { throw new IllegalArgumentException("Amount must be positive"); } if (amount > balance) { throw new IllegalStateException("Insufficient funds"); } balance -= amount; } }
Notice: callers can't set the balance directly. They must go through methods that enforce rules. Internally we could change balance from double to a BigDecimal tomorrow without any caller noticing.
The JavaBean convention
Many frameworks (Spring, Hibernate, Jackson JSON) expect classes to follow JavaBean rules:
- No-argument constructor
- Private fields
- Public getter named
getX()for fieldx - Public setter named
setX(...) - For booleans: getter named
isX()
Records (Java 14+)
For pure data carriers, the new record keyword generates fields, constructor, getters, equals, hashCode, and toString automatically:
public record Point(int x, int y) {} Point p = new Point(3, 4); System.out.println(p.x()); // 3 System.out.println(p); // Point[x=3, y=4]
Records are immutable — fields can't change after construction. Perfect for DTOs, value objects, API responses.