Skip to main content

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

Access modifiers in Java with a visibility matrix: public, protected, default (package-private) and private, the protected-across-packages trap with real compiler errors, which modifiers apply to classes vs members, and how JPMS modules add a layer above public. Verified on JDK 25.

Java has four access levels that decide who can see a class, field, method or constructor: private (same class only), package-private (no keyword; same package), protected (same package plus subclasses) and public (everyone).
public: everyoneprotected: + subclasses in other packages (through their own type)package-private (no modifier): + same packageprivate: same class only
Each ring is a bigger audience: private is the smallest, and each level adds readers. The matrix below is the whole topic in one table. The sections after it prove the parts that trip people up with real compiler output: the protected rule across packages, which modifiers are allowed on classes versus members, and how modules (JPMS) add a layer above public. All code was compiled and run on JDK 25.
Versions. Every program on this page was compiled and run on JDK 25.0.4.1 (Temurin, LTS, build 25.0.4.1+1-LTS). Where Java 8 behaved differently the text says so. The compiler errors and console output are quoted verbatim from those runs; the “Download” links at the end of each section point to the exact .java files.

The access modifier matrix

ModifierSame classSame packageSubclass (other package)Everywhere else
publicYesYesYesYes
protectedYesYesYes (see the trap below)No
no modifier (package-private)YesYesNoNo
privateYesNoNoNo
Read a row left to right: access only ever gets narrower. The modifiers are checked by the compiler, and the JVM records them too. AccessMain.java prints them with reflection from the class below, AccountBase.java:
package shop.core;

public class AccountBase {
    public    String pub  = "public";
    protected String prot = "protected";
              String pkg  = "package-private";
    private   String priv = "private";

    protected String protectedMethod() { return "AccountBase.protectedMethod()"; }
}
$ java -cp out shop.app.AccessMain
-- modifiers as the JVM records them --
field pub   -> 'public'
field prot  -> 'protected'
field pkg   -> ''
field priv  -> 'private'
The default (package-private) field shows up as an empty string because there is no keyword for it. Keep that in mind: leaving the modifier off is a decision, not a neutral default.

What each level can actually read

Three viewers read the same four fields: a class in the same package (PackageNeighbour.java), a subclass in another package (AccountSub.java), and unrelated code in another package (AccessMain.java).
package shop.core;

public class PackageNeighbour {
    public static void show() {
        AccountBase b = new AccountBase();
        System.out.println("same package reads: " + b.pub + ", " + b.prot + ", " + b.pkg);
        // b.priv would not compile here
    }
}
package shop.app;

import shop.core.AccountBase;

public class AccountSub extends AccountBase {
    public void inside(AccountSub other) {
        System.out.println("subclass, via this:  " + this.prot);
        System.out.println("subclass, via other AccountSub: " + other.prot);
        System.out.println("subclass, method:    " + protectedMethod());
    }
}
$ java -cp out shop.app.AccessMain
same package reads: public, protected, package-private
subclass, via this:  protected
subclass, via other AccountSub: protected
subclass, method:    AccountBase.protectedMethod()
world (other package, no inheritance) reads: public
The neighbour read public, protected and package-private. The subclass, in a different package, read protected through this, through another AccountSub, and through an inherited method. The unrelated class read only public. Notice the subclass never touched pkg or priv; the next section shows what happens if it tries.

The protected trap across packages

The common misreading is “protected means subclasses can use it”. The real rule, for code in a different package, is narrower: a subclass can use a protected member only through a reference of its own type (or a subtype), not through a plain reference to the parent type. BrokenAccess.java tries four things:
package shop.app;

import shop.core.AccountBase;

public class BrokenAccess extends AccountBase {
    void trap() {
        AccountBase other = new AccountBase();
        System.out.println(other.prot);        // protected, through a plain AccountBase reference
        System.out.println(other.protectedMethod());
        System.out.println(this.pkg);          // package-private in another package
        System.out.println(this.priv);         // private
    }
}
$ javac -d out shop/core/*.java shop/app/*.java
shop/app/BrokenAccess.java:8: error: prot has protected access in AccountBase
        System.out.println(other.prot);        // protected, through a plain AccountBase reference
                                ^
shop/app/BrokenAccess.java:9: error: protectedMethod() has protected access in AccountBase
        System.out.println(other.protectedMethod());
                                ^
shop/app/BrokenAccess.java:10: error: pkg is not public in AccountBase; cannot be accessed from outside package
        System.out.println(this.pkg);          // package-private in another package
                               ^
shop/app/BrokenAccess.java:11: error: priv has private access in AccountBase
        System.out.println(this.priv);         // private
                               ^
4 errors
Errors 1 and 2 are the trap: other is a plain AccountBase, so even though BrokenAccess is a subclass, it may not read other.prot or call other.protectedMethod(). Compare with the working AccountSub above, where other was an AccountSub. Errors 3 and 4 are the simple cases: package-private is invisible from another package, and private is invisible from every other class, subclasses included.
Why the rule exists. If any subclass could read protected state through any parent reference, a subclass in one package could reach into unrelated parent objects. Restricting access to the subclass’s own type keeps the parent’s protected state reachable only through objects that are actually part of the subclass.

Which modifiers are allowed where

Elementpublicprotectedpackage-privateprivate
Top-level class or interfaceYesNoYesNo
Nested classYesYesYesYes
Field, method, constructorYesYesYesYes
Interface member (method)Yes (default)NoNoYes (private helper methods, Java 9+)
A top-level class can only be public or package-private, because “private to its own class” or “protected for subclasses” has no meaning for something that is not inside another class. A nested class can be any of the four. The reflection output from AccessMain.java shows how each was recorded:
    private static class PrivateNested { }
    protected static class ProtectedNested { }
    static class PackageNested { }
    public static class PublicNested { }
$ java -cp out shop.app.AccessMain
nested PublicNested     -> 'public static'
nested PackageNested    -> 'static'
nested ProtectedNested  -> 'protected static'
nested PrivateNested    -> 'private static'
top-level AccessMain -> 'public'
Two details worth noticing: the nested classes also carry static (they were declared that way), and the top-level class shows only public. A public top-level class must live in a file of the same name, which is why the examples are one public class per file.

Modules add a layer above public (JPMS)

Since Java 9, public means “public to everyone who can see the package”. A module decides which of its packages are visible at all, using exports in module-info.java. Two small modules, bank.lib and bank.app, show it; these module files are shown inline only.
module bank.lib {
    exports bank.lib.api;          // bank.lib.internal is NOT exported
}
package bank.lib.internal;

public class Secret {                     // public class, public method...
    public static double base() { return 3.5; }
}
Secret is a public class with a public method, but its package is not exported. The app module requires the library and uses only the exported API:
module bank.app {
    requires bank.lib;
}
package bank.app;

import bank.lib.api.Rates;

public class Main {
    public static void main(String[] args) {
        System.out.println("savings rate = " + Rates.savings());
    }
}
$ java --module-path out -m bank.app/bank.app.Main
savings rate = 4.0
(The library module was compiled first with javac -d out/bank.lib $(find ./bank.lib -name '*.java'), then the app with javac --module-path out -d out/bank.app $(find ./bank.app -name '*.java').) Now change Main to import the unexported package:
import bank.lib.api.Rates;
import bank.lib.internal.Secret;
$ javac --module-path out -d out/bank.app $(find ./bank.app -name '*.java')
./bank.app/bank/app/Main.java:4: error: package bank.lib.internal is not visible
import bank.lib.internal.Secret;
               ^
  (package bank.lib.internal is declared in module bank.lib, which does not export it)
1 error
The class is public, yet the compiler says the package is not visible, because bank.lib does not export it. This is how the JDK itself hides its internals, and why a library on the module path can keep public-but-internal classes out of reach.
On the class path nothing changes. JPMS applies only when code is on the module path. Plain class-path programs, including every other example on this page, still see every public class. Reflection can also reach non-exported or private members unless the module system or a security manager stops it, so access modifiers are a design tool, not a security boundary.

Going deeper

Download the code. AccountBase.java · PackageNeighbour.java · AccountSub.java · AccessMain.java · BrokenAccess.java.

They belong to two packages: AccountBase and PackageNeighbour go in shop/core/, and AccountSub, AccessMain and BrokenAccess go in shop/app/. Compile with javac -d out shop/core/*.java shop/app/*.java (BrokenAccess is meant to fail; compile without it for a clean build) and run with java -cp out shop.app.AccessMain. The two module examples are shown inline above.

Common interview questions

1. What are the access modifiers in Java?

public, protected, package-private (no keyword) and private, from widest to narrowest visibility.

2. What is the default access modifier?

Package-private: visible inside the same package only. For interface members the default is public.

3. What is the difference between protected and default access?

Both allow the same package. Protected also allows subclasses in other packages, through references of their own type.

4. Can a top-level class be private or protected?

No. It can only be public or package-private.

5. Can a private method be overridden?

No. It is not visible to subclasses, so a same-named method in a subclass is a new, unrelated method.

6. Can a subclass reduce the visibility of an overridden method?

No. It can keep or widen it, never narrow it; the compiler rejects narrowing.

7. Can a subclass access a protected member through a parent reference?

Not from a different package. It must use this or a reference of its own type (BrokenAccess above shows the error).

8. Can a private constructor exist?

Yes, for singletons, factories and utility classes.

9. Are private members inherited?

They exist in the subclass object but are not accessible by name from subclass code. Access them through public or protected methods.

10. Does a public class need its own file?

Yes: a public top-level class must be in a file with the same name.

11. What does public mean with modules (Java 9+)?

Public to every module that can see the package. A package that is not exported by module-info.java is invisible even if its classes are public.

12. Can reflection break private?

Often yes (setAccessible(true)), unless the module system or other restrictions stop it. Modifiers are not a security mechanism.

13. Which access level should be the default choice?

The narrowest that works: private for fields and helpers, package-private for package internals, public only for the intended API.

Common mistakes

  • Making fields public. Use private fields with methods, so the class can change its internals later.
  • Assuming protected means “subclasses anywhere, any reference”. Across packages it works only through the subclass’s own type.
  • Leaving the modifier off by accident. Package-private is a real, narrow level, not a neutral default.
  • Narrowing visibility in an override. It is a compile error; keep or widen.
  • Treating private as security. Reflection and serialization can reach it; do not store secrets in a field because it is private.
  • Exposing internal classes as public in a library without a module boundary. Once users depend on them, you cannot change them; use exports to hide internals.

FAQ

What are the access modifiers in Java?

Java has four access levels: public (visible everywhere), protected (same package and subclasses), package-private with no keyword (same package only) and private (same class only).

What is the default access modifier in Java?

Package-private. A class, field or method with no modifier is visible only within its own package. Interface methods are implicitly public.

What is the difference between protected and default in Java?

Both are visible within the same package. Protected is also visible to subclasses in other packages, but only through references of the subclass type.

Can a top-level class be private in Java?

No. A top-level class can only be public or package-private. Private and protected apply to members and nested classes.

Does public always mean accessible in Java 9 and later?

No. With modules, a public class is accessible only if its package is exported by its module and the reading module requires it.

Where to go next

Other articles in this Java fundamentals series:

No Comments yet!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.