Java Access Modifiers: public, private, protected & Default Explained

โ˜• Java 21+ LTS ๐ŸŸข Chapter 43 of 47 ๐Ÿ“‚ Phase 10: Encapsulation & Access Modifiers ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter:

Encapsulation ante enti? ยท Why Access Control Matters ยท 4 Access Levels ยท private: Strictest ยท default (package-private) ยท protected: Inheritance + Package ยท public: Widest ยท Access Modifier Comparison Table ยท Applying to Fields, Methods & Classes

Comprehensive masterclass on Java Access Modifiers: understanding the 4-tier visibility system (private, default, protected, public), how each level controls access across methods, classes, packages, and inheritance hierarchies, and how choosing the right modifier builds robust, maintainable software boundaries.

1. Encapsulation Ante Enti? (Why Access Control Exists)

Encapsulation is the OOP principle of bundling data (fields) and behavior (methods) into one unit, while restricting direct access to internal implementation details.

Real-World Analogy โ€” ATM Machine:
- You (the external user) can only interact with the ATM through its public interface: insert card, enter PIN, select withdrawal amount.
- The ATM's internal banking logic (database queries, encryption keys, cash counter servo motors) is completely hidden and inaccessible to you.
- This separation protects the system from misuse and allows the bank to upgrade internal mechanics without affecting how you use the ATM.

In Java, Access Modifiers are the "access control gates" that decide who can see and interact with which parts of your code.

2. The 4 Access Levels: From Most Restrictive to Widest

Modifier Same Class Same Package Subclass (other package) World (any class)
private โœ… Yes โŒ No โŒ No โŒ No
default (no keyword) โœ… Yes โœ… Yes โŒ No โŒ No
protected โœ… Yes โœ… Yes โœ… Yes โŒ No
public โœ… Yes โœ… Yes โœ… Yes โœ… Yes

3. private โ€” The Strictest Access Level

A private member is accessible only within the same class. No other class, even a subclass or a class in the same package, can access it directly.

Use private for: Instance fields (to enforce encapsulation), internal helper methods, implementation details that must never leak outside the class boundary.

class BankAccount {
    private double balance; // No external code can touch this directly!

private void logTransaction(String msg) { // Internal helper only
System.out.println("[LOG] " + msg);
}
}

class Main {
public static void main(String[] args) {
BankAccount acc = new BankAccount();
// acc.balance = 9999; // COMPILE ERROR: balance has private access
}
}

4. Default (Package-Private) โ€” No Keyword Required

When you declare a member with no access modifier, it gets default (package-private) access. This member is visible to all classes within the same package, but invisible to classes in different packages.

// File: com/company/utils/MathHelper.java
package com.company.utils;

class MathHelper { // Default class access
double computeTax(double income) { // Default method access
return income * 0.20;
}
}

// File: com/company/ui/Dashboard.java
package com.company.ui; // DIFFERENT PACKAGE!
import com.company.utils.MathHelper;

class Dashboard {
void show() {
MathHelper h = new MathHelper(); // COMPILE ERROR: MathHelper not visible!
}
}

5. protected โ€” Package + Subclass Access

A protected member is accessible within the same package AND by subclasses in any package (through inheritance). This is the modifier designed specifically to support the Inheritance hierarchy.

// File: Animal.java
public class Animal {
    protected String name;         // Subclasses can access!
    protected void breathe() {     // Subclasses can use/override!
        System.out.println(name + " is breathing.");
    }
}

// File: Dog.java (Could be in a different package)
public class Dog extends Animal {
public void bark() {
breathe(); // Can call protected method from parent!
System.out.println(name + " says: Woof!"); // Can access protected field!
}
}

6. public โ€” Widest Access (Open to Everyone)

A public member is accessible from any class in any package across the entire application. This is the access level for your public API โ€” the methods and classes you intentionally expose.

Use public for: API entry points (service methods, constructors, getters/setters), constants, and main application classes.

The Principle of Least Privilege:
Always start with the most restrictive access (private) and only widen it when absolutely required. This minimizes unintended coupling between classes.

Beginner Example & Code Anatomy

โ˜• Main.java โ€” Chapter 43 Core Example
class BankAccount {
    // PRIVATE: Internal state, fully protected
    private double balance;
    private String pin;
    private int failedAttempts;

    // Package-private (default): Only used within same package
    static final int MAX_FAILED_ATTEMPTS = 3;

    // PUBLIC: The official deposit API (User requested snippet)
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            logTransaction("DEPOSIT", amount);
        } else {
            System.out.println("  [WARN] Deposit amount must be positive!");
        }
    }

    // PUBLIC: Getter for balance (Controlled read-only access)
    public double getBalance() {
        return balance;
    }

    // PRIVATE: Internal implementation detail, never exposed
    private void logTransaction(String type, double amount) {
        System.out.printf("  [INTERNAL LOG] %s: $%.2f | New Balance: $%.2f%n",
                type, amount, balance);
    }

    // PUBLIC: Constructor
    public BankAccount(double initialDeposit, String pin) {
        if (initialDeposit >= 0) this.balance = initialDeposit;
        this.pin = pin;
        this.failedAttempts = 0;
    }

    // PUBLIC: PIN verification
    public boolean verifyPin(String inputPin) {
        if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
            System.out.println("  [SECURITY] Account locked. Too many failed PIN attempts!");
            return false;
        }
        if (pin.equals(inputPin)) {
            failedAttempts = 0;
            return true;
        }
        failedAttempts++;
        System.out.printf("  [WARN] Wrong PIN. Attempts remaining: %d%n",
                MAX_FAILED_ATTEMPTS - failedAttempts);
        return false;
    }

    // PUBLIC: toString for display
    @Override
    public String toString() {
        return String.format("BankAccount{balance=$%.2f}", balance);
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== User Requested BankAccount Demo ===");
        BankAccount account = new BankAccount(1000.0, "9876");

        // Public API works perfectly
        account.deposit(500.0);
        account.deposit(250.0);
        System.out.println("Current Balance: $" + account.getBalance());

        System.out.println("
=== PIN Security System ===");
        account.verifyPin("1234");  // Wrong PIN
        account.verifyPin("4321");  // Wrong PIN
        account.verifyPin("9876");  // Correct PIN

        System.out.println("
=== Private Field Protection Demonstration ===");
        // These would cause COMPILE ERRORS if uncommented:
        // account.balance = 999999;    // private field
        // account.logTransaction(...); // private method
        System.out.println("Private fields cannot be accessed externally!");
        System.out.println("Final account state: " + account);
    }
}
๐Ÿ’ป Program Console Output
=== User Requested BankAccount Demo === [INTERNAL LOG] DEPOSIT: $500.00 | New Balance: $1500.00 [INTERNAL LOG] DEPOSIT: $250.00 | New Balance: $1750.00 Current Balance: $1750.0 === PIN Security System === [WARN] Wrong PIN. Attempts remaining: 2 [WARN] Wrong PIN. Attempts remaining: 1 Current PIN verified successfully. === Private Field Protection Demonstration === Private fields cannot be accessed externally! Final account state: BankAccount{balance=$1750.00}

๐Ÿ” Line-by-Line Code Explanation

private double balance;

Restricts direct access to balance โ€” only methods inside BankAccount can read or modify it.

public void deposit(double amount)

Public entry point accessible from any class; the controlled gateway to modifying the private balance.

private void logTransaction(String type, double amount)

Internal helper method invisible outside BankAccount โ€” implementation detail free to change anytime.

public double getBalance()

Controlled read-only access to balance โ€” callers can read but not set the value directly.

Practical Real-World Example

โ˜• PracticalApplication.java โ€” Industry Implementation
// Package-private class (library-internal utility)
class PasswordHasher {
    // Only used within same package โ€” no need to expose publicly
    static String hash(String password) {
        // Simplified hash simulation (not production!)
        int hash = password.hashCode();
        return "HASH_" + Math.abs(hash);
    }
}

// Public class exposing a clean API
public class PracticalApplication {
    public static void main(String[] args) {
        System.out.println("=== Access Modifier Demo ===");
        String raw = "mySecretPass123";
        String hashed = PasswordHasher.hash(raw); // Accessible (same package)
        System.out.println("Raw     : " + raw);
        System.out.println("Hashed  : " + hashed);
    }
}
๐Ÿ’ป Practical Console Output
=== Access Modifier Demo === Raw : mySecretPass123 Hashed : HASH_236872342
โš ๏ธ Common Mistakes & Professional Best Practices
  • Making all fields public for convenience โ€” this completely breaks encapsulation and creates tightly coupled code.
  • Confusing protected with private โ€” protected IS accessible to subclasses and same-package classes.
  • Using default access when you intend public โ€” external packages will get compile errors trying to use your class.
  • Declaring entire classes as private โ€” only nested/inner classes can be private; top-level classes can only be public or default.
๐ŸŽฏ Hands-on Coding Challenge

Test your understanding by writing the code directly in your editor or running in our online Java compiler:

โ˜• Challenge.java
// Coding Challenge:
// Design a SecureVault class:
// 1. private String secretCode (cannot be read externally).
// 2. private int accessAttempts counter.
// 3. public boolean attemptAccess(String code) -> returns true only if code matches, max 3 attempts.
// 4. public boolean isLocked() -> returns true if attempts >= 3.

class SecureVault {
    private final String secretCode;
    private int accessAttempts = 0;
    private static final int MAX_ATTEMPTS = 3;

    public SecureVault(String secretCode) {
        this.secretCode = secretCode;
    }

    public boolean attemptAccess(String code) {
        if (isLocked()) {
            System.out.println("  VAULT LOCKED! Contact administrator.");
            return false;
        }
        accessAttempts++;
        if (secretCode.equals(code)) {
            System.out.println("  Access GRANTED!");
            return true;
        }
        System.out.println("  Access DENIED. Attempt " + accessAttempts + "/" + MAX_ATTEMPTS);
        return false;
    }

    public boolean isLocked() { return accessAttempts >= MAX_ATTEMPTS; }
}

public class Challenge {
    public static void main(String[] args) {
        SecureVault vault = new SecureVault("JAVA2026");
        vault.attemptAccess("WRONG1");
        vault.attemptAccess("WRONG2");
        vault.attemptAccess("JAVA2026"); // Correct on 3rd attempt
    }
}

๐Ÿ’ก Frequently Asked Questions & Interview Insights

โ“ What is the default access modifier in Java (no modifier written)?

When no modifier is written, it is called "package-private" or "default" access. The member is visible only to classes within the same Java package and invisible to classes in other packages.

โ“ Can a top-level class be private in Java?

No. Top-level classes (classes not nested inside another class) can only be `public` or package-private (default). Only nested/inner classes can use `private` or `protected` modifiers.

โ“ When should I use protected instead of private for class fields?

Generally, prefer `private` for all fields even in base classes. Expose data to subclasses via `protected` getters rather than `protected` fields. This keeps control over validation even for inherited classes.

๐Ÿš€ Quick Chapter Recap

  • private restricts access to within the same class only โ€” use for all instance fields.
  • Default (no keyword) is package-private: visible to same-package classes, invisible to external packages.
  • protected extends visibility to subclasses across packages โ€” designed for inheritance hierarchies.
  • public is fully open โ€” use for intentional public APIs.
  • Apply the Principle of Least Privilege: always start with private and widen only when needed.
โ† Prev: 42. Capstone Projects (4 OOP Systems) Next: 44. Getters, Setters & Validation โ†’
OC
Curated by Our Compiler Java Technical Editorial Team
Published for 2026 Academic & Enterprise Reference ยท 100% Free & Open Access