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
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.
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
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
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());
}
}
The initial temp is 20. setTemp(5) is rejected because 5 is below 10.
setTemp(22) is accepted, so the second line prints 22.
In Java, which access modifier allows a subclass in a different package to access the member?
protected allows access within the same package AND from subclasses in other packages. default only allows same package. public allows everywhere. private allows only the declaring class.
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());
}
}
hint is public, so v.hint is allowed.
secret is private, so it is read only through getSecret().
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());
}
}
10 and 3 are added; -5 is rejected.
The total is 10 + 3 = 13.
You mark a field private and provide a public getter but no setter. What is the effect from outside the class?
A public getter allows reading. With no setter and the field private, outside code has no public path to modify the value. The class itself can still change it internally. This is the standard read-only field pattern.
Recap
privatehides 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.
protectedopens the door to subclasses, even in other packages.publicis 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.