Packages & access: interview questions & scenarios
These are the packages and access-control questions I keep seeing in Java interviews. Most sound like trivia until the follow-up arrives, and the follow-ups are where you show you've debugged a real classpath conflict or designed an API someone else depends on.
Here's how to use this page: read the question, answer it out loud first, then open the model answer. Don't skip the "What if…" chains. That's where most candidates run out of road, and where you can show real depth.
What are the four access levels in Java, and who can see each one?
Tests visibility matrix
Show answerHide answer
Model answer
| Same class | Same package | Subclass, other package | Everyone else | |
|---|---|---|---|---|
public |
✓ | ✓ | ✓ | ✓ |
protected |
✓ | ✓ | ✓ (through its own type) | ✗ |
| package-private (no keyword) | ✓ | ✓ | ✗ | ✗ |
private |
✓ | ✗ | ✗ | ✗ |
"Same class" means the whole top-level class body, including nested classes. With Java 9 modules, public reaches other modules only if the package is exported.
How I’d say it in the interview
"Private is the class, package-private adds the package, protected adds subclasses in other packages, and public is everyone. Each level includes everything the narrower one allows."
Signals depth
Pointing out that protected includes the package, so it's wider than package-private, and mentioning the module layer.
Red flags
"Protected means only subclasses can see it." Or "no modifier means public" (true in Kotlin, not in Java).
Follow-ups interviewers ask next
What if no modifier is written on an interface method?
Then it's
public(andabstract, unless it'sdefaultorstatic). Interface members are implicitly public, and fields are implicitlypublic static final. Since Java 9 you can explicitly writeprivatemethods in an interface to share code between default methods.
Does this compile? Child is in a different package from Base.
Tests protected access, JLS 6.6.2
Show answerHide answer
Model answer
// package com.acme.base
public class Base { protected int count; }
// package com.acme.app
public class Child extends Base {
void a() { count++; } // ✅ through this
void b(Child other) { other.count++; } // ✅ reference of my own type
void c(Base other) { other.count++; } // ❌ count has protected access in Base
}
Outside the package, a subclass may access a protected instance member only through this, super, or a reference whose static type is the subclass (or a subclass of it). other in c could be a Sibling extends Base, and Child isn't responsible for Sibling's implementation.
How I’d say it in the interview
"Across packages, protected lets a subclass use the member as part of its own implementation, so through this or its own type. Not on arbitrary Base objects, which might be a different subclass."
Signals depth
Citing the Object.clone() example: someObject.clone() doesn't compile from your class even though every class extends Object.
Follow-ups interviewers ask next
What if Child were in the same package as Base?
Then all three lines compile: within the package,
protectedbehaves like package-private plus more, so any class in the package can access it through any reference.What if Child needs Base's protected constructor?
A subclass can call it via
super(...), or create an anonymous subclass withnew Base() {}, butnew Base()directly is a compile error.
Can a top-level class be private or protected?
Tests top-level class rules
Show answerHide answer
Model answer
No. A top-level class can only be public or package-private. private and protected are defined relative to an enclosing class, and a top-level class has none, so the compiler rejects them ("modifier private not allowed here"). A file can contain at most one public top-level class, and it must be named after it. Nested classes can use any of the four levels.
How I’d say it in the interview
"Top-level classes are public or package-private. Private and protected only make sense for members of another class, so they're allowed on nested classes."
Follow-ups interviewers ask next
What if I want a class that only one other class can use?
Make it a
private staticnested class inside that class. If several classes in a package need it, make it a package-private top-level class.
How can an inner class read its outer class's private fields?
Tests nestmates, JEP 181
Show answerHide answer
Model answer
Because private is scoped to the top-level class body, which includes all nested classes. At the JVM level, Outer and Outer$Inner are separate class files, though. Before Java 11, javac generated synthetic package-private bridge methods (access$000) so the inner class could reach the private field. Since Java 11 (JEP 181, nestmates), class files record NestHost and NestMembers attributes and the JVM allows private access between nestmates directly. No bridges, smaller class files, and reflection follows the same rule.
How I’d say it in the interview
"Private means the whole top-level class, nested classes included. Historically the compiler faked it with synthetic accessor methods; since Java 11 the JVM understands nests natively."
Signals depth
Knowing that the old bridge methods effectively widened access to package level, which was a (small) encapsulation hole, and that you can see NestMembers with javap -v.
Red flags
"Inner classes inherit from the outer class." They don't; it's about lexical scope, not inheritance.
Why doesn't this override compile?
Tests overriding rules, Liskov substitution
Show answerHide answer
Model answer
class Parent { public void start() {} }
class Child extends Parent {
protected void start() {} // ❌ attempting to assign weaker access privileges; was public
}
An override must be at least as accessible as the method it overrides. Code holding a Parent reference is allowed to call start(); if the actual object were a Child that hid it, substitution would break. Widening (for example protected → public) is allowed.
How I’d say it in the interview
"An override can't narrow access, because callers using the parent type must still be able to call it. It can widen access."
Signals depth
Connecting it to Liskov substitution, and noting the same rule makes every method implementing an interface public.
Follow-ups interviewers ask next
What if Parent.start() were private?
Then
Child.start()isn't an override at all: private methods aren't inherited, so it's an unrelated new method with any access you like. Putting@Overrideon it would be a compile error.What if Parent.start() were package-private and Child in another package?
Also not an override.
Childcan't see the method, so itsstart()is a new method. A call tostart()through aParentreference insideParent's package still runsParent.start(), which surprises people who expected polymorphism.
What does an import actually do at runtime?
Tests imports are compile-time only
Show answerHide answer
Model answer
Nothing. import is a compile-time shortcut that tells javac how to expand simple names. The bytecode always contains fully qualified names (java/util/ArrayList). So wildcard imports cost nothing at runtime, unused imports cost nothing, and you could write every type fully qualified with no imports at all. Classes are loaded lazily when first used, regardless of imports.
How I’d say it in the interview
"Imports only exist for the compiler. The class file uses full names, so a wildcard import has no runtime cost; the argument against wildcards is readability and name clashes, not performance."
Signals depth
Knowing import java.util.* doesn't include java.util.concurrent, and that java.lang plus the current package are imported implicitly.
Red flags
"Wildcard imports load the whole package into memory."
Follow-ups interviewers ask next
What if you import both java.util.* and java.sql.* and use Date?
Compile error: "reference to Date is ambiguous". Add a single-type import,
import java.util.Date;, which takes precedence over on-demand imports, or write the fully qualified name for one of them.
The app works locally but throws NoSuchMethodError in production. How do you investigate?
Tests classpath ordering, dependency conflicts
Show answerHide answer
Model answer
NoSuchMethodError at runtime means the code was compiled against a version of a class that has the method, but a different version was loaded. On the classpath the first matching class wins, so the usual cause is two versions of the same library (or a fat JAR that shades an old copy), with the old one earlier in the order. Steps:
- Find which JAR the class came from: run with
-verbose:class(or-Xlog:class+load=info) and grep for the class, or logFoo.class.getProtectionDomain().getCodeSource().getLocation(). - Compare dependency trees between environments:
mvn dependency:treeorgradle dependencies, looking for the library at two versions. - Fix by pinning one version (dependency management / constraints), excluding the transitive copy, or relocating (shading) it.
How I’d say it in the interview
"It's almost always two versions of a library on the classpath, where the first one found wins and it's the older one. I'd confirm which JAR the class was loaded from with -verbose:class, then fix the dependency tree so only one version remains."
Signals depth
Explaining "first match wins", knowing the logging flags, and mentioning that named modules forbid split packages, which prevents this class of bug.
Follow-ups interviewers ask next
What if both environments have the same JARs?
Then check the order: classpath wildcards like
lib/*don't guarantee an order, and application servers use per-application class loaders with different delegation rules. Also check for a copy bundled inside another JAR.
Why shouldn't you put classes in the default package?
Tests unnamed package
Show answerHide answer
Model answer
Classes without a package statement are in the unnamed package, and nothing in a named package can import them: there's no name to put in the import (forbidden since Java 1.4). They also have no namespace, so two libraries with a default-package Utils collide silently on the classpath, and named modules can't contain them at all. It's fine for a one-file experiment and nothing else.
How I’d say it in the interview
"Code in a named package can't refer to default-package classes, so they can't be reused, and they have no namespace. I always declare a package, even in a prototype."
Follow-ups interviewers ask next
What if you inherit a code base full of default-package classes?
Move them into a package with your IDE's refactoring (which updates references), starting with the classes other code needs to call. Until then, the only way to reach them from named packages is reflection, which isn't a fix.
What does module-info.java add on top of packages and access modifiers?
Tests JPMS, strong encapsulation
Show answerHide answer
Model answer
A module groups packages and declares two things: which modules it requires, and which packages it exports. A public class in a package that isn't exported is not accessible to other modules, at compile time or by reflection. opens grants runtime deep reflection (for frameworks like Jackson) without exporting. The JDK uses this to hide jdk.internal.*, and since Java 16 and 17 internals are strongly encapsulated by default. Modules also forbid split packages, so the same package can't come from two modules.
module com.acme.billing {
requires java.sql;
exports com.acme.billing.api;
opens com.acme.billing.model to com.fasterxml.jackson.databind;
}
How I’d say it in the interview
"Packages organise classes, modules organise packages. Modules add a layer to access control: public only means public outside the module if the package is exported. They also make dependencies explicit and rule out split packages."
Signals depth
Knowing that classpath code runs in the unnamed module, which reads all modules, so most apps still use the old rules, and that --add-opens / --add-exports are the escape hatches.
Red flags
"Modules are the same as Maven modules."
How would you decide the access level for each member of a new class?
Tests encapsulation, API design
Show answerHide answer
Model answer
Start from the most restrictive level and widen only with a reason:
- Fields
private(andfinalwhere possible). Expose behaviour, not state. - Helpers
private, or package-private if tests or collaborators in the package need them. Package-private is a good internal API. publiconly for the API you're willing to support across versions.protectedonly if you've designed for subclassing; it's part of the public contract since anyone can extend a non-final class. Otherwise make the classfinal.- Don't leak mutable internals: return
List.copyOf(items)rather than the internal list. Modifiers protect references, not the objects behind them.
How I’d say it in the interview
"Narrowest level that works. Private fields, package-private internals, a minimal public API, and protected only for hooks I've deliberately designed. And I make sure getters don't hand out mutable internals."
Signals depth
Mentioning Effective Java's "minimize accessibility", that widening later is easy but narrowing breaks callers, and that a public mutable collection getter defeats encapsulation.
Red flags
"I make everything public and use getters and setters for everything."
Follow-ups interviewers ask next
What if a unit test needs to call a private method?
Usually that's a sign the method should be tested through the public behaviour, or that the logic belongs in its own class. If you really need direct access, make it package-private and put the test in the same package (a common convention, sometimes marked with
@VisibleForTesting). Avoid reflection hacks.
Can reflection access a private field? Does that make private useless?
Tests reflection, module encapsulation
Show answerHide answer
Model answer
On the classpath, yes: field.setAccessible(true) lets reflection read and write private fields. Across modules, it only works if the target package is opened to the caller; otherwise it throws InaccessibleObjectException. That doesn't make private useless: access modifiers are a design and compiler-enforced contract that stops accidental misuse and lets you change internals safely, not a security sandbox against code running in the same JVM.
How I’d say it in the interview
"Reflection can bypass private on the classpath, and modules tighten that with exports and opens. Private is about design and maintainability, not security against hostile code in the same process."
Signals depth
Mentioning --add-opens as the escape hatch, and that the Security Manager, the old mechanism for sandboxing, is deprecated for removal (JEP 411).
Follow-ups interviewers ask next
What if a framework needs to set private fields, like a JSON mapper?
Either give it a constructor or setters to use, or, in a modular app,
opensthe model package to that framework's module specifically.