JVM Internals • Concurrency • Memory • Architecture • Leadership • 2026

Java Interview Questions for 10+ Years Experience (Senior)

Java interviews for 10+ years of experience assume you know the basics and go after what only experience teaches: why the obvious answer is wrong, how the JVM behaves under memory and thread pressure, how you debug the rare failure, and how you make and defend decisions across many services and people. It is written for Java engineers with around eight to fifteen years behind them, interviewing for senior, staff or lead roles. Each question shows what the interviewer is really checking, a shape for your answer and a sample you can adapt. Read the sample, then say your own version out loud with a story from your own work.

Search all questions by round, difficulty and level, or save the ones you want to practice.

JVM Internals 5 questions

Hard Technical round Mid-level, Senior Practice question

1. A service logs ExceptionInInitializerError once at startup, then NoClassDefFoundError for the same class on every request. What is going on, and how do you find the real cause?

What the interviewer is really testing:
Whether you know the JVM remembers a failed class initialization, and that the useful error is the first one, not the loud one.
Answer frame:

Failed once, failed for good: if a static initializer throws, the class is marked as failed in that class loader and never retried.

Every later use: throws NoClassDefFoundError saying it could not initialize the class; older JDKs drop the original cause.

Find the cause: search for the first ExceptionInInitializerError after startup, then keep static init free of anything that can fail.

Sample spoken answer:

“When a static initializer throws, the JVM wraps the exception in ExceptionInInitializerError the first time and marks the class as failed in that class loader. It never tries again. Every later attempt to use the class throws NoClassDefFoundError saying it could not initialize the class, and on older JDKs that later error doesn't even carry the original cause. So the logs fill with the second error, and people chase the classpath, because NoClassDefFoundError usually means a missing jar. The real clue is the very first error after startup, often a static block that reads a config file, opens a connection, or parses an environment variable that was missing in that environment. The fix is that bug, but my standing rule is that static initializers do nothing that can fail at runtime. Anything touching I/O or configuration moves into explicit startup code that fails fast with a clear message.”

Red flag to avoid:

Hunting the classpath for a missing jar without looking for the first initialization error.

They may ask next:
  • How does ClassNotFoundException differ from NoClassDefFoundError?
  • Why can the same class work in one class loader and be broken in another inside the same JVM?
Say it in 60 seconds
Hard Technical round Senior Practice question

2. A hot code path got noticeably slower after someone added a third implementation of an interface it calls. What might the JIT be doing?

What the interviewer is really testing:
Whether you know how the JIT uses type profiles to inline virtual calls, and how that breaks when a call site sees many types.
Answer frame:

Profiled calls: the JIT records which types reach a call site and inlines when it sees one or two.

Megamorphic: with more types it falls back to a real virtual call and loses inlining and what inlining enables.

Confirm: inlining diagnostics or a profiler before redesigning anything.

Sample spoken answer:

“HotSpot profiles call sites. If a call to an interface method only ever sees one implementation, the JIT can inline that implementation behind a cheap type check, and with two types it can inline both. Inlining is what unlocks other optimisations, like escape analysis removing short-lived allocations. Once a third type shows up at a hot site it usually becomes megamorphic: the JIT emits a real interface dispatch, nothing gets inlined there, and allocations it used to remove come back. A new type can also force deoptimisation of code that was compiled on the old assumption. I'd confirm it with the JVM's inlining diagnostics or a profiler rather than guess. If it's real and the path is truly hot, I'd split the call site so each one sees fewer types, or handle the common type with an explicit check first. Most code never needs this.”

Red flag to avoid:

Blaming interfaces in general or proposing to remove polymorphism everywhere without measuring.

They may ask next:
  • What is deoptimisation, and when does the JVM do it?
  • Why can a microbenchmark with only one implementation give you a misleading result here?
Say it in 60 seconds
Hard Technical round Senior Practice question

3. GC logs show short pauses, yet the application sees stop-the-world stalls much longer than any logged pause. How can safepoints explain that?

What the interviewer is really testing:
Whether you know that reaching a safepoint takes time of its own, and that GC is not the only thing that stops the world.
Answer frame:

Safepoints: many JVM operations need every Java thread stopped at a safe point, not only GC.

Time to safepoint: all threads wait while the slowest one runs on to its next poll.

Diagnose: safepoint logging reports reaching the safepoint separately from the operation.

Sample spoken answer:

“Stop-the-world operations need every Java thread parked at a safepoint, and the GC pause figure usually covers the work done once they're all stopped. The time spent waiting for the last thread to get there, the time to safepoint, can be longer than the collection itself, and every other thread is already frozen while it waits. A classic cause is a long counted loop that the JIT compiled without a safepoint poll inside, so one thread crunching a huge array holds everyone up. GC isn't the only user either: deoptimisation, full thread dumps and class redefinition by agents need safepoints too. I'd turn on safepoint logging, which splits reaching the safepoint from the operation, and line it up with the stalls. Fixes include breaking the hot loop into chunks and cutting monitoring tools that take thread dumps too often.”

Code:
java -Xlog:safepoint -jar app.jar
Red flag to avoid:

Assuming the pause in the GC log is the whole stall the application felt.

They may ask next:
  • Why might a long loop over an int index behave differently here from one over a long index?
  • Which JVM operations besides garbage collection need every thread stopped?
Say it in 60 seconds
Hard Technical round Senior Practice question

4. Two threads hang at startup, and the thread dump shows no lock cycle you recognise, only both stuck in static initializers. How can class initialization deadlock?

What the interviewer is really testing:
Whether you know the JVM initializes each class under its own lock, and that cyclic static dependencies across threads can deadlock without a visible monitor cycle.
Answer frame:

Init lock: each class is initialized once; other threads wait while one thread runs its static initializer.

The cycle: thread one initializes A, which needs B; thread two initializes B, which needs A.

Fix: break the static dependency, often a base class whose static field creates a subclass.

Sample spoken answer:

“The JVM guarantees each class is initialized exactly once, so while one thread runs a class's static initializer, any other thread that needs that class waits. If A's static block uses B and B's uses A, a single thread gets through, because it may re-enter a class it's already initializing, though it can see statics that aren't set yet. With two threads, one starting from A and the other from B, each holds one initialization and waits for the other forever. It doesn't look like a normal monitor deadlock in the dump; the threads often show as RUNNABLE or waiting inside a static initializer. A common version is a base class with a static field holding an instance of its own subclass, since initializing the subclass needs the base class first. I fix it by breaking the cycle: move the constant into its own holder class, or make that initialization lazy and explicit.”

Code:
class Base {
    static final Base DEFAULT = new Child(); // initializing Base needs Child
}

class Child extends Base {
    // initializing Child needs Base initialized first
}

// Thread 1 reads Base.DEFAULT while Thread 2 calls new Child(): can deadlock
Red flag to avoid:

Saying static initialization is thread-safe so it can never deadlock.

They may ask next:
  • Why does a single thread get through the same cycle, and what values can it see?
  • How does the holder-class idiom for lazy singletons rely on these same rules?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

5. Production logs fill with NullPointerException entries that have no stack trace at all. How can that happen, and how do you get the trace back?

What the interviewer is really testing:
Whether you know the JIT's fast-throw optimisation and can still debug a failure the JVM has hidden from you.
Answer frame:

JIT shortcut: when compiled code throws the same implicit exception often, HotSpot can reuse a preallocated one with no trace.

Find the start: the earliest occurrences still have full traces, so search back to them.

Turn it off: disable OmitStackTraceInFastThrow while you debug.

Sample spoken answer:

“HotSpot has an optimisation for hot implicit exceptions like NullPointerException, ArithmeticException or ArrayIndexOutOfBoundsException. Once compiled code throws the same one from the same place many times, it can switch to throwing a preallocated exception with no stack trace, because building traces is expensive. So after a while the logs show just the exception name. The first occurrences, before the switch, still have the full trace, so the quickest move is to search back to the earliest entries since the last restart. If those have rotated away, I'd restart with the OmitStackTraceInFastThrow option turned off, so every throw builds its trace, find the bug, then remove the flag. The bigger lesson is that an exception thrown thousands of times a minute is a problem in itself: something is hitting the same bug on every request.”

Code:
java -XX:-OmitStackTraceInFastThrow -jar app.jar
Red flag to avoid:

Blaming the logging framework, or assuming the exception comes from native code.

They may ask next:
  • Why is filling in a stack trace expensive?
  • How can you make your own exception type cheap when you truly need exceptions on a hot path?
Say it in 60 seconds

Memory 2 questions

Hard Technical round Senior Practice question

6. An app server hits OutOfMemoryError in metaspace after several redeploys without a restart. How does a class loader leak happen, and how do you find it?

What the interviewer is really testing:
Whether you know a class loader is only freed when nothing reachable points at it, any class it loaded, or any instance of those classes.
Answer frame:

Why it leaks: one live reference into the old app pins its class loader and every class it loaded.

Usual culprits: threads the app never stopped, ThreadLocals on container threads, drivers or listeners in a shared registry.

Find it: heap dump, count the app's class loaders, follow the stale one to GC roots.

Sample spoken answer:

“Every class the old deployment loaded stays in metaspace until its class loader can be collected, and the loader can only go when nothing reachable refers to it, any of its classes, or any of their instances. One stray reference pins the whole lot, so each redeploy leaves another full copy behind. The usual causes are a thread the app started and never stopped, a ThreadLocal value left on a thread the container owns, a JDBC driver registered with DriverManager from a parent loader, or a static cache in a shared library holding an app object. To find it, I take a heap dump after two or three redeploys, look for more than one instance of the app's class loader, and follow the shortest path to GC roots from the stale one. That path names the culprit. The fix is proper cleanup on undeploy, and until then, restarting instead of hot redeploying.”

Red flag to avoid:

Just raising the metaspace limit, or thinking classes are unloaded one at a time like ordinary objects.

They may ask next:
  • Why doesn't raising the metaspace limit fix this?
  • How can one ThreadLocal on a pooled container thread keep a whole application in memory?
Say it in 60 seconds
Hard Technical round Senior Practice question

7. Finalizers are deprecated for removal. What was wrong with them, and how would you migrate a codebase that relies on finalize to release native resources?

What the interviewer is really testing:
Whether you understand how finalization interacts with the garbage collector and can plan a safe move to explicit closing plus Cleaner.
Answer frame:

Why they failed: no timing promise, extra GC cycles before memory is freed, one slow finalizer backs up the queue, objects can be resurrected.

Real fix: make the resource AutoCloseable and close it with try-with-resources.

Safety net: a Cleaner action that holds only the native handle, never the owning object.

Prove it: run the tests with finalization disabled.

Sample spoken answer:

“Finalizers looked like destructors but behaved nothing like them. There's no promise when, or even whether, finalize runs. An object with a finalizer survives at least one extra GC cycle, so its memory comes back late, and finalizers run on a single thread, so one slow finalizer backs up the whole queue and the heap can grow until it fails. Finalize can also resurrect the object, and exceptions thrown in it are silently ignored. To migrate, I'd first make every native resource AutoCloseable and fix callers to use try-with-resources, because explicit closing is the real fix. Then, as a safety net for leaks, I'd register a Cleaner action that holds only the native handle. If the action refers to the owning object, that object can never become unreachable. Newer JDKs can run with finalization disabled, so I'd run the test suite that way to prove nothing still depends on it.”

Code:
final class NativeBuffer implements AutoCloseable {
    private static final Cleaner CLEANER = Cleaner.create();
    private final Cleaner.Cleanable cleanable;

    NativeBuffer(long handle) {
        // the action captures only the handle, never this
        this.cleanable = CLEANER.register(this, () -> freeNative(handle));
    }

    @Override
    public void close() { cleanable.clean(); } // runs the action at most once

    private static void freeNative(long handle) { /* release the native memory */ }
}
Red flag to avoid:

Suggesting System.gc() or runFinalization to make cleanup happen on time.

They may ask next:
  • Why must the cleaning action never be a lambda that reads fields of the object?
  • How would you detect resources that are never closed in production?
Say it in 60 seconds

Concurrency 3 questions

Hard Technical round Senior Practice question

8. A class has only final fields and is meant to be immutable, yet another thread sometimes sees one of those fields as null. How is that possible?

What the interviewer is really testing:
Whether you know exactly what the Java Memory Model promises for final fields, and the construction mistakes that quietly void that promise.
Answer frame:

The promise: once the constructor finishes, any thread that gets the reference sees the final fields as set, even through a data race.

Escaping this: registering this with a listener, a registry or a new thread inside the constructor publishes it before the fields are set.

Overridable calls: a base constructor calling a method the subclass overrides reads subclass fields before they are assigned.

Fix: a constructor that only assigns fields, and a static factory that publishes afterwards.

Sample spoken answer:

“Final fields get a special guarantee in the memory model. Once the constructor finishes, any thread that obtains the reference sees the final fields, and what they pointed to at that moment, as the constructor set them, even if the reference was handed over through a data race. The catch is the words once the constructor finishes. If the constructor lets this escape, say by registering itself with an event bus, putting itself in a static registry or starting a thread that uses it, another thread can reach the object while its fields still hold their defaults, and the guarantee doesn't cover that. A single-threaded cousin of the same bug is a base constructor calling a method the subclass overrides, which then reads a subclass field that hasn't been assigned yet. My fix is a private constructor that only assigns fields, and a static factory that builds the object first and registers it afterwards.”

Code:
class PriceBoard implements Listener {
    private final Map<String, Price> prices;

    private PriceBoard(Map<String, Price> initial) {
        this.prices = Map.copyOf(initial); // assign only, never publish this here
    }

    static PriceBoard create(EventBus bus, Map<String, Price> initial) {
        PriceBoard board = new PriceBoard(initial);
        bus.register(board); // published after the constructor has finished
        return board;
    }

    @Override
    public void onEvent(Event e) { /* reads prices */ }
}
Red flag to avoid:

Saying final fields make an object thread-safe however it is constructed, or not knowing what letting this escape means.

They may ask next:
  • Does the guarantee cover changes made to a final field's list after the constructor has returned?
  • Why do many teams ban starting a thread inside a constructor?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

9. Why is catching InterruptedException and just logging it a bug, and what should library code do instead?

What the interviewer is really testing:
Whether you understand interruption as Java's cancellation signal and how swallowing it breaks shutdown and timeouts for every caller above you.
Answer frame:

What interrupt is: a request to stop, used by executor shutdown, Future.cancel and timeout helpers.

Catching clears it: by the time InterruptedException is thrown, the thread's interrupt flag is already cleared.

Do one of two: let it propagate, or restore the flag with Thread.currentThread().interrupt() and stop the work.

Sample spoken answer:

“Interruption is how Java asks a thread to stop. Executor shutdownNow, Future.cancel with interruption, and many timeout helpers all rely on it. When a blocking call throws InterruptedException, the thread's interrupt flag has already been cleared. If I catch it and only log, the request to stop is simply gone: the loop carries on, the pool can't shut down cleanly, and a deploy hangs until the process gets killed. In library code I don't own the thread, so it isn't my call to decide the interrupt doesn't matter. Either I let InterruptedException propagate by declaring it, or, if the signature can't throw it, I restore the flag with Thread.currentThread().interrupt() and then stop what I'm doing so the caller can see it. Wrapping it in a RuntimeException without restoring the flag is the same bug in disguise.”

Code:
try {
    item = queue.take();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // keep the stop request visible
    return;                             // and actually stop
}
Red flag to avoid:

Logging and carrying on, or saying an interrupt forcibly kills the thread.

They may ask next:
  • What happens to a thread blocked on a classic socket read when you interrupt it?
  • How would you write a long CPU-bound loop so it still responds to interruption?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

10. A cache guarded by ReentrantReadWriteLock takes the read lock, finds the entry missing, then takes the write lock to add it. The first cache miss hangs that thread for good. Why, and what is the right pattern?

What the interviewer is really testing:
Whether you know the read-write lock's no-upgrade rule and the check-again pattern, and can judge when a read-write lock is worth it at all.
Answer frame:

No upgrades: the write lock waits until no thread holds the read lock, including the thread asking for it.

Right pattern: release the read lock, take the write lock, check again, then write; downgrading from write to read is allowed.

Is it worth it: readers still update shared lock state, so for short lookups a ConcurrentHashMap or a plain lock often wins.

Sample spoken answer:

“ReentrantReadWriteLock doesn't support upgrading. The write lock is only granted when no thread holds the read lock, and that includes the thread asking for it. So a thread holding the read lock that asks for the write lock waits for itself forever, and every writer queued behind it waits too. It slips through tests that warm the cache first, because it only bites on a miss. The fix is to release the read lock, take the write lock and check again, because another thread may have added the entry in between. The other direction is allowed: while holding the write lock you can take the read lock and then release the write lock. I'd also ask whether this lock earns its keep. Readers still update shared state inside the lock, so for short lookups a ConcurrentHashMap, or even a plain lock, is often simpler and faster.”

Code:
V get(K key) {
    rw.readLock().lock();
    try {
        V v = map.get(key);
        if (v != null) return v;
    } finally {
        rw.readLock().unlock(); // release before asking to write
    }
    rw.writeLock().lock();
    try {
        V v = map.get(key); // another thread may have added it
        if (v == null) {
            v = load(key);
            map.put(key, v);
        }
        return v;
    } finally {
        rw.writeLock().unlock();
    }
}
Red flag to avoid:

Assuming the read lock upgrades by itself, or releasing it and writing without checking again.

They may ask next:
  • How does StampedLock's optimistic read work, and what does it give up compared with this lock?
  • What would this hang look like in a thread dump?
Say it in 60 seconds

Collections Edge Cases 1 question

Hard Technical round Mid-level, Senior Practice question

11. A TreeMap built with String.CASE_INSENSITIVE_ORDER finds 'ALICE' when 'Alice' was stored. What breaks when that map meets code that expects an ordinary Map?

What the interviewer is really testing:
Whether you know sorted maps decide key identity by their Comparator, and how that quietly breaks the Map contract once equals-based code gets involved.
Answer frame:

Comparator decides: TreeMap never calls equals on keys; a compare result of zero means the same key.

Surprises: putting 'ALICE' replaces the value but keeps the key 'Alice'; copying into a HashMap changes lookups.

Broken symmetry: equals with a HashMap can be true one way and false the other.

Rule: keep the ordering consistent with equals, or normalise keys before they go in.

Sample spoken answer:

“A TreeMap only uses its Comparator to decide whether two keys are the same, never equals. With a case-insensitive comparator, 'Alice' and 'ALICE' are one key, so putting 'ALICE' replaces the value but keeps the original 'Alice' as the key. Inside the map that's fine, but the Map contract is written in terms of equals, so things break at the edges. If I copy it into a HashMap, lookups suddenly become case-sensitive. Equality goes lopsided too: a HashMap holding 'ALICE' says it equals the TreeMap, because it asks the TreeMap to look up its key, while the TreeMap says it doesn't equal the HashMap, because the HashMap can't find 'Alice'. Code that caches, compares or merges maps then behaves strangely. When the rule really is case-insensitive, I normalise keys, lower-casing with a fixed locale, before they enter any map.”

Code:
Map<String, Integer> tree = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
tree.put("Alice", 1);
tree.put("ALICE", 2);                 // same key: value replaced, key stays "Alice"
Map<String, Integer> hash = new HashMap<>(Map.of("ALICE", 2));
System.out.println(tree);              // {Alice=2}
System.out.println(hash.equals(tree)); // true
System.out.println(tree.equals(hash)); // false
Red flag to avoid:

Assuming a TreeMap uses equals and hashCode like a HashMap, or insisting equals can never be asymmetric.

They may ask next:
  • Which JDK types have a natural ordering that disagrees with equals?
  • Why does lower-casing keys need a fixed locale?
Say it in 60 seconds

Performance 2 questions

Medium Technical round Mid-level, Senior Practice question

12. A teammate proves a change is faster with a loop and System.nanoTime. Why don't you trust the number, and how would you measure it properly?

What the interviewer is really testing:
Whether you know how the JIT distorts naive timing, and you insist on proper tooling and system-level proof.
Answer frame:

Warm-up: early runs are interpreted, later ones compiled; the numbers mix both.

Dead code: if the result is never used, the JIT can remove the work being timed.

Tooling: JMH handles warm-up, forked JVMs and consuming results.

Reality check: a load test proves the service got faster, not just one method.

Sample spoken answer:

“A hand-written loop times a mix of the interpreter, JIT compilation and compiled code, and the numbers shift as it warms up. Worse, if the loop computes something nobody reads, the JIT can prove it's dead and remove it, so you end up timing an empty loop. Constant inputs can be folded away too, and a long loop gets compiled with on-stack replacement, which isn't how the method runs in production. A GC can also land in one variant and not the other. So I'd use JMH: it forks fresh JVMs, runs warm-up iterations, consumes results so they can't be eliminated, and reports error bars. Even then, a microbenchmark only speaks for that method. If the claim is that the service got faster, I want a load test with realistic data and latency percentiles.”

Code:
@State(Scope.Thread)
public class ParseBench {
    String input = "12345"; // not final, so it is not folded into a constant

    @Benchmark
    public int parse() {
        return Integer.parseInt(input); // returned values are consumed by JMH
    }
}
Red flag to avoid:

Trusting one run of a timing loop, or not knowing the JIT can delete work whose result is unused.

They may ask next:
  • Why does JMH fork a new JVM for each benchmark?
  • When is a microbenchmark still worth writing?
Say it in 60 seconds
Hard Technical round Senior Practice question

13. What is false sharing, and how can it slow down a Java program that uses no locks at all?

What the interviewer is really testing:
Whether you understand hardware-level contention that no lock or thread dump will show you.
Answer frame:

Cache lines: CPUs move memory in lines, and a core must own a line exclusively to write it.

False sharing: two threads writing different fields in one line make it bounce between cores.

Fixes: padding, the Contended annotation, or per-thread cells like LongAdder uses.

Sample spoken answer:

“CPU caches work in cache lines, usually 64 bytes on common hardware. To write a line, a core needs it exclusively, so every other core's copy gets invalidated. If two threads each update their own counter, but both counters sit in the same line, like adjacent fields of one object or neighbouring slots of an array, every write steals the line from the other core. There's no lock and no logical sharing, yet the program scales badly as you add threads. I'd suspect it when per-thread work gets slower with more cores and the profiler shows time on simple stores. The fixes are spacing hot fields apart with padding, the Contended annotation, which the JDK uses internally and which needs a JVM flag for application code, or structures built for this: LongAdder keeps separate padded cells and sums them when you read.”

Red flag to avoid:

Saying lock-free code can't suffer contention, or not knowing what a cache line is.

They may ask next:
  • Why might hand-written padding stop working after a JVM upgrade?
  • How would you prove false sharing rather than guess at it?
Say it in 60 seconds

Architecture 4 questions

Hard System design round Senior Practice question

14. You're responsible for hundreds of Java services. What would you standardise at the platform level, like base images, JVM flags and observability, and what would you leave to teams?

What the interviewer is really testing:
Whether you can raise reliability across a whole product with sensible central defaults, without taking ownership away from the teams.
Answer frame:

Golden path: one maintained base image, supported JDK versions and a shared dependency bill of materials.

Safe JVM defaults: heap sized to the container, GC logs on, a heap dump on out of memory, and exit on out of memory.

Observability built in: the same metrics, traces and log format everywhere.

Teams own tuning: changes backed by evidence, tracked as exceptions rather than forks.

Sample spoken answer:

“I'd build a golden path rather than a rulebook. Every service starts from one maintained base image with a supported JDK, plus a shared bill of materials so library versions move together and a security fix lands in one place. The JVM defaults live there too: heap as a share of container memory, GC logging with rotation, a heap dump on OutOfMemoryError written somewhere that outlives the container, and exiting on out of memory so the orchestrator restarts a broken instance instead of leaving it limping. Observability comes built in, with the same metrics, tracing and log format everywhere, so any on-call engineer can read any service. Teams still own tuning, but they change a default with evidence, and we track exceptions instead of letting them fork the image. I'd judge it by how fast a patch reaches the whole fleet and how rarely an incident is missing the heap dump we needed.”

Code:
JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=70 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps -XX:+ExitOnOutOfMemoryError -Xlog:gc*:file=/logs/gc.log:time,uptime:filecount=5,filesize=20m"
Red flag to avoid:

Letting every team pick its own image and flags, or locking every setting centrally with no way to change it.

They may ask next:
  • How would you roll a JDK upgrade through the fleet without breaking teams?
  • Where should heap dumps go, given they can contain customer data?
Say it in 60 seconds
Hard System design round Senior Practice question

15. You own a large Java monolith that many teams change every day. How would you enforce boundaries inside it before deciding what, if anything, becomes a separate service?

What the interviewer is really testing:
Whether you can impose structure on a big shared codebase with enforcement in the build, and treat extraction as a cost, not a goal.
Answer frame:

Map it: the real dependency graph and what changes together in commit history.

Enforce in the build: build modules with one-way dependencies, plus architecture tests for other rules.

Encapsulate: a small public API per module, internals package-private or not exported.

Extract last: only where separate scaling, release pace or ownership pays for the network cost.

Sample spoken answer:

“I'd start by mapping the real dependencies, not the diagram on the wiki: which packages call which, and what changes together in the commit history. Then I'd draw target modules around business areas and make the build enforce them, splitting into Maven or Gradle modules whose dependencies only point one way, so a cycle simply fails to compile. For rules the build can't express, like controllers never touching repositories directly, I'd add architecture tests with a library like ArchUnit that run in CI. Each module gets a small public API and keeps the rest package-private, or we use Java modules with explicit exports if we can afford that migration. Only once the boundaries hold would I extract a service, and only for a module that really needs its own scaling, release pace or ownership, because every extraction adds network failures and data consistency work.”

Red flag to avoid:

Jumping straight to microservices, or drawing boundaries with no way to enforce them.

They may ask next:
  • How would you handle a database table that two modules both write to?
  • What would you measure to know the modularisation is actually working?
Say it in 60 seconds
Hard Technical round Senior Practice question

16. During a Java upgrade, many libraries fail with InaccessibleObjectException at startup. What changed, and how would you handle it across hundreds of services?

What the interviewer is really testing:
Whether you understand JDK strong encapsulation and can run the fix as a platform programme rather than a pile of flags.
Answer frame:

What changed: the JDK strongly encapsulates its internals, so deep reflection into them is refused by default.

Find usage: jdeps reports static use of JDK internals; the error names the module and package for reflective use.

Order of fixes: upgrade the library first; add-opens only as a tracked, temporary exception.

Sample spoken answer:

“Newer JDKs strongly encapsulate their internals. Older releases only warned about illegal reflective access; now deep reflection into packages the JDK doesn't open, like reaching into private fields of java.lang classes, throws InaccessibleObjectException. It usually hits serialization, mocking, bytecode and dependency-injection libraries. Across hundreds of services I'd run it as a platform task. First, scan: jdeps with its JDK internals option finds static use, and a test run on the new JDK finds reflective use, since the message names the exact module and package. Then fix at the source by upgrading the library versions most services share, in one central dependency bill of materials. Where no fixed version exists yet, I'd allow one specific add-opens flag, record it in a register with an owner and a removal date, and never allow a blanket open-everything setting.”

Code:
jdeps --jdk-internals app.jar
java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar
Red flag to avoid:

Adding broad add-opens flags to every service and calling the upgrade done.

They may ask next:
  • What is the difference between add-exports and add-opens?
  • Why can a library that works on the classpath fail once the application runs as named modules?
Say it in 60 seconds
Medium Situational round Senior Practice question

17. Half your senior engineers want new services in Kotlin, the other half want to stay on Java. The debate has stalled for a month. How do you decide?

What the interviewer is really testing:
Whether you can break a technical stalemate with agreed criteria, a time-boxed trial and a decision people can commit to.
Answer frame:

Criteria first: hiring, tooling, interop with existing libraries, build times, support cost.

Time-boxed trial: one real service, measured against those criteria.

Decide and commit: make the call, write down why, set a date to revisit.

Sample spoken answer:

“A month of debate usually means we're arguing preferences, so I'd move it to criteria. I'd get both sides to agree on what matters for us: hiring and onboarding, interop with our existing Java libraries and frameworks, build and CI times, static analysis and security tooling, and the cost of supporting two languages on call. Then I'd run a time-boxed trial with one new service in Kotlin, owned by a mixed team, tracking those measures. After the trial I make the decision, write it up with the reasons and the dissent, and set a date to revisit it. Whatever we choose, I'd set boundaries, for example Kotlin only in new services, never mixed into existing Java modules, and shared libraries kept in Java so every team can use them. The worst outcome is no decision, where each team quietly picks its own.”

Red flag to avoid:

Deciding by seniority or a show of hands with no criteria, or letting each team pick its own language.

They may ask next:
  • What would you do if the trial results were mixed?
  • How do you keep the engineers whose side lost engaged?
Say it in 60 seconds

Security 1 question

Hard Situational round Senior Practice question

18. A critical remote-code-execution flaw is announced in a Java library most of your services use. You're leading the response. What happens in the first day?

What the interviewer is really testing:
Whether you can run a fleet-wide incident: find exposure fast, mitigate before patching, patch centrally and keep everyone informed.
Answer frame:

Find exposure: dependency inventory or software bills of materials, including transitive and shaded copies.

Mitigate first: config switches or blocking at the edge while patches roll out.

Patch centrally: bump the shared bill of materials and rebuild, most exposed services first.

Communicate: one channel, one owner, updates on a schedule.

Sample spoken answer:

“In the first hour I'd open an incident with one channel and one owner, and pin down the facts: which versions are affected, what triggers the flaw, and whether there's a mitigation that doesn't need a new build. Then I find exposure. If we have software bills of materials or a dependency inventory, I query that; if not, I scan the built artifacts, because the library can arrive transitively, or shaded inside another jar where searching build files won't find it. I rank services by exposure, internet-facing ones that handle user input first. Where a config switch or a block at the edge buys time, we apply it straight away. The real fix goes into the shared bill of materials, and teams rebuild and deploy in priority order while I track every service on one board. I'd post updates every few hours, and afterwards push for the inventory we wished we'd had.”

Red flag to avoid:

Asking each team to check its own build files and report back, with no central tracking.

They may ask next:
  • How do you find a vulnerable library that another jar has shaded inside itself?
  • What would you do about an exposed service that nobody currently owns?
Say it in 60 seconds

Leadership 5 questions

Medium Behavioral round Senior Practice question

19. Tell me about a Java engineer you grew from solid mid-level into someone who could lead a service. What did you actually do?

What the interviewer is really testing:
Whether you develop people on purpose, with stretch work and real ownership, rather than only answering their questions.
Answer frame:

Spot the gap: what they already did well, and what stood between them and leading.

Stretch with a net: real ownership of something visible, with you close enough to catch problems.

Step back: hand over the decisions, then give them the credit in public.

Sample spoken answer:

“At my last company, one engineer wrote excellent Java but waited to be told what to build and never pushed back in design discussions. I asked where she wanted to be in a year, and she said leading a service. So I gave her ownership of the next big change to our order-status service, moving it off a legacy scheduler. She wrote the design, and instead of editing it myself I asked questions until she found the gaps, like what happens when a job runs twice. I had her run the design review with the senior engineers, and I stayed quiet unless it went off track. She joined me on call for two incidents, then led the next one herself. Within about nine months she was the service's lead, and when we presented the result, I made sure it was her name on it.”

Red flag to avoid:

A story where growing someone just meant sending them to training or answering their questions.

They may ask next:
  • How do you handle it when someone you are stretching makes a costly mistake?
  • How do you decide who on the team gets the visible stretch work?
Say it in 60 seconds
Medium Behavioral round Senior Practice question

20. Tell me how you set Java coding standards for a large team. What did you enforce with tools, and what did you leave to review?

What the interviewer is really testing:
Whether you set standards that prevent real incidents, automate the rest, and get engineers to accept them.
Answer frame:

Few rules, written down: the ones that stop real incidents, each with its reason.

Automate the boring part: formatter, static analysis and architecture tests in CI.

Earn buy-in: draft with senior engineers, trial on one team, drop rules that cost more than they save.

Sample spoken answer:

“At my last company I led the Java platform for about forty engineers, and review threads kept arguing about formatting while real problems slipped through. I wrote a short standard focused on things that had caused incidents: no swallowed exceptions, a timeout on every remote call, immutable value objects by default, no mutable static state, and clear null handling at API boundaries. Each rule had one line explaining why. Then I automated what I could: a formatter that runs on commit, static analysis with a few checks set to fail the build, and architecture tests for layering. Style comments in review almost disappeared, so reviewers spent their time on design. I drafted it with three senior engineers, trialled it on one team for a month, and dropped two rules that caused more friction than they prevented.”

Red flag to avoid:

A long rulebook nobody reads, or standards enforced only by one person's reviews.

They may ask next:
  • How do you handle a senior engineer who refuses a standard they disagree with?
  • How do you roll a new static analysis rule onto a codebase with thousands of existing violations?
Say it in 60 seconds
Medium Behavioral round Senior Practice question

21. Tell me about a Java platform decision you had to defend to senior leadership, like leaving an application server or changing a core framework. How did you make the case?

What the interviewer is really testing:
Whether you can turn a technical choice into cost, risk and delivery terms, with evidence and an honest price.
Answer frame:

Business framing: the risk or cost of not doing it, in terms leaders already track.

Evidence: a measured pilot, not opinion.

Honest cost: effort, risk and the rollback plan, stated up front.

Sample spoken answer:

“Our core services still ran inside a commercial application server, and I proposed moving them to standalone Spring Boot jars in containers. Leadership saw a risky migration with no new features. I reframed it around things they already tracked: a licence renewal coming up, deploys that took most of an evening and so happened rarely, and how hard it was to hire people who knew that server. Then I ran a pilot on one busy service. Deploys went from hours to minutes, it started far faster, and we could scale it on its own. I brought those measurements, the effort in engineer-weeks, the server features with no drop-in replacement, like transactions it managed for us, and a rollback plan per service. I was clear it would slow feature work for about a quarter. They approved a phased programme, and I reported progress monthly against the same numbers I'd pitched.”

Red flag to avoid:

Arguing the new approach is simply better or more modern without tying it to cost, risk or delivery.

They may ask next:
  • What would you have done if leadership had said no?
  • How did you keep feature teams on board while the migration slowed them down?
Say it in 60 seconds
Hard Behavioral round Senior Practice question

22. Tell me about a large Java initiative you led that failed or had to be rolled back. What did you get wrong, and what changed afterwards?

What the interviewer is really testing:
Whether you own a failure at the level you operated, name the wrong assumption, and changed how decisions get made.
Answer frame:

Own it: the decision you made and why it looked right then.

What broke: the specific assumption that turned out wrong.

What changed: the practice you put in place so it doesn't repeat.

Sample spoken answer:

“I led building an internal Java framework meant to give every team the same service skeleton, with config, security, HTTP clients and metrics already wired in. I designed it with two senior engineers and rolled it out over a quarter. A year later only a handful of services used it, and those teams complained they couldn't upgrade the libraries underneath without waiting for my team. What I got wrong was building a framework that owned too much, which made my team the bottleneck for every release, and I never sat with the teams to learn what they actually struggled with. We deprecated most of it and kept the parts people liked, the shared metrics and client setup, as small independent libraries. Afterwards I changed how we start platform work: talk to three teams first, ship the smallest useful piece to one of them, and only grow it when a second team asks for it.”

Red flag to avoid:

Choosing a failure that was entirely someone else's fault, or a story with no lasting change.

They may ask next:
  • How did you tell the team and leadership that you were stopping it?
  • Looking back, what signal should have made you stop earlier?
Say it in 60 seconds
Medium Culture fit round Senior Practice question

23. How would you design the interview loop for senior Java engineers, and what separates a senior answer from a mid-level one?

What the interviewer is really testing:
Whether you can hire for judgement rather than trivia, and describe seniority in concrete, observable terms.
Answer frame:

Signals first: depth, design judgement, debugging, and how they lift others.

Realistic tasks: review flawed code, debug a production scenario, discuss a design.

Senior answer: explains why, names what else could fail, and says how they'd prove the fix.

Sample spoken answer:

“I'd start by agreeing on the signals: depth in Java and the JVM, design judgement, debugging under pressure, and how they raise the people around them. Then one exercise per signal. Instead of trivia, I like handing over a pull request with a subtle concurrency bug and a leaked resource and asking for a review. A production scenario, like a service slowly running out of memory, tests debugging. A design session tests trade-offs. What separates senior from mid is rarely knowing more APIs. A mid-level engineer finds the bug; a senior explains why it happens, what else could fail the same way, how they'd prove the fix and what would stop it coming back. They also say what they wouldn't do. Every interviewer scores against written criteria before talking to the others, which keeps one loud opinion from deciding.”

Red flag to avoid:

A loop built on puzzles or API trivia, or judging seniority by years alone.

They may ask next:
  • How do you keep many interviewers consistent across candidates?
  • What would make you reject a candidate who gave strong technical answers?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

You practiced these. On the real call, ClapAssist helps with the rest.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your live interview audio and screen are never stored. Your resume and notes are saved to your account so the app fills them in on any computer. It stays out of screen share on every plan, including Free; only you can see it.

Download with 10 free minutes
Mac and Windows · Stays out of screen share · No card