Java Backend Interview Questions
Java backend interviews split into two layers. The first is language fluency — collections, generics, exceptions — which is a filter most candidates clear. The second is the JVM as a runtime: memory behaviour, the memory model, garbage collection, and what you do when a service degrades in production. That second layer is where offers are decided, and it is where preparation is usually thinnest.
The reason is that Java is the language where the runtime is most visible in interviews. Nobody asks a Python candidate to read a GC log. Java candidates are asked, because Java backends are where long-running JVMs accumulate heap, where thread pools starve, and where the difference between knowing `HashMap` and knowing what happens to it under concurrent access is a production incident.
Written and reviewed by Sahil Srivastav
What the bar actually is
You should be fluent in the collections framework and its concurrency story: why `HashMap` is unsafe under concurrent mutation, what `ConcurrentHashMap` actually guarantees, why `compute` is atomic while `get` followed by `put` is not. These are asked constantly and answered approximately.
On the runtime, you should be able to read a stack trace properly, explain the difference between the three common `OutOfMemoryError` variants, and say what you would capture when a service slows down. You are not expected to tune a collector from memory, but you are expected to know that the stack trace of an OutOfMemoryError names the victim rather than the cause.
How the rounds are structured
Machine coding in Java, with concurrency
A service where two threads or two requests collide. Expect the interviewer to add concurrency to a working single-threaded solution and watch what breaks.
Collections and language rapid-fire
Equality and hashing contracts, fail-fast iterators, immutability, generics erasure, exception hierarchy. Fast, broad, and mostly a filter.
A memory or performance conversation
Often framed as an incident: the service is using more heap each day, or latency degraded after a release. What do you capture and what does it tell you?
Framework questions if the role names one
Spring in most Indian backend roles: dependency injection, transaction boundaries, where a self-invoked `@Transactional` method silently does nothing.
What this interview bar tests
Concurrent collection semantics
That `ConcurrentHashMap` iterators are weakly consistent, that `compute` holds the bin lock across the function, and that `get`-then-`put` is a race. The detail that separates fluent from approximate.
The memory model, not just synchronized
Why a non-volatile stop flag can be hoisted out of a loop, what `happens-before` orders, and why double-checked locking without `volatile` can publish a half-built object.
Diagnosing from a dump or a GC log
Post-full-GC old-gen occupancy rising means retention; flat and high means undersizing; flat with high GC time means allocation rate. One series, three diagnoses.
Executor lifecycle
Bounded queues, rejection policies as a deliberate choice, and never letting a pooled task block on another task in the same pool.
A preparation plan that works
- 1Write the broken version of each classic concurrency bug yourself and watch it fail: a non-volatile flag, double-checked locking, `get`-then-`put` on a shared map, a lock not released on an exception path. Reading about these does not produce the fluency that reproducing them does.
- 2Capture a heap dump from a toy leak and open it in Eclipse MAT. One real "path to GC roots" exercise is worth a week of reading, and it comes up in incident rounds constantly.
- 3Enable GC logging on a small service and look at the three shapes: leak, undersized heap, high allocation rate. Learn to tell them apart from the log alone.
- 4Practise the collections rapid-fire out loud. It is pure recall, it is quick to secure, and fumbling it damages credibility before the rounds that matter.
Questions you should expect
Why is `ConcurrentHashMap.get` followed by `put` not thread-safe?
Because each call is individually atomic and the pair is not. Two threads can both read absent and both write, losing one update. `compute`, `computeIfAbsent` and `merge` hold the bin lock across the read-modify-write, which is what makes them atomic — the map being concurrent does not make your use of it atomic.
A thread does not stop when you set a boolean flag. Why?
Without `volatile` there is no happens-before edge between the writing and reading threads, so the JIT may hoist the field read out of the loop and the thread never observes the change. It is not a timing issue and adding a `sleep` or a log statement only hides it by accident. Mark the flag `volatile`, or use interruption.
You get OutOfMemoryError: Java heap space. Where do you start?
Not with the stack trace, which names whichever allocation was unlucky. Start with post-full-GC old-gen occupancy over time: rising means a leak and needs a heap dump, a single spike from a flat baseline means one unbounded operation, flat and high means genuine undersizing. Only the third is fixed by `-Xmx`.
What does `@Transactional` not do?
It does not apply when the method is invoked from within the same bean, because the proxy is bypassed — a very common silent bug. It also does not make an external HTTP call inside the boundary safe: the transaction stays open for the round trip, holding a connection and a snapshot.
How do you size a thread pool?
From the bottleneck. Roughly core count for CPU-bound work; for I/O-bound work, target throughput multiplied by service time. Then bound the queue and choose a rejection policy deliberately — an unbounded queue converts a visible rejection into invisible latency and eventual heap exhaustion.
What gets candidates rejected
- Saying `ConcurrentHashMap` makes code thread-safe without distinguishing per-call atomicity from compound-operation atomicity
- Treating a visibility bug as a timing problem and "fixing" it with a sleep or a log line
- Reading the OutOfMemoryError stack trace as the cause rather than the victim
- Reaching for `synchronized` to fix a `ConcurrentModificationException` that is single-threaded
- Recommending an unbounded queue or a bigger pool as the answer to saturation
- Not knowing that a self-invoked `@Transactional` method skips the proxy
- Claiming GC tuning experience without being able to describe what a GC log shows
What to practise, in order
Unbounded cache heap exhaustion
A cache with no bound and a retention path through a long-lived registry. Tests assert the live set stays bounded under sustained load, so raising the heap does not pass.
Lock ordering deadlock in a transfer path
Mirrored concurrent transfers that deadlock. Tests drive both directions hard and assert progress plus ledger consistency.
Thread pool starvation with nested tasks
Pooled tasks submitting sub-tasks to their own bounded pool and waiting. Throughput goes to zero at low load — the failure a bigger pool makes worse.
ThreadLocal retention in pooled workers
Values left on threads that outlive the request. The leak that explains why "clear it in a finally block" is not pedantry.
FAQ
How deep do I need to go on garbage collection?
Deep enough to diagnose, not to tune from memory. Know the three heap-pressure shapes and how to tell them apart from a GC log, know that premature promotion is a sizing problem rather than a code problem, and know why humongous allocations under G1 bypass the young generation. Nobody expects you to recite flag defaults.
Do virtual threads change the answers?
For blocking I/O concurrency, substantially — millions of virtual threads map onto a small carrier pool, so the OS thread ceiling stops being the constraint. They do not make CPU-bound work faster, do not fix an executor you never shut down, and do not remove the need for timeouts.
Is Spring knowledge required?
For most Indian backend roles, effectively yes. The questions that matter are transaction boundaries, proxy behaviour and dependency injection scope — not annotation trivia. Knowing why a self-invoked transactional method does nothing is worth more than knowing twenty annotations.
Which Java version should I target?
Write in whatever is current and be honest about what you have used in production. Records, sealed types, pattern matching and virtual threads are reasonable to mention; claiming production experience with a feature you have only read about is the risk, because the follow-up is usually specific.