00:00

Access Modifiers in Java – public, protected, default and private

Access modifiers control who can use a class, field, method or constructor. They let you expose a clean public interface while hiding the internal details that other code should not touch, which is the heart of encapsulation.

Java has four access levels: public, protected, default (no keyword, also called package-private) and private. This tutorial explains each one with a multi-package example, including the real compiler errors you get when rules are broken.

1. The Four Access Levels

ModifierSame classSame packageSubclass in another packageAny other class
publicYesYesYesYes
protectedYesYesYes (through inheritance)No
default (no keyword)YesYesNoNo
privateYesNoNoNo

From most open to most restricted: public → protected → default → private.

2. A Multi-Package Example

To see the rules in action we need two packages. The shop.core package contains Product, with one field at each access level, and Inventory:

Product.java
package shop.core;

public class Product {
    public String name;            // visible everywhere
    protected double price;        // same package + subclasses
    int stock;                     // (default) same package only
    private double costPrice;      // this class only

    public Product(String name, double price, int stock, double costPrice) {
        this.name = name;
        this.price = price;
        this.stock = stock;
        this.costPrice = costPrice;
    }

    public double profitPerUnit() {
        return price - costPrice;  // private field is fine inside the class
    }
}
Inventory.java
package shop.core;

// Same package as Product
public class Inventory {
    public static String check(Product p) {
        return p.name + ": price " + p.price + ", stock " + p.stock;
        // p.costPrice would not compile: private
    }
}

The shop.app package contains a subclass, DiscountedProduct, and an unrelated class, Store:

DiscountedProduct.java
package shop.app;

import shop.core.Product;

// Different package, but a subclass of Product
public class DiscountedProduct extends Product {
    public DiscountedProduct(String name, double price) {
        super(name, price, 10, price * 0.6);
    }

    public double salePrice() {
        return price * 0.9;        // protected field is inherited: allowed
        // stock would not compile: default access, different package
    }
}
Store.java
package shop.app;

import shop.core.Inventory;
import shop.core.Product;

// Different package, NOT a subclass
public class Store {
    public static void main(String[] args) {
        Product pen = new Product("Pen", 20.0, 100, 12.0);

        System.out.println("public name:        " + pen.name);
        System.out.println("public method:      " + pen.profitPerUnit());
        System.out.println("via same package:   " + Inventory.check(pen));
        // pen.price, pen.stock, pen.costPrice would not compile here

        DiscountedProduct bag = new DiscountedProduct("Bag", 1000.0);
        System.out.println("subclass method:    " + bag.salePrice());
    }
}
Output
public name:        Pen
public method:      8.0
via same package:   Pen: price 20.0, stock 100
subclass method:    900.0

Compile all four files with javac -d out *.java and run with java -cp out shop.app.Store. Each class used only the members it is allowed to see.

3. What Happens When You Break the Rules

If Store-like code in another package tries to read the hidden fields directly, the compiler refuses:

javac output
Peek.java:8: error: price has protected access in Product
        System.out.println(pen.price);
                              ^
Peek.java:9: error: stock is not public in Product; cannot be accessed from outside package
        System.out.println(pen.stock);
                              ^
Peek.java:10: error: costPrice has private access in Product
        System.out.println(pen.costPrice);
                              ^
3 errors

Access is checked at compile time, so mistakes are caught before the program ever runs.

4. public

public members are visible to all code everywhere. Use it for the API that other classes are meant to call: public classes, their important constructors and methods, and constants. A public class must be declared in a file with the same name.

5. protected

protected members are visible within the same package and to subclasses in other packages. In another package, a subclass can use the protected member through inheritance, as DiscountedProduct.salePrice() does with price, but not on an arbitrary Product object it does not own. Use protected for hooks that subclasses are designed to use or override.

6. Default (Package-Private)

If you write no modifier, the member is visible only inside its own package. Inventory can read stock, but DiscountedProduct cannot, even though it is a subclass, because it lives in another package. Package-private is a good choice for helper classes and methods that are shared inside one feature package but are not part of its public API.

Note: there is no default keyword for access. Writing default before a method means something else entirely: a default method in an interface.

7. private

private members are visible only inside the class that declares them (including its nested classes). Fields should almost always be private, with access through methods that can enforce rules:

Account.java
public class Account {
    private double balance;                     // hidden: cannot be set to an invalid value from outside

    public double getBalance() { return balance; }

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

    public static void main(String[] args) {
        Account acc = new Account();
        acc.deposit(500);
        try {
            acc.deposit(-50);
        } catch (IllegalArgumentException e) {
            System.out.println("Rejected: " + e.getMessage());
        }
        System.out.println("Balance: " + acc.getBalance());
    }
}
Output
Rejected: Deposit must be positive
Balance: 500.0

Because balance is private, no other class can set it to a negative value; every change goes through deposit().

8. Private Constructors

A private constructor stops other classes from creating objects with new. It is used for singletons, utility classes with only static methods, and classes that offer static factory methods:

AppConfig.java
public class AppConfig {
    private static final AppConfig INSTANCE = new AppConfig();
    private final String environment;

    private AppConfig() {                       // private constructor: no "new AppConfig()" outside
        environment = "production";
    }

    public static AppConfig getInstance() {     // the only way to get the object
        return INSTANCE;
    }

    public String getEnvironment() { return environment; }

    public static void main(String[] args) {
        AppConfig a = AppConfig.getInstance();
        AppConfig b = AppConfig.getInstance();
        System.out.println("Environment: " + a.getEnvironment());
        System.out.println("Same object: " + (a == b));
    }
}
Output
Environment: production
Same object: true

See Private Constructor in Java.

9. Access Modifiers for Classes

  • A top-level class can be public or default (package-private) only. It cannot be private or protected.
  • A nested class can use any of the four modifiers.
  • Interface members are implicitly public (methods may also be private since Java 9).
  • Enum constructors are always private.

10. Access Modifiers and Overriding

An overriding method cannot reduce visibility. If a parent method is protected, the child's version can be protected or public, but not default or private. Private methods are not inherited, so they cannot be overridden at all.

Parent methodAllowed in overriding method
publicpublic
protectedprotected, public
defaultdefault, protected, public
privateNot inherited; a method with the same name is a new method

11. Beyond Classes: Modules

Since Java 9, the module system adds another layer: a module's module-info.java decides which packages it exports. Even public classes in a non-exported package are hidden from other modules. Most applications on the class path are not affected, but libraries use this to hide internal packages.

12. Best Practices

  • Start with the most restrictive level and open up only when needed.
  • Make fields private; expose behaviour through methods.
  • Keep helper classes package-private instead of public.
  • Use protected deliberately, for an API you design for subclasses.
  • Avoid public mutable fields; public static final constants of immutable types are fine.

13. Summary

  • public: everywhere. protected: package plus subclasses. Default: package only. private: class only.
  • Access rules are enforced by the compiler.
  • Top-level classes can only be public or package-private.
  • Overriding methods cannot reduce visibility.
  • Private fields and controlled methods are the foundation of encapsulation.

Related Tutorials

Reference: Java Language Specification §6.6: Access Control