Encapsulation — Access Modifiers, Getters/Setters, JavaBeans, and Data Hiding
In this tutorial, you will learn about Encapsulation. We cover key concepts, practical examples, and best practices to help you master this topic.
Encapsulation in Java hides internal object state and exposes controlled access through getters and setters, enforced by access modifiers. Encapsulation is the first principle of object-oriented programming — it prevents external code from putting an object into an invalid state by restricting direct access to its fields.
What You'll Learn
- The four Java access modifiers:
private,default,protected,public - Getter and setter conventions (JavaBeans style)
- Why encapsulation is critical for maintainable code
- Validation logic in setters
Why It Matters
Without encapsulation, any code can set any field to any value — including invalid ones like a negative age or an empty name. Encapsulation centralizes validation in one place (the setter) rather than scattering checks everywhere the field is used.
Real-World Use
Every production Java framework relies on encapsulation. JPA entities use private fields with getters/setters, Spring beans encapsulate configuration, and DTOs (Data Transfer Objects) shield internal data structures from external consumers.
Access Modifiers
Java provides four access levels:
| Modifier | Same Class | Same Package | Subclass (different package) | Any Class |
|---|---|---|---|---|
private |
Yes | No | No | No |
default (no modifier) |
Yes | Yes | No | No |
protected |
Yes | Yes | Yes | No |
public |
Yes | Yes | Yes | Yes |
Private
public class Person {
private String name;
private int age;
}
private fields are accessible only within the same class. This is the most restrictive modifier and the default for fields in well-designed classes.
Package-Private (Default)
class Helper {
int value; // accessible within the same package only
}
If no modifier is specified, the member is accessible from any class in the same package. This is useful for internal helper classes that should not be part of the public API.
Protected
public class Parent {
protected int id;
}
public class Child extends Parent {
void show() {
System.out.println(id); // accessible in subclass
}
}
protected allows access from subclasses (even in different packages) and from any class in the same package.
Public
public class MathUtils {
public static int max(int a, int b) {
return a > b ? a : b;
}
}
public members are accessible from anywhere. Use public for the API of your class — constructors, methods intended for callers, and constants.
Getters and Setters
The standard pattern is to make fields private and expose access through public methods:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
this.age = age;
}
}
JavaBeans Naming Convention
- For a
booleanfieldactive:- Getter:
isActive()(notgetActive())
- Getter:
- For other types:
- Getter:
getFieldName() - Setter:
setFieldName()
- Getter:
This convention is required by many frameworks (JSP, Spring, Jackson for JSON Serialization).
Validation and Data Integrity
Setters are the natural place to enforce invariants:
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email address");
}
this.email = email;
}
This guarantees that every Person object always has a valid email, regardless of which code creates or modifies it.
Immutable Objects with No Setters
Sometimes you want an object that cannot be modified after creation:
public class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
}
Immutable objects are inherently thread-safe, can be cached freely, and are easier to reason about. Use final fields and omit setters.
Common Mistakes
- Making all fields
public. This breaks encapsulation — any code can modify fields without validation. Always start withprivatefields. - Exposing mutable objects in getters. If a field is a
ListorDate, returning the reference lets callers modify it. Return a copy or an unmodifiable view:public List<String> getTags() { return Collections.unmodifiableList(tags); }
- Leaking
thisin a constructor. Do not passthisto another method during construction — the object is not fully initialized yet. - Using
protectedfields for inheritance. Protected fields couple the parent and child classes tightly. Preferprivatefields withprotectedgetters/setters. - Writing getters/setters for every field by default. Some fields should not have setters (e.g.,
id,createdAt). Use your judgment.
Practice Questions
1. What is the difference between private and protected?
private restricts access to the same class only. protected allows access from subclasses and same-package classes.
2. Why should fields typically be private?
To enforce encapsulation — only the owning class can modify its state, ensuring validation and consistency.
3. What is the JavaBeans naming convention for boolean getters?
Use isXxx() instead of getXxx(). For example, isActive() rather than getActive().
4. How do you protect a mutable list from external modification?
Return Collections.unmodifiableList(list) from the getter, or return a defensive copy with new ArrayList<>(list).
5. Can a class be both immutable and have getters?
Yes. Immutable classes have only getters, no setters, and all fields are final. The object is fully initialized in the constructor.
Challenge Question:
Design an ImmutableStudent class with fields id (final int), name (final String), and grades (final ListaddGrade(int grade) that returns a new ImmutableStudent with the grade appended.
FAQ
Mini Project
Write a program BankAccount.java that:
- Has
privatefields:accountNumber(String),balance(double, initialized to 0) - Provides getters for both fields
- Setter for
accountNumberthat rejects null or empty strings - Methods
deposit(double amount)andwithdraw(double amount)with validation (no negative deposits, no overdraft) - Uses a
protectedmethodcalculateInterest()that subclasses can override - Create a
SavingsAccountsubclass that overridescalculateInterest()with a different rate - Demonstrates that the
balancefield cannot be directly modified from outside
What's Next
Encapsulation organizes data within a single class. But real systems need relationships between classes. Lesson 13 introduces inheritance — the extends keyword, super for parent access, method overriding, and the methods inherited from Object.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro