Access Modifiers in Java

Four levels of visibility control who can see a member, and choosing the narrowest one that works is the single easiest design win.

The four levels

ModifierSame classSame packageSubclass, other packageAnywhere
privateYesNoNoNo
package private (no keyword)YesYesNoNo
protectedYesYesYesNo
publicYesYesYesYes

Writing no modifier at all is a real choice, called package private or default access. It is not the same as public.

Example

package com.example.billing;

public class Invoice {

    private double total;        // this class only
    double taxRate;              // package private: com.example.billing
    protected String reference;  // package, plus subclasses anywhere
    public String customerName;  // everywhere

    private double tax() {       // an internal helper
        return total * taxRate;
    }

    public double payable() {    // the public operation
        return total + tax();
    }
}

What protected really means

package com.example.reports;

import com.example.billing.Invoice;

public class TaxReport extends Invoice {

    void show() {
        System.out.println(this.reference);   // allowed: through inheritance
    }

    void showOther(Invoice other) {
        // System.out.println(other.reference);   // not allowed
    }
}
From another package, protected gives a subclass access to the member through its own inheritance, not on arbitrary instances of the superclass. This restriction surprises people and is worth remembering.

Top level types

public class Order { }     // visible everywhere, file must be Order.java
class OrderValidator { }   // package private, a helper for this package only

A top level class can only be public or package private. private and protected apply to nested types, not to top level ones.

Access and overriding

class Base {
    protected void run() { }
}

class Derived extends Base {
    @Override
    public void run() { }        // widening is allowed
    // private void run() { }    // narrowing is a compile error
}

An override may widen visibility but never narrow it, because a subclass must remain usable everywhere the superclass is.

A practical layout

public class NoteService {

    private final NoteRepository repository;   // internal collaborator

    public NoteService(NoteRepository repository) {
        this.repository = repository;
    }

    public Note publish(long id) {             // the API
        Note note = repository.find(id);
        validate(note);
        return repository.save(note.withStatus("published"));
    }

    private void validate(Note note) {         // an implementation detail
        if (note.title().isBlank()) {
            throw new IllegalArgumentException("Title is required");
        }
    }
}

Two public members and one private helper. Anyone reading the class knows immediately what it offers and what is internal.

Modules

Since Java 9 a module adds a further layer: a package is only visible outside the module if the module declaration exports it. A public class in a package that is not exported is unreachable from other modules. Access control is therefore a combination of the modifier and the module declaration.

Common mistakes

  • Making everything public so the code compiles quickly, and then being unable to change anything later.
  • Using protected for fields, which permanently ties every subclass to the current representation.
  • Assuming no modifier means public.
  • Trying to narrow visibility in an override.
  • Assuming private hides a member from reflection. It does not by itself.

Best practices

  • Start with private and widen only when something outside genuinely needs access.
  • Keep fields private and expose behaviour instead.
  • Use package private for helper classes that support one package.
  • Treat protected as part of the public API, because subclasses will depend on it forever.
  • Keep the public surface of a class small enough to describe in one sentence.

Practice

  1. For a Payment class, decide the modifier for each member and justify each choice.
  2. Why can a subclass in another package not read other.reference on a different instance?
  3. What happens when an override tries to change public to protected?
  4. Explain when package private is a better choice than public, with an example.
  5. Describe how a module declaration can make a public class unreachable.

Conclusion

Visibility is a design decision, not paperwork. Choose the narrowest access that works, remember that anything public or protected becomes a promise, and keep the rest free to change.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.