Skip to content
2 min read

Access Modifiers in Java

Access modifiers control who can see and use your classes, methods, and variables. Getting this wrong leads to broken encapsulation, security issues, and tightly coupled code.


The Four Access Levels

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    subgraph PUBLIC["🌍 public — Visible Everywhere"]
        subgraph PROTECTED["🏘️ protected — Same Package + Subclasses"]
            subgraph DEFAULT["📦 default — Same Package Only"]
                subgraph PRIVATE["🔒 private — Same Class Only"]
                    P(("Fields & Methods"))
                end
            end
        end
    end

    style PUBLIC fill:#ECFDF5,stroke:#6EE7B7,stroke-width:3px,color:#065F46
    style PROTECTED fill:#EFF6FF,stroke:#DBEAFE,stroke-width:2px,color:#1E40AF
    style DEFAULT fill:#FFFBEB,stroke:#FCD34D,stroke-width:2px,color:#92400E
    style PRIVATE fill:#FEF2F2,stroke:#FCA5A5,stroke-width:2px,color:#991B1B
    style P fill:#EFF6FF,stroke:#93C5FD,color:#1E40AF
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    subgraph scope["🎯 Quick Reference"]
        direction LR
        PUB{{"🟢 public"}} --> |"Any class, any package"| ALL(["✅ Everyone"])
        PROT{{"🔵 protected"}} --> |"Same package OR subclass"| SUB(["✅ Package + Children"])
        DEF{{"🟠 default"}} --> |"Same package only"| PKG(["✅ Package Only"])
        PRIV{{"🔴 private"}} --> |"Same class only"| CLS(["✅ Class Only"])
    end

    style PUB fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
    style PROT fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
    style DEF fill:#FEF3C7,stroke:#FCD34D,color:#92400E
    style PRIV fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B

Visibility Table

Modifier Same class Same package Subclass (different package) Any class
private Yes No No No
default (no keyword) Yes Yes No No
protected Yes Yes Yes No
public Yes Yes Yes Yes

Each Modifier in Detail

private — Class-level encapsulation

Only accessible within the same class. This is the default you should start with for all fields.

Java
public class BankAccount {
    private double balance;  // no one outside can touch this directly

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        this.balance += amount;
    }

    public double getBalance() {
        return balance;  // controlled read access
    }
}

Why it matters: If balance were public, any class could set it to -1000 and bypass validation.

default (Package-private) — No keyword

When you write no modifier at all, it's package-private. Only classes in the same package can access it.

Java
class InternalHelper {  // no 'public' — only visible in this package
    void processData() {
        // utility method for classes in this package only
    }
}

When to use: Internal implementation classes that shouldn't be part of your public API.

protected — Package + subclasses

Accessible within the same package AND by subclasses in any package.

Java
public class Animal {
    protected String name;  // subclasses can access

    protected void makeSound() {
        System.out.println("Some sound");
    }
}

// Different package
public class Dog extends Animal {
    public void bark() {
        makeSound();  // OK — Dog is a subclass
        System.out.println(name + " barks!");  // OK — protected field
    }
}

public — Visible everywhere

Java
public class UserService {
    public User findById(Long id) {  // API — anyone can call this
        return userRepository.findById(id);
    }
}

Access Modifiers on Classes

Where Allowed modifiers
Top-level class public or default only
Inner class All four (private, default, protected, public)
Local class (inside method) No modifier allowed
Java
public class Outer {
    private class Secret { }       // only Outer can see this
    protected class Helper { }     // Outer + subclasses
    public class PublicInner { }   // everyone
    class PackageInner { }         // same package only
}

The Golden Rule: Start Private, Open Up as Needed

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    START(("🏁 Start Here")) --> PRIV{{"🔒 private"}}
    PRIV -->|"Need package access?"| DEF{{"📦 default"}}
    DEF -->|"Need subclass access?"| PROT{{"🛡️ protected"}}
    PROT -->|"Need global access?"| PUB(["🌍 public"])

    style START fill:#EFF6FF,stroke:#93C5FD,color:#1E40AF
    style PRIV fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
    style DEF fill:#FEF3C7,stroke:#FCD34D,color:#92400E
    style PROT fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
    style PUB fill:#D1FAE5,stroke:#6EE7B7,color:#065F46

This is called the principle of least privilege. Always start with private and only increase visibility when you have a concrete reason.


Common Interview Traps

1. Can you access a protected member from a different package without extending the class?

No. This is the most common mistake. protected does NOT mean "accessible everywhere." A class in a different package must extend the class to access protected members. Simply importing the class is not enough.

Java
// package com.app.service
public class Animal { protected void eat() {} }

// package com.app.controller
public class Test {
    void test() {
        Animal a = new Animal();
        a.eat();  // COMPILE ERROR — not a subclass
    }
}
2. What is the default access modifier for interface methods?

public. All methods in an interface are implicitly public. You cannot make them private (before Java 9) or protected. Since Java 9, private methods are allowed in interfaces for internal helper logic.

3. Can a top-level class be private?

No. A top-level class can only be public or default (package-private). If it were private, nothing could see it — it would be useless. Only inner classes can be private.

4. Why should fields always be private? What if I need external access?

Direct field access breaks encapsulation. If you make a field public, you can never add validation, logging, or lazy loading later without breaking all callers. Use private fields with getter/setter methods so you can control access. This is why frameworks like Spring and Hibernate require getters/setters.

5. What happens if a subclass tries to reduce the visibility of an overridden method?

Compile error. You can increase visibility (e.g., protectedpublic) but never decrease it. This is because of the Liskov Substitution Principle — code using the parent type expects the method to be accessible.

Java
public class Parent {
    public void show() {}
}
public class Child extends Parent {
    private void show() {}  // COMPILE ERROR — cannot reduce visibility
}