0Pricing
Java Academy · Lesson

Error Handling and Async HTTP Pipeline

Handle errors with exceptionally, handle, and whenComplete, then build an async HTTP client pipeline.

Error Handling and Async HTTP Pipeline is a free Java Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Async Error Handling Matters

In async pipelines, exceptions don't propagate the same way as in synchronous code. You must explicitly handle errors using exceptionally, handle, or whenComplete.

exceptionally: Recovering From Errors

exceptionally(Function<Throwable, T>) is called only if the future completes with an exception. It provides a fallback value, keeping the pipeline alive.

CompletableFuture<String> safe = fetchUser(id)
    .thenApply(User::getName)
    .exceptionally(ex -> {
        log.warn("Fetch failed: " + ex.getMessage());
        return "Anonymous";
    });
System.out.println(safe.join()); // "Anonymous" if fetch failed

handle: Transform Success and Failure

handle(BiFunction<T, Throwable, U>) is called for both success and failure. One of the two arguments will be null.

fetchUser(id).handle((user, ex) -> {
    if (ex != null) return UserDto.empty();
    return UserDto.from(user);
}).thenAccept(dto -> sendResponse(dto));

whenComplete: Side Effect After Any Outcome

whenComplete(BiConsumer) is called after completion regardless of outcome. It does not change the result — use it for logging, metrics, or cleanup.

fetchUser(id)
    .whenComplete((user, ex) -> {
        if (ex != null) metrics.increment("user.fetch.error");
        else            metrics.increment("user.fetch.success");
    })
    .thenAccept(u -> sendResponse(u));

Retrying Failed Futures

Implement retry logic by recursively calling the operation on failure up to a maximum number of attempts.

CompletableFuture<String> withRetry(Supplier<CompletableFuture<String>> task, int retries) {
    return task.get().exceptionallyCompose(ex -> {
        if (retries > 0) {
            System.out.println("Retrying... " + retries);
            return withRetry(task, retries - 1);
        }
        return CompletableFuture.failedFuture(ex);
    });
}

Java 11 HttpClient: Async GET

Java 11's HttpClient provides fully async HTTP via sendAsync(), returning a CompletableFuture<HttpResponse<String>>.

HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users/1"))
    .build();
CompletableFuture<String> body = client
    .sendAsync(req, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body);

Chaining Multiple HTTP Calls

Use thenCompose to chain dependent HTTP calls: first fetch the user, then use the user's ID to fetch their orders.

client.sendAsync(userReq, HttpResponse.BodyHandlers.ofString())
    .thenApply(r -> parseUser(r.body()))
    .thenCompose(user -> client.sendAsync(
        buildOrdersRequest(user.getId()),
        HttpResponse.BodyHandlers.ofString()))
    .thenApply(r -> parseOrders(r.body()))
    .thenAccept(orders -> System.out.println("Orders: " + orders.size()));

Parallel HTTP Calls with allOf

Fan out to several endpoints in parallel, then collect all responses when all complete.

List<URI> endpoints = List.of(uri1, uri2, uri3);
List<CompletableFuture<String>> requests = endpoints.stream()
    .map(uri -> client.sendAsync(
            HttpRequest.newBuilder(uri).build(),
            HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body))
    .collect(Collectors.toList());
List<String> responses = CompletableFuture
    .allOf(requests.toArray(new CompletableFuture[0]))
    .thenApply(v -> requests.stream().map(CompletableFuture::join).collect(Collectors.toList()))
    .join();

Timeout on HTTP Calls

Apply orTimeout directly to the async HTTP call to cancel it if the server takes too long.

CompletableFuture<String> result = client
    .sendAsync(req, HttpResponse.BodyHandlers.ofString())
    .orTimeout(5, TimeUnit.SECONDS)
    .thenApply(HttpResponse::body)
    .exceptionally(ex -> "timeout or error");

Status Code Validation in the Pipeline

Use thenApply to check the HTTP status code and throw a domain exception if it indicates an error, keeping the pipeline's error handling consistent.

.thenApply(response -> {
    if (response.statusCode() != 200)
        throw new HttpResponseException(response.statusCode());
    return response.body();
})

Combining Error Recovery and Logging

Layer whenComplete for logging and exceptionally for recovery in the same pipeline — they compose cleanly.

fetchData()
    .whenComplete((r, ex) -> { if (ex != null) log.error("Failed", ex); })
    .exceptionally(ex -> fallback())
    .thenAccept(result -> process(result));

Quick Check

Which CompletableFuture method is called ONLY when the future fails?

Recap

Use exceptionally for fallback values, handle for transforming both outcomes, whenComplete for side effects. Build async HTTP pipelines with Java 11 HttpClient + thenCompose + allOf.

Frequently asked questions

Is the “Error Handling and Async HTTP Pipeline” lesson free?

Yes — the full text of “Error Handling and Async HTTP Pipeline” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.

What will I learn in “Error Handling and Async HTTP Pipeline”?

Handle errors with exceptionally, handle, and whenComplete, then build an async HTTP client pipeline. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Java Academy?

No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Error Handling and Async HTTP Pipeline” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Java Academy lesson?

Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Creating and Completing CompletableFutures
  2. Chaining with thenApply and thenCompose
  3. Combining Futures: allOf and anyOf
  4. Error Handling and Async HTTP Pipeline
← Back to Java Academy