Topic 67 of 119 6. Object-Oriented Programming in Java Interactive Tutorial

Java Access Modifiers: public, protected, default, and private

16 mins read Last updated: 22 Aug 2026 26,591 views

Direct Answer & Core Concept

Java has four access levels, and one of them has no keyword at all. private is the narrowest, then package-private, then protected, then public. Package-private access is expressed by writing nothing at all; default is a label used in discussion, not a Java keyword, and writing it gives "modifier default not allowed here".
Main.java
Run Online
public class AccessDemo {
    private String ledgerKey() {
        ...
    } // this class and its nested classes
    String feeFor(long units) {
        ...
    } // the whole package
    protected double rate() {
        ...
    } // the package, plus subclasses
    public String receipt(String id) {
        ...
    } // anywhere visible
}

Two of these behave in ways that surprise people. Private is not inherited. A subclass may declare a method with the same name, and the compiler accepts it, but it is an unrelated new method rather than an override; adding @Override is what turns that mistake into a compile error. A nested class, by contrast, can reach the enclosing class's private members directly, because since Java 11 the two are compiled as a single nest rather than bridged by synthetic accessors.

The other surprise is protected, which is the only modifier with a condition attached. A subclass may read an inherited protected member directly, but not through a variable typed as the superclass:

Main.java
Run Online
int readDirect() {
    return value;
} // fine
int readWrong(Base other) {
    return other.value;
}// error: value has protected access in Base

Outside the package the reference type must be the subclass. Inside the package you would not hit this at all, since package access already allows the direct read.

Two rules are worth knowing that are not about choosing a modifier. Interface members are implicitly public, so an interface method is stored as public abstract, its fields as public static final, and any class declared inside it as public static. And a top-level class may only be public or package-private, since private and protected give "modifier not allowed here"; all four are fine on a nested class.

Finally, access is enforced twice. javac rejects unreachable members up front, and the JVM checks the class file's access flags again as it loads it. That second check is why invoking a private member reflectively throws IllegalAccessException unless setAccessible(true) was called first. And since Java 9 a fifth rule sits above all four: a class must be public and its package exported by its module, or another module cannot reach it however public the member is.

Detailed Explanation & Mechanics

1) Four Modifiers, One of Which Is Not a Keyword

Java has four access levels, arranged from most restrictive to least. Three of them are keywords you write. The fourth has no keyword at all, and the name people use for it, default, is not part of the language. Writing it is rejected outright:

Main.java
Run Online
public class DefTest {
    default void x() {
    }
}// error: modifier default not allowed here

Package-private access is what you get by writing nothing. Here is one class using all four levels, which is the shape most real code settles into:

Main.java
Run Online
package com.acme.billing;
public class AccessDemo {
    private String ledgerKey() {
        return "sk-live-4821";
    } // this class only
    String feeFor(long units) {
        ...
    } // the package
    protected double rate() {
        return 0.075;
    } // package + subclasses
    public String receipt(String id) {
        ...
    } // everywhere visible
}

The order of declaration in that snippet is deliberate and is the conventional one: narrowest first, widest last. It reads as a widening of the surface area, and it makes the intended API obvious at a glance.

2) Where Each One Stops


Diagram of how far private, package-private, protected and public reach in Java, plus the protected access trap and the module export rule
Reach of each modifier, the protected trap, and the module rule above all four.

ModifierSame classNested classSame packageSubclass elsewhereAnywhere
privateYesYesNoNoNo
package-privateYesYesYesNoNo
protectedYesYesYesYes, with a conditionNo
publicYesYesYesYesYes

Read the "nested class" column carefully, because it is the one most people get wrong. A nested class is inside the declaring class, so it can reach that class's private members with no ceremony at all:

Main.java
Run Online
class Auditor {
    String peek() {
        return ledgerKey();
        // legal, no accessor needed
    }
}

That has been true in principle for a long time, but the mechanism changed. Before Java 11 the compiler generated synthetic bridge methods to let this work. Since Java 11 classes that reference one another this way are compiled as a single nest, and the JVM grants nestmates access to each other's private members directly. No bridge methods are generated, and the rule is now enforced by the runtime rather than papered over by the compiler.

3) The Protected Trap

protected is the only modifier with a condition attached to it, and the condition is the single most common surprise in this topic. Protected means "the whole package, plus subclasses in other packages", but outside the package a subclass may only reach the inherited member through a reference whose type is that subclass or a further descendant of it.

Here is the case that fails, with the exact error:

Main.java
Run Online
// package p1
public class Base {
    protected int value = 7;
}// package p2
public class Child extends p1.Base {
    int readDirect() {
        return value;
    } // fine
    int readWrong(p1.Base other) {
        return other.value;
    } // error: value has protected access in Base
}

Both lines touch the same field on the same inherited member. The first is the subclass using something it inherited, which is what protected is for. The second reaches through a variable typed as the superclass, which the language treats as code outside the subclass reaching in, and refuses.

The reasoning behind the rule is sound. If any subclass could read the field through any superclass-typed reference, then a subclass of Base could reach into a completely unrelated part of the object graph and mutate state it is not responsible for. Java makes you go through your own type so that the access stays inside the part of the object the subclass actually owns.

The practical consequence is that if you need to read an inherited protected member from a method that only has a supertype reference, you have to cast, or add an accessor on the subclass:

Main.java
Run Online
int readWrongFixed(p1.Base other) {
    return((Child) other).value;
    // now the reference type is Child
}

Inside the same package this never comes up, because package access already allows the direct read. The trap only exists at a package boundary.

4) Private Is Not Inherited

A private member is not inherited in any sense that matters. A subclass may declare a method with the same name and signature, and the compiler will accept it, but that creates an entirely new, unrelated method:

Main.java
Run Online
class PremiumAccount extends AccessDemo {
    @Override public String feeFor(long units) {
        ...
    } // a real override
    String ledgerKey() {
        return "UNRELATED";
    } // NOT an override
}

Adding @Override to the second one is what turns the mistake into a compile error, which is the cheapest way to catch it. A subclass also cannot narrow access when overriding, so a package-private method may be widened to public but never the reverse. The full set of rules for that lives on the method overriding page; the point here is only that the two topics meet at the modifier.

Access may also be widened on a member you declare in a subclass, exactly as on any other declaration. Widening a package-private method to protected or public is always legal, since it only ever makes the method easier to reach.

5) Access Is Enforced Twice

Everything above is checked by javac, which is why the errors arrive at compile time. That is not the whole story, because the JVM performs its own check on the class file as it loads and links it. The rules are encoded in the access flags of the class file and enforced again at run time.

The place you meet this is reflection. Looking up a private member and invoking it without permission fails while the program is running:

Main.java
Run Online
Method m = Holder.class.getDeclaredMethod("secret");
m.invoke(new Holder());
// IllegalAccessException
m.setAccessible(true);
m.invoke(new Holder());
// "classified"

Three lines of output from a program that does exactly that:

Main.java
Run Online
public method, no setAccessible = openprivate, no setAccessible = IllegalAccessExceptionprivate, with setAccessible = classified

That second check is why a class can compile cleanly and still fail later. It is also the reason access rules are respected even when the compiler is not in the picture, such as for classes loaded by a different loader or deserialised from untrusted bytes.

One nuance worth knowing, because it looks like a contradiction when you first hit it: reflection from inside the same nest does not throw. Calling the same private method from a nested class succeeds without setAccessible, for exactly the reason described earlier, because both classes share a nest.

6) Two Rules That Are Not About Your Modifier

Interface members are all public. You cannot write private or protected on an interface method, and omitting public is not the same as omitting it on a class. Reflection confirms what is stored:

Main.java
Run Online
interface Shape {
    double area();
    int SIDES = 1;
    class Nested {
    }
}Method m = Shape.class.getMethod("area");
Modifier.toString(m.getModifiers());
// "public abstract"
Field f = Shape.class.getDeclaredField("SIDES");
Modifier.toString(f.getModifiers());
// "public static final"

A class declared inside an interface is also implicitly public static. This follows from the topic covered on the interfaces page, where the implicit modifiers were shown for methods and fields; nested types follow the same rule.

A top-level class may only be public or package-private. The other two modifiers are meaningless for a type that nothing can extend yet:

Main.java
Run Online
private class Hidden {
}// error: modifier private not allowed here
protected class AlsoHidden {
}// error: modifier protected not allowed here

On a nested class all four are legal, because by then there is something to extend and something to be extended by. The restriction is about position in the file, not about the modifier itself.

7) Modules Add a Layer Above All Four

This is the part that makes the four modifiers feel incomplete, and it arrives with Java 9. A class can be perfectly public and still be unreachable, because its package may not be exported by its module.

Main.java
Run Online
// com.acme.lib/module-info.java
module com.acme.lib {
    exports com.acme.api;
    // this package is visible
    // com.acme.internal is NOT listed, so it stays private to the module
}

With that declaration, an application module may import the exported package:

Main.java
Run Online
// com.acme.app/module-info.java
module com.acme.app {
    requires com.acme.lib;
}
Main.java
Run Online
import com.acme.api.Api;
// compiles and runs
import com.acme.internal.Hidden;
// error: package com.acme.internal is not visible
//   (package com.acme.internal is declared in module com.acme.lib,
//    which does not export it)

Both halves of that were run to confirm the wording. The permitted import compiles, the module launches, and the forbidden one is rejected at compile time with a message naming the module that is withholding it.

This is why public alone no longer means "anywhere". Access now has two independent questions: is the member public, and is its package exported. Both must be yes. On the classpath without modules, everything sits in an unnamed module which exports everything, so the familiar behaviour returns and most code never notices the difference.

8) Common Misconceptions

ClaimVerdict
default is the keyword for package accessFalse. There is no such keyword; writing it is a compile error.
private is inherited by subclassesFalse. A same-named child method is unrelated.
A nested class cannot touch outer private membersFalse. Nestmates share access.
protected works through any supertype referenceFalse. Only through the subclass type.
public guarantees reachabilityFalse since Java 9. The module must export the package.
Interface methods can be declared protectedFalse. They are implicitly public abstract.
A nested class can be privateTrue. Only top-level types are restricted.
Overriding may narrow accessFalse. Access may widen only.

9) Choosing One

The modifiers are not a style ladder to climb. A useful default is to make the narrowest thing that works public, and to keep the public surface small enough that you can remember it.

  • Fields. Private, always. Expose through accessors when a caller needs them, so you keep the option to add validation later.
  • Methods that are called from outside the package. Public, and expect to support them.
  • Methods for subclasses you do not control. Protected. If nobody extends the class, use private and final.
  • Package-internal helpers. Leave the modifier off. It is the right default for code you share inside one package.

Two habits pay for themselves quickly. Put the narrowest modifier first and the widest last in each class, so a reader can see the intended surface without parsing every line. And add @Override to anything that implements or overrides a parent member, because it converts a mistaken re-declaration of a private method into a compile error instead of a method that is never called.

Widening a member to public is the one change here that cannot be undone without breaking callers. Narrowing, renaming, or changing a parameter type all have the same problem, so decide the widest access early and treat it as a commitment.

Frequently Asked Questions

What are the four access modifiers in Java and what does each allow?


private allows the declaring class only, plus any nested class in the same nest. Package-private, written by omitting the modifier, allows every type in the same package. protected allows the whole package plus subclasses in other packages, subject to a condition on the reference type. public allows anywhere the declaring class is itself visible, which since Java 9 also depends on whether the module exports the package.

Is default a keyword in Java?


No. Package-private access is expressed by writing no modifier at all. Using the word default produces the error "modifier default not allowed here". The name is a convenient label used in discussion, but it is not part of the language, and the reserved word list does not contain it.

Can a subclass access a private method of its parent?


No. A private method is not inherited, so a subclass that declares a method with the same name is defining something new and unrelated rather than overriding anything. Adding @Override to that declaration is what turns the mistake into a compile error. A nested class is a different case: it can reach the enclosing class's private members directly, because the two share a nest.

Why can a subclass not read a protected field through a superclass reference?


Because outside the package, protected access is only permitted through a reference of the subclass's own type. Reading an inherited member directly is fine, but going through a variable typed as the superclass is treated as unrelated code reaching in, and is rejected with "value has protected access in Base". Cast the reference to the subclass type if that access is genuinely what you need.

Can a nested class be private?


Yes. All four modifiers are legal on a nested class. The restriction applies only to top-level types, where private and protected produce "modifier not allowed here", since nothing outside the file could extend or reach such a type in the first place.

Are interface methods always public?


Yes, and you cannot mark them otherwise. An interface method without a body is stored as public abstract, an interface field as public static final, and a class declared inside an interface as public static. Since Java 9 a private method is allowed inside an interface, but only to share implementation between its own default methods.

Does public guarantee that a class is reachable?


Not by itself, since Java 9. A class must be public and its package must be exported by its module before another module can use it, and the error names the withholding module explicitly. Code on the ordinary classpath lives in the unnamed module, which exports everything, so the old behaviour returns and the distinction only bites modular builds.

Is access checked at compile time or at run time?


Both. javac rejects an unreachable member before the program starts, and the JVM checks the access flags in the class file again as it loads and links. The runtime check is what makes reflection throw IllegalAccessException for a private member unless setAccessible(true) was called first.

Can an override reduce visibility?


No. An override may widen access but never narrow it, so a public parent method cannot become protected or private in the child. The reasoning is substitutability: code holding a supertype reference was promised access to that method and must keep it. Widening, on the other hand, is always allowed.

What is the best default access level for a field?


Private, with accessors where callers genuinely need one. Fields are mutable state, and a private field with a getter lets you add validation, caching, or deprecation later without breaking anyone. The exception worth considering is a genuinely immutable constant, which is often written public static final or exposed through a package-private accessor for a small group of collaborating classes.

Runnable Code Example: Java Access Modifiers

Java 17+
Main.java
Run Online
// Shows where each access modifier actually stops, what it means when two
// classes sit in different packages, and what a subclass may change.
// Everything here lives in ONE package, so what is being demonstrated is
// the modifier itself rather than package separation.
package com.acme.billing;
public class AccessDemo {
    // PRIVATE: reachable only from this class and from nested classes
    // declared inside it, which share the same nest.
    private String ledgerKey() {
        return "sk-live-4821";
    } // PACKAGE-PRIVATE: no modifier is written, because "default" is NOT a
    // Java keyword. Every class in com.acme.billing can call this.
    String feeFor(long units) {
        return "fee for " + units + " units";
    } // PROTECTED: the whole package, plus subclasses in other packages.
    protected double rate() {
        return 0.075;
    } // PUBLIC: reachable from anywhere that can see this class at all.
    public String receipt(String id) {
        return "receipt " + id + " @ " + rate();
    } // A nested class reaches the outer private member with no ceremony,
    // because both are members of the same nest.
    class Auditor {
        String peek() {
            return ledgerKey();
        }
    } public static void main(String[] args) {
        AccessDemo demo = new AccessDemo();
        System.out.println("private      = " + demo.ledgerKey());
        System.out.println("package      = " + demo.feeFor(10L));
        System.out.println("protected    = " + demo.rate());
        System.out.println("public       = " + demo.receipt("R-1"));
        System.out.println("nest private = " + demo.new Auditor().peek());
        // A subclass may WIDEN access when overriding, and may not narrow it.
        System.out.println("sub widened  = " + new PremiumAccount().feeFor(10L));
        System.out.println("sub method   = " + new PremiumAccount().receipt("R-2"));
    }
}// A subclass that widens a package-private method to public, which is the
// only direction an override is allowed to move.
class PremiumAccount extends AccessDemo {
    @Override public String feeFor(long units) {
        return "premium fee for " + units + " units";
    } // The private method above is NOT inherited, so redeclaring a method
    // with that name here creates an unrelated new method rather than an
    // override. Calling ledgerKey() from PremiumAccount would not compile.
    String ledgerKey() {
        return "UNRELATED, not an override";
    }
}// Actual output:
// private      = sk-live-4821
// package      = fee for 10 units
// protected    = 0.075
// public       = receipt R-1 @ 0.075
// nest private = sk-live-4821
// sub widened  = premium fee for 10 units
// sub method   = receipt R-2 @ 0.075

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

What are the four access modifiers in Java and what does each one allow?

private allows the declaring class and any nested class in the same nest. Package-private, written by omitting the modifier, allows every type in the same package. protected allows the whole package plus subclasses in other packages, subject to the reference-type condition. public allows anywhere the declaring class is itself visible, which since Java 9 also depends on whether the module exports the package.
Q2

Is default a keyword in Java?

No. Package-private access is written by using no modifier at all. Supplying the word default produces the error modifier default not allowed here. The name is a convenient label for discussion, but it is not a reserved word and does not appear in the language grammar.
Q3

Can a subclass access a private method of its parent class?

No, because a private method is not inherited. Declaring a same-named method in the subclass creates an unrelated new method rather than an override, and adding @Override to it is what turns the mistake into a compile error. A nested class is the exception: it can reach the enclosing class's private members because the two share a nest.
Q4

Why can a subclass not read a protected field through a superclass-typed reference?

Because outside the package protected access is only granted through a reference of the subclass's own type. Reading the inherited member directly is permitted, but reaching through a variable typed as the superclass is treated as unrelated code and is rejected with value has protected access in Base. Cast the reference, or add an accessor on the subclass.
Q5

Can a nested class be private?

Yes, and all four modifiers are legal on a nested class. The restriction is only on top-level types, where private and protected give modifier not allowed here, since a type with no enclosing context could not be reached or extended by anything outside its own file anyway.
Q6

Are interface methods always public?

Yes, and you cannot mark them otherwise. An interface method with no body is stored as public abstract, an interface field as public static final, and a class declared inside an interface as public static. Since Java 9 a private method is permitted inside an interface, but only to share implementation between its own default methods.
Q7

Does public guarantee that a class is reachable from anywhere?

Not by itself since Java 9. The class must be public and its package must be exported by its module before another module can use it, and the compiler names the withholding module in the error. Code on the ordinary classpath sits in the unnamed module, which exports everything, so the familiar behaviour returns for most projects.
Q8

Is access checked at compile time or at run time?

Both. javac rejects an unreachable member before the program starts, and the JVM checks the access flags in the class file again while loading and linking it. The runtime check is what makes reflection throw IllegalAccessException for a private member unless setAccessible(true) was called first.
Q9

Can an override reduce the visibility of the method it overrides?

No. Access may be widened on an override but never narrowed, so a public parent method cannot become protected or private in a child. Code holding a supertype reference was promised access to that method, and narrowing would break every such caller at compile time.
Q10

What is the best access level for a field?

Private by default, with accessors where callers genuinely need one. A private field behind a getter leaves room to add validation or caching later without breaking anyone, whereas a public field is a promise you cannot withdraw. The reasonable exception is a genuinely immutable constant, often written public static final.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Keep fields private and expose them through accessors that can validate later.
Declare the narrowest access first and the widest last in each class.
Omit the modifier for package-private rather than inventing a default keyword.
Treat public as a permanent promise, because callers depend on it forever.
Use protected only when a subclass in another package genuinely extends the class.
Remember protected needs a subclass-typed reference once you cross a package boundary.
Add @Override whenever you implement or override, so a re-declared private method fails to compile.
Do not put protected on a top-level class, it is a compile error.
Prefer a nested private class over a package-private top-level one for a helper.
Export only the packages a module intends other modules to use.
Check module-info before assuming a public class is reachable from another module.
Do not use setAccessible in production code to reach into private state.
Order members private, package-private, protected, then public for readability.
Do not rely on a same-named child method to call a private parent method, it will not.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.