Access modifiers

A bank's ATM screen may show your balance, but it must never let any code write to it directly. Java's access modifiers (public, protected, default and private) decide which code is allowed to use a class, field, method or constructor. You'll learn the four levels, the table every exam asks for, and how the compiler enforces them.

Module 6 · OOP Basics⏱ 20 minBeginner8 practice problems
1 The story

Who changed the balance?

In the college bank project, Kabir's report code accidentally wrote acc.balance = 0; while trying to print it. Every account in the demo showed zero. Riya asks Arjun Sir how to stop other code from touching what it shouldn't.

public: everyone protected: package + subclasses default: same package private: this classbalance acc.balance = 0; ✗ balance has private access in Account acc.getBalance() ✓ myphatshala.in
Riya

Sir, Kabir only wanted to print the balance, but his code wiped it. Can Java stop that?

Arjun Sir

Yes, before the program even runs. Mark the field private. Then only code written inside the Account class can touch it, and the compiler rejects acc.balance = 0 anywhere else.

Riya

But then how does the report print it?

Arjun Sir

Give the class a public method, getBalance(), that only reads. You choose exactly what the outside world may do. There are four levels; let's learn them all.

2 Definition

What are access modifiers?

Formal definition (write this in exams)

Access modifiers are keywords that set the visibility of classes, constructors, fields and methods, that is, which other code may refer to them. Java has four access levels: public (accessible from everywhere), protected (accessible within the same package and from subclasses in other packages), default or package-private, written with no keyword (accessible only within the same package), and private (accessible only within the class that declares it). Access is checked by the compiler, and again by the JVM when classes are linked.

In simple words

A label on each member saying who may use it: everyone, family (subclasses) and neighbours (same package), neighbours only, or nobody outside the class.

Words you'll meet in this lesson

VisibilityWhere in the code a name can be used.
PackageA group of related classes, like a folder (bank, java.util). More in Lesson 6.6.
Package-privateThe default level: no keyword written. Visible inside the same package only.
SubclassA class that extends another (Lesson 7.2). Relevant for protected.
Getter / setterPublic methods that read or change a private field under the class's rules.
EncapsulationHiding data behind methods. Access modifiers are its main tool (Lesson 7.1).

The access table (learn this one)

ModifierSame classSame packageSubclass in another packageAnywhere else
public✓✓✓✓
protected✓✓✓ (through inheritance)✗
default (no keyword)✓✓✗✗
private✓✗✗✗

Exam tip: from most open to most restricted: public > protected > default > private. A top-level class can only be public or default; private and protected are allowed only on members and nested classes. A .java file can have at most one public top-level class, and the file must be named after it.

3 Types and ways to use it

Where each level is used

public

The class's promise to the world: getBalance(), deposit(), the class itself. Keep this list short and stable.

API methods

protected

Hooks meant for subclasses to use or override, like a template method's steps. Also visible inside the package.

for child classes

default (package)

Helpers shared by classes in one package that outsiders shouldn't depend on. Also the level you get if you forget to write one.

internal to a package

And private: the default choice for fields. Everything not needed outside the class should be private.

Private members inside one file

class Account {
    private int balance = 5000;      // only Account's own code can use this
    int branchCode = 7;              // default: any class in this package

    public int getBalance() {        // read-only window
        return balance;
    }
}

public class Main {
    public static void main(String[] args) {
        Account acc = new Account();
        System.out.println(acc.getBalance());   // OK: public method
        System.out.println(acc.branchCode);     // OK: same package (same file)
        // System.out.println(acc.balance);     // error: balance has private access in Account
    }
}
5000 7

One field, four modifiers: what compiles and what prints

The best way to learn the table is to run it. Here is a small project with two packages. Class bank.Account has one field, ifsc. Five classes try to read it from different places. We will change only the modifier on ifsc and see, each time, which classes compile and how the output changes.

src/
├── bank/
│   ├── Account.java    declares ifsc
│   └── Branch.java     same package, not a subclass
├── shop/
│   ├── Savings.java    other package, subclass (uses its inherited ifsc)
│   ├── Kiosk.java      other package, not related
│   └── Loan.java       other package, subclass (reads ifsc of a plain Account object)
└── app/
    └── Main.java       calls show() on each class
Show the five classes
package bank;

public class Account {
    public String ifsc = "SBIN0001234";   // ← the only word we change

    public void showOwn() {
        System.out.println("1. Account itself     : " + ifsc);
    }
}
package bank;

public class Branch {
    public void show() {
        Account a = new Account();
        System.out.println("2. Branch (same pkg)  : " + a.ifsc);
    }
}
package shop;

import bank.Account;

public class Savings extends Account {
    public void show() {
        System.out.println("3. Savings (subclass) : " + ifsc);   // its own inherited copy
    }
}
package shop;

import bank.Account;

public class Kiosk {
    public void show() {
        Account a = new Account();
        System.out.println("4. Kiosk (other pkg)  : " + a.ifsc);
    }
}
package shop;

import bank.Account;

public class Loan extends Account {
    public void show() {
        Account other = new Account();
        System.out.println("5. Loan (via parent)  : " + other.ifsc);   // someone else's Account
    }
}

Press a modifier below. Every ✓, ✗, error message and output line was produced by the real javac and java. When a class does not compile, the whole build fails, so we leave that class out of Main and run the rest.

Choose the access level of ifsc:

public String ifsc = "SBIN0001234";

5 of 5 places compile
  1. ✓Account itself (same class)ifsc // inside showOwn()compiles · prints: SBIN0001234
  2. ✓Branch (same package bank, not a subclass)a.ifsccompiles · prints: SBIN0001234
  3. ✓Savings (package shop, subclass of Account)ifsc // inherited, same as this.ifsccompiles · prints: SBIN0001234
  4. ✓Kiosk (package shop, not related)a.ifsccompiles · prints: SBIN0001234
  5. ✓Loan (package shop, subclass, but reads a plain Account object)other.ifsccompiles · prints: SBIN0001234

public: everyone. No checks fail, so all five classes compile and the program prints 5 lines. Convenient, but now any code in any package can also change the IFSC (a.ifsc = "FAKE"; would compile too).

package app;

import bank.Account;
import bank.Branch;
import shop.Kiosk;
import shop.Loan;
import shop.Savings;

public class Main {
    public static void main(String[] args) {
        new Account().showOwn();
        new Branch().show();
        new Savings().show();
        new Kiosk().show();
        new Loan().show();
    }
}
1. Account itself : SBIN0001234 2. Branch (same pkg) : SBIN0001234 3. Savings (subclass) : SBIN0001234 4. Kiosk (other pkg) : SBIN0001234 5. Loan (via parent) : SBIN0001234

protected String ifsc = "SBIN0001234";

3 of 5 places compile
  1. ✓Account itself (same class)ifsc // inside showOwn()compiles · prints: SBIN0001234
  2. ✓Branch (same package bank, not a subclass)a.ifsccompiles · prints: SBIN0001234
  3. ✓Savings (package shop, subclass of Account)ifsc // inherited, same as this.ifsccompiles · prints: SBIN0001234
  4. ✗Kiosk (package shop, not related)a.ifscjavac: Kiosk.java:8: error: ifsc has protected access in Account
  5. ✗Loan (package shop, subclass, but reads a plain Account object)other.ifscjavac: Loan.java:8: error: ifsc has protected access in Account

protected: package + subclasses. Compared with public, two lines disappear. Kiosk is outside package bank and is not a subclass, so it is refused. Loan is a subclass, but it reads other.ifsc from a plain Account object, not its own inherited field, so it is refused too. Savings still works because it uses its own inherited ifsc. Output drops to 3 lines.

package app;

import bank.Account;
import bank.Branch;
import shop.Savings;

public class Main {
    public static void main(String[] args) {
        new Account().showOwn();
        new Branch().show();
        new Savings().show();
    }
}
1. Account itself : SBIN0001234 2. Branch (same pkg) : SBIN0001234 3. Savings (subclass) : SBIN0001234

String ifsc = "SBIN0001234";

2 of 5 places compile
  1. ✓Account itself (same class)ifsc // inside showOwn()compiles · prints: SBIN0001234
  2. ✓Branch (same package bank, not a subclass)a.ifsccompiles · prints: SBIN0001234
  3. ✗Savings (package shop, subclass of Account)ifsc // inherited, same as this.ifscjavac: Savings.java:7: error: ifsc is not public in Account; cannot be accessed from outside package
  4. ✗Kiosk (package shop, not related)a.ifscjavac: Kiosk.java:8: error: ifsc is not public in Account; cannot be accessed from outside package
  5. ✗Loan (package shop, subclass, but reads a plain Account object)other.ifscjavac: Loan.java:8: error: ifsc is not public in Account; cannot be accessed from outside package

default (no keyword): same package only. Now even the subclass Savings fails, because it lives in package shop. Only the two classes inside bank compile. Note the different wording of the error: is not public in Account; cannot be accessed from outside package. Output drops to 2 lines.

package app;

import bank.Account;
import bank.Branch;

public class Main {
    public static void main(String[] args) {
        new Account().showOwn();
        new Branch().show();
    }
}
1. Account itself : SBIN0001234 2. Branch (same pkg) : SBIN0001234

private String ifsc = "SBIN0001234";

1 of 5 places compile
  1. ✓Account itself (same class)ifsc // inside showOwn()compiles · prints: SBIN0001234
  2. ✗Branch (same package bank, not a subclass)a.ifscjavac: Branch.java:6: error: ifsc has private access in Account
  3. ✗Savings (package shop, subclass of Account)ifsc // inherited, same as this.ifscjavac: Savings.java:7: error: ifsc has private access in Account
  4. ✗Kiosk (package shop, not related)a.ifscjavac: Kiosk.java:8: error: ifsc has private access in Account
  5. ✗Loan (package shop, subclass, but reads a plain Account object)other.ifscjavac: Loan.java:8: error: ifsc has private access in Account

private: this class only. Even Branch, a neighbour in the same package, is refused. Only showOwn(), written inside Account, may read the field. Output drops to 1 line. To let others read it safely, add public String getIfsc() { return ifsc; }: the field stays private and nobody outside can change it.

package app;

import bank.Account;

public class Main {
    public static void main(String[] args) {
        new Account().showOwn();
    }
}
1. Account itself : SBIN0001234

How the output changes: the whole experiment in one table

Who uses ifscpublicprotecteddefaultprivate
Account itself (same class)✓✓✓✓
Branch (same package bank, not a subclass)✓✓✓✗
Savings (package shop, subclass of Account)✓✓✗✗
Kiosk (package shop, not related)✓✗✗✗
Loan (package shop, subclass, but reads a plain Account object)✓✗✗✗
Lines printed5321

Read the table column by column: every step down from public to private removes one more group of users. That is exactly the access table above, now with real output.

The protected trap (row 5): a subclass in another package can use a protected member only through its own inheritance: ifsc, this.ifsc, or a reference of its own type (Loan). Reading it from a plain parent object (other.ifsc where other is an Account) is refused. The same rule applies to a protected constructor: the subclass may call super(), but may not write new Account().

Why the online runner can't show this: it runs one file, and all classes in one file are in the same package. There, public, protected and default all behave alike, and only private changes anything. To see the cross-package results yourself, create the folders above on your PC and run javac -d out bank/*.java shop/*.java app/Main.java then java -cp out app.Main.

Methods and constructors follow exactly the same rules. Change ifsc to a method getIfsc() and every ✓ and ✗ stays the same.

Private constructors

final class GstUtil {
    private GstUtil() { }                      // nobody can create a GstUtil object

    static int gst(int price) { return price * 18 / 100; }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(GstUtil.gst(500));
        // new GstUtil();   // error: GstUtil() has private access in GstUtil
    }
}
90

Rule of thumb: start with private and open up only when you need to. It's easy to make something more visible later, but once other teams depend on a public field, you can never take it back.

4 Step by step

How javac checks an access

  1. Find the member being used

    int b = acc.balance;
    javac resolves acc's type (bank.Account) and finds the field balance and its modifier.
  2. Compare where the code is with where the member is declared

    The using code is in class shop.Report; the member is declared in class bank.Account. Different class, different package, and Report is not a subclass of Account.
  3. Apply the table

    private requires the same class: ✗. javac reports balance has private access in Account and writes no class file.
  4. The JVM checks again when linking

    If someone edits bytecode or compiles against an old version of Account, the JVM still refuses with IllegalAccessError at run time. Access control is part of Java's security, not just a compile-time hint.
  5. Reflection can bypass it, with permission

    Field f = Account.class.getDeclaredField("balance");
    f.setAccessible(true);   // frameworks do this for private fields
    Frameworks such as JSON mappers use reflection to read private fields. Since Java 9 the module system can block this for JDK internals. Treat private as “not part of the contract”, not as encryption.
5 See it happen

Four accesses, four answers

Left: code in another package trying to use members of bank.Account. Right: javac checking each access against the four levels.

Access from package shop to class bank.Account 6 steps
What you see x.owner x.type x.branchCode x.balance x.getBalance() class Savings extends Account → type ✓ compiles prints: Riya prints: 100 error: type has protected access in Account error: branchCode is not public in Account; cannot be accessed from outside package error: balance has private access in Account ✓ compiles inside Savings: this.type is inherited public: anyone may use it. protected: shop is not bank, and not a subclass. default: only classes in package bank. private: only code inside Account. The public getter reads the private field for us. A subclass in another package sees protected. myphatshala.in
Behind the screen javac's checks for each access Same class (bank.Account)? Same package (bank)? Subclass, using an inherited member? Anywhere else no no no yes ← shop.Main no public needs: anywhere ✓ → allowed protected needs: package or subclass → denied default needs: same package → denied private needs: same class → denied getBalance() is public → allowed (it reads balance itself) protected + inherited in a subclass → allowed Denied accesses never reach the class file. myphatshala.in
6 Real-world use

Access control in real systems

Access modifiers are how teams keep large codebases safe to change: what's private can be rewritten freely, what's public must keep working.

Watch it with an example: a hospital record with layers of access

A Patient class in package hospital. Different code in the system asks for different parts of it. The compiler lets each one see only what its access level allows.

Who may read a Patient's fields? 6 steps
Animated example class Patient (package hospital) public String name protected String ward default int bedNo private String diagnosis Visitor kioskother package NurseStationother package, subclass WardBoardsame package Patient itselfsame class Visitor kioskother package Doctor appuses a public method ✓ ✗ ✗ ✗ ✓ ✓ ✗ ✗ ✓ ✓ ✓ ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✗ ✓ p.diagnosis p.summaryFor(doc) public: everyone can read the name protected: + subclasses in other packages default: + any class in package hospital private: only Patient's own code compile error: diagnosis has private access a public method decides what to reveal, and to whom myphatshala.in
privatebalance

1. Protecting money and personal data

Balances, PAN numbers and passwords stay private. Changes go through methods that validate input and write audit logs; reads may be masked (XXXX1234).

Caller→public withdraw(…)→private balance updated safely

2. Stable public APIs

Libraries keep a small public surface. The JDK's ArrayList has a private elementData array; its implementation has changed across versions without breaking your code.

Your code→public add(), get()→Private internals can change

3. Package-private helpers

A payments package may have public PaymentService plus package-private UpiAdapter and CardAdapter that only the service uses. Other teams can't depend on them by accident.

Other packages→PaymentService only→Adapters hidden

4. Frameworks and protected hooks

Base classes expose protected methods for subclasses to customise, e.g. HttpServlet.doGet and doPost, which your servlet overrides.

Framework base class→protected doGet(…)→Your subclass fills it in

Examples are simplified. Since Java 9, modules add another layer: a package is only visible to other modules if its module exports it.

7 Run the code

A wallet with private data and public methods

class Wallet {
    private final String owner;
    private int balance;

    public Wallet(String owner, int balance) {
        this.owner = owner;
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public boolean pay(int amount) {
        if (amount <= 0 || amount > balance) {
            return false;
        }
        balance -= amount;
        log("paid " + amount);
        return true;
    }

    private void log(String msg) {
        System.out.println("[" + owner + "] " + msg);
    }
}

public class Main {
    public static void main(String[] args) {
        Wallet w = new Wallet("Riya", 500);
        System.out.println(w.pay(120));
        System.out.println(w.pay(900));
        System.out.println("Left: " + w.getBalance());
    }
}
[Riya] paid 120 true false Left: 380
  • private int balanceOnly Wallet's own methods can change it.
  • public boolean pay(…)The only way to spend money, and it checks the rules first.
  • private void log(…)An internal helper. Outside code can't call it, so it can be changed freely.
  • private final String ownerPrivate and never reassigned after the constructor.
8 Line by line

Private data changed only through a public method

Access rules are checked by the compiler, so they leave no trace at run time. What you can see is the effect: the only path to the private field goes through methods of its own class.

Worked examples: real problems, solved and traced in memory

Three designs where the right access level does the protecting. Watch the private fields change only inside their own class's methods.

Example 1Banking · PAN

Show only the last 4 characters

Problem statement

A customer's PAN must never be shown in full on screens. Keep it private and offer only maskedPan(), which returns XXXXXX234F-style text.

Input
PAN "ABCDE1234F"
Expected output
PAN: XXXXXX234F

Approach

  1. Store the PAN in a private final field.
  2. The public method builds a masked copy; the original never leaves the class.
  3. main can't read pan even if it wanted to.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Education · marks

Marks that can only be set within 0–100

Problem statement

A results portal stores marks privately. The public setter rejects values outside 0–100, so a typo like 850 can never be saved.

Input
set 85, then try 850
Expected output
true false marks=85

Approach

  1. private int marks.
  2. public boolean setMarks(int m) validates before assigning.
  3. The getter returns the current value.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Platform · utilities

A utility class nobody can instantiate

Problem statement

A Phone utility formats Indian mobile numbers. It has only static methods, so creating objects of it makes no sense: give it a private constructor.

Input
"9876543210"
Expected output
+91 98765 43210

Approach

  1. private Phone() {} blocks new Phone() outside the class.
  2. public static String format(String digits) does the work.
  3. Call it on the class name.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 6.5 in one screen

  • Four levels: public (everyone) > protected (package + subclasses) > default (package) > private (class).
  • Same program, one field, change only the modifier: public → 5 of 5 users compile, protected → 3, default → 2, private → 1 (the class itself).
  • Protected across packages works only through the subclass's own inheritance (ifsc, this.ifsc), not through a plain parent object.
  • No keyword = default (package-private).
  • Top-level classes: only public or default; one public class per file, named like the file.
  • Errors: has private access in …, is not public in …; cannot be accessed from outside package, has protected access in ….
  • Make fields private; expose behaviour through public methods (getters, validated setters).
  • Private constructors stop object creation (utility classes, singletons).
  • Checked by javac and again by the JVM (IllegalAccessError); reflection can bypass it with setAccessible.

Interview and viva questions

Explain the four access modifiers with an example.

In a bank system: public deposit() can be called by any code; protected calculateInterest() can be customised by subclasses like SavingsAccount; a default-level AuditHelper is used only inside the bank package; private balance can only be changed by Account's own methods.

A subclass in another package gets "has protected access" for a protected field. Why?

Because it read the field through a reference of the parent type, e.g. Account other = new Account(); other.ifsc. Outside the package, a subclass may use a protected member only on itself or objects of its own type (ifsc, this.ifsc, loan.ifsc). It is not a key to every Account object in the system.

What is the difference between protected and default?

Both allow access within the same package. protected additionally allows subclasses in other packages to use the member through inheritance. Default does not.

Why should fields be private?

So the class controls every change: it can validate values, keep related fields consistent, log changes, and change its internal representation later without breaking callers. Public fields can be set to anything by anyone, forever.

Can you reduce visibility when overriding a method?

No. An overriding method must be at least as visible as the method it overrides: a public method can't become protected in a subclass. You'll see this again in Lesson 7.4.

Is private access a security guarantee?

It's a strong design boundary enforced by the compiler and the JVM, but code with enough permission can use reflection to read private fields, and anyone with the class file can read its contents. Never rely on private to hide secrets such as passwords or API keys.

Practice problems

Choose the right access level, fix access errors, and design classes with a small public surface. The last three are business tasks.

P1Easy

Getter for a private field

Make temperature private in Sensor and add a public getter. Print the reading from main.

Input
reading 31
Expected output
Temp: 31 C
Show solution and explanation
class Sensor {
    private int temperature = 31;

    public int getTemperature() {
        return temperature;
    }
}

public class Main {
    public static void main(String[] args) {
        Sensor s = new Sensor();
        System.out.println("Temp: " + s.getTemperature() + " C");
    }
}
Temp: 31 C

How it works: other classes can read the value but not change it, because there is no setter.

P2Easy

Validated setter

A Product has a private price. The setter must ignore negative prices. Set 499, then try −50, and print the price.

Input
499 then -50
Expected output
499
Show solution and explanation
class Product {
    private int price;

    public void setPrice(int p) {
        if (p >= 0) {
            price = p;
        }
    }

    public int getPrice() {
        return price;
    }
}

public class Main {
    public static void main(String[] args) {
        Product pen = new Product();
        pen.setPrice(499);
        pen.setPrice(-50);
        System.out.println(pen.getPrice());
    }
}
499

How it works: because price is private, the setter is the only door, and it rejects bad values.

P3Easy

Same package can see default members

Two classes in one file share a package. Show that a default-level field and method are visible, but a private one isn't used.

Input
branch "Kharghar"
Expected output
Kharghar
Branch: Kharghar
Show solution and explanation
class Branch {
    String city = "Kharghar";
    private String vaultCode = "7731";

    String label() {
        return "Branch: " + city;
    }
}

public class Main {
    public static void main(String[] args) {
        Branch b = new Branch();
        System.out.println(b.city);
        System.out.println(b.label());
    }
}
Kharghar Branch: Kharghar

How it works: no keyword means package-private: Main is in the same (unnamed) package, so it can use city and label(). Adding b.vaultCode would not compile.

P4Medium

Read-only object

Design Ticket so its PNR and seat can be read but never changed after creation: private final fields, a constructor, and getters only.

Input
PNR "8421937750", seat "S4-32"
Expected output
8421937750 S4-32
Show solution and explanation
class Ticket {
    private final String pnr;
    private final String seat;

    public Ticket(String pnr, String seat) {
        this.pnr = pnr;
        this.seat = seat;
    }

    public String getPnr() { return pnr; }
    public String getSeat() { return seat; }
}

public class Main {
    public static void main(String[] args) {
        Ticket t = new Ticket("8421937750", "S4-32");
        System.out.println(t.getPnr() + " " + t.getSeat());
    }
}
8421937750 S4-32

How it works: private stops outside writes; final stops even the class itself from reassigning after construction. This “immutable object” style is covered in Lesson 7.1.

P5Medium

Hide a helper method

A Bill computes a total with a private helper round2. Only total() is public.

Input
items 99.999 and 50.004
Expected output
150.0
Show solution and explanation
class Bill {
    private double[] items;

    public Bill(double... items) {
        this.items = items;
    }

    public double total() {
        double t = 0;
        for (double i : items) t += i;
        return round2(t);
    }

    private double round2(double x) {
        return Math.round(x * 100) / 100.0;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Bill(99.999, 50.004).total());
    }
}
150.0

How it works: callers only need total(). The rounding rule is private, so it can change later (for example to banker's rounding) without affecting any other class. (A varargs constructor from Lesson 5.6 accepts any number of items.)

P6Business problem

OTP that can't be read back

An Otp object stores a code privately. It offers verify(String typed) but no getter, so no screen can ever display the code.

Input
code "482913"; user types "482931", then "482913"
Expected output
false
true
Show solution and explanation
class Otp {
    private final String code;
    private int attemptsLeft = 3;

    public Otp(String code) {
        this.code = code;
    }

    public boolean verify(String typed) {
        if (attemptsLeft == 0) return false;
        attemptsLeft--;
        return code.equals(typed);
    }
}

public class Main {
    public static void main(String[] args) {
        Otp otp = new Otp("482913");
        System.out.println(otp.verify("482931"));
        System.out.println(otp.verify("482913"));
    }
}
false true

How it works: the class exposes behaviour (verify) instead of data (the code). Attempts are private too, so no caller can reset them.

P7Business problem

Salary visible to HR only

An Employee keeps salary private. salaryFor(String role) returns the number only if the role is "HR", otherwise "hidden".

Input
role "HR", role "TEAM"
Expected output
Kavya: Rs 85000
Kavya: hidden
Show solution and explanation
class Employee {
    private final String name;
    private final int salary;

    public Employee(String name, int salary) {
        this.name = name;
        this.salary = salary;
    }

    public String salaryFor(String role) {
        if (role.equals("HR")) {
            return name + ": Rs " + salary;
        }
        return name + ": hidden";
    }
}

public class Main {
    public static void main(String[] args) {
        Employee e = new Employee("Kavya", 85000);
        System.out.println(e.salaryFor("HR"));
        System.out.println(e.salaryFor("TEAM"));
    }
}
Kavya: Rs 85000 Kavya: hidden

How it works: the rule about who may see the salary lives inside the class, next to the data. (Real systems check the logged-in user's permissions rather than trusting a role string passed in.)

P8Business problem

Package-style service with an internal adapter

Model a PaymentService (public) that uses an internal UpiAdapter (default access) and a private fee rule. Pay Rs 1,000 and print the result.

Input
amount 1000, fee 0.5%
Expected output
UPI ok: Rs 1005 (incl. fee Rs 5)
Show solution and explanation
class UpiAdapter {
    String send(int amount) {
        return "UPI ok: Rs " + amount;
    }
}

public class Main {
    static class PaymentService {
        private final UpiAdapter upi = new UpiAdapter();

        public String pay(int amount) {
            int fee = fee(amount);
            return upi.send(amount + fee) + " (incl. fee Rs " + fee + ")";
        }

        private int fee(int amount) {
            return amount * 5 / 1000;
        }
    }

    public static void main(String[] args) {
        PaymentService ps = new PaymentService();
        System.out.println(ps.pay(1000));
    }
}
UPI ok: Rs 1005 (incl. fee Rs 5)

How it works: in a real project, UpiAdapter would sit in the payments package with default access, so only PaymentService uses it. The fee rule is private to the service. (Here a static nested class keeps everything in one runnable file; nested classes come in Lesson 10.5.)

Real-world scenario questions (asked in company interviews)

Access-modifier questions in interviews are really design questions: what should the rest of the system be allowed to depend on?

Banking · code reviewFresher

A public balance field

A pull request adds public double balance; to Account “so the report screen can show it”. What do you suggest?

The interviewer is checking: do you understand why fields are private?

Show a strong answer
  • A public field lets any code anywhere write any value, skipping validation and audit logging.
  • Keep it private and add public double getBalance(). The report only needs to read.
  • Changes go through deposit/withdraw, which enforce rules. Later the field can even become a BigDecimal without breaking callers.

Avoid: “we'll just be careful”. The compiler should enforce it.

Platform · library design1–3 years

Another team depends on your helper

You built a payments package. Another team started calling your public class UpiSignatureHelper directly. Now you want to replace it, but their code would break. What should have been done, and what now?

The interviewer is checking: do you think about public surface as a commitment?

Show a strong answer
  • The helper was an internal detail; it should have been package-private so only your package could use it.
  • Now: offer the needed function through your public service, mark the helper @Deprecated with a migration note, and remove it in a later release.
  • For bigger systems, Java modules let you export only the API packages.

Avoid: silently deleting it and breaking another team's build.

E-commerce · inheritance1–3 years

protected or private?

A base class Order has a discount field. Subclasses PrimeOrder and FestiveOrder need different discount rules. A teammate makes the field protected so subclasses can set it. Any concerns?

The interviewer is checking: do you know protected fields also leak to the whole package?

Show a strong answer
  • A protected field is writable by every class in the package and by every subclass, so the base class loses control of its value.
  • Better: keep the field private and add a protected abstract int discountPercent() method (Lesson 7.6) that each subclass implements. The base class applies the rule.
  • Protected methods as hooks are fine; protected fields are usually a smell.

Avoid: thinking protected means “only my subclasses”.

Security · secretsFresher

“It's private, so the key is safe”

A developer stores an API key as private static final String KEY = "sk_live_…"; in the app's code and argues it's safe because it's private. Is it?

The interviewer is checking: do you know what access modifiers can and can't protect?

Show a strong answer
  • No. Access modifiers stop other code from using a name; they don't encrypt anything. The string sits in the class file, and tools like javap -v or any decompiler reveal it.
  • Secrets belong in environment variables or a secrets manager, read at run time, and never in source control.
  • Rotate the leaked key immediately.

Avoid: confusing visibility with confidentiality.