Skip to content
Laviyx Learn

Try

Java course menu

Java Encapsulation: private Fields, Getters, Setters and Access Modifiers

Learn encapsulation in Java: access modifiers (private, default, protected, public), private fields with getters and setters, validation, class invariants, immutable classes, and defensive copies of mutable fields.

Intermediate13 min readUpdated
On this page

Encapsulation means keeping an object's data private and allowing changes only through methods that keep the object valid. Outside code sees what an object can do, not how it stores its data. That makes objects hard to misuse, and lets you change their internals without breaking the code that uses them.

Access modifiers#

ModifierAccessible from
privateOnly the same class
(none) "package-private"Classes in the same package
protectedSame package, plus subclasses
publicEverywhere

The usual rule: make fields private, and expose public methods for the operations that make sense.

Private fields with validated methods#

Java
class BankAccount {
    private final String owner;
    private double balance;

    BankAccount(String owner) {
        this.owner = owner;
    }

    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Deposit must be positive");
        }
        balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount <= 0 || amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }

    public double getBalance() {
        return balance;
    }

    public String getOwner() {
        return owner;
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount acc = new BankAccount("Asha");
        acc.deposit(1000);
        System.out.println(acc.withdraw(300) + " " + acc.withdraw(5000));
        System.out.println(acc.getOwner() + ": " + acc.getBalance());
        // acc.balance = 1_000_000;   // error: balance has private access
    }
}
Output
true false
Asha: 700.0

The balance can now change only through deposit and withdraw, which enforce the rules. The rule "the balance is never negative" is a class invariant, and encapsulation is what makes it impossible to break from outside.

Getters and setters#

A getter (getBalance(), or isActive() for booleans) reads a value; a setter (setName(...)) changes it. They are a common Java convention, and many frameworks expect them.

Be careful not to add a setter for every field automatically. setBalance(double) would let any code set any balance and make the validation pointless. Prefer methods that describe real operations: deposit, withdraw, ship, cancel.

When a setter is appropriate, it should validate:

Java
class Thermostat {
    private double temperature = 20;

    public double getTemperature() {
        return temperature;
    }

    public void setTemperature(double value) {
        if (value < 10 || value > 30) {
            throw new IllegalArgumentException(value + " is outside 10-30");
        }
        temperature = value;
    }
}

public class Main {
    public static void main(String[] args) {
        Thermostat t = new Thermostat();
        t.setTemperature(22);
        System.out.println(t.getTemperature());
        try {
            t.setTemperature(45);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
        System.out.println(t.getTemperature());
    }
}
Output
22.0
Error: 45.0 is outside 10-30
22.0

Hiding the representation#

Because callers use methods rather than fields, you can change how data is stored without changing any calling code:

Java
class Duration {
    private int seconds;   // could later become minutes, or java.time.Duration

    Duration(int hours, int minutes) {
        this.seconds = hours * 3600 + minutes * 60;
    }

    public int getHours() { return seconds / 3600; }
    public int getMinutes() { return seconds % 3600 / 60; }
    public void addMinutes(int m) { seconds += m * 60; }
}

public class Main {
    public static void main(String[] args) {
        Duration d = new Duration(1, 50);
        d.addMinutes(25);
        System.out.println(d.getHours() + "h " + d.getMinutes() + "m");
    }
}
Output
2h 15m

Immutable classes#

The strongest form of encapsulation is an immutable object, whose state can never change after construction. String, Integer and LocalDate are immutable. To make your own:

  1. make the class final (no subclasses can add mutable state);
  2. make all fields private final;
  3. provide no setters; "changing" methods return a new object;
  4. defensively copy any mutable data passed in or returned.
Java
final class Money {
    private final long paise;

    Money(long paise) {
        this.paise = paise;
    }

    Money plus(Money other) {
        return new Money(this.paise + other.paise);   // a new object
    }

    @Override
    public String toString() {
        return String.format("Rs %d.%02d", paise / 100, paise % 100);
    }
}

public class Main {
    public static void main(String[] args) {
        Money price = new Money(129950);
        Money fee = new Money(705);
        Money total = price.plus(fee);
        System.out.println(price + " + " + fee + " = " + total);
    }
}
Output
Rs 1299.50 + Rs 7.05 = Rs 1306.55

Immutable objects are safe to share between methods and threads. Java's records create simple immutable data classes in one line; see Records and Enums.

Defensive copies#

If a class stores a mutable object (such as a List) and returns it directly, callers can change the class's private data through it. Return an unmodifiable view or a copy:

Java
import java.util.ArrayList;
import java.util.List;

class Course {
    private final List<String> students = new ArrayList<>();

    public void enroll(String name) {
        students.add(name);
    }

    public List<String> getStudents() {
        return List.copyOf(students);   // read-only copy
    }
}

public class Main {
    public static void main(String[] args) {
        Course c = new Course();
        c.enroll("Asha");
        List<String> list = c.getStudents();
        try {
            list.add("Intruder");
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify the course from outside");
        }
        System.out.println(c.getStudents());
    }
}
Output
Cannot modify the course from outside
[Asha]

Common mistakes#

Practice#

Practice exercise

Write a class Stock with a private quantity that can never go negative, methods receive(int n) and boolean dispatch(int n), and a getter. Receive 50, dispatch 30, try to dispatch 40, and print the results.

Show solution
Java
class Stock {
    private int quantity;

    public void receive(int n) {
        if (n > 0) quantity += n;
    }

    public boolean dispatch(int n) {
        if (n <= 0 || n > quantity) return false;
        quantity -= n;
        return true;
    }

    public int getQuantity() {
        return quantity;
    }
}

public class Warehouse {
    public static void main(String[] args) {
        Stock s = new Stock();
        s.receive(50);
        System.out.println(s.dispatch(30) + " " + s.dispatch(40));
        System.out.println(s.getQuantity());
    }
}
Output
true false
20

Key takeaway#

Finished this lesson?