skip to content
everything.oop.accessControl

access control

private, package-private, protected, public. who can see what.

chapter
chapter 03.03
reading time
6 min read

four levels, from tight to loose: private (this class only), package-private (no keyword: this package), protected (this package plus subclasses anywhere), public (everyone). javac checks them at compile time.

default to the tightest one that works. every public is a promise you'll have to keep.

the picture

think of a house. private is your diary (only you). package-private is the fridge (anyone living in the house). protected is the family recipe (the house, plus your kids who moved out, but they only get their own copy). public is the front porch.

the important bit: access is about which code is asking, not which object. it's checked by javac, file by file, using two facts: what class the code is in, and what package that class is in.

balance =

(nothing): package-private: anything in package shop. subclasses elsewhere are out.

pick a modifier for Account.balance. green classes can read and write it, the rest get a compile error. notice that protected lights up everything (nothing) does, plus one more.

every cell, compiled

a table is a claim. here are the sixteen proofs. each cell is two real files and what javac (25) said about them.

modifierclasspackagesubclassworld
private
(nothing)
protected
public

* only through a reference to your own class. click it.

via =
shop/Account.java
1package shop;
2
3public class Account {
4 protected int balance;
5}
bank/Savings.java
1package bank;
2
3import shop.Account;
4
5class Savings extends Account {
6 void poke() {
7 this.balance = 1;
8 }
9}
$ javac shop/*.java bank/*.java
compiles. no output, which is javac for yes.
click any cell to see the code that tests it. the amber line in Account.java is the declaration, the green or red line is the access. then open the protected × subclass cell and switch via.

protected's weird rule

you know how everyone says "protected means subclasses can access it"? that's only half true, and the other half is the one you hit in prod.

a subclass in another package can touch a protected member only through a reference of its own type (or a subtype). this.balance is fine. otherSavings.balance is fine. someAccount.balance is not, even though Savings extends Account.

bank/Savings.java
package bank;
 
import shop.Account;
 
class Savings extends Account {
    void poke(Savings mine, Account any) {
        this.balance = 1;   // ok: it's me
        mine.balance = 1;   // ok: a Savings, same family line as me
        any.balance = 1;
    }
}

error: balance has protected access in Account

why? because any might be a Checking, a totally different subclass written by someone else. protected means "my subclasses can use what they inherited", not "any subclass can poke into any account". the rule keeps Savings out of Checking's business.

private is per class, not per object

predict the output
java
class Wallet {
    private int cash;
 
    Wallet(int cash) { this.cash = cash; }
 
    void steal(Wallet other) {
        this.cash += other.cash;
        other.cash = 0;
    }
 
    int cash() { return cash; }
}
// in main:
Wallet mine = new Wallet(10);
Wallet yours = new Wallet(50);
mine.steal(yours);
IO.println(mine.cash() + " " + yours.cash());

the same idea covers nesting: a class and the classes nested inside it can all see each other's privates (they're "nestmates" since java 11, so the JVM checks it directly, no synthetic accessor methods).

Main.java
public class Main {
    private int secret = 42;
 
    class Peeker {
        int peek() {
            return secret;          // inner reads outer's private
        }
    }
 
    static class Box {
        private int hidden = 7;
    }
 
    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.new Peeker().peek() + " " + new Box().hidden); // outer reads nested private
    }
}
output
42 7

the smaller rules

  • a top-level class can only be public or package-private. one public top-level class per file, and the file must be named after it.
  • overriding can't shrink access. a public method stays public in every subclass, or javac says attempting to assign weaker access privileges; was public. (it can widen: protected → public is fine.)
  • interface members are implicitly public (methods can be private since java 9, for helpers).
  • local variables have no modifiers at all. their scope is the block.
  • modules sit on top. since java 9, a public class in a package your module doesn't export is invisible outside the module. see modules.
don'tpublic mutable field
java
public class Account {
    public int balance;  // anyone, anywhere, any value
}
doprivate field, a method that guards it
java
public class Account {
    private int balance;
 
    public void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException();
        balance += amount;
    }
 
    public int balance() { return balance; }
}

the field is an implementation detail. the method is the API. once a field is public, you can never add validation, change its type, or make it computed, because someone out there reads it directly.

keylines
  • private → class, (nothing) → package, protected → package + subclasses, public → everyone.
  • access is checked on the code doing the access, not the object being accessed.
  • protected from another package only works through your own type's references.
  • protected is wider than package-private. it's not "subclasses only".
  • start private. widen only when something real needs it.