Java private Modifier
Java private Modifier
private is the most restrictive access modifier in Java. A private member is accessible only from within the class that declares it — not from subclasses, not from classes in the same package, and not from anywhere else in the entire program.
This total invisibility is a feature, not a limitation. private is what makes encapsulation real. Without it, every field in every class is a target for external code to read, write, and corrupt. With private fields and carefully designed public methods, you define exactly what callers can do — and the rules never bend.
What private Can Be Applied To
private can be placed on:
✓ Instance field → only this class reads or writes it
✓ Static field → only this class reads or writes it
✓ Instance method → only this class calls it
✓ Static method → only this class calls it
✓ Constructor → only this class (or static factory) calls new
✓ Nested / inner class → only the enclosing class can use it
✗ Top-level class → compile error — not valid on top-level class
(use default or public for top-level classes)
Key rule:
private is class-scoped, not instance-scoped.
One instance CAN access private members of ANOTHER instance
of the SAME class. This is why equals() works:
public boolean equals(Object obj) {
if (obj instanceof MyClass other) {
return this.privateField == other.privateField; // valid!
}
return false;
}
1 — private Fields: The Foundation of Encapsulation
Private fields are the most common use of private. They prevent external code from directly reading or writing an object's state, forcing all changes to go through methods that enforce validation and business rules.
1// File: com/devstackflow/model/StudentRecord.java
2package com.devstackflow.model;
3
4public class StudentRecord {
5
6 // ALL fields private — no external direct access
7 private final String studentId;
8 private final String name;
9 private double gpa;
10 private int creditsCompleted;
11 private boolean graduated;
12
13 public StudentRecord(String studentId, String name) {
14 if (studentId == null || studentId.isBlank())
15 throw new IllegalArgumentException("Student ID required.");
16 if (name == null || name.isBlank())
17 throw new IllegalArgumentException("Name required.");
18 this.studentId = studentId;
19 this.name = name;
20 this.gpa = 0.0;
21 this.creditsCompleted = 0;
22 this.graduated = false;
23 }
24
25 // Controlled write — validates before changing state
26 public void recordGrade(double gradePoints, int credits) {
27 if (gradePoints < 0 || gradePoints > 10)
28 throw new IllegalArgumentException("Grade points must be 0-10.");
29 if (credits <= 0)
30 throw new IllegalArgumentException("Credits must be positive.");
31
32 // Weighted GPA recalculation
33 int totalCredits = this.creditsCompleted + credits;
34 this.gpa = ((this.gpa * this.creditsCompleted)
35 + (gradePoints * credits)) / totalCredits;
36 this.creditsCompleted = totalCredits;
37 checkGraduationEligibility(); // private helper
38 }
39
40 // Controlled read — getters expose value, not reference
41 public String getStudentId() { return studentId; }
42 public String getName() { return name; }
43 public double getGpa() { return Math.round(gpa * 100.0) / 100.0; }
44 public int getCreditsCompleted() { return creditsCompleted; }
45 public boolean isGraduated() { return graduated; }
46
47 // Private helper — internal logic, not part of public contract
48 private void checkGraduationEligibility() {
49 if (creditsCompleted >= 160 && gpa >= 5.0) {
50 graduated = true;
51 System.out.println(" Graduation criteria met for: " + name);
52 }
53 }
54
55 @Override
56 public String toString() {
57 return String.format("[%s] %s | GPA: %.2f | Credits: %d | %s",
58 studentId, name, gpa, creditsCompleted,
59 graduated ? "GRADUATED" : "IN PROGRESS");
60 }
61}1// File: StudentRecordDemo.java
2package com.devstackflow.model;
3
4public class StudentRecordDemo {
5
6 public static void main(String[] args) {
7
8 StudentRecord student = new StudentRecord("S-001", "Priya Sharma");
9
10 student.recordGrade(9.0, 30); // 30 credits, 9.0 grade points
11 student.recordGrade(8.5, 40);
12 student.recordGrade(8.0, 50);
13 student.recordGrade(9.5, 40); // total = 160 credits
14
15 System.out.println(student);
16 System.out.println("GPA : " + student.getGpa());
17 System.out.println("Graduated: " + student.isGraduated());
18
19 // These would cause compile errors:
20 // student.gpa = 10.0; ← private field
21 // student.graduated = true; ← private field
22 // student.creditsCompleted = 200; ← private field
23 // student.checkGraduationEligibility(); ← private method
24 }
25}Output:
Graduation criteria met for: Priya Sharma
[S-001] Priya Sharma | GPA: 8.74 | Credits: 160 | GRADUATED
GPA : 8.74
Graduated: true
gpa is private — no external code can set it to 10.0 to cheat. Every grade change goes through recordGrade(), which validates input and recalculates correctly. The checkGraduationEligibility() private helper is an internal concern — the caller does not need to know it exists.
2 — private Methods: Internal Helpers
Private methods are implementation helpers that support the public methods. They break complex logic into readable steps without exposing those steps as part of the public API.
1// File: com/devstackflow/payment/PaymentCalculator.java
2package com.devstackflow.payment;
3
4import java.math.BigDecimal;
5import java.math.RoundingMode;
6
7public class PaymentCalculator {
8
9 private static final double GST_RATE = 0.18;
10 private static final double PLATFORM_FEE = 0.02;
11 private static final double FREE_SHIPPING_ABOVE = 499.0;
12 private static final double SHIPPING_CHARGE = 40.0;
13
14 // Public method — the only thing callers need
15 public PaymentBreakdown calculate(double basePrice,
16 int quantity,
17 String couponCode) {
18 double subtotal = computeSubtotal(basePrice, quantity); // private
19 double discount = computeDiscount(subtotal, couponCode); // private
20 double afterDisc = subtotal - discount;
21 double shipping = computeShipping(afterDisc); // private
22 double gst = computeGst(afterDisc); // private
23 double platformFee = computePlatformFee(afterDisc); // private
24 double total = round(afterDisc + shipping + gst + platformFee);
25
26 return new PaymentBreakdown(subtotal, discount, shipping,
27 gst, platformFee, total);
28 }
29
30 // ── ALL HELPERS ARE PRIVATE ───────────────────────────────────────
31
32 private double computeSubtotal(double price, int qty) {
33 return price * qty;
34 }
35
36 private double computeDiscount(double subtotal, String coupon) {
37 if (coupon == null || coupon.isBlank()) return 0.0;
38 return switch (coupon.toUpperCase()) {
39 case "SAVE10" -> subtotal * 0.10;
40 case "FLAT50" -> subtotal >= 500 ? 50.0 : 0.0;
41 case "NEW20" -> subtotal * 0.20;
42 default -> 0.0;
43 };
44 }
45
46 private double computeShipping(double afterDiscount) {
47 return afterDiscount >= FREE_SHIPPING_ABOVE ? 0.0 : SHIPPING_CHARGE;
48 }
49
50 private double computeGst(double amount) {
51 return round(amount * GST_RATE);
52 }
53
54 private double computePlatformFee(double amount) {
55 return round(amount * PLATFORM_FEE);
56 }
57
58 private double round(double value) {
59 return BigDecimal.valueOf(value)
60 .setScale(2, RoundingMode.HALF_UP)
61 .doubleValue();
62 }
63
64 // Inner result class — also has only what callers need
65 public static class PaymentBreakdown {
66 public final double subtotal;
67 public final double discount;
68 public final double shipping;
69 public final double gst;
70 public final double platformFee;
71 public final double total;
72
73 private PaymentBreakdown(double subtotal, double discount,
74 double shipping, double gst,
75 double platformFee, double total) {
76 this.subtotal = subtotal;
77 this.discount = discount;
78 this.shipping = shipping;
79 this.gst = gst;
80 this.platformFee = platformFee;
81 this.total = total;
82 }
83
84 @Override
85 public String toString() {
86 return String.format(
87 " Subtotal : Rs.%7.2f%n" +
88 " Discount : Rs.%7.2f%n" +
89 " Shipping : Rs.%7.2f%n" +
90 " GST (18%%) : Rs.%7.2f%n" +
91 " Platform Fee: Rs.%7.2f%n" +
92 " ──────────────────────%n" +
93 " TOTAL : Rs.%7.2f",
94 subtotal, discount, shipping, gst, platformFee, total);
95 }
96 }
97}1// File: PaymentCalculatorDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.payment.PaymentCalculator;
5
6public class PaymentCalculatorDemo {
7
8 public static void main(String[] args) {
9
10 PaymentCalculator calc = new PaymentCalculator();
11
12 System.out.println("=== Order 1: 2 items at Rs.349, coupon SAVE10 ===");
13 System.out.println(calc.calculate(349.0, 2, "SAVE10"));
14
15 System.out.println("\n=== Order 2: 1 item at Rs.1299, no coupon ===");
16 System.out.println(calc.calculate(1299.0, 1, null));
17
18 System.out.println("\n=== Order 3: 3 items at Rs.199, coupon NEW20 ===");
19 System.out.println(calc.calculate(199.0, 3, "NEW20"));
20
21 // calc.computeGst(...) ← compile error — private
22 // calc.computeDiscount(...) ← compile error — private
23 // calc.computeShipping(...) ← compile error — private
24 }
25}Output:
=== Order 1: 2 items at Rs.349, coupon SAVE10 ===
Subtotal : Rs. 698.00
Discount : Rs. 69.80
Shipping : Rs. 0.00
GST (18%) : Rs. 113.08
Platform Fee: Rs. 12.56
──────────────────────
TOTAL : Rs. 753.84
=== Order 2: 1 item at Rs.1299, no coupon ===
Subtotal : Rs.1299.00
Discount : Rs. 0.00
Shipping : Rs. 0.00
GST (18%) : Rs. 233.82
Platform Fee: Rs. 25.98
──────────────────────
TOTAL : Rs.1558.80
=== Order 3: 3 items at Rs.199, coupon NEW20 ===
Subtotal : Rs. 597.00
Discount : Rs. 119.40
Shipping : Rs. 0.00
GST (18%) : Rs. 86.69
Platform Fee: Rs. 9.63
──────────────────────
TOTAL : Rs. 573.92
Six private methods handle the calculation steps. The caller only needs one method: calculate(). If the GST rate changes or a new discount type is added, the private methods are updated internally with zero impact on callers.
3 — private Constructors: Controlled Instantiation
A private constructor prevents direct object creation with new. It is used in three important design patterns: Singleton, Utility class, and Builder.
1// File: com/devstackflow/util/MathUtils.java
2package com.devstackflow.util;
3
4// Utility class — only static methods, no instances needed
5public final class MathUtils {
6
7 // private constructor — prevents: new MathUtils()
8 private MathUtils() {
9 throw new UnsupportedOperationException("Utility class — do not instantiate.");
10 }
11
12 public static int factorial(int n) {
13 if (n < 0) throw new IllegalArgumentException("n must be non-negative.");
14 if (n == 0 || n == 1) return 1;
15 return n * factorial(n - 1);
16 }
17
18 public static boolean isPrime(int n) {
19 if (n < 2) return false;
20 for (int i = 2; i <= Math.sqrt(n); i++) {
21 if (n % i == 0) return false;
22 }
23 return true;
24 }
25
26 public static double percentageOf(double part, double total) {
27 if (total == 0) throw new ArithmeticException("Total cannot be zero.");
28 return (part / total) * 100.0;
29 }
30}1// File: com/devstackflow/model/UserProfile.java
2package com.devstackflow.model;
3
4// Builder pattern — private constructor, public static Builder
5public class UserProfile {
6
7 private final String userId;
8 private final String name;
9 private final String email;
10 private final String phone;
11 private final String city;
12 private final String role;
13
14 // private constructor — only the Builder can call it
15 private UserProfile(Builder builder) {
16 this.userId = builder.userId;
17 this.name = builder.name;
18 this.email = builder.email;
19 this.phone = builder.phone;
20 this.city = builder.city;
21 this.role = builder.role;
22 }
23
24 public String getUserId() { return userId; }
25 public String getName() { return name; }
26 public String getEmail() { return email; }
27 public String getPhone() { return phone; }
28 public String getCity() { return city; }
29 public String getRole() { return role; }
30
31 @Override
32 public String toString() {
33 return String.format(
34 "UserProfile{id=%s, name=%s, email=%s, phone=%s, city=%s, role=%s}",
35 userId, name, email, phone, city, role);
36 }
37
38 // public static Builder — the only way to construct UserProfile
39 public static class Builder {
40 private final String userId; // required
41 private final String name; // required
42 private String email = "";
43 private String phone = "";
44 private String city = "";
45 private String role = "USER";
46
47 public Builder(String userId, String name) {
48 if (userId == null || userId.isBlank())
49 throw new IllegalArgumentException("userId required.");
50 if (name == null || name.isBlank())
51 throw new IllegalArgumentException("name required.");
52 this.userId = userId;
53 this.name = name;
54 }
55
56 public Builder email(String email) { this.email = email; return this; }
57 public Builder phone(String phone) { this.phone = phone; return this; }
58 public Builder city(String city) { this.city = city; return this; }
59 public Builder role(String role) { this.role = role; return this; }
60
61 public UserProfile build() {
62 return new UserProfile(this); // calls private constructor
63 }
64 }
65}1// File: PrivateConstructorDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.model.UserProfile;
5import com.devstackflow.util.MathUtils;
6
7public class PrivateConstructorDemo {
8
9 public static void main(String[] args) {
10
11 // Utility class — static methods only, no instantiation
12 System.out.println("5! : " + MathUtils.factorial(5));
13 System.out.println("isPrime(7): " + MathUtils.isPrime(7));
14 System.out.println("isPrime(9): " + MathUtils.isPrime(9));
15 System.out.printf("80/120 : %.1f%%%n", MathUtils.percentageOf(80, 120));
16 // new MathUtils(); ← compile error — private constructor
17
18 System.out.println();
19
20 // Builder pattern — readable construction with any optional fields
21 UserProfile admin = new UserProfile.Builder("USR-001", "Priya Sharma")
22 .email("priya@razorpay.com")
23 .phone("9876543210")
24 .city("Bengaluru")
25 .role("ADMIN")
26 .build();
27
28 UserProfile viewer = new UserProfile.Builder("USR-002", "Rohan Mehta")
29 .email("rohan@razorpay.com")
30 .build(); // city, phone, role use defaults
31
32 System.out.println("Admin : " + admin);
33 System.out.println("Viewer : " + viewer);
34
35 // new UserProfile(...) ← compile error — private constructor
36 // must use the Builder
37 }
38}Output:
5! : 120
isPrime(7): true
isPrime(9): false
80/120 : 66.7%
Admin : UserProfile{id=USR-001, name=Priya Sharma, email=priya@razorpay.com, phone=9876543210, city=Bengaluru, role=ADMIN}
Viewer : UserProfile{id=USR-002, name=Rohan Mehta, email=rohan@razorpay.com, phone=, city=, role=USER}
4 — private and Inheritance
Private members are not inherited by subclasses. A subclass cannot access or override a private method of its parent. It can define a method with the same name — but this is not overriding, it is a completely independent new method.
1// File: InheritancePrivateDemo.java
2package com.devstackflow.demo;
3
4class Vehicle {
5
6 private String engineType = "Petrol"; // private — not visible to subclass
7
8 private void startEngine() { // private — not inherited
9 System.out.println("Engine started: " + engineType);
10 }
11
12 // protected helper calls private method — this is the bridge
13 protected void ignite() {
14 startEngine(); // can call own private method from within the class
15 }
16
17 public String describe() {
18 return "Vehicle with " + engineType + " engine";
19 }
20}
21
22class Car extends Vehicle {
23
24 // Car CANNOT access Vehicle.engineType — it is private
25 // Car CANNOT call Vehicle.startEngine() — it is private
26 // This is NOT an override — it is a NEW, independent method in Car
27 private void startEngine() {
28 System.out.println("Car engine started (new method in Car)");
29 }
30
31 public void drive() {
32 ignite(); // calls protected ignite() in Vehicle
33 startEngine(); // calls THIS class's private startEngine (not Vehicle's)
34 }
35}
36
37public class InheritancePrivateDemo {
38
39 public static void main(String[] args) {
40
41 Car car = new Car();
42 car.drive();
43
44 System.out.println(car.describe()); // inherited public method
45
46 // car.startEngine() ← compile error — Car.startEngine is private
47 // car.engineType ← compile error — Vehicle.engineType is private
48 }
49}Output:
Engine started: Petrol
Car engine started (new method in Car)
Vehicle with Petrol engine
Vehicle.startEngine() is called from ignite() — inside Vehicle itself, where private access is permitted. Car.startEngine() is a completely unrelated method — not an override. Private methods are not part of the inheritance chain.
private — Complete Behaviour Summary Table
| Scenario | private Accessible? | Notes |
|---|---|---|
| Within the same class | ✓ Yes | Full access — all private members |
| Different class, same package | ✗ No | Package membership does not override private |
| Subclass in same package | ✗ No | Inheritance does not grant private access |
| Subclass in different package | ✗ No | Neither inheritance nor package helps |
| Non-subclass in different package | ✗ No | Completely inaccessible |
| Same class, different instance | ✓ Yes | private is class-scoped, not instance-scoped |
| Reflection with setAccessible(true) | ✓ Yes (bypasses) | Allowed in Java <16, restricted in Java 16+ |
| Inner class accessing outer's private | ✓ Yes | Inner class has access to outer class's private |
| Outer class accessing inner's private | ✓ Yes | Outer class has access to inner class's private |
| Interface methods | ✗ No | Interfaces cannot have private abstract methods (except Java 9+ private default/static) |
Real-World Example — Secure Password Manager
The Business Problem
A credential management service at a platform like CRED or Razorpay stores hashed passwords. The raw password, the salt used for hashing, and the hashing algorithm are all private — no external code should ever see or touch them. Only the verify() method is public — the entire security mechanism lives behind that single entry point.
1// File: com/credapp/security/PasswordManager.java
2package com.credapp.security;
3
4import java.security.MessageDigest;
5import java.security.SecureRandom;
6import java.util.Base64;
7import java.util.HashMap;
8import java.util.Map;
9
10public class PasswordManager {
11
12 // private — internal storage; no external access to hashed values
13 private final Map<String, String> hashedPasswords = new HashMap<>();
14 private final Map<String, String> salts = new HashMap<>();
15
16 private static final String HASH_ALGORITHM = "SHA-256";
17 private static final int SALT_BYTES = 16;
18
19 // public API — the only two operations callers need
20 public boolean register(String userId, String rawPassword) {
21 if (userId == null || userId.isBlank()) return false;
22 if (!isStrongPassword(rawPassword)) { // private validator
23 System.out.println(" Rejected: password too weak for " + userId);
24 return false;
25 }
26 String salt = generateSalt(); // private
27 String hashed = hash(rawPassword, salt); // private
28 hashedPasswords.put(userId, hashed);
29 salts.put(userId, salt);
30 System.out.println(" Registered: " + userId);
31 return true;
32 }
33
34 public boolean verify(String userId, String rawPassword) {
35 if (!hashedPasswords.containsKey(userId)) return false;
36 String storedSalt = salts.get(userId);
37 String storedHash = hashedPasswords.get(userId);
38 String attemptHash = hash(rawPassword, storedSalt); // private
39 return storedHash.equals(attemptHash);
40 }
41
42 // ── ALL IMPLEMENTATION IS PRIVATE ────────────────────────────────
43
44 private String generateSalt() {
45 SecureRandom random = new SecureRandom();
46 byte[] saltBytes = new byte[SALT_BYTES];
47 random.nextBytes(saltBytes);
48 return Base64.getEncoder().encodeToString(saltBytes);
49 }
50
51 private String hash(String password, String salt) {
52 try {
53 MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
54 String salted = salt + password;
55 byte[] hashBytes = digest.digest(salted.getBytes());
56 return Base64.getEncoder().encodeToString(hashBytes);
57 } catch (Exception e) {
58 throw new RuntimeException("Hashing failed", e);
59 }
60 }
61
62 private boolean isStrongPassword(String password) {
63 if (password == null || password.length() < 8) return false;
64 boolean hasUpper = password.chars().anyMatch(Character::isUpperCase);
65 boolean hasDigit = password.chars().anyMatch(Character::isDigit);
66 boolean hasSpecial = password.chars().anyMatch(c -> "!@#$%^&*".indexOf(c) >= 0);
67 return hasUpper && hasDigit && hasSpecial;
68 }
69}1// File: com/credapp/PasswordManagerDemo.java
2package com.credapp;
3
4import com.credapp.security.PasswordManager;
5
6public class PasswordManagerDemo {
7
8 public static void main(String[] args) {
9
10 System.out.println("╔══════════════════════════════════════════╗");
11 System.out.println("║ CRED — SECURE PASSWORD MANAGER DEMO ║");
12 System.out.println("╚══════════════════════════════════════════╝\n");
13
14 PasswordManager pm = new PasswordManager();
15
16 System.out.println("=== Registration ===");
17 pm.register("priya", "weak"); // too weak
18 pm.register("priya", "StrongPass@1"); // strong — accepted
19 pm.register("rohan", "Secure#99"); // strong — accepted
20 pm.register("sneha", "NoDigit!"); // missing digit — rejected
21 pm.register("sneha", "Digit1Only"); // missing special — rejected
22 pm.register("sneha", "D1git&Special"); // strong — accepted
23
24 System.out.println("\n=== Verification ===");
25 System.out.printf("priya + correct : %s%n", pm.verify("priya", "StrongPass@1") ? "GRANTED" : "DENIED");
26 System.out.printf("priya + wrong : %s%n", pm.verify("priya", "WrongPass@1") ? "GRANTED" : "DENIED");
27 System.out.printf("rohan + correct : %s%n", pm.verify("rohan", "Secure#99") ? "GRANTED" : "DENIED");
28 System.out.printf("rohan + wrong : %s%n", pm.verify("rohan", "secure#99") ? "GRANTED" : "DENIED");
29 System.out.printf("sneha + correct : %s%n", pm.verify("sneha", "D1git&Special") ? "GRANTED" : "DENIED");
30 System.out.printf("unknown + any : %s%n", pm.verify("unknown", "anything") ? "GRANTED" : "DENIED");
31
32 // No external access to implementation:
33 // pm.hashedPasswords ← compile error — private field
34 // pm.salts ← compile error — private field
35 // pm.hash(...) ← compile error — private method
36 // pm.generateSalt() ← compile error — private method
37 // pm.isStrongPassword() ← compile error — private method
38 }
39}Output:
╔══════════════════════════════════════════╗
║ CRED — SECURE PASSWORD MANAGER DEMO ║
╚══════════════════════════════════════════╝
=== Registration ===
Rejected: password too weak for priya
Registered: priya
Registered: rohan
Rejected: password too weak for sneha
Rejected: password too weak for sneha
Registered: sneha
=== Verification ===
priya + correct : GRANTED
priya + wrong : DENIED
rohan + correct : GRANTED
rohan + wrong : DENIED
sneha + correct : GRANTED
unknown + any : DENIED
The entire security mechanism — hashing, salting, strength validation — lives behind private. External code has exactly two capabilities: register() and verify(). The hashed passwords, the salts, and the algorithm are completely invisible. Even if an attacker gains access to the running Java process through reflection, they still cannot get the raw passwords — because those are never stored.
Best Practices
Make every field private by default. Start every field declaration with private. Only change this when you have a specific, justified reason. The discipline of starting with private and consciously widening access when needed produces far fewer bugs than starting with no modifier or public and trying to restrict later.
Prefer private methods for any logic that is not part of the public contract. If a method only exists to serve other methods in the same class — a helper, a validator, a calculator step — it belongs private. This keeps the public API focused, makes the class easier to understand, and lets you refactor the internals without any risk of breaking callers.
Use private constructor for utility classes. A class that contains only static methods should prevent instantiation with a private constructor. This communicates intent — "this is not an object, it is a namespace for functions" — and prevents accidental new MathUtils() calls that do nothing useful.
Expose as little state as possible through getters. A getter that returns a mutable collection — public List<Item> getItems() — gives callers direct access to internal state. They can add(), remove(), or clear() your collection. Return a defensive copy or an unmodifiable view: return List.copyOf(items) or return Collections.unmodifiableList(items).
Common Mistakes
Mistake 1 — Thinking Subclasses Can Access private
1class Parent {
2 private int secret = 42;
3}
4
5class Child extends Parent {
6 void reveal() {
7 System.out.println(secret); // compile error — cannot access private field
8 // Inheritance does NOT grant access to private members
9 }
10}
11
12// Fix — use protected if subclasses need access:
13class Parent {
14 protected int secret = 42; // now Child can access it
15}Mistake 2 — Returning Mutable Private Collections Directly
1public class ShoppingCart {
2 private List<String> items = new ArrayList<>();
3
4 // WRONG — caller gets direct reference to internal list
5 public List<String> getItems() {
6 return items; // caller can do: cart.getItems().clear()
7 }
8
9 // CORRECT — return immutable view
10 public List<String> getItems() {
11 return List.copyOf(items); // or Collections.unmodifiableList(items)
12 }
13}Mistake 3 — Using private on a Top-Level Class
1// compile error — 'private' not allowed here
2private class PaymentValidator {
3 void validate() { }
4}
5
6// Fix — use default (no modifier) for package-private, or public
7class PaymentValidator { // package-private — valid
8 void validate() { }
9}Mistake 4 — Bypassing Validation by Making Fields Less Private
1// WRONG — making field package-private "just for testing"
2class Temperature {
3 double celsius; // default — test class in same package can set it directly
4}
5
6// CORRECT — keep private, test through the public API
7class Temperature {
8 private double celsius;
9
10 public void setCelsius(double value) {
11 if (value < -273.15) throw new IllegalArgumentException("Below absolute zero.");
12 this.celsius = value;
13 }
14 public double getCelsius() { return celsius; }
15}
16// Test: temperature.setCelsius(25.0) — goes through validationInterview Questions
Q1. What does the private access modifier do in Java?
private restricts access to the declaring class only. A private field, method, constructor, or nested class is invisible to all other classes — including subclasses in the same package. Only code inside the class body where the member is declared can access it. This is the most restrictive modifier, and it is the primary tool for encapsulation: forcing all state changes through validated public methods while keeping implementation details completely hidden.
Q2. Can a subclass access a private member of its parent class?
No. Private members are not inherited and are not accessible in subclasses. A subclass cannot read a private field, call a private method, or override a private method of the parent. A subclass can define a method with the same name as a parent's private method, but this is not an override — it is a brand new method with no relationship to the parent's version. To make members accessible to subclasses while keeping them hidden from unrelated classes, use protected.
Q3. Is private in Java instance-scoped or class-scoped?
Class-scoped. One instance of a class can access the private members of a different instance of the same class, as long as the accessing code is inside that class. This is why equals() implementations can directly compare this.privateField == other.privateField — other is a different instance, but the code is inside the class, so private access is granted. Languages like Python enforce instance-level privacy; Java enforces it at the class level.
Q4. What is the purpose of a private constructor?
A private constructor prevents direct instantiation with new ClassName() from any code outside the class. It is used in three patterns: the Singleton pattern (only one instance ever created, returned through a static factory method); the Utility class pattern (only static methods, no instances needed); and the Builder pattern (a public static Builder inner class calls the private constructor after assembling all parameters). In all three cases, private constructors give the class full control over how and whether instances are created.
Q5. What is the difference between private and default access in Java?
private restricts access to the declaring class only. default (no modifier) restricts access to the declaring class plus all other classes in the same package. A private field cannot be accessed from any other class — even a class sitting right next to it in the same package. A default field can be accessed by all classes in the same package. private is about class-level encapsulation; default is about package-level encapsulation.
Q6. Can you use private on an interface method in Java?
In Java 9 and later, interfaces can have private methods with a body — used as internal helpers shared between default and static methods within the interface itself. These private interface methods are not accessible to implementing classes or any external code — they are purely for code reuse within the interface. Before Java 9, all interface methods had to be either abstract (implicitly public) or default. Abstract private methods in interfaces are not allowed — a private interface method must have a body.
FAQs
Can reflection access private members in Java?
Yes — field.setAccessible(true) bypasses the private restriction and grants reflective access. This was freely available in Java 8 and earlier. Java 9 introduced the module system with stronger encapsulation, and Java 16 added --illegal-access restrictions. In modern Java (16+), accessing private members of classes in other modules via reflection throws InaccessibleObjectException by default unless the module explicitly opens the package. Within the same module or application (without modules), setAccessible(true) still works but is considered a code smell.
Does private affect performance?
No. private, public, protected, and default produce identical bytecode for method calls and field access. The JVM performs no visibility check at runtime for private members — the check is done and resolved entirely by the compiler. A private method executes at the exact same speed as a public method.
Can a private field have a getter but no setter?
Yes — this creates an effectively read-only field from the outside. private final String name with only public String getName() and no setName() means external code can read the name but never change it. The final modifier also prevents the class's own methods from reassigning the field after construction. Together, private final with only a getter is the standard way to create immutable-from-the-outside fields.
What is the difference between private final and just private?
private restricts who can access the field — only the declaring class. final restricts when the field can be assigned — only once, at declaration or in the constructor. A private field without final can be reassigned any number of times within the class. A private final field is assigned once and cannot change after that, even within the declaring class. private final is the default choice for fields that represent fixed identity attributes — IDs, names, creation timestamps.
Should you write getters and setters for every private field?
No. Blindly generating getters and setters for every field defeats the purpose of private. A setter gives external code indirect but complete control over the field — no different from making it public without validation. Only add getters when external code genuinely needs to read the value, and only add setters when external code genuinely needs to change it — with full validation inside. Many fields are internal implementation details that should have neither getter nor setter.
Summary
private is the enforcement mechanism for encapsulation. It guarantees that a field, method, or constructor is only accessible within the class that declares it — no exceptions, no package exceptions, no inheritance exceptions.
Every field that is an internal implementation detail should be private. Every helper method that supports public methods but is not part of the contract should be private. Every constructor where you want to control instantiation should be private, paired with a public static factory method or Builder.
The discipline: declare everything private, then consciously widen to default, protected, or public only when something genuinely needs to be accessible beyond the class boundary. Code built on this discipline is easier to change, easier to test through the public API, and much harder to misuse.
What to Read Next
Learn how the protected modifier works across packages and subclasses.