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.
Sir, Kabir only wanted to print the balance, but his code wiped it. Can Java stop that?
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.
But then how does the report print it?
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.
What are access modifiers?
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.
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
bank, java.util). More in Lesson 6.6.extends another (Lesson 7.2). Relevant for protected.The access table (learn this one)
| Modifier | Same class | Same package | Subclass in another package | Anywhere 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.
Where each level is used
public
The class's promise to the world: getBalance(), deposit(), the class itself. Keep this list short and stable.
protected
Hooks meant for subclasses to use or override, like a template method's steps. Also visible inside the package.
for child classesdefault (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 packageAnd 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
}
}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 classShow 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.
public String ifsc = "SBIN0001234";
5 of 5 places compile
- ✓Account itself (same class)
ifsc // inside showOwn()compiles · prints: SBIN0001234 - ✓Branch (same package bank, not a subclass)
a.ifsccompiles · prints: SBIN0001234 - ✓Savings (package shop, subclass of Account)
ifsc // inherited, same as this.ifsccompiles · prints: SBIN0001234 - ✓Kiosk (package shop, not related)
a.ifsccompiles · prints: SBIN0001234 - ✓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();
}
}protected String ifsc = "SBIN0001234";
3 of 5 places compile
- ✓Account itself (same class)
ifsc // inside showOwn()compiles · prints: SBIN0001234 - ✓Branch (same package bank, not a subclass)
a.ifsccompiles · prints: SBIN0001234 - ✓Savings (package shop, subclass of Account)
ifsc // inherited, same as this.ifsccompiles · prints: SBIN0001234 - ✗Kiosk (package shop, not related)
a.ifscjavac: Kiosk.java:8: error: ifsc has protected access in Account - ✗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();
}
}String ifsc = "SBIN0001234";
2 of 5 places compile
- ✓Account itself (same class)
ifsc // inside showOwn()compiles · prints: SBIN0001234 - ✓Branch (same package bank, not a subclass)
a.ifsccompiles · prints: SBIN0001234 - ✗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 - ✗Kiosk (package shop, not related)
a.ifscjavac: Kiosk.java:8: error: ifsc is not public in Account; cannot be accessed from outside package - ✗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();
}
}private String ifsc = "SBIN0001234";
1 of 5 places compile
- ✓Account itself (same class)
ifsc // inside showOwn()compiles · prints: SBIN0001234 - ✗Branch (same package bank, not a subclass)
a.ifscjavac: Branch.java:6: error: ifsc has private access in Account - ✗Savings (package shop, subclass of Account)
ifsc // inherited, same as this.ifscjavac: Savings.java:7: error: ifsc has private access in Account - ✗Kiosk (package shop, not related)
a.ifscjavac: Kiosk.java:8: error: ifsc has private access in Account - ✗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();
}
}How the output changes: the whole experiment in one table
Who uses ifsc | public | protected | default | private |
|---|---|---|---|---|
| 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 printed | 5 | 3 | 2 | 1 |
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
}
}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.
How javac checks an access
Find the member being used
javac resolvesint b = acc.balance;acc's type (bank.Account) and finds the fieldbalanceand its modifier.Compare where the code is with where the member is declared
The using code is in classshop.Report; the member is declared in classbank.Account. Different class, different package, and Report is not a subclass of Account.Apply the table
privaterequires the same class: ✗. javac reports balance has private access in Account and writes no class file.The JVM checks again when linking
If someone edits bytecode or compiles against an old version ofAccount, the JVM still refuses withIllegalAccessErrorat run time. Access control is part of Java's security, not just a compile-time hint.Reflection can bypass it, with permission
Frameworks such as JSON mappers use reflection to read private fields. Since Java 9 the module system can block this for JDK internals. TreatField f = Account.class.getDeclaredField("balance"); f.setAccessible(true); // frameworks do this for private fieldsprivateas “not part of the contract”, not as encryption.
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.
owneris public, so code in any package can read it.typeis protected. The code is in packageshopand the classMainisn't a subclass of Account: not allowed.branchCodehas no modifier (default). Only classes inside packagebankcan use it.balanceis private: only the Account class's own code may touch it.- The public method
getBalance()is declared inside Account, so it may readbalanceand return the value. Outsiders get the value without getting write access. - A class
Savings extends Accountin packageshopcan usetypein its own code, because it inherits it. That's what protected is for.
- javac works out where the using code lives (class
shop.Main) and compares it with whereowneris declared. Public members pass from anywhere. - Protected requires the same package, or a subclass using the member through inheritance. Neither applies, so it is denied.
- Default (package-private) requires the same package.
shopisn'tbank: denied. - Private requires the same class. Denied, with the error Kabir would have seen.
- The call to the public method passes. Inside the method body, the use of
balanceis in class Account itself, so that check passes too. - Inside
Savings, a subclass,this.typerefers to its own inherited field, so protected access is granted even across packages.
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.
- Visitor kiosk (another package, not related): it can use only
publicmembers, so it may show the patient's name. - NurseStation is a subclass of Patient in another package: it also gets the
protectedward, through inheritance. - WardBoard is in the same package
hospital: it gets public, protected and default members, including the bed number. - Patient's own methods can use everything, including the
privatediagnosis. - The kiosk tries
p.diagnosis: javac refuses: diagnosis has private access in Patient. Sensitive data can't leak by accident. - The right way: a public method like
summaryFor(doctor), written inside Patient, checks who is asking and returns only what they may see. The rule lives in one place. (Simplified: real hospital systems also need permission checks at run time.)
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).
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.
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.
4. Frameworks and protected hooks
Base classes expose protected methods for subclasses to customise, e.g. HttpServlet.doGet and doPost, which your servlet overrides.
Examples are simplified. Since Java 9, modules add another layer: a package is only visible to other modules if its module exports it.
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());
}
}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.
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.
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.
PAN "ABCDE1234F"
PAN: XXXXXX234F
Approach
- Store the PAN in a private final field.
- The public method builds a masked copy; the original never leaves the class.
- main can't read
paneven 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.
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.
set 85, then try 850
true false marks=85
Approach
private int marks.public boolean setMarks(int m)validates before assigning.- 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.
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.
"9876543210"
+91 98765 43210
Approach
private Phone() {}blocksnew Phone()outside the class.public static String format(String digits)does the work.- 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.
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 withsetAccessible.
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.
Getter for a private field
Make temperature private in Sensor and add a public getter. Print the reading from main.
reading 31
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");
}
}How it works: other classes can read the value but not change it, because there is no setter.
Validated setter
A Product has a private price. The setter must ignore negative prices. Set 499, then try −50, and print the price.
499 then -50
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());
}
}How it works: because price is private, the setter is the only door, and it rejects bad values.
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.
branch "Kharghar"
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());
}
}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.
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.
PNR "8421937750", seat "S4-32"
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());
}
}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.
Hide a helper method
A Bill computes a total with a private helper round2. Only total() is public.
items 99.999 and 50.004
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());
}
}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.)
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.
code "482913"; user types "482931", then "482913"
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"));
}
}How it works: the class exposes behaviour (verify) instead of data (the code). Attempts are private too, so no caller can reset them.
Salary visible to HR only
An Employee keeps salary private. salaryFor(String role) returns the number only if the role is "HR", otherwise "hidden".
role "HR", role "TEAM"
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"));
}
}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.)
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.
amount 1000, fee 0.5%
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));
}
}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?
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
privateand addpublic double getBalance(). The report only needs to read. - Changes go through
deposit/withdraw, which enforce rules. Later the field can even become aBigDecimalwithout breaking callers.
Avoid: “we'll just be careful”. The compiler should enforce it.
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
@Deprecatedwith 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.
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”.
“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 -vor 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.