Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)Collections & Concurrency at Scale
✓ FreeAdvanced· 11 min read

CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

CompletableFuture vs Future, supplyAsync vs runAsync, chaining (thenApply/thenCompose/thenCombine), exceptions in chains, combining many futures, advanced features (timeouts, executors, delayed execution), when to avoid CompletableFuture, parallelStream() and its drawbacks, concurrency vs parallelism, blocking vs non-blocking I/O, NIO selectors, the Netty event loop and the Reactive Streams specification.

Published September 25, 2026


How to use this lesson

Asynchronous questions test whether you know:

  • which thread runs your code;
  • where exceptions go;
  • why blocking inside async code is poison.

Tie the answers to how virtual threads change the calculation: much of what people used CompletableFuture and reactive code for can now be written as simple blocking code on virtual threads.

Q1. What's the difference between CompletableFuture and Future?

Short answer:

  • Future (Java 5) is a read-only handle to a result. You can only get() (blocking), isDone() and cancel(). There's no way to attach a callback, combine futures, or complete one manually.
  • CompletableFuture (Java 8) is a Future plus a CompletionStage:
    • non-blocking composition (thenApply, thenCompose, thenCombine, allOf/anyOf);
    • exception pipelines (exceptionally, handle, whenComplete);
    • manual completion (complete, completeExceptionally), which bridges callback APIs;
    • timeouts (Java 9);
    • choice of executor per stage.

Learn it in depth → CompletableFuture

Q2. How does supplyAsync() differ from runAsync()?

Short answer:

  • supplyAsync(Supplier<T>) runs a task that returns a value, giving CompletableFuture<T>.
  • runAsync(Runnable) runs a task with no result, giving CompletableFuture<Void>.

Both run on ForkJoinPool.commonPool() unless you pass an Executor as the second argument. Always pass one for I/O-bound work. A virtual-thread executor is ideal.

ExecutorService io = Executors.newVirtualThreadPerTaskExecutor();
CompletableFuture<Customer> customer = CompletableFuture.supplyAsync(() -> customerClient.get(id), io);
CompletableFuture<Void> audit = CompletableFuture.runAsync(() -> auditLog.write(event), io);

Q3. How do you chain CompletableFutures?

Short answer:

  • thenApply(fn): transform the result, synchronously (like map).
  • thenCompose(fn): the next step returns another future (like flatMap). It avoids CompletableFuture<CompletableFuture<T>>.
  • thenCombine(other, fn): combine two independent futures when both complete.
  • thenAccept/thenRun: consume the result, or run afterwards.
  • The *Async variants run the stage on an executor. The non-async variants run on whichever thread completed the previous stage, or the calling thread if it's already complete.
CompletableFuture<Quote> quote =
    CompletableFuture.supplyAsync(() -> catalog.find(sku), io)                 // Product
        .thenCompose(p -> CompletableFuture.supplyAsync(() -> pricing.price(p), io))   // async step returning a future
        .thenCombine(CompletableFuture.supplyAsync(() -> tax.rateFor(region), io),
                     (price, rate) -> new Quote(price, price.multiply(rate)))
        .orTimeout(800, TimeUnit.MILLISECONDS)
        .exceptionally(ex -> Quote.unavailable(sku));

Q4. What happens when an exception is thrown in a CompletableFuture chain?

Short answer: The failing stage completes exceptionally, and the exception propagates down the chain. The dependent stages are skipped (their functions don't run), and each completes exceptionally with a CompletionException wrapping the cause. It stops at the first handler:

  • exceptionally(fn): recover with a fallback value (it runs only on failure).
  • handle((result, ex) -> ...): always runs, and can map both success and failure.
  • whenComplete((result, ex) -> ...): a side effect (logging) that doesn't change the outcome.
  • Java 12+ added exceptionallyCompose (an asynchronous fallback).

If nobody handles it and nobody calls join/get, the error is silently lost. join() throws CompletionException, and get() throws ExecutionException: unwrap getCause() before logging or classifying.

Q5. How do you combine several CompletableFutures, and wait for all of them to finish?

Short answer: Use CompletableFuture.allOf(futures...), which completes when all complete (exceptionally if any failed). Then collect the results with join() (non-blocking at that point). anyOf completes with the first one to finish.

List<CompletableFuture<Price>> futures = skus.stream()
        .map(sku -> CompletableFuture.supplyAsync(() -> pricing.price(sku), io))
        .toList();

CompletableFuture<List<Price>> all = CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
        .thenApply(v -> futures.stream().map(CompletableFuture::join).toList());

Key points to cover:

  • allOf doesn't cancel the others when one fails. Handle partial failures per future (handle), or cancel explicitly.
  • With structured concurrency (StructuredTaskScope, preview in recent JDKs), fan-out with automatic cancellation on failure becomes simpler: ShutdownOnFailure semantics.
  • Bound the fan-out concurrency (semaphores), so you don't overload downstream services.

Q6. What are the advanced features of CompletableFuture?

Short answer:

  • Timeouts (Java 9): orTimeout(t, unit) fails with TimeoutException, and completeOnTimeout(value, t, unit) falls back to a default.
  • Delayed execution: CompletableFuture.delayedExecutor(delay, unit[, executor]), for retries with back-off.
  • Custom executors per stage (the *Async(fn, executor) methods).
  • Manual completion and bridging: new CompletableFuture<>() plus complete(...) from a callback API. failedFuture/completedFuture/completedStage.
  • exceptionallyCompose/exceptionallyAsync (Java 12).
  • copy()/minimalCompletionStage(), for defensive APIs (Java 9).
  • Status APIs (Java 19): state(), resultNow(), exceptionNow().
  • Cancellation: cancel() completes the future with CancellationException, but doesn't interrupt the running task. CompletableFuture has no link to the thread doing the work.

Q7. When should you avoid CompletableFuture?

Short answer:

  • For simple blocking I/O fan-out on Java 21+: virtual threads with plain blocking code (or structured concurrency) are easier to read, debug and profile, with the same scalability.
  • When you need cancellation that stops work, or strict timeouts on in-flight work. cancel() doesn't interrupt. Use executors plus Future.cancel(true), or structured concurrency.
  • For streaming or back-pressure (many items over time): use Reactive Streams (Reactor, Kafka consumers), or queues.
  • For CPU-bound parallel data processing: parallel streams or ForkJoin are more natural.
  • When the team's debugging and observability can't follow deep async chains (lost stack traces, context propagation for MDC and tracing needing extra work).
  • On the default common pool with blocking calls. That's never right.

Q8. What is parallelStream(), and what are its main drawbacks?

Short answer: collection.parallelStream() (or .parallel()) runs the pipeline on the common ForkJoinPool: the source is split with a Spliterator, the chunks are processed concurrently, and the partial results are combined. Its drawbacks:

  • A shared pool: every parallel stream and default async future in the JVM competes for about one thread per core. Blocking I/O inside a parallel stream starves everything.
  • Overhead: splitting, task management and merging often exceed the gain for small collections, or cheap per-element work. The rule of thumb: N × cost per element should be above about 10,000 "units".
  • Poor sources: LinkedList, Stream.iterate and I/O-backed streams split badly.
  • Ordering costs: forEachOrdered, limit/skip on ordered streams, and sorted reduce parallelism. findAny and unordered operations are cheaper.
  • Correctness: stateful or shared-mutation lambdas cause races. Reductions must be associative, with a correct identity.
  • Unpredictability in servers: parallelism inside request handling multiplies thread usage under load. Measure it (JMH), and usually avoid it in request paths.
long primes = LongStream.rangeClosed(2, 50_000_000).parallel().filter(Primes::isPrime).count();  // a good fit: big, CPU-bound, splittable

Learn it in depth → Streams API

Q9. What's the difference between concurrency and parallelism?

Short answer:

  • Concurrency is about structure: dealing with many tasks at once, whose lifetimes overlap, and which interleave. It's possible even on one core (a web server handling 10,000 connections, most of them waiting on I/O).
  • Parallelism is about execution: doing many computations at the same instant on multiple cores, to finish CPU-bound work faster (a parallel sort, a matrix multiply).

The Java tools:

  • concurrency: virtual threads, CompletableFuture, reactive code, executors;
  • parallelism: parallel streams, ForkJoin, Arrays.parallelSort, and the Vector API.

As Rob Pike put it, concurrency is a way to structure a program. Parallelism is one possible way to execute it.

Q10. What's the difference between blocking and non-blocking (I/O)?

Short answer:

  • Blocking: a call like socket.read() or a JDBC query suspends the calling thread until data arrives. It's simple to write, but with platform threads each waiting connection costs a thread (about 1 MB of stack, and context switches), so scalability is limited by thread count.
  • Non-blocking: the call returns immediately (with data, or "nothing yet"). The application is notified when it's ready (a selector, a callback, a completion stage). A few threads (event loops) handle thousands of connections. The costs are callback or reactive complexity, the requirement that nothing in the chain blocks, and harder debugging.
  • Virtual threads (Java 21) give blocking-style code with non-blocking scalability: a virtual thread blocked on I/O unmounts from its carrier thread, and the JDK uses non-blocking I/O underneath.

Q11. What is an NIO Selector?

Short answer: A java.nio.channels.Selector lets one thread monitor many non-blocking channels (sockets) for readiness events (OP_ACCEPT, OP_CONNECT, OP_READ, OP_WRITE). It's built on the OS's multiplexing facility (epoll on Linux, kqueue on macOS/BSD). The loop:

  1. register the channels with their interest sets;
  2. call select(), which blocks until at least one channel is ready;
  3. iterate the selected keys, and handle each ready channel without blocking;
  4. repeat.

This reactor pattern is the core of Netty, Tomcat's NIO connector, and Jetty.

Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open().bind(new InetSocketAddress(8080));
server.configureBlocking(false).register(selector, SelectionKey.OP_ACCEPT);
while (true) {
    selector.select();
    for (Iterator<SelectionKey> it = selector.selectedKeys().iterator(); it.hasNext(); ) {
        SelectionKey key = it.next(); it.remove();
        if (key.isAcceptable()) server.accept().configureBlocking(false).register(selector, SelectionKey.OP_READ);
        else if (key.isReadable()) handleRead((SocketChannel) key.channel());
    }
}

Q12. How does the Netty event loop work?

Short answer:

  • Netty has EventLoopGroups. Typically a small boss group accepts connections, and a worker group (about 2 × cores) handles I/O.
  • Each EventLoop is a single thread running a loop with a Selector (or native epoll/io_uring transports). It processes I/O readiness events for its assigned channels, plus queued tasks and scheduled tasks.
  • Each channel is bound to one event loop for its lifetime, so all of its handlers run on the same thread. There's no locking needed within a channel's pipeline, and ordering is preserved.
  • Data flows through a ChannelPipeline of handlers (decoders, business handlers, encoders), using pooled, reference-counted ByteBufs (often direct memory).

The golden rule: never block an event loop thread. A blocking call stalls every connection on that loop. Offload blocking work to a separate executor, or to virtual threads. Spring WebFlux and Reactor Netty run on this model, which is why a blocking JDBC call inside a WebFlux handler is so harmful.

Q13. What is the Reactive Streams specification?

Short answer: A small standard for asynchronous stream processing with non-blocking back-pressure. It has four interfaces, Publisher, Subscriber, Subscription and Processor, which are also in the JDK as java.util.concurrent.Flow (Java 9), plus rules and a TCK. The protocol is:

  1. subscribe → onSubscribe(subscription);
  2. the subscriber signals demand with request(n);
  3. the publisher emits at most n onNext signals;
  4. then a terminal onComplete or onError.

It's implemented by Project Reactor (Mono/Flux), RxJava, Akka Streams, the MongoDB and R2DBC drivers, and reactive Kafka. Interoperability means a Reactor Flux can consume from any compliant publisher.

Learn it in depth → Event-Driven Architecture Patterns

Q14. What is CompletableFuture chaining? (The rapid-fire version)

Short answer: Building a pipeline of dependent asynchronous steps, where each stage runs when the previous one completes:

  • thenApply: transform;
  • thenCompose: an asynchronous next step;
  • thenCombine: join two futures;
  • thenAccept/thenRun: side effects;
  • exceptionally/handle: recovery.

Pass executors for blocking stages, add orTimeout, and terminate the chain with a handler, so failures are never lost.

Follow-up questions this topic invites — and their answers

Q: Which thread runs thenApply if the future is already complete? A: The thread that calls thenApply runs the function immediately. If the future isn't complete yet, the function runs on the thread that completes it. That's why heavy work belongs in thenApplyAsync(fn, executor).

Q: How do you propagate MDC and tracing context through CompletableFuture? A: Wrap the executors with context-propagating decorators: Micrometer's ContextExecutorService/ContextSnapshot, Spring's TaskDecorator, or OpenTelemetry's Context.taskWrapping. Otherwise the logs lose their trace IDs after the first async hop.

Q: Why does a parallel stream inside a ForkJoinPool.submit() use that pool? A: Because ForkJoin tasks forked from a worker thread run in the pool that thread belongs to. It's an implementation detail people use to isolate parallel streams. It works, but isn't specified behaviour.

Q: Is WebFlux faster than Spring MVC? A: Not per request. It scales better for high concurrency with slow I/O, using fewer threads. With virtual threads, Spring MVC reaches similar scalability for most workloads, with simpler code. Choose WebFlux for streaming, back-pressure, or when you're already reactive end-to-end.

Previous

Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions

Next

Lambdas & Functional Interfaces Internals — Interview Questions

AI Tutor

Lesson: CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.