Java backend interviews rarely test one framework in isolation. A single conversation may move from collections and object equality to concurrency, JVM memory, Spring proxies, transactions, and database isolation.
This guide organizes 15 common questions into four themes. Each answer starts with the essential idea, then adds the implementation details and caveats that often distinguish a strong interview response from a memorized definition.
Java Collections and Core Language
1. How does HashMap work, and what changed in JDK 8?
A HashMap stores entries in an array of buckets. It spreads the key’s hash code, maps that hash to a bucket index, and searches the entries in that bucket. Keys that map to the same bucket form a collision chain.
Since JDK 8, a busy bucket can use either a linked list or a red-black tree. A tree changes lookup in a heavily collided bucket from linear time, , to logarithmic time, .
The important thresholds are:
TREEIFY_THRESHOLD = 8: a sufficiently long bucket becomes eligible for treeification.MIN_TREEIFY_CAPACITY = 64: the table must have at least 64 buckets; otherwise,HashMapresizes instead.UNTREEIFY_THRESHOLD = 6: a small tree bin may return to a linked list during resize operations.
In short, treeification happens when the collision chain reaches the threshold and the table capacity is at least 64. The capacity rule avoids paying for a tree when expanding the table may distribute the keys more effectively.
JDK 8 also changed resize behavior. Nodes are split between their existing index and oldIndex + oldCapacity, preserving their relative order instead of using the JDK 7 head-insertion approach. This made resizing more efficient and avoided the circular-list failure associated with unsafe concurrent resizing in older implementations. HashMap is still not thread-safe; use ConcurrentHashMap when concurrent access is required.
2. How do ArrayList and LinkedList differ?
ArrayList uses a resizable array; LinkedList uses a doubly linked chain of nodes.
| Operation or property | ArrayList | LinkedList |
|---|---|---|
| Indexed access | ||
| Append | Amortized | |
| Insert/remove at a known node | Requires shifting elements | |
| Find an insertion/removal position | by index, but shifting follows | traversal |
| Memory locality | Good | Poor |
| Per-element overhead | Low | Higher due to two links per node |
The phrase “linked lists are faster for insertion and deletion” needs context. They avoid shifting elements only after the target node has been reached. Finding that node is usually , and pointer-heavy nodes are less cache-friendly.
Use ArrayList as the general-purpose default. If the real requirement is efficient queue or deque operations at both ends, ArrayDeque is usually a better choice than LinkedList.
3. What is the difference between == and equals()?
For primitives, == compares values. For references, it tests whether both references point to the same object.
equals() represents logical equality. The default implementation inherited from Object behaves like reference identity, but classes such as String, records, and value objects override it to compare content.
The relationship with hashCode() is essential:
- If
a.equals(b)istrue, thena.hashCode()andb.hashCode()must be equal. - Equal hash codes do not imply that two objects are equal; collisions are allowed.
- An object’s hash code should remain stable while it is stored in a hash-based collection.
- Override
hashCode()whenever you overrideequals().
Breaking this contract can make a logically equal key impossible to find in a HashMap or HashSet.
4. How do String, StringBuilder, and StringBuffer differ?
Stringis immutable. Operations that appear to modify it produce a new string.StringBuilderis mutable and unsynchronized, making it the usual choice for assembling text within one thread.StringBufferis mutable and synchronizes its operations. It exists mainly for cases where the same buffer truly must be shared across threads.
Modern JDKs store string data in an internal byte[] when possible, rather than the char[] used by older releases. The implementation keeps that storage private and exposes no operation that can mutate a String after construction.
Immutability makes strings safe to share, suitable as hash keys, cacheable, and reusable through the string pool. It also prevents security-sensitive values such as class names, file paths, and URLs from changing after validation.
Concurrency, Thread Pools, and the JVM
5. How do synchronized and Lock differ?
synchronized is built into the language and JVM. Lock acquisition and release are tied to a block or method, and the monitor is released automatically when control leaves that scope—even when an exception is thrown.
Lock, most commonly ReentrantLock, is an API. It supports features such as timed acquisition, interruptible acquisition, optional fairness, multiple Condition objects, and attempts that do not block. It must be released explicitly, normally in a finally block:
lock.lock();try { updateSharedState();} finally { lock.unlock();}The traditional interview description of monitor optimization is:
biased -> lightweight -> heavyweightThat model is version-dependent. Biased locking was disabled by default in JDK 15 and removed in JDK 18. Modern JVMs still optimize uncontended monitors and may inflate them under contention, but candidates should avoid presenting the old three-stage sequence as a universal rule for every JDK.
6. What does volatile guarantee?
volatile provides visibility and ordering, but not general-purpose atomicity.
A write to a volatile field happens-before a later read of that field. This means the reading thread sees that write and also sees ordinary writes that happened before it in the writing thread. The JVM and CPU enforce these semantics with compiler constraints and memory barriers appropriate to the platform.
A volatile read or write of the field itself is atomic, but a read-modify-write operation is not:
volatile int count;count++; // read, add, and write: not an atomic unitUse synchronization or an atomic type such as AtomicInteger when multiple threads must update a value safely. volatile is well suited to state flags and safely publishing immutable snapshots.
7. What are the seven ThreadPoolExecutor parameters?
The constructor accepts:
corePoolSize: the number of core workers.maximumPoolSize: the maximum number of workers.keepAliveTime: how long excess idle workers may remain alive.unit: the time unit for the keep-alive value.workQueue: the queue holding tasks before execution.threadFactory: the strategy used to create workers.handler: the policy used when the executor cannot accept a task.
Tasks are generally handled in this order: create workers up to the core size, enqueue work, create additional workers up to the maximum when the queue cannot accept more, and finally invoke the rejection policy.
The built-in rejection policies are:
AbortPolicy: throwRejectedExecutionException.CallerRunsPolicy: execute the task in the submitting thread, providing a simple form of backpressure.DiscardPolicy: silently discard the new task.DiscardOldestPolicy: remove the oldest queued task and retry submission.
In production, size the pool for the workload, prefer bounded queues, give threads useful names, expose queue and rejection metrics, and define a shutdown strategy. Convenience factories in Executors are not inherently wrong, but some create unbounded queues or an unbounded number of threads. Constructing ThreadPoolExecutor directly makes those capacity decisions explicit.
8. How is JVM memory organized, and how do CMS and G1 compare?
The JVM runtime data areas include:
- the program counter register;
- one JVM stack per thread;
- native method stacks;
- the shared heap;
- the shared method area, implemented as Metaspace in HotSpot since JDK 8.
CMS and G1 take different approaches to garbage collection:
| Collector | CMS | G1 |
|---|---|---|
| Heap organization | Traditional generations with a contiguous old generation | Many equal-sized regions forming logical generations |
| Main goal | Reduce old-generation pauses | Balance throughput with a configurable pause-time goal |
| Reclamation | Primarily mark-sweep | Evacuates live objects and compacts selected regions |
| Main drawback | Fragmentation and concurrent-mode failures | More complex heuristics and some overhead |
CMS was deprecated in JDK 9 and removed in JDK 14. G1 became the default HotSpot collector in JDK 9 and prioritizes regions expected to yield the most reclaimable space.
Spring and Spring Boot
9. What is Spring IoC, and what is the bean lifecycle?
Inversion of Control means the Spring container creates objects, supplies their dependencies, configures them, and manages their lifecycle. Application code declares relationships instead of manually constructing the entire object graph.
A simplified singleton bean lifecycle is:
instantiate -> populate dependencies and properties -> invoke aware callbacks -> BeanPostProcessor before initialization -> initialization callbacks -> BeanPostProcessor after initialization -> bean is ready -> destruction callbacks when the context closesInitialization can include @PostConstruct, InitializingBean, or a configured init method. Destruction can include @PreDestroy, DisposableBean, or a configured destroy method.
Common scopes are singleton, prototype, request, session, application, and websocket. Spring does not automatically call destruction callbacks for prototype beans after handing them to the client.
10. How does Spring AOP work, and how do JDK and CGLIB proxies differ?
Spring AOP wraps a bean in a proxy. Calls that pass through that proxy can run advice before, after, or around the target method, which supports concerns such as transactions, logging, caching, and authorization.
- A JDK dynamic proxy implements one or more interfaces.
- A CGLIB proxy creates a subclass of the target class.
Subclass-based proxies cannot advise final classes or override final methods. With either approach, a call from one method to another on this does not pass through the proxy, which explains many self-invocation surprises.
Spring Framework can choose a JDK proxy when interfaces are available; Spring Boot configures class-based proxying by default for AOP auto-configuration. The exact choice can be changed through configuration.
11. How does Spring Boot auto-configuration work?
@EnableAutoConfiguration, included by @SpringBootApplication, imports candidate auto-configuration classes. Current Spring Boot releases list candidates in:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsOlder releases commonly registered them through META-INF/spring.factories.
Each candidate uses conditions such as @ConditionalOnClass, @ConditionalOnMissingBean, and @ConditionalOnProperty. Spring evaluates those conditions against the classpath, configuration, and current application context. Auto-configuration therefore supplies sensible defaults while backing off when the application defines its own bean or disables a feature.
12. When can @Transactional fail to roll back?
Common causes include:
- Self-invocation: one method calls another transactional method on the same instance, bypassing the proxy.
- An exception is swallowed: the method catches the failure and returns normally.
- The exception does not match rollback rules: by default, runtime exceptions and errors trigger rollback; checked exceptions require configuration such as
rollbackFor. - The method cannot be advised: this depends on proxy type and Spring version, but private or final methods are common problems.
- The work runs in another thread: transaction context is normally bound to the current thread.
- Propagation suspends or creates another transaction: settings such as
NOT_SUPPORTEDorREQUIRES_NEWchange the expected boundary. - The resource is not transactional: for example, a non-transactional database engine or an operation outside the configured transaction manager.
- The transaction was never activated: the class is not a managed bean, transaction management is not enabled, or the wrong transaction manager is selected.
Debug these cases by identifying the proxy boundary, the actual exception leaving the method, the propagation mode, and the resource participating in the transaction.
Security and Data
13. What is the Spring Security authentication and authorization flow?
A request passes through Spring Security’s filter chain. An authentication filter extracts credentials and creates an unauthenticated Authentication token. An AuthenticationManager delegates to an AuthenticationProvider, which may call UserDetailsService to load the account and a PasswordEncoder to verify the password.
On success, the resulting authenticated object is stored in the SecurityContext. Later authorization components compare that identity and its authorities with the rules for the requested resource. Authentication answers “Who are you?”; authorization answers “May you do this?”
UserDetailsService has one focused responsibility: load UserDetails by username. It does not itself authenticate the password. That comparison is normally performed by an authentication provider such as DaoAuthenticationProvider.
14. How do you integrate Spring Security with JWT?
A typical stateless flow is:
- Configure the API with
SessionCreationPolicy.STATELESS. - Authenticate credentials at a login endpoint.
- Issue a short-lived, signed access token after successful authentication.
- Add a filter before
UsernamePasswordAuthenticationFilterto read the bearer token. - Validate the token’s signature, expiry, issuer, audience, and other required claims.
- Build an authenticated principal and store it in
SecurityContextHolderfor the current request. - Return
401 Unauthorizedwhen authentication is required but invalid or absent, and403 Forbiddenwhen an authenticated user lacks permission.
CSRF protection is often disabled for a bearer-token API whose credentials are sent explicitly in the Authorization header and not stored in cookies. If tokens are cookie-based, disabling CSRF without another defense is unsafe.
Production designs should also address key rotation, refresh-token storage and revocation, token leakage, narrow claims, and HTTPS. A JWT is a signed credential format—not encryption and not a complete session-management strategy.
15. What are InnoDB’s transaction isolation levels, and how does MVCC work?
InnoDB supports the four SQL isolation levels:
READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READ—the InnoDB defaultSERIALIZABLE
MVCC allows consistent, non-locking reads by combining transaction metadata, undo-log version chains, and a read view. If the latest row version is not visible to a transaction’s snapshot, InnoDB follows the undo chain to find a visible version.
Under READ COMMITTED, a new read view is generally created for each consistent read. Under REPEATABLE READ, consistent reads in a transaction normally share a snapshot, which prevents non-repeatable reads for those snapshot queries.
It is also important to distinguish two kinds of reads:
- Consistent reads use an MVCC snapshot and normally do not lock records.
- Locking/current reads, including
SELECT ... FOR UPDATEand data modifications, inspect current versions and acquire locks.
InnoDB combines MVCC with record, gap, and next-key locking where needed. MVCC improves read/write concurrency, but it does not mean that every query is lock-free or that application-level race conditions disappear.
Final Review Checklist
Strong interview answers connect definitions to consequences:
- Describe complexity together with the data structure that causes it.
- State concurrency guarantees precisely—visibility, ordering, and atomicity are different properties.
- Explain Spring features through proxy and lifecycle boundaries.
- Separate authentication from authorization and JWT validation from session design.
- Distinguish database snapshot reads from locking reads.
Those connections make the material easier to remember and make follow-up questions much easier to answer.