Java Tutorial
🔍

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.

// File: com/devstackflow/model/StudentRecord.java package com.devstackflow.model; public class StudentRecord { // ALL fields private — no external direct access private final String studentId; private final String name; private double gpa; private int creditsCompleted; private boolean graduated; public StudentRecord(String studentId, String name) { if (studentId == null || studentId.isBlank()) throw new IllegalArgumentException("Student ID required."); if (name == null || name.isBlank()) throw new IllegalArgumentException("Name required."); this.studentId = studentId; this.name = name; this.gpa = 0.0; this.creditsCompleted = 0; this.graduated = false; } // Controlled write — validates before changing state public void recordGrade(double gradePoints, int credits) { if (gradePoints < 0 || gradePoints > 10) throw new IllegalArgumentException("Grade points must be 0-10."); if (credits <= 0) throw new IllegalArgumentException("Credits must be positive."); // Weighted GPA recalculation int totalCredits = this.creditsCompleted + credits; this.gpa = ((this.gpa * this.creditsCompleted) + (gradePoints * credits)) / totalCredits; this.creditsCompleted = totalCredits; checkGraduationEligibility(); // private helper } // Controlled read — getters expose value, not reference public String getStudentId() { return studentId; } public String getName() { return name; } public double getGpa() { return Math.round(gpa * 100.0) / 100.0; } public int getCreditsCompleted() { return creditsCompleted; } public boolean isGraduated() { return graduated; } // Private helper — internal logic, not part of public contract private void checkGraduationEligibility() { if (creditsCompleted >= 160 && gpa >= 5.0) { graduated = true; System.out.println(" Graduation criteria met for: " + name); } } @Override public String toString() { return String.format("[%s] %s | GPA: %.2f | Credits: %d | %s", studentId, name, gpa, creditsCompleted, graduated ? "GRADUATED" : "IN PROGRESS"); } }
// File: StudentRecordDemo.java package com.devstackflow.model; public class StudentRecordDemo { public static void main(String[] args) { StudentRecord student = new StudentRecord("S-001", "Priya Sharma"); student.recordGrade(9.0, 30); // 30 credits, 9.0 grade points student.recordGrade(8.5, 40); student.recordGrade(8.0, 50); student.recordGrade(9.5, 40); // total = 160 credits System.out.println(student); System.out.println("GPA : " + student.getGpa()); System.out.println("Graduated: " + student.isGraduated()); // These would cause compile errors: // student.gpa = 10.0; ← private field // student.graduated = true; ← private field // student.creditsCompleted = 200; ← private field // student.checkGraduationEligibility(); ← private method } }
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.

// File: com/devstackflow/payment/PaymentCalculator.java package com.devstackflow.payment; import java.math.BigDecimal; import java.math.RoundingMode; public class PaymentCalculator { private static final double GST_RATE = 0.18; private static final double PLATFORM_FEE = 0.02; private static final double FREE_SHIPPING_ABOVE = 499.0; private static final double SHIPPING_CHARGE = 40.0; // Public method — the only thing callers need public PaymentBreakdown calculate(double basePrice, int quantity, String couponCode) { double subtotal = computeSubtotal(basePrice, quantity); // private double discount = computeDiscount(subtotal, couponCode); // private double afterDisc = subtotal - discount; double shipping = computeShipping(afterDisc); // private double gst = computeGst(afterDisc); // private double platformFee = computePlatformFee(afterDisc); // private double total = round(afterDisc + shipping + gst + platformFee); return new PaymentBreakdown(subtotal, discount, shipping, gst, platformFee, total); } // ── ALL HELPERS ARE PRIVATE ─────────────────────────────────────── private double computeSubtotal(double price, int qty) { return price * qty; } private double computeDiscount(double subtotal, String coupon) { if (coupon == null || coupon.isBlank()) return 0.0; return switch (coupon.toUpperCase()) { case "SAVE10" -> subtotal * 0.10; case "FLAT50" -> subtotal >= 500 ? 50.0 : 0.0; case "NEW20" -> subtotal * 0.20; default -> 0.0; }; } private double computeShipping(double afterDiscount) { return afterDiscount >= FREE_SHIPPING_ABOVE ? 0.0 : SHIPPING_CHARGE; } private double computeGst(double amount) { return round(amount * GST_RATE); } private double computePlatformFee(double amount) { return round(amount * PLATFORM_FEE); } private double round(double value) { return BigDecimal.valueOf(value) .setScale(2, RoundingMode.HALF_UP) .doubleValue(); } // Inner result class — also has only what callers need public static class PaymentBreakdown { public final double subtotal; public final double discount; public final double shipping; public final double gst; public final double platformFee; public final double total; private PaymentBreakdown(double subtotal, double discount, double shipping, double gst, double platformFee, double total) { this.subtotal = subtotal; this.discount = discount; this.shipping = shipping; this.gst = gst; this.platformFee = platformFee; this.total = total; } @Override public String toString() { return String.format( " Subtotal : Rs.%7.2f%n" + " Discount : Rs.%7.2f%n" + " Shipping : Rs.%7.2f%n" + " GST (18%%) : Rs.%7.2f%n" + " Platform Fee: Rs.%7.2f%n" + " ──────────────────────%n" + " TOTAL : Rs.%7.2f", subtotal, discount, shipping, gst, platformFee, total); } } }
// File: PaymentCalculatorDemo.java package com.devstackflow.demo; import com.devstackflow.payment.PaymentCalculator; public class PaymentCalculatorDemo { public static void main(String[] args) { PaymentCalculator calc = new PaymentCalculator(); System.out.println("=== Order 1: 2 items at Rs.349, coupon SAVE10 ==="); System.out.println(calc.calculate(349.0, 2, "SAVE10")); System.out.println("\n=== Order 2: 1 item at Rs.1299, no coupon ==="); System.out.println(calc.calculate(1299.0, 1, null)); System.out.println("\n=== Order 3: 3 items at Rs.199, coupon NEW20 ==="); System.out.println(calc.calculate(199.0, 3, "NEW20")); // calc.computeGst(...) ← compile error — private // calc.computeDiscount(...) ← compile error — private // calc.computeShipping(...) ← compile error — private } }
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.

// File: com/devstackflow/util/MathUtils.java package com.devstackflow.util; // Utility class — only static methods, no instances needed public final class MathUtils { // private constructor — prevents: new MathUtils() private MathUtils() { throw new UnsupportedOperationException("Utility class — do not instantiate."); } public static int factorial(int n) { if (n < 0) throw new IllegalArgumentException("n must be non-negative."); if (n == 0 || n == 1) return 1; return n * factorial(n - 1); } public static boolean isPrime(int n) { if (n < 2) return false; for (int i = 2; i <= Math.sqrt(n); i++) { if (n % i == 0) return false; } return true; } public static double percentageOf(double part, double total) { if (total == 0) throw new ArithmeticException("Total cannot be zero."); return (part / total) * 100.0; } }
// File: com/devstackflow/model/UserProfile.java package com.devstackflow.model; // Builder pattern — private constructor, public static Builder public class UserProfile { private final String userId; private final String name; private final String email; private final String phone; private final String city; private final String role; // private constructor — only the Builder can call it private UserProfile(Builder builder) { this.userId = builder.userId; this.name = builder.name; this.email = builder.email; this.phone = builder.phone; this.city = builder.city; this.role = builder.role; } public String getUserId() { return userId; } public String getName() { return name; } public String getEmail() { return email; } public String getPhone() { return phone; } public String getCity() { return city; } public String getRole() { return role; } @Override public String toString() { return String.format( "UserProfile{id=%s, name=%s, email=%s, phone=%s, city=%s, role=%s}", userId, name, email, phone, city, role); } // public static Builder — the only way to construct UserProfile public static class Builder { private final String userId; // required private final String name; // required private String email = ""; private String phone = ""; private String city = ""; private String role = "USER"; public Builder(String userId, String name) { if (userId == null || userId.isBlank()) throw new IllegalArgumentException("userId required."); if (name == null || name.isBlank()) throw new IllegalArgumentException("name required."); this.userId = userId; this.name = name; } public Builder email(String email) { this.email = email; return this; } public Builder phone(String phone) { this.phone = phone; return this; } public Builder city(String city) { this.city = city; return this; } public Builder role(String role) { this.role = role; return this; } public UserProfile build() { return new UserProfile(this); // calls private constructor } } }
// File: PrivateConstructorDemo.java package com.devstackflow.demo; import com.devstackflow.model.UserProfile; import com.devstackflow.util.MathUtils; public class PrivateConstructorDemo { public static void main(String[] args) { // Utility class — static methods only, no instantiation System.out.println("5! : " + MathUtils.factorial(5)); System.out.println("isPrime(7): " + MathUtils.isPrime(7)); System.out.println("isPrime(9): " + MathUtils.isPrime(9)); System.out.printf("80/120 : %.1f%%%n", MathUtils.percentageOf(80, 120)); // new MathUtils(); ← compile error — private constructor System.out.println(); // Builder pattern — readable construction with any optional fields UserProfile admin = new UserProfile.Builder("USR-001", "Priya Sharma") .email("priya@razorpay.com") .phone("9876543210") .city("Bengaluru") .role("ADMIN") .build(); UserProfile viewer = new UserProfile.Builder("USR-002", "Rohan Mehta") .email("rohan@razorpay.com") .build(); // city, phone, role use defaults System.out.println("Admin : " + admin); System.out.println("Viewer : " + viewer); // new UserProfile(...) ← compile error — private constructor // must use the Builder } }
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.

// File: InheritancePrivateDemo.java package com.devstackflow.demo; class Vehicle { private String engineType = "Petrol"; // private — not visible to subclass private void startEngine() { // private — not inherited System.out.println("Engine started: " + engineType); } // protected helper calls private method — this is the bridge protected void ignite() { startEngine(); // can call own private method from within the class } public String describe() { return "Vehicle with " + engineType + " engine"; } } class Car extends Vehicle { // Car CANNOT access Vehicle.engineType — it is private // Car CANNOT call Vehicle.startEngine() — it is private // This is NOT an override — it is a NEW, independent method in Car private void startEngine() { System.out.println("Car engine started (new method in Car)"); } public void drive() { ignite(); // calls protected ignite() in Vehicle startEngine(); // calls THIS class's private startEngine (not Vehicle's) } } public class InheritancePrivateDemo { public static void main(String[] args) { Car car = new Car(); car.drive(); System.out.println(car.describe()); // inherited public method // car.startEngine() ← compile error — Car.startEngine is private // car.engineType ← compile error — Vehicle.engineType is private } }
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

Scenarioprivate Accessible?Notes
Within the same class✓ YesFull access — all private members
Different class, same package✗ NoPackage membership does not override private
Subclass in same package✗ NoInheritance does not grant private access
Subclass in different package✗ NoNeither inheritance nor package helps
Non-subclass in different package✗ NoCompletely inaccessible
Same class, different instance✓ Yesprivate 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✓ YesInner class has access to outer class's private
Outer class accessing inner's private✓ YesOuter class has access to inner class's private
Interface methods✗ NoInterfaces cannot have private abstract methods (except Java 9+ private default/static)

Basic Example

public class Main { private int balance; // private field — accessible only within this class Main(int balance) { this.balance = balance; } private boolean isValid(int amount) { // private method — internal helper return amount > 0 && amount <= balance; } public boolean withdraw(int amount) { if (!isValid(amount)) { System.out.println("Invalid withdrawal: " + amount); return false; } balance -= amount; return true; } public int getBalance() { return balance; } public static void main(String[] args) { Main account = new Main(100); account.withdraw(30); account.withdraw(200); // rejected — exceeds balance System.out.println("Balance: " + account.getBalance()); } }

Output

Invalid withdrawal: 200
Balance: 70
  1. ›balance is private, so no code outside Main can read or change it directly — every change must go through withdraw().
  2. ›isValid() is a private helper method — it exists only to support withdraw() and is not part of the class's public contract.
  3. ›withdraw() and getBalance() are public — they are the only two ways the outside world can interact with an account's balance.
  4. ›Because isValid() is called before subtracting, an invalid withdrawal (200, which exceeds the balance of 100) is rejected and the balance stays unaffected until the next valid withdrawal.

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

class Parent { private int secret = 42; } class Child extends Parent { void reveal() { System.out.println(secret); // compile error — cannot access private field // Inheritance does NOT grant access to private members } } // Fix — use protected if subclasses need access: class Parent { protected int secret = 42; // now Child can access it }

Mistake 2 — Returning Mutable Private Collections Directly

public class ShoppingCart { private List<String> items = new ArrayList<>(); // WRONG — caller gets direct reference to internal list public List<String> getItems() { return items; // caller can do: cart.getItems().clear() } // CORRECT — return immutable view public List<String> getItems() { return List.copyOf(items); // or Collections.unmodifiableList(items) } }

Mistake 3 — Using private on a Top-Level Class

// compile error — 'private' not allowed here private class PaymentValidator { void validate() { } } // Fix — use default (no modifier) for package-private, or public class PaymentValidator { // package-private — valid void validate() { } }

Mistake 4 — Bypassing Validation by Making Fields Less Private

// WRONG — making field package-private "just for testing" class Temperature { double celsius; // default — test class in same package can set it directly } // CORRECT — keep private, test through the public API class Temperature { private double celsius; public void setCelsius(double value) { if (value < -273.15) throw new IllegalArgumentException("Below absolute zero."); this.celsius = value; } public double getCelsius() { return celsius; } } // Test: temperature.setCelsius(25.0) — goes through validation

Interview 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

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →