Module 5 · Object-Oriented Fundamentals ⏱ 18 min

Encapsulation and Access Modifiers

By the end of this lesson you will be able to:
  • Apply public, private, protected, and default access correctly
  • Write getters and setters that protect object state
  • Predict which members are visible from another class or package

Encapsulation is the rule that an object owns its data.* If every field were public, any code anywhere could put a bank account into overdraft, set a temperature to absolute zero, or wipe a user ID. Encapsulation prevents that by hiding the fields and exposing only the operations the class chooses to allow. Access modifiers — public, private, protected, and the default — are the mechanism Java gives you to enforce that boundary at compile time.

flowchart TD
  A["private — same class only"] --> B["default — same package"]
  B --> C["protected — package + subclasses"]
  C --> D["public — everywhere"]
  style A fill:#b45309,color:#fff
  style D fill:#166534,color:#fff
Four visibility levels, from most restrictive to least.

The four access levels

private means only code inside the same class can see the member. It is the strongest wall: not even a subclass peeks through. Use it for fields you never want touched directly.

Default — also called package-private — means any class in the same package can access the member. There is no keyword; you simply omit the modifier. It is useful for collaborators that live together, but it breaks the moment you refactor code into a different package.

protected widens the door to subclasses, even those in other packages. It is narrower than public but wider than default, and it is designed for inheritance hierarchies where a child needs to reach into its parent.

public removes every restriction. Use it for the stable API surface you are willing to support forever, because once other code depends on it, changing it breaks callers.

Main.java — different members carry different access levels. Real, compiling Java.
public class Main {
    static class Account {
        private int balance;
        String owner;          // default (package-private)
        protected int pin;
        public String accountId;

        public int getBalance() {
            return balance;
        }
    }

    public static void main(String[] args) {
        Account acc = new Account();
        acc.accountId = "A-001";
        System.out.println(acc.getBalance());
    }
}

Getters and setters: controlled access

A getter is a public method that returns a private field's value. A setter is a public method that accepts a new value, validates it, and then assigns it. Together they let you change your mind later: you can add logging, validation, or a cache inside the method without changing the callers, because the callers were never touching the field directly.

The JavaBeans convention names them getField and setField for most types, isField for booleans. Following the convention matters because frameworks, serializers, and test libraries all rely on it to discover properties automatically.

flowchart LR
  A["caller"] -->|"getBalance()"| B["public method"]
  B --> C["private field: balance"]
  A -.->|"direct access blocked"| C
  style C fill:#b45309,color:#fff
  style B fill:#166534,color:#fff
Encapsulation forces callers through a public gate; the private field stays hidden.
Main.java — a setter rejects invalid input before it ever reaches the field. Real, compiling Java.
public class Main {
    static class Thermostat {
        private int temp = 20;

        public void setTemp(int t) {
            if (t >= 10 && t <= 30) {
                this.temp = t;
            }
        }

        public int getTemp() {
            return temp;
        }
    }

    public static void main(String[] args) {
        Thermostat th = new Thermostat();
        th.setTemp(5);
        System.out.println(th.getTemp());
        th.setTemp(22);
        System.out.println(th.getTemp());
    }
}

The default-access trap

Leaving a member with no modifier feels casual, but it carries a real visibility promise: every class in the same package can read and write it. That seems safe when your project is small, but packages often split as the project grows. A field that was once safely hidden inside com.app.model becomes exposed the moment someone adds a utility in com.app.util and needs the same data. If you cannot imagine a reason for outside access, start with private. You can always widen it later; narrowing access breaks callers.

flowchart TD
  A["com.app.model"] --> B["Person.java"]
  A --> C["Account.java"]
  D["com.app.util"] --> E["Formatter.java"]
  C -.->|"can see default members"| B
  E -.->|"cannot see default members"| B
  style A fill:#166534,color:#fff
  style D fill:#b45309,color:#fff
Default access is package-private: same package sees it, different package does not.
Exercise

What does this program print? The setter silently rejects invalid values.

public class Main {
    static class Thermostat {
        private int temp = 20;

        void setTemp(int t) {
            if (t >= 10 && t <= 30) {
                this.temp = t;
            }
        }

        int getTemp() {
            return temp;
        }
    }

    public static void main(String[] args) {
        Thermostat t = new Thermostat();
        t.setTemp(5);
        System.out.println(t.getTemp());
        t.setTemp(22);
        System.out.println(t.getTemp());
    }
}
Exercise

In Java, which access modifier allows a subclass in a different package to access the member?

Exercise

Private vs public. Predict the exact two-line output.

public class Main {
    static class Vault {
        private int secret = 42;
        public int hint = 7;

        public int getSecret() {
            return secret;
        }
    }

    public static void main(String[] args) {
        Vault v = new Vault();
        System.out.println(v.hint);
        System.out.println(v.getSecret());
    }
}
Exercise

Setter with validation. Predict the exact one-line output.

public class Main {
    static class Score {
        private int points;

        public void add(int p) {
            if (p > 0) {
                points += p;
            }
        }

        public int get() {
            return points;
        }
    }

    public static void main(String[] args) {
        Score s = new Score();
        s.add(10);
        s.add(-5);
        s.add(3);
        System.out.println(s.get());
    }
}
Exercise

You mark a field private and provide a public getter but no setter. What is the effect from outside the class?

Recap

  • private hides a member from everything except the declaring class. Start here and widen only when necessary.
  • Default (no modifier) is package-private: convenient for internal collaborators but fragile under refactoring.
  • protected opens the door to subclasses, even in other packages.
  • public is a permanent promise: any caller may depend on it.
  • Getters and setters are not ceremony; they are gates where you can add validation, logging, or transformation later.
  • Encapsulation means controlling both read and write access, and never handing out mutable internal state blindly.

Next you will meet static — fields and methods that belong to the class itself rather than to any single instance.

Checkpoint quiz

A class in com.app.util tries to read a field declared with no access modifier in com.app.model. What happens?

Why prefer a private field with a public getter over a public field?

Go deeper — technical resources