Home Java Java Access Modifiers Explained — public, private, protected and default

Java Access Modifiers Explained — public, private, protected and default

In Plain English 🔥
Imagine your house has different rooms. Your front garden is public — anyone walking past can see it. Your living room is protected — only family and close relatives can enter. Your bedroom is private — only you go in there. Your neighbour's shared driveway has no lock, but it's understood only people on that street use it. Java access modifiers work exactly like this: they control who is allowed to 'enter' a class, method, or variable.
⚡ Quick Answer
Imagine your house has different rooms. Your front garden is public — anyone walking past can see it. Your living room is protected — only family and close relatives can enter. Your bedroom is private — only you go in there. Your neighbour's shared driveway has no lock, but it's understood only people on that street use it. Java access modifiers work exactly like this: they control who is allowed to 'enter' a class, method, or variable.

Every piece of software you have ever used — your bank app, your favourite game, a weather app — is built on one golden rule: not everything should be accessible to everyone. When a banking app processes your PIN, it doesn't let any random part of the program read or change that number freely. That restriction is enforced in Java using access modifiers, and they are one of the very first tools a Java developer reaches for when designing anything serious.

Without access modifiers, every variable, method, and class in your program would be completely exposed to every other part of the codebase. That sounds harmless on a small project, but on a real-world application with hundreds of classes and multiple developers, it creates chaos. Someone accidentally modifies a value they shouldn't touch, a bug appears, and nobody knows where it came from. Access modifiers solve this by letting you draw clear boundaries — you decide exactly what is visible and what is locked away.

By the end of this article you'll know all four Java access modifiers, understand exactly when and why to use each one, be able to spot access-related compiler errors and fix them instantly, and feel confident answering access modifier questions in a Java interview. Let's build this up from scratch.

The Four Access Modifiers and What They Actually Mean

Java gives you four access levels. Think of them as four different lock types on a door. From most open to most locked, they are: public, protected, default (no keyword), and private.

'public' means absolutely anyone can access it — any class, in any package, anywhere in the project. It's the front door of a shop: open to the world.

'protected' means the class itself, any class in the same package, and any subclass (a class that inherits from this one) can access it. Think of a family recipe — you share it with family members and people in your household, but not strangers.

'default' (also called package-private) is what you get when you write NO modifier at all. Only classes inside the same package can access it. It's like an unwritten agreement between neighbours — people on the same street can use the shared path, but outsiders can't.

'private' is the strictest. Only the class that owns the member can access it. Nobody else — not even a subclass — can touch it directly. It's your diary with a lock.

These aren't just theory. They map directly to how Java enforces encapsulation, which is one of the four pillars of object-oriented programming. Getting these right is the difference between code that's easy to maintain and code that becomes a nightmare.

AccessModifierDemo.java · JAVA
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
// This file demonstrates all four access modifiers in one place.
// Run this as a single file (Java 11+) or create the classes in separate files.

// A public class — visible to everyone, everywhere
public class AccessModifierDemo {

    // public field — any code in the entire project can read/change this
    public String brandName = "TheCodeForge";

    // protected field — accessible in this class, same package, and subclasses
    protected int articleCount = 42;

    // default (no keyword) field — only accessible within the same package
    String internalCategory = "Java Basics"; // no modifier = package-private

    // private field — ONLY this class can read or change this value
    private String editorPassword = "s3cr3tP@ss";

    // public method — anyone can call this
    public void showBrandName() {
        // This method is the 'front door' — safe to expose publicly
        System.out.println("Brand: " + brandName);
    }

    // private method — internal helper, no outside class should call this
    private String encryptPassword(String rawPassword) {
        // Hiding the implementation detail — callers don't need to know HOW
        return "[ENCRYPTED]" + rawPassword.hashCode();
    }

    // public method that uses the private method internally
    public void printEncryptedPassword() {
        // We expose a safe action publicly, but keep the logic private
        System.out.println("Encrypted: " + encryptPassword(editorPassword));
    }

    public static void main(String[] args) {
        AccessModifierDemo demo = new AccessModifierDemo();

        // Accessing public field directly — perfectly fine
        System.out.println("Public field: " + demo.brandName);

        // Accessing protected field from within the SAME class — fine
        System.out.println("Protected field: " + demo.articleCount);

        // Accessing default field from within the SAME package — fine
        System.out.println("Default field: " + demo.internalCategory);

        // Accessing private field from within the SAME class — fine
        System.out.println("Private field: " + demo.editorPassword);

        // Calling public method — fine from anywhere
        demo.showBrandName();

        // Calling public method that internally uses a private method
        demo.printEncryptedPassword();

        // NOTE: If you tried to call demo.encryptPassword() from OUTSIDE
        // this class, the compiler would throw an error. Try it and see!
    }
}
▶ Output
Public field: TheCodeForge
Protected field: 42
Default field: Java Basics
Private field: s3cr3tP@ss
Brand: TheCodeForge
Encrypted: [ENCRYPTED]-1722786358
⚠️
Pro Tip: Default is Not a KeywordYou never write the word 'default' as an access modifier on a field or method — you simply omit any modifier entirely. Writing 'default int count = 0;' is a compiler error. The absence of a modifier IS the default access level.

private and public in Practice — The Bank Account Example

The most important pair to master first is private and public — and the classic teaching example is a bank account, because it maps perfectly to the real world.

Imagine a BankAccount class. It has a balance. Should the balance field be public? Absolutely not. If balance is public, any other class in your program can write something like: account.balance = 1000000; — with no checks, no history, no validation. That's terrifying.

Instead, you make balance private. Now nobody outside the class can touch it directly. You then provide public methods — called getters and setters — that control all access. The getter lets someone READ the balance. The setter (or a deposit/withdraw method) lets someone CHANGE it, but only after you've run whatever checks you need. This pattern is called encapsulation, and it's the whole point of private.

Public is the opposite philosophy — use it for things you genuinely want to expose as part of your class's contract with the rest of the world. Method names, constructors, anything another class legitimately needs to call. The golden rule: default to private first. Only make something public if you have a clear reason to.

BankAccount.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
public class BankAccount {

    // private — no outside class can directly read or write the balance
    // This is the single most important use of 'private' you'll encounter
    private double balance;

    // private — internal account identifier, nobody outside needs this raw
    private String accountNumber;

    // public constructor — anyone who wants to create an account can do so
    public BankAccount(String accountNumber, double openingBalance) {
        this.accountNumber = accountNumber;

        // Validate even at construction time — private data, our rules
        if (openingBalance < 0) {
            throw new IllegalArgumentException("Opening balance cannot be negative.");
        }
        this.balance = openingBalance;
    }

    // public getter — allows READ access to balance, but not direct WRITE access
    public double getBalance() {
        return balance; // we return a copy of the value, not the field itself
    }

    // public method to deposit money — enforces our business rules
    public void deposit(double amount) {
        if (amount <= 0) {
            System.out.println("Deposit amount must be positive. Ignoring.");
            return;
        }
        balance += amount; // only THIS class modifies balance directly
        System.out.println("Deposited £" + amount + ". New balance: £" + balance);
    }

    // public method to withdraw money — enforces our business rules
    public void withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("Withdrawal amount must be positive. Ignoring.");
            return;
        }
        if (amount > balance) {
            // private data lets us protect this logic completely
            System.out.println("Insufficient funds. Balance is only £" + balance);
            return;
        }
        balance -= amount;
        System.out.println("Withdrew £" + amount + ". New balance: £" + balance);
    }

    // private helper — only used internally, no outside class needs to know this exists
    private boolean isHighValueAccount() {
        return balance > 10000;
    }

    // public method that uses the private helper — safe, controlled exposure
    public void printAccountSummary() {
        System.out.println("Account: " + accountNumber);
        System.out.println("Balance: £" + balance);
        System.out.println("High-value account: " + isHighValueAccount());
    }

    public static void main(String[] args) {
        BankAccount myAccount = new BankAccount("ACC-00192", 500.00);

        // Reading balance through the public getter — correct approach
        System.out.println("Initial balance: £" + myAccount.getBalance());

        myAccount.deposit(250.00);
        myAccount.withdraw(100.00);
        myAccount.withdraw(800.00); // should fail — insufficient funds

        System.out.println();
        myAccount.printAccountSummary();

        // The line below would cause a COMPILE ERROR if you uncommented it:
        // myAccount.balance = 999999; // ERROR: balance has private access in BankAccount
        // myAccount.isHighValueAccount(); // ERROR: isHighValueAccount() has private access
    }
}
▶ Output
Initial balance: £500.0
Deposited £250.0. New balance: £750.0
Withdrew £100.0. New balance: £650.0
Insufficient funds. Balance is only £650.0

Account: ACC-00192
Balance: £650.0
High-value account: false
⚠️
Watch Out: Making Everything PublicNew Java developers often make every field public 'to make things easier'. This destroys encapsulation. The moment your balance field is public, any class anywhere can set it to -99999 with no validation. Always start with private and only open things up when you have a specific, justified reason to.

protected and Default — When Packages and Inheritance Come In

Once you understand private and public, the next step is understanding when you need something in between. That's where protected and default (package-private) come in — and they're the two most confused access modifiers for beginners.

Default (no keyword) is simpler: if two classes are in the same package — the same folder, essentially — they can see each other's default members. This is useful for utility classes or helper logic that belongs to a module but shouldn't be exposed as part of a public API. Think of it as 'internal' — visible within your team's workspace, invisible to the outside world.

Protected goes one step further: it adds inheritance to the mix. A protected member is visible in the same package AND in any subclass, even if that subclass is in a completely different package. This is specifically designed for the parent-child class relationship in object-oriented programming. You use protected when you're building a base class and you know subclasses will need to access or override something, but you still don't want the whole world touching it.

A good real-world analogy: a protected family recipe. Your children (subclasses) can have it and adapt it. Strangers (unrelated classes outside the package) cannot.

ProtectedAndDefaultDemo.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
// === FILE 1: Vehicle.java (in package: com.codeforge.vehicles) ===
// Imagine this is in: com/codeforge/vehicles/Vehicle.java

package com.codeforge.vehicles;

public class Vehicle {

    // public — any class anywhere can read the brand
    public String brand;

    // protected — this class AND any subclass (even in other packages) can access this
    // It's the parent sharing something specifically with its children
    protected int engineCapacityCC;

    // default (no modifier) — only classes in com.codeforge.vehicles can see this
    String factoryLocation; // package-private

    // private — only Vehicle itself can touch this
    private String internalSerialCode;

    public Vehicle(String brand, int engineCapacityCC, String factoryLocation, String internalSerialCode) {
        this.brand = brand;
        this.engineCapacityCC = engineCapacityCC;
        this.factoryLocation = factoryLocation;
        this.internalSerialCode = internalSerialCode;
    }

    // protected method — subclasses can call and even override this
    protected String getEngineDescription() {
        return brand + " engine: " + engineCapacityCC + "cc";
    }

    // public method — the 'front door' for external callers
    public void displayInfo() {
        System.out.println("Brand: " + brand);
        System.out.println("Engine: " + engineCapacityCC + "cc");
        System.out.println("Factory: " + factoryLocation);
        // Internal serial code stays private — even displayInfo() is careful about it
        System.out.println("Serial: [RESTRICTED]");
    }
}


// === FILE 2: ElectricCar.java (in package: com.codeforge.electric) ===
// A DIFFERENT package — notice it can still access the protected member
// Imagine this is in: com/codeforge/electric/ElectricCar.java

package com.codeforge.electric;

import com.codeforge.vehicles.Vehicle; // importing the parent class

public class ElectricCar extends Vehicle { // 'extends' means ElectricCar is a subclass of Vehicle

    private int batteryRangeKm;

    public ElectricCar(String brand, int batteryRangeKm) {
        // Calling the parent constructor — electric cars have near-zero CC engine
        super(brand, 0, "Tesla Gigafactory", "EV-" + brand + "-001");
        this.batteryRangeKm = batteryRangeKm;
    }

    @Override
    protected String getEngineDescription() {
        // Subclass CAN access engineCapacityCC because it's protected
        // Even though ElectricCar is in a DIFFERENT package, inheritance allows this
        return brand + " electric motor (no CC), range: " + batteryRangeKm + "km";
    }

    public void showElectricInfo() {
        // We can call the protected method because we're a subclass
        System.out.println(getEngineDescription());

        // We can access protected field engineCapacityCC directly
        System.out.println("Engine CC (should be 0 for EV): " + engineCapacityCC);

        // We CANNOT access factoryLocation here — it's default/package-private
        // and ElectricCar is in a DIFFERENT package. Uncommenting this would fail:
        // System.out.println(factoryLocation); // COMPILE ERROR
    }
}


// === FILE 3: Main.java (in package: com.codeforge.electric) ===

package com.codeforge.electric;

public class Main {
    public static void main(String[] args) {
        ElectricCar tesla = new ElectricCar("Tesla Model 3", 560);

        // Public method — accessible from anywhere
        tesla.displayInfo();

        System.out.println();

        // Calling the subclass-specific method
        tesla.showElectricInfo();

        // Accessing public field directly — fine
        System.out.println("\nBrand directly: " + tesla.brand);

        // From OUTSIDE the class hierarchy, protected members are NOT accessible:
        // System.out.println(tesla.engineCapacityCC); // COMPILE ERROR from here
    }
}
▶ Output
Brand: Tesla Model 3
Engine: 0cc
Factory: Tesla Gigafactory
Serial: [RESTRICTED]

Tesla Model 3 electric motor (no CC), range: 560km
Engine CC (should be 0 for EV): 0

Brand directly: Tesla Model 3
🔥
Interview Gold: protected vs defaultThe key difference interviewers love to ask about: 'default' is visible within the same package only. 'protected' is visible within the same package AND in subclasses across any package. A subclass in a different package can access protected members but NOT default members of its parent.

Access Modifiers on Classes — Not Just Fields and Methods

Everything we've covered so far applies to fields and methods inside a class. But access modifiers also control the class itself — and this trips up a lot of beginners.

A top-level class (a class not nested inside another class) can only be public or default (no modifier). You cannot make a top-level class private or protected — the Java compiler will throw an error immediately. This makes sense: a private class that nothing can see would be completely useless.

A public class is visible to everyone. A default class is visible only within its own package. This is useful when you want to create helper classes that are only relevant internally — you don't want other packages to depend on them.

However, inner classes (classes defined inside another class) can use all four access modifiers. A private inner class is a popular pattern for implementation details that are tightly coupled to their outer class.

One important Java rule: if a file contains a public class, the filename MUST match that class name exactly (including case). If your class is 'public class UserProfile', your file must be 'UserProfile.java'. Get this wrong and the compiler will refuse to compile. This is why you'll rarely see more than one public class per file.

ClassAccessModifierDemo.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
// This file demonstrates access modifiers applied at the CLASS level
// A single .java file can have only ONE public class
// The filename must match: ClassAccessModifierDemo.java

public class ClassAccessModifierDemo {

    // private inner class — completely hidden from outside
    // Only ClassAccessModifierDemo can create or use a Node
    // This is a common pattern in data structures (LinkedList nodes, tree nodes, etc.)
    private class Node {
        int value;
        Node nextNode; // points to the next node in a chain

        Node(int value) {
            this.value = value;
            this.nextNode = null;
        }
    }

    // public inner class — anyone can use this if they have a ClassAccessModifierDemo instance
    public class Statistics {
        private int totalNodes;

        public Statistics(int totalNodes) {
            this.totalNodes = totalNodes;
        }

        public void printStats() {
            System.out.println("Total nodes in chain: " + totalNodes);
        }
    }

    // The outer class uses its private inner class freely
    private Node firstNode;
    private int nodeCount;

    // public method — builds an internal chain using private Node objects
    public void addValue(int value) {
        Node newNode = new Node(value); // Node is private, but we're inside the owner class

        if (firstNode == null) {
            firstNode = newNode;
        } else {
            // Walk to the end of the chain
            Node currentNode = firstNode;
            while (currentNode.nextNode != null) {
                currentNode = currentNode.nextNode;
            }
            currentNode.nextNode = newNode; // attach the new node at the end
        }
        nodeCount++;
    }

    // public method — lets outsiders read values without knowing Node exists
    public void printAllValues() {
        System.out.print("Chain: ");
        Node currentNode = firstNode;
        while (currentNode != null) {
            System.out.print(currentNode.value);
            if (currentNode.nextNode != null) System.out.print(" -> ");
            currentNode = currentNode.nextNode;
        }
        System.out.println();
    }

    public Statistics getStatistics() {
        return new Statistics(nodeCount); // returns a public inner class instance
    }

    public static void main(String[] args) {
        ClassAccessModifierDemo chain = new ClassAccessModifierDemo();

        chain.addValue(10);
        chain.addValue(25);
        chain.addValue(37);

        chain.printAllValues();

        Statistics stats = chain.getStatistics();
        stats.printStats();

        // The line below would cause a COMPILE ERROR:
        // ClassAccessModifierDemo.Node n = new ClassAccessModifierDemo.Node(5);
        // ERROR: Node has private access in ClassAccessModifierDemo
    }
}
▶ Output
Chain: 10 -> 25 -> 37
Total nodes in chain: 3
⚠️
Watch Out: One Public Class Per FileIn Java, a .java file can contain multiple classes, but only ONE of them can be public — and the filename must exactly match that public class name. If you have 'public class OrderProcessor' in a file named 'Processor.java', you'll get: 'class OrderProcessor is public, should be declared in a file named OrderProcessor.java'. This is one of the most common beginner compiler errors.
Access LevelSame ClassSame PackageSubclass (diff. package)Any Class (anywhere)
public✅ Yes✅ Yes✅ Yes✅ Yes
protected✅ Yes✅ Yes✅ Yes❌ No
default (no keyword)✅ Yes✅ Yes❌ No❌ No
private✅ Yes❌ No❌ No❌ No

🎯 Key Takeaways

  • There are exactly four access levels in Java — public, protected, default (no keyword), and private — each one progressively more restrictive than the last.
  • Default access (no keyword) is NOT 'open to everything' — it means package-private. Only classes in the same package can see it. This surprises developers who assume 'no modifier = no restriction'.
  • protected is specifically designed for the inheritance relationship: subclasses in any package can access protected members, but unrelated classes outside the package cannot.
  • The golden rule of encapsulation: default to private for all fields. Only make something public or protected when you have a clear, deliberate reason. Opening things up is easy — locking them down later when other code depends on them is painful.

⚠ Common Mistakes to Avoid

  • Mistake 1: Making all fields public for convenience — Symptom: Other classes can change your object's data without any validation, causing hard-to-trace bugs (e.g., a negative age or a null username). Fix: Always declare fields as private. Provide public getters for read access and carefully controlled setters or action methods for write access. The extra typing is worth every second.
  • Mistake 2: Confusing protected with private when it comes to subclasses — Symptom: A developer makes a field private, then writes a subclass that needs to access it, and gets 'has private access' compiler error. They're confused because the subclass 'is a' version of the parent. Fix: Use protected for fields or methods you specifically intend subclasses to access or override. Private means even child classes are locked out — by design.
  • Mistake 3: Forgetting that default access is NOT 'no restriction' — Symptom: A class in a different package tries to use a default-access method and gets a compile error that says 'is not public in [class]; cannot be accessed from outside package', which surprises beginners who assumed no modifier means no restriction. Fix: Understand that omitting a modifier is a deliberate choice meaning 'package-private'. If you want something universally accessible, you must explicitly write 'public'.

Interview Questions on This Topic

  • QWhat is the difference between protected and default (package-private) access in Java? Can a subclass in a different package access a protected member? What about a default member?
  • QWhy would you ever use private access for a field when a getter method that returns the same value is also public — isn't that the same thing?
  • QIf a class has no access modifier on its constructor, can you instantiate it from a different package? What error would you see, and how would you fix it?

Frequently Asked Questions

What is the default access modifier in Java?

The default access level in Java is called package-private. You get it by writing NO modifier at all before the field, method, or class. It means only classes within the same package can access that member. It's NOT a keyword — writing 'default int count = 0;' is actually a syntax error.

Can a subclass access the private members of its parent class in Java?

No. Private members are strictly limited to the class they're declared in — not even subclasses can access them directly. If a parent class has a private field that subclasses need to work with, the parent should provide a protected getter method or use protected access on the field itself.

What is the difference between private and protected in Java?

Private restricts access to the single class where the member is declared — no other class can touch it, including subclasses. Protected is less restrictive: it allows access from the same class, any class in the same package, AND any subclass regardless of package. Use private when you want to hide implementation details completely, and protected when you're designing a class that's meant to be extended.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousJVM Memory ModelNext →Java Wrapper Classes
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged