Skip to main content

Senior Java Interview Questions

Curated Senior-level Java interview questions for developers targeting senior positions. 20 questions available.

Last updated:

Java Interview Questions & Answers

Skip to Questions

Welcome to our comprehensive collection of Java interview questions and answers. This page contains expertly curated interview questions covering all aspects of Java, from fundamental concepts to advanced topics. Whether you're preparing for an entry-level position or a senior role, you'll find questions tailored to your experience level.

Our Java interview questions are designed to help you:

  • Understand core concepts and best practices in Java
  • Prepare for technical interviews at all experience levels
  • Master both theoretical knowledge and practical application
  • Build confidence for your next Java interview

Each question includes detailed answers and explanations to help you understand not just what the answer is, but why it's correct. We cover topics ranging from basic Java concepts to advanced scenarios that you might encounter in senior-level interviews.

Use the filters below to find questions by difficulty level (Entry, Junior, Mid, Senior, Expert) or focus specifically on code challenges. Each question is carefully crafted to reflect real-world interview scenarios you'll encounter at top tech companies, startups, and MNCs.

Questions

20 questions
Q1:

Explain Java concurrency utilities.

Senior

Answer

The java.util.concurrent package provides high-level concurrency support.
Includes ExecutorService, ThreadPoolExecutor, ScheduledExecutorService, BlockingQueue, Semaphore, CountDownLatch.
Reduces boilerplate synchronization and improves performance in multithreaded applications.
Quick Summary: java.util.concurrent provides: Executors/ExecutorService (thread pools), Future/CompletableFuture (async results), BlockingQueue (producer-consumer), ConcurrentHashMap/CopyOnWriteArrayList (thread-safe collections), AtomicInteger/AtomicReference (lock-free atomics), Semaphore/CountDownLatch/CyclicBarrier (coordination), and StampedLock/ReentrantReadWriteLock (read-write locking).
Q2:

What is ExecutorService and why is it used?

Senior

Answer

ExecutorService manages threads and executes tasks asynchronously.
Supports thread pools, scheduling, submit(), invokeAll(), shutdown().
Improves resource management and avoids manual thread creation.
Quick Summary: ExecutorService manages a thread pool and submits tasks. Benefits over manual thread creation: thread reuse (no creation overhead), task queuing, controlled parallelism (fixed pool prevents resource exhaustion), and built-in lifecycle management. Key methods: submit() (returns Future), execute() (fire-and-forget), shutdown() (wait for tasks to complete), shutdownNow() (interrupt running tasks).
Q3:

Difference between Callable and Runnable.

Senior

Answer

Runnable has no return value and cannot throw checked exceptions.
Callable returns values and can throw exceptions.
Callable works with Future for asynchronous results.
Quick Summary: Runnable.run() takes no args, returns void, can't throw checked exceptions. Callable.call() takes no args, returns a value (V), can throw checked exceptions. Use Runnable with execute(). Use Callable with submit() which returns Future - get() blocks until result is ready, throws ExecutionException wrapping any exception thrown in call(). CompletableFuture is the modern, composable alternative.
Q4:

Explain Future and CompletableFuture.

Senior

Answer

Future represents the result of an asynchronous computation.
CompletableFuture extends Future with non-blocking, chainable, and combinational operations.
Useful for reactive, asynchronous workflows.
Quick Summary: Future represents a pending async result. get() blocks until done (optionally with timeout). Limited: can't chain, no callbacks, no combining multiple futures. CompletableFuture (Java 8+) is much richer: thenApply() (transform result), thenCompose() (chain futures), thenCombine() (combine two futures), exceptionally() (handle errors), allOf() (wait for all), anyOf() (first to complete). Non-blocking with callbacks.
Q5:

Difference between synchronized and ReentrantLock.

Senior

Answer

synchronized is simple and blocks until a lock is released.
ReentrantLock provides tryLock(), timeouts, fairness, and multiple conditions.
ReentrantLock offers more control and flexibility.
Quick Summary: synchronized: built-in, simpler syntax, automatic lock release. ReentrantLock: explicit lock()/unlock() (must use try-finally), tryLock() with timeout (avoids deadlock), lockInterruptibly(), fair mode, multiple Condition objects. ReentrantReadWriteLock: multiple concurrent readers, exclusive writer - great for read-heavy shared data. Use synchronized for simple cases, ReentrantLock for complex concurrency requirements.
Q6:

Explain CountDownLatch, CyclicBarrier, and Semaphore.

Senior

Answer

CountDownLatch waits for a set of threads to finish.
CyclicBarrier allows threads to wait for each other repeatedly.
Semaphore controls access to limited resources using permits.
Quick Summary: CountDownLatch: one-time gate - await() blocks until count reaches zero. Used to wait for N tasks to complete. CyclicBarrier: reusable rendezvous - await() blocks until N threads all arrive, then all proceed together. Used for parallel computation phases. Semaphore: limits N concurrent accesses to a resource. acquire() blocks if permits exhausted, release() returns a permit. All are in java.util.concurrent.
Q7:

How does Java ensure thread safety?

Senior

Answer

Thread safety is achieved using synchronized blocks, locks, atomic variables, and concurrent collections.
Prefer immutability and minimize shared mutable state.
Use functional programming constructs where possible.
Quick Summary: Thread safety approaches: make classes stateless (no shared mutable state = automatically thread-safe), use immutable objects (share freely), use synchronized or locks for mutable shared state, use concurrent collections (ConcurrentHashMap), use atomic variables (AtomicInteger), or use ThreadLocal to give each thread its own copy. The safest code avoids shared mutable state entirely.
Q8:

Explain volatile keyword and atomicity.

Senior

Answer

volatile guarantees visibility of updates across threads.
Does not ensure atomic operations for increments or composite actions.
Use atomic classes or synchronization for atomicity.
Quick Summary: volatile ensures visibility: reads come from main memory, writes go directly to main memory (not CPU cache). Does NOT ensure atomicity. count++ on a volatile int is still a race condition (read, increment, write - three separate operations). For atomic operations use AtomicInteger.incrementAndGet(). volatile is correct for: simple boolean flags, double-checked locking with proper pattern.
Q9:

Explain parallel streams.

Senior

Answer

Parallel streams process data using multiple threads.
Increase performance for CPU-intensive tasks.
Operations must be thread-safe to avoid race conditions.
Quick Summary: list.parallelStream() processes the stream using the common ForkJoinPool (default: CPU core count - 1). The pipeline is split into chunks, processed in parallel, then merged. Good for: large datasets, CPU-intensive operations, stateless operations. Bad for: small collections (overhead > benefit), I/O operations (threads block), order-sensitive side effects. Profile before using parallel - it's not always faster.
Q10:

Explain Fork/Join framework.

Senior

Answer

Fork/Join supports parallel processing using divide-and-conquer approach.
Uses ForkJoinPool and RecursiveTask or RecursiveAction.
Ideal for large recursive computations.
Quick Summary: Fork/Join splits a task recursively into smaller subtasks (fork), processes them in parallel, then combines results (join). Uses work-stealing: idle threads steal tasks from busy threads' queues. Implement RecursiveTask (returns result) or RecursiveAction (no result). Use ForkJoinPool or parallelStream (which uses the common ForkJoinPool internally). Best for divide-and-conquer algorithms on large datasets.
Q11:

How do you avoid deadlocks?

Senior

Answer

Acquire locks in a consistent order.
Avoid nested locks where possible.
Use tryLock() or timed locks.
Prefer higher-level concurrency utilities.
Quick Summary: Avoid deadlocks: always acquire multiple locks in the same consistent order across all threads. Use tryLock() with timeout instead of lock() - if timeout expires, release what you have and retry. Minimize lock scope and duration. Use lock-free data structures (AtomicInteger, ConcurrentHashMap) when possible. Use a single lock instead of multiple locks when feasible. Detect with thread dumps (jstack).
Q12:

Explain ThreadLocal.

Senior

Answer

ThreadLocal stores per-thread variables.
Each thread gets its own isolated copy.
Useful for caching, user sessions, or context propagation.
Quick Summary: ThreadLocal gives each thread its own independent variable copy. Changes in one thread don't affect other threads. Common use: per-request context (user session, database connection, locale) in web frameworks. Spring's RequestContextHolder uses ThreadLocal. Important: always remove() when done (especially in thread pools - threads are reused and old values persist, causing bugs and memory leaks).
Q13:

Explain CompletableFuture chaining.

Senior

Answer

CompletableFuture allows async task chaining with thenApply, thenAccept, thenCombine, exceptionally.
Enables building non-blocking, reactive pipelines.
Quick Summary: CompletableFuture chaining: thenApply(fn) transforms result synchronously. thenApplyAsync(fn) transforms in another thread. thenCompose(fn) chains another CompletableFuture (flatMap). thenCombine(other, fn) combines two futures' results when both complete. thenAccept(consumer) consumes without returning. Build complex async pipelines: fetch data -> transform -> combine -> handle errors -> deliver result.
Q14:

Difference between parallel streams and CompletableFuture.

Senior

Answer

Parallel streams provide easy parallelism for collection processing.
CompletableFuture gives fine-grained control over async tasks.
Use based on concurrency requirements and task complexity.
Quick Summary: Parallel streams use the shared ForkJoinPool - all parallel streams compete for the same threads. CompletableFuture lets you specify your own executor (isolate from shared pool), compose complex async workflows with callbacks (non-blocking), and handle errors per step. Use parallel streams for simple CPU-intensive pipelines. Use CompletableFuture for complex async workflows, I/O operations, and when you need control over thread pools.
Q15:

How does Java handle thread priorities?

Senior

Answer

Threads have priorities from 1 to 10.
Higher-priority threads are scheduled preferentially but order is not guaranteed.
Actual scheduling depends on JVM and OS.
Quick Summary: Thread priorities (1-10, default 5) hint to the OS scheduler but aren't guaranteed. The JVM maps Java priorities to OS-level priorities which vary by platform. High-priority threads get more CPU time but low-priority threads can still run. Thread.MIN_PRIORITY=1, NORM_PRIORITY=5, MAX_PRIORITY=10. Don't rely on priorities for correctness - use proper synchronization instead.
Q16:

Explain reactive programming in Java.

Senior

Answer

Reactive programming uses asynchronous, non-blocking data streams.
Implemented via libraries like RxJava and Reactor.
Useful for microservices, streaming, and event-driven systems.
Quick Summary: Reactive programming in Java handles async event streams with backpressure. Libraries: Project Reactor (Spring WebFlux) and RxJava. Core types: Mono (0 or 1 element), Flux (0 to N elements). Operators: map, flatMap, filter, zip. Backpressure lets consumers signal producers to slow down. Benefits: non-blocking end-to-end, high throughput with few threads, composable async pipelines.
Q17:

Explain CompletableFuture vs Future.

Senior

Answer

Future is limited; get() is blocking and lacks chaining.
CompletableFuture supports non-blocking operations, chaining, combination, and exception handling.
More powerful for async workflows.
Quick Summary: Future: get() blocks the calling thread, no callbacks, can't compose multiple futures, limited error handling. CompletableFuture: non-blocking callbacks (thenApply, thenAccept), composable (thenCompose, thenCombine), built-in exception handling (exceptionally, handle), can be manually completed (complete(), completeExceptionally()). CompletableFuture is strictly superior for complex async workflows.
Q18:

How do you handle exceptions in parallel processing?

Senior

Answer

Handle exceptions inside threads individually.
Use CompletableFuture.exceptionally() or handle().
Avoid uncaught exceptions that silently terminate threads.
Quick Summary: In parallel streams, exceptions in one thread are wrapped in a RuntimeException and rethrown in the calling thread. Use a try-catch around the terminal operation. In CompletableFuture, use exceptionally(fn) to handle exceptions per step, or handle(fn) which gets both result and exception. In ExecutorService, exceptions from Callable are wrapped in ExecutionException from Future.get().
Q19:

Best practices for thread safety and performance.

Senior

Answer

Prefer immutability and stateless logic.
Minimize synchronized sections.
Use atomic types and concurrent collections.
Load-test multithreaded code thoroughly.
Quick Summary: Thread safety and performance best practices: prefer immutability (zero synchronization cost), use concurrent collections instead of synchronized wrappers, minimize lock scope (only lock around the actual shared state, not expensive operations), avoid nested locks (deadlock risk), use lock-free atomics for counters/flags, profile to find actual bottlenecks before optimizing, and use VirtualThreads (Java 21) for I/O-bound tasks.
Q20:

Modern Java features for performance optimization.

Senior

Answer

Use streams and parallel streams carefully.
Use Fork/Join for divide-and-conquer workloads.
Leverage CompletableFuture for async tasks.
Prefer immutable objects to reduce synchronization overhead.
Quick Summary: Modern Java performance features: Virtual Threads (Java 21) - millions of lightweight threads for I/O-bound workloads. Records (Java 16) - compact immutable value objects. Sealed classes for exhaustive pattern matching. JEP improvements to GC (ZGC, Shenandoah with sub-ms pauses). String templates (Java 21). Vector API for SIMD operations. Always profile with JMH before claiming an optimization helps.

Curated Sets for Java

No curated sets yet. Group questions into collections from the admin panel to feature them here.

Ready to level up? Start Practice