Question

Java Access Modifiers Explained: public, protected, package-private, and private

javaprivatepublicprotectedaccess-modifiers

Question

In Java, what are the clear rules for choosing between the access modifiers public, protected, package-private (the default), and private when designing classes and interfaces, especially when inheritance is involved?

Short Answer

By the end of this page, you will understand what each Java access modifier allows, where it can be used, and how it affects visibility from the same class, same package, subclasses, and other classes. You will also learn practical guidelines for choosing the most restrictive modifier that still supports your design.

Concept

In Java, access modifiers control who can see and use a class, method, field, or constructor.

These modifiers are a core part of encapsulation, which means hiding internal details and exposing only what other code truly needs.

Java has four access levels:

  • public — accessible from anywhere
  • protected — accessible within the same package, and in subclasses outside the package
  • package-private — accessible only within the same package; this is the default when no modifier is written
  • private — accessible only within the same class

Why this matters

Choosing the right access level helps you:

  • protect internal implementation details
  • prevent accidental misuse
  • make APIs easier to understand
  • reduce coupling between classes
  • change code later without breaking other parts of the program

A common rule in real Java code is:

  • start with the most restrictive access possible
  • only make something more visible when there is a real need

Where each modifier can be used

For top-level types

Top-level classes and interfaces can only be:

  • public
  • package-private

They cannot be private or protected.

For members inside a class

Fields, methods, constructors, and nested classes can use all four:

  • public
  • protected
  • package-private
  • private

Visibility summary

ModifierSame classSame packageSubclass in other packageOther package non-subclass
privateYesNoNoNo
package-privateYesYesNoNo
protectedYesYesYesNo
publicYesYesYesYes

Important note about protected

protected is often misunderstood.

It does two things:

  • allows access from any class in the same package
  • allows access from subclasses in other packages

So protected is not just “for subclasses.” In the same package, even non-subclasses can access it.

Practical design guidance

A simple way to choose:

  • Use private for fields and helper methods unless something outside the class truly needs access.
  • Use package-private for code that should be shared only inside the same package.
  • Use protected when subclasses are expected to use or override something.
  • Use public only for the API you intentionally expose to everyone.

This is why many Java classes have:

  • private fields
  • public methods for safe access
  • package-private helper classes
  • protected extension points in frameworks or base classes

Mental Model

Think of a class as a house with rooms and doors:

  • private = a locked personal drawer. Only the owner can open it.
  • package-private = rooms shared with people in the same building.
  • protected = rooms shared with the building, plus family members who live elsewhere.
  • public = the front desk open to everyone.

The key idea is: not every door should be open.

Good Java design means opening only the doors that need to be open. The fewer things other code can depend on, the easier your code is to maintain.

Take Quiz

Syntax and Examples

Basic syntax

public class PublicClass {
}

class PackagePrivateClass {
}
public class User {
    private String password;
    String username;           // package-private
    protected void reset() {}
    public String getUsername() {
        return username;
    }
}

Example: fields and methods

package app;

public class Account {
    private double balance;
    String ownerName;
    protected void logAccess() {
        System.out.println("Access logged");
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
}

What this means

  • balance is private, so only Account can access it directly.
  • ownerName is package-private, so only classes in the same package can access it.
  • logAccess() is protected, so same-package classes and subclasses can use it.
  • getBalance() and deposit() are public, so any code with access to Account can call them.

Example: inheritance with protected

package animals;

public class Animal {
    protected void makeSound() {
        System.out.println("Some sound");
    }
}
package pets;

import animals.Animal;

public class Dog extends Animal {
    public void bark() {
        makeSound();
    }
}

Here, Dog can call makeSound() because it is a subclass of Animal, even though it is in a different package.

Step by Step Execution

Consider this example:

package demo;

class Counter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.increment();
        counter.increment();
        System.out.println(counter.getCount());
    }
}

Step by step

  1. Counter counter = new Counter();

    • A new Counter object is created.
    • Its private field count starts at 0.
  2. counter.increment();

    • The increment() method runs.
    • Because increment() is inside the same class, it can access the private field count.
    • count becomes 1.
  3. counter.increment();

    • The method runs again.
    • count becomes 2.
  4. System.out.println(counter.getCount());

    • getCount() returns the value of count.
    • The output is:
2

Key lesson

Outside code cannot do this:

counter.count = 100;

That would fail because count is private. The class controls access through public methods instead.

Real World Use Cases

1. Hiding sensitive data

public class User {
    private String passwordHash;

    public boolean checkPassword(String input) {
        return passwordHash.equals(input); // simplified example
    }
}

Sensitive fields should not be directly accessible.

2. Exposing a public API

Libraries often make only a small set of classes and methods public so users interact with a stable API.

public class JsonParser {
    public Object parse(String json) {
        return null;
    }
}

3. Keeping helpers inside a package

A package may contain utility classes meant only for internal use.

class TokenValidator {
    static boolean isValid(String token) {
        return token != null && !token.isEmpty();
    }
}

This avoids exposing internal logic to the whole application.

4. Designing for inheritance

Frameworks often use protected methods as extension points.

public class BaseController {
    protected void beforeHandle() {
        System.out.println("Common setup");
    }
}

Subclasses can customize behavior without exposing the method to every class everywhere.

5. Safe object creation

Constructors may use restricted access to control how objects are created.

public class Config {
    private Config() {}

    public static Config load() {
        return new Config();
    }
}

This pattern is common in factories and utility classes.

Take Quiz

Real Codebase Usage

In real Java projects, access modifiers are usually chosen as part of API design.

Common patterns

private for fields

Most fields are private.

public class Product {
    private String name;
    private double price;

    public String getName() {
        return name;
    }
}

This protects object state and allows validation later.

public for stable operations

Methods that form the intended API are often public.

public void addItem(Item item) {
    if (item == null) {
        throw new IllegalArgumentException("item cannot be null");
    }
    items.add(item);
}

package-private for internal collaboration

In larger codebases, several classes in one package may work together without exposing themselves to the rest of the system.

Examples:

  • parsers and tokenizers in the same parser package
  • service helpers used only inside one module
  • implementation classes hidden behind public interfaces

protected for extension hooks

Base classes may expose methods to subclasses but keep them out of the wider public API.

public abstract class Job {
    public final void run() {
        validate();
        execute();
    }

    protected void validate() {
    }

    protected abstract void execute();
}

This is common in template method patterns and framework code.

Good rule in production code

  • prefer private
  • use package-private for package internals
  • use protected only when subclass access is part of the design
  • use public only for deliberate external APIs

If you make everything public, other code can depend on too many details, making future refactoring harder.

Common Mistakes

1. Making fields public

Broken design:

public class Person {
    public int age;
}

Why this is a problem:

  • any code can set invalid values
  • you cannot easily add validation later

Better:

public class Person {
    private int age;

    public void setAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}

2. Confusing package-private with protected

Many beginners think protected means “subclasses only.” It does not.

protected void doWork() {}

This is accessible:

  • in the same package, even from non-subclasses
  • in subclasses outside the package

3. Forgetting that no modifier means package-private

class Helper {
}

This class is not public. It is visible only inside its package.

4. Trying to use private or protected on top-level classes

Broken code:

private class Helper {
}

Top-level classes can only be public or package-private.

5. Exposing methods too early

Beginners often make methods public “just in case.”

A better habit:

  • start with private
  • widen access only when needed

6. Assuming private means inaccessible to the whole object

Code inside the same class can access private members of another instance of the same class.

public class Box {
    private int value;

    public boolean sameValue(Box other) {
        return this.value == other.value;
    }
}

This is valid because both accesses happen inside Box.

Comparisons

Access modifier comparison

ModifierWhere usableVisible in same classVisible in same packageVisible in subclass in other packageVisible everywhere
privateMembers, nested classesYesNoNoNo
package-privateTop-level types, members, nested classesYesYesNoNo
protectedMembers, nested classesYesYesYesNo
publicTop-level types, members, nested classesYesYesYesYes

protected vs package-private

Featureprotectedpackage-private
Same package accessYesYes
Subclass access from another packageYesNo
Good for inheritance APIsYesNo
Hidden outside package in non-subclassesYesYes

private vs public

ChoiceWhen to use
privateInternal state and helper logic
publicIntended external API

Practical default choice

If you are unsure:

  1. start with private
  2. use package-private if neighboring classes need access
  3. use protected if subclasses need access
  4. use public if all callers should access it
Take Quiz

Cheat Sheet

Quick rules

  • private = only inside the same class
  • package-private = no modifier; same package only
  • protected = same package + subclasses in other packages
  • public = everywhere

Top-level class rules

Allowed:

public class A {}
class B {}

Not allowed:

protected class C {}
private class D {}

Member example

public class Example {
    private int a;
    int b;
    protected int c;
    public int d;
}

Best practice

  • keep fields private
  • expose behavior through methods
  • prefer the least visible modifier that works
  • use protected intentionally, not automatically

Memory aid

Visibility from smallest to widest:

private < package-private < protected < public

Common exam/interview fact

protected includes:

  • same package access
  • subclass access across packages

It is not limited to subclasses.

FAQ

When should I use private in Java?

Use private for fields and helper methods that should only be used inside the class. This is the safest default.

What is package-private in Java?

Package-private is the default access level when no modifier is written. It allows access only within the same package.

Is protected only for subclasses?

No. protected also allows access from any class in the same package, even if it is not a subclass.

Can a top-level Java class be private?

No. A top-level class can only be public or package-private.

Should Java fields be public?

Usually no. Public fields expose internal state directly and make validation and future changes harder.

When should I use protected instead of package-private?

Use protected when subclasses outside the package need access. If access is only needed inside the same package, package-private is often better.

What is the safest default access modifier?

Usually private for members, and package-private for helper top-level classes that should stay internal to a package.

Take Quiz

Mini Project

Description

Build a small Java example that models a bank account and uses all four access levels correctly. This project demonstrates encapsulation, package-level collaboration, and controlled inheritance.

Goal

Create a simple set of classes where sensitive data stays private, internal helpers remain package-private, subclasses use protected behavior, and public methods expose the safe API.

Requirements

  • Create a BankAccount class with a private balance field.
  • Add public methods to deposit money and read the balance.
  • Add a protected method that subclasses can use for audit behavior.
  • Add a package-private helper class used only inside the same package.
Take Quiz

Keep learning

Related questions

View more