Definition
- orTimeout(duration, unit) – Java 9; completes the CF exceptionally with
TimeoutExceptionif not done in time. - completeOnTimeout(fallback, duration, unit) – Java 9; completes with a fallback value instead of failing.
- Non-optional – every external call (HTTP, JDBC, gRPC) must have a timeout; without one, a hung connection blocks the thread forever.
- Thread impact – timeout completes the CF but does not interrupt the underlying task; use HTTP client-level timeouts for true cancellation.
- Before Java 9 – use
get(timeout, unit)which blocks the caller and throwsTimeoutException.
At National Bank of India: orTimeout is "if the credit bureau does not respond in 5 seconds, reject the application." Hard deadline, fail fast. completeOnTimeout is "if the bureau does not respond in 5 seconds, use the cached score." Graceful degradation. Without timeout: "Wait for the bureau forever." One teller waiting forever blocks the counter for all subsequent customers.
Understanding the Concepts
How does orTimeout work?
orTimeout completes the CompletableFuture exceptionally with a TimeoutException if it has not completed within the specified duration. This is the fail-fast approach for strict deadlines.
package com.bank.timeout;
import java.util.concurrent.*;
public class OrTimeoutDemo {
public static void main(String[] args) {
CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> callCreditBureau()) // might take 30 seconds
.orTimeout(5, TimeUnit.SECONDS); // fail after 5 seconds
try {
String result = cf.join();
System.out.println("Score: " + result);
} catch (CompletionException e) {
if (e.getCause() instanceof TimeoutException) {
System.out.println("Credit bureau unavailable, try later");
}
}
}
}
How does completeOnTimeout work?
completeOnTimeout completes the CompletableFuture with a fallback value instead of failing. This is the graceful degradation approach.
package com.bank.timeout;
import java.util.concurrent.*;
public class CompleteOnTimeoutDemo {
public static void main(String[] args) {
CompletableFuture<Integer> scoreCF = CompletableFuture
.supplyAsync(() -> callCreditBureau()) // might be slow
.completeOnTimeout(500, 5, TimeUnit.SECONDS); // fallback: 500
int score = scoreCF.join();
// Always succeeds: real score or default 500
System.out.println("Credit score: " + score);
}
}
How do they compare?
| Feature | orTimeout | completeOnTimeout |
|---|---|---|
| On timeout | TimeoutException | Fallback value |
| Behavior | Fail fast | Graceful degradation |
| Use case | Strict deadline, reject request | Resilient display, show cached data |
| Pipeline continues? | No (exception propagates) | Yes (with fallback) |
| When to use | Payments, trades | Dashboard widgets, reports |
Why are timeouts non-optional?
Without a timeout, a hung HTTP call blocks a thread for the TCP timeout, which can be 300 seconds. This exhausts the thread pool and cascades to all requests.
WITHOUT timeout:
External service hangs -> thread blocked 300s
200 threads x 300s = pool exhausted in seconds
Circuit breaker never triggers (no failure signal, just hanging)
Cascading failure -> entire service DOWN
WITH timeout:
5s timeout -> TimeoutException after 5s
Thread freed -> circuit breaker counts the failure
Circuit opens after threshold -> fast-fail in 10ms
Service degraded but alive!
Does orTimeout cancel the underlying task?
No. orTimeout completes the CompletableFuture, but the thread running the actual task may still be blocked on IO. The task continues running until the IO call finishes or the thread is interrupted. For true cancellation, combine CF-level timeout with HTTP client-level timeouts.
package com.bank.timeout;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
public class LayeredTimeoutDemo {
// Layer 1: HTTP client timeout (true cancellation)
private static final HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.build();
// Layer 2: CF timeout (pipeline protection)
public CompletableFuture<String> fetchScore(String customerId) {
return CompletableFuture
.supplyAsync(() -> callBureauWithHttpClient(customerId), ioPool)
.orTimeout(5, TimeUnit.SECONDS);
}
}
How do you chain timeouts in a pipeline?
Each stage can have its own timeout and fallback strategy. The overall pipeline also gets a timeout.
CompletableFuture<String> result = CompletableFuture
.supplyAsync(() -> fetchProfile(customerId), ioPool)
.orTimeout(3, TimeUnit.SECONDS)
.thenCompose(profile -> fetchScore(profile.getId()))
.orTimeout(5, TimeUnit.SECONDS)
.thenApply(score -> decide(score))
.completeOnTimeout("PENDING", 10, TimeUnit.SECONDS)
.exceptionally(ex -> "ERROR: " + ex.getMessage());
What about before Java 9?
Before Java 9, you must use the blocking get(timeout, unit) method, which blocks the calling thread.
try {
String result = cf.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
cf.cancel(true);
return "fallback";
} catch (InterruptedException | ExecutionException e) {
return "error";
}
The Java 9 methods are better because they are non-blocking and compose naturally in async pipelines.
What are common interview follow-up questions?
- Can you chain orTimeout after completeOnTimeout? Yes, but it is rarely useful since completeOnTimeout already provides a fallback.
- What is the scheduler thread for timeouts? Java 9+ uses a shared
ScheduledThreadPoolExecutorfor timeout scheduling. This thread only triggers the completion, not the actual work. - How do timeouts interact with circuit breakers? A timeout triggers a failure count in the circuit breaker. After enough failures, the circuit opens and subsequent calls fail immediately without waiting.
Visual Overview
orTimeout (fail fast) ┌──────────────┐ 5 seconds ┌──────────────────┐ │ supplyAsync │────────────────▶ │ TimeoutException │ │ (slow call) │ not done yet │ (CF fails) │ └──────────────┘ └──────────────────┘ completeOnTimeout (graceful) ┌──────────────┐ 5 seconds ┌──────────────────┐ │ supplyAsync │────────────────▶ │ Fallback value │ │ (slow call) │ not done yet │ (CF succeeds) │ └──────────────┘ └──────────────────┘
Key Takeaway
Java 9 added two timeout methods for CompletableFuture. orTimeout() completes the CF exceptionally with TimeoutException for strict deadlines. completeOnTimeout() provides a fallback value for graceful degradation. Both are non-optional for every external service call.
Without timeouts, a hung HTTP call blocks a thread for the TCP timeout (often 300 seconds). This exhausts the thread pool and cascades to all requests. With a 5-second timeout, the thread is freed after 5 seconds, the circuit breaker opens, and the service degrades gracefully instead of going down completely.
Note that timeout completes the CF but does not cancel the underlying task. The blocked thread may still be waiting on IO. For true cancellation, combine with HTTP client-level timeouts. At National Bank of India, a 5-minute credit bureau outage with orTimeout(5s) meant 3,000 customers got instant "try later" responses instead of 5-minute hangs.
Remember one sentence: orTimeout fails fast, completeOnTimeout degrades gracefully, and every external call needs one.
---