Encapsulation in Java

Learn to protect your data and control how it's accessed

💡 Think of encapsulation like a capsule that keeps medicine safe inside, or a backpack that protects your stuff! You can't just reach inside someone's backpack directly - you have to ask them to open it (getters) or put something in (setters). Encapsulation wraps data and methods together and controls access to keep things safe!

🔒 What is Encapsulation?

Encapsulation is the bundling of data (fields) and methods that operate on that data into a single unit (class), and restricting direct access to some of the object's components. It's like putting your valuables in a safe - only authorized people can access them through the proper methods!

EncapsulationBasics.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// BAD EXAMPLE - No Encapsulation ❌
class PersonBad {
// Public fields - anyone can modify directly!
public String name;
public int age;
public double salary;
}
class BadDemo {
public static void main(String[] args) {
PersonBad person = new PersonBad();
// Direct access - can set invalid values!
person.age = -5; // Negative age?!
person.salary = -1000; // Negative salary?!
person.name = ""; // Empty name?!
// No validation, no control - BAD!
}
}
// GOOD EXAMPLE - With Encapsulation ✓
class PersonGood {
// Private fields - hidden from outside
private String name;
private int age;
private double salary;
// Constructor
public PersonGood(String name, int age, double salary) {
setName(name); // Use setters for validation
setAge(age);
setSalary(salary);
}
// Getter for name - controlled READ access
public String getName() {
return name;
}
// Setter for name - controlled WRITE access with validation
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
} else {
System.out.println("Error: Name cannot be empty!");
}
}
// Getter for age
public int getAge() {
return age;
}
// Setter for age with validation
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("Error: Invalid age!");
}
}
// Getter for salary
public double getSalary() {
return salary;
}
// Setter for salary with validation
public void setSalary(double salary) {
if (salary >= 0) {
this.salary = salary;
} else {
System.out.println("Error: Salary cannot be negative!");
}
}
public void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Salary: $" + salary);
}
}
class GoodDemo {
public static void main(String[] args) {
PersonGood person = new PersonGood("Alice", 30, 75000);
// Can't access fields directly
// person.age = -5; // ❌ Compilation error!
// Must use setters - validation happens automatically!
person.setAge(-5); // Rejected! "Invalid age!"
person.setSalary(-1000); // Rejected! "Salary cannot be negative!"
// Valid values work fine
person.setAge(31);
person.setSalary(80000);
// Read data using getters
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
person.displayInfo();
}
}

Why Use Encapsulation?

  • Data Protection: Prevents unauthorized or invalid modifications
  • Flexibility: Change internal implementation without affecting users
  • Maintainability: Easier to modify and debug code
  • Control: Validate data before allowing changes
  • Hide Complexity: Users don't need to know internal details

🚦 Access Modifiers

🔴

private

Accessible only within the same class

Use for sensitive data that should be hidden

🟡

default (no modifier)

Accessible within the same package

Use for package-level access

🟠

protected

Accessible in same package and subclasses

Use when child classes need access

🟢

public

Accessible from anywhere

Use for methods that form the public API

AccessModifiers.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class AccessDemo {
// Private - only accessible within this class
private String secret = "Top Secret!";
// Default (no modifier) - accessible within same package
String packageData = "Package level data";
// Protected - accessible in same package and subclasses
protected String familySecret = "Family only";
// Public - accessible from anywhere
public String publicInfo = "Everyone can see this";
// Private method - only this class can call
private void privateMethod() {
System.out.println("This is private: " + secret);
}
// Public method - anyone can call
public void publicMethod() {
System.out.println("This is public: " + publicInfo);
privateMethod(); // Can call private method from inside
}
}
class TestAccess {
public static void main(String[] args) {
AccessDemo demo = new AccessDemo();
// Can access public
System.out.println(demo.publicInfo); // Works
demo.publicMethod(); // Works
// Cannot access private
// System.out.println(demo.secret); // ❌ Compilation error!
// demo.privateMethod(); // ❌ Compilation error!
// Can access default (if in same package)
System.out.println(demo.packageData); // Works (same package)
// Can access protected (if in same package or subclass)
System.out.println(demo.familySecret); // Works (same package)
}
}

🏦 Real-World Example: Bank Account

BankAccount.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class BankAccount {
// Private fields - hidden from outside
private String accountNumber;
private String ownerName;
private double balance;
private String pin;
// Constructor
public BankAccount(String accountNumber, String ownerName, String pin) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.pin = pin;
this.balance = 0.0;
}
// Public getter - controlled READ access
public String getAccountNumber() {
return accountNumber;
}
public String getOwnerName() {
return ownerName;
}
// Balance can only be read, not set directly!
public double getBalance() {
return balance;
}
// NO setter for PIN - once set, cannot be changed this way!
// (In real app, would have changePin method with verification)
// Controlled methods to modify balance
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: $" + amount);
System.out.println("New balance: $" + balance);
} else {
System.out.println("Error: Deposit amount must be positive!");
}
}
public boolean withdraw(double amount, String enteredPin) {
// Verify PIN first!
if (!verifyPin(enteredPin)) {
System.out.println("Error: Invalid PIN!");
return false;
}
// Validate amount
if (amount <= 0) {
System.out.println("Error: Withdrawal amount must be positive!");
return false;
}
// Check sufficient funds
if (amount > balance) {
System.out.println("Error: Insufficient funds!");
System.out.println("Balance: $" + balance + ", Requested: $" + amount);
return false;
}
// All checks passed - perform withdrawal
balance -= amount;
System.out.println("Withdrew: $" + amount);
System.out.println("New balance: $" + balance);
return true;
}
// Private helper method - only used internally
private boolean verifyPin(String enteredPin) {
return this.pin.equals(enteredPin);
}
public void displayAccountInfo(String enteredPin) {
if (!verifyPin(enteredPin)) {
System.out.println("Error: Invalid PIN! Cannot display info.");
return;
}
System.out.println("=== Account Information ===");
System.out.println("Account: " + accountNumber);
System.out.println("Owner: " + ownerName);
System.out.println("Balance: $" + balance);
}
}
class BankDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount("ACC001", "Alice", "1234");
// Cannot access private fields directly
// account.balance = 1000000; // ❌ Compilation error!
// account.pin = "0000"; // ❌ Compilation error!
// Must use public methods
account.deposit(1000);
account.deposit(500);
// Try withdrawal with wrong PIN
account.withdraw(200, "9999"); // Invalid PIN!
// Try withdrawal with correct PIN
account.withdraw(200, "1234"); // Success!
// Try to withdraw more than balance
account.withdraw(2000, "1234"); // Insufficient funds!
// Display info with correct PIN
account.displayAccountInfo("1234");
// This is encapsulation - data is protected and controlled!
}
}

🎓 Student Example with Validation

Student.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class Student {
// Private fields
private String studentId;
private String name;
private int age;
private double gpa;
private String email;
// Constructor with validation
public Student(String studentId, String name, int age, String email) {
this.studentId = studentId;
setName(name);
setAge(age);
setEmail(email);
this.gpa = 0.0; // Default GPA
}
// Getters - provide READ access
public String getStudentId() {
return studentId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getGpa() {
return gpa;
}
public String getEmail() {
return email;
}
// Setters with validation - provide controlled WRITE access
public void setName(String name) {
if (name != null && name.trim().length() >= 2) {
this.name = name.trim();
} else {
System.out.println("Error: Name must be at least 2 characters!");
}
}
public void setAge(int age) {
if (age >= 16 && age <= 100) {
this.age = age;
} else {
System.out.println("Error: Age must be between 16 and 100!");
}
}
public void setGpa(double gpa) {
if (gpa >= 0.0 && gpa <= 4.0) {
this.gpa = gpa;
System.out.println("GPA updated to: " + this.gpa);
} else {
System.out.println("Error: GPA must be between 0.0 and 4.0!");
}
}
public void setEmail(String email) {
// Simple email validation
if (email != null && email.contains("@") && email.contains(".")) {
this.email = email.toLowerCase();
} else {
System.out.println("Error: Invalid email format!");
}
}
// Business logic method
public String getAcademicStatus() {
if (gpa >= 3.5) {
return "Honor Student";
} else if (gpa >= 3.0) {
return "Good Standing";
} else if (gpa >= 2.0) {
return "Satisfactory";
} else {
return "Academic Probation";
}
}
public void displayInfo() {
System.out.println("=== Student Information ===");
System.out.println("ID: " + studentId);
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Email: " + email);
System.out.println("GPA: " + gpa);
System.out.println("Status: " + getAcademicStatus());
}
}
class StudentDemo {
public static void main(String[] args) {
Student student = new Student("S12345", "Bob Smith", 20, "bob@university.edu");
student.displayInfo();
System.out.println();
// Try setting invalid values - they will be rejected!
student.setAge(-5); // Invalid age
student.setAge(200); // Invalid age
student.setGpa(5.0); // Invalid GPA
student.setEmail("bademail"); // Invalid email
System.out.println();
// Set valid values - they will be accepted
student.setAge(21); // Valid
student.setGpa(3.7); // Valid
student.setEmail("bob.smith@university.edu"); // Valid
System.out.println();
student.displayInfo();
// Encapsulation protects data integrity!
}
}

📖 Read-Only Properties (No Setters)

ReadOnly.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
class Product {
// Read-only fields - set once in constructor, never changed
private final String productId;
private final String sku;
private final String manufacturer;
// Regular fields - can be changed
private String name;
private double price;
private int stockQuantity;
public Product(String productId, String sku, String manufacturer,
String name, double price) {
// Set read-only fields
this.productId = productId;
this.sku = sku;
this.manufacturer = manufacturer;
// Set regular fields
this.name = name;
this.price = price;
this.stockQuantity = 0;
}
// Getters for read-only fields - NO SETTERS!
public String getProductId() {
return productId;
}
public String getSku() {
return sku;
}
public String getManufacturer() {
return manufacturer;
}
// Getters and setters for changeable fields
public String getName() {
return name;
}
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
}
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (price > 0) {
this.price = price;
System.out.println("Price updated to: $" + price);
}
}
public int getStockQuantity() {
return stockQuantity;
}
// Controlled method to modify stock
public void addStock(int quantity) {
if (quantity > 0) {
stockQuantity += quantity;
System.out.println("Added " + quantity + " units. Stock: " + stockQuantity);
}
}
public void removeStock(int quantity) {
if (quantity > 0 && quantity <= stockQuantity) {
stockQuantity -= quantity;
System.out.println("Removed " + quantity + " units. Stock: " + stockQuantity);
} else {
System.out.println("Error: Invalid quantity!");
}
}
public void displayInfo() {
System.out.println("Product ID: " + productId + " (Read-only)");
System.out.println("SKU: " + sku + " (Read-only)");
System.out.println("Manufacturer: " + manufacturer + " (Read-only)");
System.out.println("Name: " + name);
System.out.println("Price: $" + price);
System.out.println("Stock: " + stockQuantity);
}
}
class ProductDemo {
public static void main(String[] args) {
Product laptop = new Product("P001", "LAP-001", "TechCorp",
"Gaming Laptop", 1299.99);
laptop.displayInfo();
System.out.println();
// Can change name and price
laptop.setName("Pro Gaming Laptop");
laptop.setPrice(1199.99);
// Can modify stock through controlled methods
laptop.addStock(50);
laptop.removeStock(5);
System.out.println();
// Cannot change read-only fields
// laptop.productId = "P999"; // ❌ Compilation error - private final!
// No setProductId() method exists!
laptop.displayInfo();
// Read-only properties ensure data consistency!
}
}

🔐 Immutable Class (Cannot Change After Creation)

ImmutableClass.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// Immutable class - once created, cannot be modified
final class Point { // final class - cannot be extended
// All fields are private and final
private final int x;
private final int y;
// Constructor - only way to set values
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// Only getters, NO setters!
public int getX() {
return x;
}
public int getY() {
return y;
}
// If you need to "change" values, return NEW object
public Point moveTo(int newX, int newY) {
return new Point(newX, newY); // New object, original unchanged!
}
public Point moveBy(int deltaX, int deltaY) {
return new Point(x + deltaX, y + deltaY); // New object!
}
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}
}
// Another immutable class example
final class Money {
private final double amount;
private final String currency;
public Money(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
public double getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
// Operations return NEW objects
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch!");
}
return new Money(this.amount + other.amount, this.currency);
}
public Money subtract(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch!");
}
return new Money(this.amount - other.amount, this.currency);
}
public Money multiply(double factor) {
return new Money(this.amount * factor, this.currency);
}
@Override
public String toString() {
return currency + " " + amount;
}
}
class ImmutableDemo {
public static void main(String[] args) {
// Create immutable point
Point p1 = new Point(10, 20);
System.out.println("p1: " + p1);
// "Move" the point - actually creates new point!
Point p2 = p1.moveTo(30, 40);
System.out.println("p1: " + p1); // Original unchanged!
System.out.println("p2: " + p2); // New point created
Point p3 = p1.moveBy(5, 5);
System.out.println("p1: " + p1); // Still unchanged!
System.out.println("p3: " + p3);
System.out.println();
// Create immutable money
Money wallet1 = new Money(100.00, "USD");
Money wallet2 = new Money(50.00, "USD");
System.out.println("Wallet 1: " + wallet1);
System.out.println("Wallet 2: " + wallet2);
// Add money - creates new object!
Money total = wallet1.add(wallet2);
System.out.println("Total: " + total);
System.out.println("Wallet 1 (unchanged): " + wallet1);
System.out.println("Wallet 2 (unchanged): " + wallet2);
// Immutability = thread-safe, predictable, no side effects!
}
}

🔑 Key Concepts

Private Fields

Data hidden from outside access

private String password;

Getter Methods

Public methods to read private data

public String getName() { return name; }

Setter Methods

Public methods to modify private data (with validation)

public void setAge(int age) { if(age > 0) this.age = age; }

Data Hiding

Hide implementation details from users

Users call methods, don't access fields directly

Best Practices

  • Always make fields private unless there's a good reason not to
  • Provide public getter/setter methods only when needed
  • Validate data in setter methods before assigning
  • Use meaningful names: getName(), setAge(), not get() or set()
  • Make classes immutable when possible (final fields, no setters)
  • Follow JavaBeans naming convention: getXxx() and setXxx()

💼 Interview Tips

  • Encapsulation is one of the four pillars of OOP
  • It provides data hiding and abstraction
  • Getters return field values, setters modify them
  • Validation in setters prevents invalid states
  • Private fields + public methods = encapsulation
  • Encapsulation makes code more maintainable and flexible