Course contents

Packages & access: interview questions & scenarios

Free11 questions · 11 what-if follow-ups

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.

Question 1. ConceptWarm-up

What are the four access levels in Java, and who can see each one?

Tests visibility matrix

Show answerHide answer1 follow-up

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

  1. What if no modifier is written on an interface method?

    Then it's public (and abstract, unless it's default or static). Interface members are implicitly public, and fields are implicitly public static final. Since Java 9 you can explicitly write private methods in an interface to share code between default methods.

Question 2. What does it print?Core

Does this compile? Child is in a different package from Base.

Tests protected access, JLS 6.6.2

Show answerHide answer2 follow-ups

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

  1. What if Child were in the same package as Base?

    Then all three lines compile: within the package, protected behaves like package-private plus more, so any class in the package can access it through any reference.

    1. What if Child needs Base's protected constructor?

      A subclass can call it via super(...), or create an anonymous subclass with new Base() {}, but new Base() directly is a compile error.

Question 3. ConceptWarm-up

Can a top-level class be private or protected?

Tests top-level class rules

Show answerHide answer1 follow-up

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

  1. What if I want a class that only one other class can use?

    Make it a private static nested class inside that class. If several classes in a package need it, make it a package-private top-level class.

Question 4. ConceptCore

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.

Question 5. DebuggingCore

Why doesn't this override compile?

Tests overriding rules, Liskov substitution

Show answerHide answer2 follow-ups

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

  1. 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 @Override on it would be a compile error.

    1. What if Parent.start() were package-private and Child in another package?

      Also not an override. Child can't see the method, so its start() is a new method. A call to start() through a Parent reference inside Parent's package still runs Parent.start(), which surprises people who expected polymorphism.

Question 6. ConceptWarm-up

What does an import actually do at runtime?

Tests imports are compile-time only

Show answerHide answer1 follow-up

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

  1. 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.

Question 7. ScenarioSenior

The app works locally but throws NoSuchMethodError in production. How do you investigate?

Tests classpath ordering, dependency conflicts

Show answerHide answer1 follow-up

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:

  1. Find which JAR the class came from: run with -verbose:class (or -Xlog:class+load=info) and grep for the class, or log Foo.class.getProtectionDomain().getCodeSource().getLocation().
  2. Compare dependency trees between environments: mvn dependency:tree or gradle dependencies, looking for the library at two versions.
  3. 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

  1. 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.

Question 8. ConceptCore

Why shouldn't you put classes in the default package?

Tests unnamed package

Show answerHide answer1 follow-up

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

  1. 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.

Question 9. ConceptSenior

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."

Question 10. DesignCore

How would you decide the access level for each member of a new class?

Tests encapsulation, API design

Show answerHide answer1 follow-up

Model answer

Start from the most restrictive level and widen only with a reason:

  • Fields private (and final where 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.
  • public only for the API you're willing to support across versions.
  • protected only if you've designed for subclassing; it's part of the public contract since anyone can extend a non-final class. Otherwise make the class final.
  • 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

  1. 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.

Question 11. ScenarioSenior

Can reflection access a private field? Does that make private useless?

Tests reflection, module encapsulation

Show answerHide answer1 follow-up

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

  1. 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, opens the model package to that framework's module specifically.

Lessons in Packages & access