Java: Designing API Clients with Clear Error Handling

Applications that consume external APIs must handle more than successful responses. Networks fail, services return error status codes, response bodies may be malformed, and requests may exceed their timeouts. A reliable Java API client makes these outcomes explicit instead of allowing unexpected failures to spread throughout the application.

Use a Suitable HTTP Client

Modern Java provides the java.net.http.HttpClient API. It supports synchronous and asynchronous requests and can be configured with connection timeouts.

import java.net.http.HttpClient;
import java.time.Duration;

HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))
    .build();

A connection timeout limits how long the client waits to establish a connection. Individual requests should also define appropriate timeouts because connecting successfully does not guarantee that the server will respond promptly.

Check HTTP Status Codes

Receiving a response is not the same as receiving a successful result. A client should distinguish successful status codes from client errors and server errors.

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class ApiClient {
    private final HttpClient client;

    public ApiClient() {
        this.client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5))
            .build();
    }

    public String get(String url)
            throws IOException, InterruptedException {

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .timeout(Duration.ofSeconds(10))
            .GET()
            .build();

        HttpResponse<String> response = client.send(
            request,
            HttpResponse.BodyHandlers.ofString()
        );

        if (response.statusCode() < 200
                || response.statusCode() >= 300) {
            throw new IOException(
                "API request failed with status "
                    + response.statusCode()
            );
        }

        return response.body();
    }
}

This basic client reports unsuccessful HTTP responses as errors. Production code may benefit from more specific exceptions that preserve the status code, response details, and request context.

Distinguish Failure Types

Different failures call for different responses.

  • Connection failures: The remote service could not be reached.
  • Timeouts: A connection or request took too long.
  • HTTP errors: The server returned a response indicating failure.
  • Parsing errors: The response could not be interpreted.
  • Business errors: The response was technically valid but violated an application expectation.

Keeping these categories separate improves diagnostics and prevents every failure from being treated as an identical problem.

Retry Carefully

Retries can help with transient failures, but retrying every request is dangerous. A request that creates an order or charges a payment may succeed on the server even if the response never reaches the client.

Prefer bounded retries for appropriate transient failures, use exponential backoff with jitter, and respect server-provided retry guidance when applicable. For non-idempotent operations, use an idempotency mechanism supported by the API before retrying uncertain outcomes.

Keep Transport Details Out of Business Logic

The rest of the application should not need to understand low-level HTTP details everywhere. Wrap API communication in a dedicated client or service that converts responses into application-level results.

This makes the code easier to test and allows authentication, logging, timeouts, and error mapping to be maintained centrally.

Conclusion

A dependable Java API client validates responses, applies sensible timeouts, and represents failures clearly. Thoughtful retry policies and a dedicated service boundary help external integrations remain manageable as an application grows.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *