Table of Contents

Access specifiers in Java

  • Last Updated: March 10, 2025
  • By: javahandson
  • Series
img

Access specifiers in Java

Access specifiers in Java decide who may see and use your classes, fields, methods, and constructors. Java gives you four levels: private, default, protected, and public. This guide walks through each one with runnable examples, a full comparison table, the rules that trip people up in interviews, and the mistakes worth avoiding.

1. Introduction

Think about your house for a moment. The front porch welcomes anyone. The living room suits guests. Your bedroom stays open to family only. Your diary belongs to you alone.

Java works the same way. Every class you write holds some parts meant for the world and some parts meant only for itself. Access specifiers draw those boundaries.

Why bother? Because a field that anyone can change becomes a field that anyone can break. Once you lock a field down, you control every path that touches it. That single habit prevents a huge share of real bugs.

1.1 What This Article Covers

  • What access specifiers mean, and why Java ships four of them
  • Each level in depth: private, default (package-private), protected, and public
  • A complete table showing exactly where each level reaches
  • How the rules shift across classes, fields, methods, constructors, and interfaces
  • What happens to visibility when a subclass overrides a method
  • Six mistakes that catch beginners, including two that catch experienced developers
  • A small banking program that puts every level to work
  • Ten interview questions with answers you can say out loud

2. What Are Access Specifiers in Java?

An access specifier is a keyword that answers a single question: from where can code reach this member? You write it in front of a class, field, method, or constructor, and the compiler enforces your answer everywhere.

2.1 A Quick Analogy

Picture an office building. The lobby stays open to the public. Reaching the engineering floor needs a badge. Only two people hold keys to the server room. Your locker holds your own things.

Java hands you the same four doors. Public means the lobby. Protected means the engineering floor plus anyone who inherits a badge. Default means the floor you already work on. Private means your locker.

2.2 Specifier or Modifier?

You will hear both terms. Most tutorials and interviewers say “access specifier”. The Java Language Specification says “access modifier”.

They point at the same four keywords, so either name works in conversation. Just remember the wider family. Java also has non-access modifiers such as static, final, abstract, and synchronized, and those change behaviour rather than visibility.

2.3 The Four Levels at a Glance

  • private – the declaring class, and nothing else
  • default – every class in the same package, written with no keyword at all
  • protected – the same package, plus subclasses anywhere
  • public – any class in any package

Notice the ordering. Each level opens a little wider than the one above it. Java arranges them as private, then default, then protected, then public.

2.4 Where You Can Write Them

You may put an access specifier on a class, a field, a method, a constructor, or a nested class. One place refuses them completely: local variables inside a method.

public class Demo {
    private int field = 1;        // fine

    public void run() {
        private int local = 2;    // compile error
    }
}
// error: illegal start of expression

That rule makes sense once you think it through. A local variable dies when the method returns, and no outside code could ever reach it anyway.

3. The private Access Specifier

Private locks a member inside the class that declares it. No other class reaches it, not even a subclass, and not even a neighbour in the same package.

3.1 A Class With All Four Levels

Here is one small class carrying one field of each kind. We will keep returning to it.

package com.javahandson.pkg1;

public class Student {
    private int rollNumber;       // this class only
    String name;                  // same package only
    protected int marks;          // package + subclasses
    public String mainSubject;    // everywhere

    public Student(int rollNumber, String name, int marks) {
        this.rollNumber = rollNumber;
        this.name = name;
        this.marks = marks;
    }

    public int getRollNumber() {
        return rollNumber;        // fine, we are inside Student
    }
}

3.2 Reaching a private Field From Outside

Now try to read rollNumber from another class in the very same package.

package com.javahandson.pkg1;

public class Main {
    public static void main(String[] args) {
        Student s = new Student(101, "Suraj", 70);

        System.out.println(s.getRollNumber()); // Output: 101
        System.out.println(s.rollNumber);      // compile error
    }
}
// error: rollNumber has private access in com.javahandson.pkg1.Student

The getter sails through. The direct field read fails. Same package, same JVM, still blocked, because private stops at the class boundary.

3.3 private Is Per Class, Not Per Object

Here comes a detail that surprises almost everyone. One object can read the private fields of another object, as long as both share the same class.

public class Box {
    private int size;

    public Box(int size) {
        this.size = size;
    }

    public boolean sameSizeAs(Box other) {
        return this.size == other.size;   // reading another object's private field
    }
}

// usage
Box small = new Box(10);
Box large = new Box(20);
System.out.println(small.sameSizeAs(large)); // Output: false

Why does Java permit that? Because the compiler checks the class you wrote the code in, never the object you happen to hold. Methods such as equals rely on exactly this behaviour.

3.4 Why private Should Be Your Default

  • Every path to the data now runs through code you control
  • Validation lives in one place, so a bad value never sneaks in
  • You may rename or restructure the field later without breaking callers
  • Readers of your class instantly see which parts form the real API

Start every field as private. Widen it only when a concrete need appears. Loosening a field later costs nothing, while tightening one after other teams depend on it costs a great deal.

4. The default (Package-Private) Access Specifier

Leave the keyword off entirely and Java applies default access, also called package-private. The member then belongs to its package.

4.1 No Keyword Is the Keyword

Java offers no word named “default” for this purpose. You simply write nothing.

String name;          // package-private field
void helper() { }     // package-private method
class Helper { }      // package-private class

Careful here. Java does own a keyword spelled default, and it shows up in switch statements and in interface methods. That keyword has nothing to do with access levels.

4.2 Same Package Works

package com.javahandson.pkg1;

public class Main {
    public static void main(String[] args) {
        Student s = new Student(101, "Suraj", 70);
        System.out.println("Name: " + s.name);   // Output: Name: Suraj
    }
}

Main and Student share the package com.javahandson.pkg1, so the field opens right up.

4.3 A Different Package Fails

package com.javahandson.pkg2;

import com.javahandson.pkg1.Student;

public class Main {
    public static void main(String[] args) {
        Student s = new Student(101, "Suraj", 70);
        System.out.println(s.name);   // compile error
    }
}
// error: name is not public in com.javahandson.pkg1.Student;
//        cannot be accessed from outside package

One package boundary changed everything. The import statement finds the class, yet the field stays shut.

4.4 When default Fits

  • Helper classes that support one feature and serve nobody outside it
  • Methods that two or three classes in a package share between themselves
  • Test hooks, since a test class in the same package can reach them
  • Anything you plan to refactor freely, because no outside caller can depend on it

Package-private acts as a quiet middle ground. Many libraries keep whole classes at this level so users never see the moving parts.

5. The protected Access Specifier

Protected covers two groups at once. Every class in the same package qualifies. Every subclass also qualifies, even a subclass living in a far-off package.

5.1 A Subclass in Another Package

package com.javahandson.pkg2;

import com.javahandson.pkg1.Student;

public class EngineeringStudent extends Student {

    public EngineeringStudent(int rollNumber, String name, int marks) {
        super(rollNumber, name, marks);
    }

    public void showMarks() {
        System.out.println("Marks: " + marks);  // inherited protected field
    }
}

// usage
EngineeringStudent e = new EngineeringStudent(101, "Suraj", 70);
e.showMarks();   // Output: Marks: 70

The marks field travelled across a package boundary through inheritance. Default access would have blocked it here.

5.2 The Rule Almost Everyone Misses

Now for the sharpest edge in this whole topic. A subclass in a different package may touch a protected member only through its own type. Hold a plain parent reference and the compiler refuses.

package com.javahandson.pkg2;

import com.javahandson.pkg1.Student;

public class EngineeringStudent extends Student {

    public EngineeringStudent(int rollNumber, String name, int marks) {
        super(rollNumber, name, marks);
    }

    void compare(Student other, EngineeringStudent peer) {
        System.out.println(this.marks);    // fine
        System.out.println(peer.marks);    // fine, same subclass type
        System.out.println(other.marks);   // compile error
    }
}
// error: marks has protected access in com.javahandson.pkg1.Student

What drives that restriction? Java grants the subclass access to its own inheritance, never a licence to poke at every sibling branch of the family tree. Inside the original package, no such limit applies.

5.3 protected Includes Package Access

Plenty of developers read protected as “subclasses only”. It actually grants strictly more than default access, never less.

An unrelated class sitting in the same package reaches a protected member with no inheritance at all. Keep that in mind when you weigh protected against default.

5.4 Use protected Sparingly

A protected member forms a contract with every future subclass. Change it and you break code you have never seen.

Reach for protected when a subclass genuinely must extend behaviour, such as a hook method in an abstract base class. Prefer private fields with protected methods, so children get the behaviour without holding the raw state.

6. The public Access Specifier

Public throws the doors open. Any class in any package may use the member, provided it can see the class itself.

package com.javahandson.pkg2;

import com.javahandson.pkg1.Student;

public class Main {
    public static void main(String[] args) {
        Student s = new Student(101, "Suraj", 70);

        s.mainSubject = "Maths";                       // public field, no barrier
        System.out.println("Subject: " + s.mainSubject); // Output: Subject: Maths
    }
}

6.1 What public Really Costs

Marking something public feels harmless in the moment. The bill arrives later.

  • Any rename becomes a breaking change for every caller
  • Bad data can enter from anywhere, so bugs get hard to trace
  • Your class grows a large surface that readers must understand
  • Removing the member later turns into a migration project

Keep public for the handful of methods that describe what your class does. Everything supporting those methods can stay hidden.

7. The Complete Access Table

Every rule above collapses into one grid. Learn this and you have learned the topic.

Access SpecifierSame ClassSame PackageSubclass (Other Package)Anywhere Else
privateYesNoNoNo
defaultYesYesNoNo
protectedYesYesYesNo
publicYesYesYesYes

7.1 How to Read the Grid

Read each row from left to right and watch the doors close. Private says yes once. Public says yes four times.

One column carries a footnote. That “Subclass (Other Package)” cell for protected assumes access through the subclass type, exactly as section 5.2 showed.

7.2 A Memory Trick

Count the audience as it grows: me, my package, my children, everybody. Those four phrases map straight onto private, default, protected, and public.

7.3 Choosing the Right Level

Stuck on which keyword to write? Walk down this short list and stop at the first line that fits.

  • Does anything outside this class need it? If not, write private and move on
  • Do a few classes in the same package need it? Leave the keyword off for package-private access
  • Must subclasses extend or reuse it? Choose protected, and prefer a protected method over a protected field
  • Is it part of the promise your class makes to the world? Only then reach for public

Notice the direction of travel. You begin closed and open up on evidence, rather than starting open and hoping to tighten later.

8. Access Specifiers in Different Contexts

The four keywords stay the same everywhere. Which ones you may legally write, however, depends on what you are declaring.

8.1 Top-Level Classes

A class sitting directly in a file accepts just two options.

SpecifierAllowed?Meaning
publicYesVisible everywhere; the file must carry the class name
defaultYesVisible inside the package only
privateNoNothing could ever use it
protectedNoA top-level class has no enclosing class to inherit from

8.2 Nested Classes

Nest a class inside another class and all four levels become legal. The nested class now behaves like any other member.

public class Outer {

    private class PrivateInner { }      // Outer only

    class DefaultInner { }              // same package

    protected class ProtectedInner { }  // package + subclasses

    public class PublicInner { }        // everywhere
}

8.3 Fields and Methods

Fields and methods take all four specifiers with no restrictions. They follow the grid in section 7 exactly.

8.4 Constructors

A constructor takes any of the four, and the choice controls who may create objects.

public class Example {

    private Example() { }              // nobody outside can call new

    Example(int x) { }                 // same package

    protected Example(String s) { }    // package + subclasses

    public Example(double d) { }       // anywhere
}

A private constructor powers two familiar patterns. Utility classes use it so nobody instantiates a bag of static methods. Singletons use it to funnel every caller through one factory method.

public class Singleton {
    private static Singleton instance;

    private Singleton() { }            // the door is locked

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

One honest warning about that snippet. It shows the access idea clearly, yet two threads calling getInstance together could build two objects. Production code adds synchronization or uses an enum.

8.5 Interfaces

Interfaces play by their own rules, and those rules have shifted across Java versions.

  • Fields turn into public static final constants automatically
  • Abstract methods turn into public abstract, so you may omit both words
  • Java 8 introduced default and static methods, and both stay public
  • Java 9 introduced private and private static methods for sharing internal logic
public interface Payable {

    int RATE = 100;                    // public static final

    void pay();                        // public abstract

    default void payTwice() {          // Java 8, public
        log("paying twice");
        pay();
        pay();
    }

    private void log(String message) { // Java 9, hidden from implementers
        System.out.println(message);
    }
}

So the old line “interface methods are always public” no longer holds. Since Java 9 an interface can keep helper logic to itself.

8.6 Modules Add One More Gate

Java 9 brought the module system, and it changed what public truly means. Marking a class public no longer guarantees that other code can touch it.

A module lists the packages it shares in a file called module-info.java. Leave a package out of that list and its public classes stay invisible outside the module.

module com.javahandson.banking {
    exports com.javahandson.bank;       // public types here are visible outside
    // com.javahandson.bank.internal is not exported,
    // so its public classes stay inside this module
}

Think of it as two gates in a row. Your access specifier opens the first gate, and the module declaration opens the second. Both must open before outside code gets through.

Most beginner projects run on the classpath and never notice this layer. Once you build libraries or work on a modular codebase, it matters a great deal.

9. Access Specifiers and Inheritance

Overriding adds one more rule, and interviewers love it.

9.1 You May Widen Visibility

class Parent {
    protected void display() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    @Override
    public void display() {        // protected widened to public: allowed
        System.out.println("Child");
    }
}

9.2 You May Not Narrow It

class Broken extends Parent {
    @Override
    private void display() { }     // compile error
}
// error: display() in Broken cannot override display() in Parent
//        attempting to assign weaker access privileges; was protected

The reason sits in polymorphism. Someone holding a Parent reference expects display to work. If a child could hide that method, the promise would collapse.

9.3 private Methods Never Override

A private method stays invisible to subclasses, so a child never overrides one. Write a method with a matching name in the child and you create a brand new method that merely looks similar.

Add @Override to such a method and the compiler will tell you plainly that nothing gets overridden. That annotation earns its keep here.

10. Common Mistakes and Pitfalls

10.1 Expecting protected Through a Parent Reference

This one bites hardest because the code reads perfectly. Your subclass sits in another package, holds a Parent variable, and touches a protected field. The compiler says no, and section 5.2 explains why.

10.2 Thinking private Hides Objects From Each Other

Two objects of one class see each other completely. Beginners often expect a wall there and find none.

10.3 Treating private as Security

Private guards your design, not your secrets. Reflection can flip a field open at runtime through setAccessible, and anyone reading the class file sees the value anyway.

Never store a password or key in a private field and call the job done. Real protection needs encryption and proper secret management.

10.4 Confusing default Access With the default Keyword

Writing default in front of a field produces a syntax error. Package-private access means writing nothing, while the default keyword belongs to switch blocks and interface methods.

10.5 Leaking Mutable State Through a Getter

A private field feels safe until a getter hands out the original object.

public class Team {
    private List<String> players = new ArrayList<>();

    public List<String> getPlayers() {
        return players;               // caller now edits your list
    }

    public List<String> getPlayersSafely() {
        return new ArrayList<>(players);   // caller edits a copy
    }
}

The keyword did its job. The method threw the protection away. Return a copy, or wrap the list with Collections.unmodifiableList.

10.6 Making Everything public “For Now”

Temporary public fields have a habit of turning permanent. Six months later, four modules depend on them, and nobody dares to touch the class.

10.7 Loosening Access to Satisfy a Test

A private method resists your unit test, so the quickest fix looks obvious. Bump it to public and the test compiles.

Resist that urge. A private method usually needs testing through the public method that calls it, since that path is what users actually run. When you truly must reach in, package-private plus a test class in the same package beats going public.

11. A Practical Walkthrough

Let us pull all four levels into one small bank account class. Watch how each keyword earns its place.

11.1 The Class

package com.javahandson.bank;

public class BankAccount {

    private final String holder;   // nobody edits the owner
    private double balance;        // nobody edits the money

    public BankAccount(String holder, double opening) {
        this.holder = holder;
        this.balance = opening;
    }

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

    public boolean withdraw(double amount) {
        if (amount <= 0 || amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }

    public double getBalance() {
        return balance;
    }

    public String status() {
        return isOverdrawn() ? "Overdrawn" : "Healthy";
    }

    protected void applyInterest(double rate) {   // subclasses tune this
        balance += balance * rate;
    }

    private boolean isOverdrawn() {               // internal detail
        return balance < 0;
    }
}

11.2 Why Each Level Sits Where It Does

  • balance is private so every change passes through deposit or withdraw, and the validation always runs
  • deposit and withdraw are public because they form the actual API customers call
  • applyInterest is protected so a SavingsAccount subclass can reuse it without exposing it to the whole application
  • isOverdrawn is private since it supports status and nobody else should care

11.3 Running It

package com.javahandson.bank;

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("Suraj", 1000);

        account.deposit(500);
        System.out.println(account.getBalance());  // Output: 1500.0

        boolean ok = account.withdraw(2000);
        System.out.println(ok);                    // Output: false
        System.out.println(account.getBalance());  // Output: 1500.0

        System.out.println(account.status());      // Output: Healthy

        // account.balance = 999999;               // compile error: private
    }
}

11.4 Reading the Result

The oversized withdrawal returned false and left the balance untouched. No caller could reach around the guard clause, because the field stays private.

Delete that last comment marker and the build fails immediately. Your access specifiers turned a runtime accident into a compile-time error, which is exactly the trade you want.

12. Interview Questions

Q: What are the four access specifiers in Java?

A: Java gives you private, default, protected, and public. Private stays inside the declaring class. Default reaches the whole package. Protected adds subclasses in other packages. Public opens to every class everywhere.

Q: What is default access in Java, and how do you write it?

A: Default access, also called package-private, applies when you write no access keyword at all. The member then reaches every class in the same package and nothing beyond it. Java has no keyword named default for this purpose.

Q: Can a top-level class be private or protected?

A: No. A top-level class takes public or default only. Private would leave the class unusable, and protected needs an enclosing class to inherit from. Nested classes, on the other hand, accept all four levels.

Q: What is the difference between protected and default access?

A: Both reach every class in the same package. Protected goes further and reaches subclasses in other packages through inheritance. So protected always grants more than default, never less.

Q: Can a subclass in another package access a protected field through a parent reference?

A: No, and this catches many people. The subclass may use a protected member only through its own type or a subtype of it. Holding a plain parent-typed variable and reading the field gives a compile error. Inside the parent’s own package the restriction disappears.

Q: Can one object read the private field of another object?

A: Yes, when both objects belong to the same class. Private works per class, not per object. That rule lets methods like equals and compareTo compare internal state directly.

Q: Can you reduce visibility when overriding a method?

A: No. An overriding method must keep the parent’s visibility or widen it. Turning a protected method into public works fine, while turning it into private fails to compile with a weaker-access-privileges error.

Q: Are all interface methods public in Java?

A: Not since Java 9. Abstract, default, and static interface methods remain public, and fields remain public static final. Java 9 added private and private static interface methods so an interface can hide shared helper logic.

Q: Why use a private constructor?

A: A private constructor stops outside code from calling new. Utility classes use it so nobody creates a pointless instance. Singletons use it to route every caller through one static factory method.

Q: Does private keep my data secure?

A: No. Private enforces a design boundary at compile time, and reflection can open the field at runtime through setAccessible. Treat it as a tool for clean structure rather than a security control.

13. Conclusion

Let us wrap up what we covered. Access specifiers in Java control who may reach your classes, fields, methods, and constructors.

Four levels run from tightest to widest: private, default, protected, and public. Private stops at the class. Default stops at the package. Protected adds subclasses anywhere, and public adds everyone.

Two subtleties separate a confident answer from a shaky one. A subclass in another package reaches protected members only through its own type. Private applies per class, so sibling objects read each other freely.

Start every field private and open it only when something real demands it. That habit alone will make your classes easier to change and far harder to break.

Further Reading

Leave a Comment