What does join() method do and how does it differ from get()
get() has a timeout version: get(5, TimeUnit.SECONDS). join() has NO timeout variant. In production code where you need a timeout, use get() or wrap join() via orTimeout().
π’ Junior Level
join() and get() β both wait for CompletableFuture to complete and return the result.
Main difference:
get()β throws checked exceptions (InterruptedException,ExecutionException)join()β throws unchecked (CompletionException)
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "Hello");
// get() β requires try-catch
try {
String result = cf.get(); // throws checked exceptions
} catch (InterruptedException | ExecutionException e) {
// handle
}
// join() β simpler
String result = cf.join(); // throws CompletionException (unchecked)
π‘ Middle Level
When to use which
get() β when you need checked exceptions:
try {
String result = cf.get(5, TimeUnit.SECONDS); // with timeout
} catch (TimeoutException e) {
// timeout handling
} catch (InterruptedException e) {
// interrupt handling
}
Critical practical difference
get() has a timeout version: get(5, TimeUnit.SECONDS). join() has NO timeout variant. In production code where you need a timeout, use get() or wrap join() via orTimeout().
join() β for chains and tests:
// In chains
CompletableFuture.allOf(cf1, cf2, cf3)
.thenApply(v -> {
String r1 = cf1.join(); // no try-catch needed
String r2 = cf2.join();
String r3 = cf3.join();
return combine(r1, r2, r3);
});
// In tests
@Test
void test() {
String result = cf.join(); // simpler
assertEquals("expected", result);
}
Typical mistakes
- join() without error handling: ```java // β If CF fails β CompletionException String result = cf.join(); // may throw
// β With handling cf.exceptionally(ex -> βfallbackβ) .join(); // safe
---
## π΄ Senior Level
### Internal Implementation
```java
// Simplified representation. Actual JDK methods may differ.
public T get() throws InterruptedException, ExecutionException {
Object r = reportGet();
if (r instanceof AltResult alt) {
if (alt.ex != null) throw new ExecutionException((Throwable) alt.ex);
}
return (T) r;
}
public T join() {
Object r = reportGet();
if (r instanceof AltResult alt) {
if (alt.ex != null) throw new CompletionException((Throwable) alt.ex);
}
return (T) r;
}
// The only difference is the exception type!
Performance
get(): ~100 ns (checked exception handling)
join(): ~100 ns (same internals)
Difference is negligible β choice is based on convenience
Best Practices
// β
join() in chains
cf.thenApply(v -> cf2.join());
// β
join() in tests
assertEquals("expected", cf.join());
// β
get(timeout) for production
cf.get(5, TimeUnit.SECONDS);
// β join() without error handling
// β get() without timeout in production
π― Interview Cheat Sheet
Must know:
- join() β unchecked CompletionException, get() β checked InterruptedException + ExecutionException
- join() has NO timeout, get(timeout, unit) HAS timeout
- join() is preferred in chains and tests, get(timeout) for production
- Internally identical β difference is only in exception type
- Both block the thread until CF completes
Common follow-up questions:
- join() or get() β when which? β join() in chains/tests, get(timeout) in production
- Can join() throw TimeoutException? β No. Need orTimeout() before join()
- CompletionException vs ExecutionException? β CompletionException (join) β unchecked, ExecutionException (get) β checked
- Does join() after allOf() block? β No, if allOf() is completed. But join() without allOf() β blocks
Red flags (DO NOT say):
- βjoin() is non-blockingβ β it blocks until CF completes
- βget() and join() are completely identicalβ β different exception types, get has timeout
- βjoin() without timeout in production is OKβ β infinite waiting on error
Related topics:
- [[21. How to implement timeout for CompletableFuture]]
- [[24. How to test code with CompletableFuture]]
- [[6. How to handle exceptions in CompletableFuture chain]]
- [[14. What is blocking code and how to distinguish it from non-blocking]]