HTTP client abstraction for LangChain4j with synchronous/asynchronous execution and Server-Sent Events (SSE) streaming support
This guide covers error handling strategies for both synchronous and asynchronous HTTP requests.
Thrown when the server returns a client error (4XX) or server error (5XX) status code.
import dev.langchain4j.exception.HttpException;
try {
SuccessfulHttpResponse response = client.execute(request);
System.out.println("Success: " + response.body());
} catch (HttpException e) {
// Handle 4XX/5XX errors
System.err.println("HTTP error: " + e.getMessage());
}When thrown:
Thrown for unexpected errors during request execution.
try {
SuccessfulHttpResponse response = client.execute(request);
System.out.println("Success: " + response.body());
} catch (RuntimeException e) {
// Handle network errors, timeouts, etc.
System.err.println("Request failed: " + e.getMessage());
}When thrown:
connectTimeout)readTimeout)import dev.langchain4j.exception.HttpException;
try {
SuccessfulHttpResponse response = client.execute(request);
System.out.println("Success: " + response.body());
} catch (HttpException e) {
System.err.println("HTTP error: " + e.getMessage());
} catch (RuntimeException e) {
System.err.println("Request failed: " + e.getMessage());
}try {
SuccessfulHttpResponse response = client.execute(request);
System.out.println("Success: " + response.body());
} catch (HttpException e) {
String message = e.getMessage();
if (message.contains("401") || message.contains("403")) {
System.err.println("Authentication failed");
} else if (message.contains("404")) {
System.err.println("Resource not found");
} else if (message.contains("429")) {
System.err.println("Rate limit exceeded");
} else if (message.contains("500") || message.contains("502") || message.contains("503")) {
System.err.println("Server error - retry later");
} else {
System.err.println("HTTP error: " + message);
}
} catch (RuntimeException e) {
System.err.println("Request failed: " + e.getMessage());
}import java.net.SocketTimeoutException;
try {
SuccessfulHttpResponse response = client.execute(request);
System.out.println("Success: " + response.body());
} catch (RuntimeException e) {
if (e.getCause() instanceof SocketTimeoutException) {
System.err.println("Request timed out");
// Consider retrying or using longer timeout
} else {
System.err.println("Request failed: " + e.getMessage());
}
}import dev.langchain4j.http.client.sse.*;
client.execute(request, new ServerSentEventListener() {
@Override
public void onEvent(ServerSentEvent event) {
System.out.println("Received: " + event.data());
}
@Override
public void onError(Throwable throwable) {
System.err.println("Stream error: " + throwable.getMessage());
throwable.printStackTrace();
}
@Override
public void onClose() {
System.out.println("Stream closed");
}
});client.execute(request, new ServerSentEventListener() {
@Override
public void onEvent(ServerSentEvent event) {
System.out.println("Received: " + event.data());
}
@Override
public void onError(Throwable throwable) {
if (throwable instanceof HttpException) {
System.err.println("HTTP error in stream: " + throwable.getMessage());
} else if (throwable.getCause() instanceof SocketTimeoutException) {
System.err.println("Stream timed out");
} else if (throwable instanceof IOException) {
System.err.println("I/O error: " + throwable.getMessage());
} else {
System.err.println("Unknown error: " + throwable.getMessage());
}
}
@Override
public void onClose() {
System.out.println("Stream closed");
}
});Important: If any method of the ServerSentEventListener throws an exception, the stream processing will be terminated immediately.
client.execute(request, new ServerSentEventListener() {
@Override
public void onEvent(ServerSentEvent event) {
try {
// Process event - may throw exceptions
processEvent(event);
} catch (Exception e) {
// Handle exception without propagating
System.err.println("Error processing event: " + e.getMessage());
// Stream continues
}
}
@Override
public void onError(Throwable throwable) {
System.err.println("Stream error: " + throwable.getMessage());
}
});public SuccessfulHttpResponse executeWithRetry(HttpRequest request, int maxRetries) {
int attempt = 0;
while (attempt < maxRetries) {
try {
return client.execute(request);
} catch (RuntimeException e) {
attempt++;
if (attempt >= maxRetries) {
throw e; // Re-throw on final attempt
}
System.err.println("Attempt " + attempt + " failed, retrying...");
try {
Thread.sleep(1000 * attempt); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during retry", ie);
}
}
}
throw new RuntimeException("Should not reach here");
}public SuccessfulHttpResponse executeWithBackoff(HttpRequest request, int maxRetries) {
int attempt = 0;
Random random = new Random();
while (attempt < maxRetries) {
try {
return client.execute(request);
} catch (HttpException e) {
// Don't retry 4XX client errors
if (e.getMessage().contains("4")) {
throw e;
}
attempt++;
if (attempt >= maxRetries) {
throw e;
}
} catch (RuntimeException e) {
attempt++;
if (attempt >= maxRetries) {
throw e;
}
}
// Exponential backoff with jitter
long baseDelay = 1000L * (1L << attempt); // 1s, 2s, 4s, 8s...
long jitter = random.nextInt(1000);
long delay = baseDelay + jitter;
System.err.println("Attempt " + attempt + " failed, retrying in " + delay + "ms");
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during retry", ie);
}
}
throw new RuntimeException("Should not reach here");
}public SuccessfulHttpResponse executeWithTimeoutFallback(HttpRequest request) {
HttpClient normalClient = HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
HttpClient lenientClient = HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(30))
.readTimeout(Duration.ofMinutes(2))
.build();
try {
return normalClient.execute(request);
} catch (RuntimeException e) {
if (e.getCause() instanceof SocketTimeoutException) {
System.err.println("Timeout with normal client, retrying with lenient timeout");
return lenientClient.execute(request);
}
throw e;
}
}public class CircuitBreakerHttpClient {
private final HttpClient delegate;
private final int failureThreshold;
private final long retryTimeout;
private int failureCount = 0;
private long lastFailureTime = 0;
private boolean circuitOpen = false;
public CircuitBreakerHttpClient(HttpClient delegate, int failureThreshold, long retryTimeout) {
this.delegate = delegate;
this.failureThreshold = failureThreshold;
this.retryTimeout = retryTimeout;
}
public SuccessfulHttpResponse execute(HttpRequest request) {
if (circuitOpen) {
if (System.currentTimeMillis() - lastFailureTime < retryTimeout) {
throw new RuntimeException("Circuit breaker is OPEN");
} else {
// Try to close circuit
circuitOpen = false;
failureCount = 0;
}
}
try {
SuccessfulHttpResponse response = delegate.execute(request);
// Success - reset failure count
failureCount = 0;
return response;
} catch (RuntimeException e) {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= failureThreshold) {
circuitOpen = true;
System.err.println("Circuit breaker OPENED after " + failureCount + " failures");
}
throw e;
}
}
}private Map<String, String> cache = new ConcurrentHashMap<>();
public String fetchWithCache(String url) {
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.GET)
.url(url)
.build();
try {
SuccessfulHttpResponse response = client.execute(request);
String body = response.body();
cache.put(url, body); // Update cache
return body;
} catch (RuntimeException e) {
System.err.println("Request failed, using cached value: " + e.getMessage());
String cached = cache.get(url);
if (cached != null) {
return cached;
}
throw new RuntimeException("No cached value available", e);
}
}public String fetchWithDefault(String url, String defaultValue) {
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.GET)
.url(url)
.build();
try {
SuccessfulHttpResponse response = client.execute(request);
return response.body();
} catch (RuntimeException e) {
System.err.println("Request failed, using default: " + e.getMessage());
return defaultValue;
}
}import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ErrorLoggingClient {
private static final Logger logger = LoggerFactory.getLogger(ErrorLoggingClient.class);
private final HttpClient client;
public SuccessfulHttpResponse execute(HttpRequest request) {
try {
SuccessfulHttpResponse response = client.execute(request);
logger.info("Request successful: {} {}", request.method(), request.url());
return response;
} catch (HttpException e) {
logger.error("HTTP error for {} {}: {}",
request.method(), request.url(), e.getMessage());
throw e;
} catch (RuntimeException e) {
logger.error("Request failed for {} {}: {}",
request.method(), request.url(), e.getMessage(), e);
throw e;
}
}
}import java.util.concurrent.CompletableFuture;
public CompletableFuture<String> fetchAsync(String url) {
CompletableFuture<String> future = new CompletableFuture<>();
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.GET)
.url(url)
.build();
try {
SuccessfulHttpResponse response = client.execute(request);
future.complete(response.body());
} catch (Exception e) {
future.completeExceptionally(e);
}
return future;
}
// Usage
fetchAsync("https://api.example.com/data")
.thenAccept(body -> System.out.println("Success: " + body))
.exceptionally(error -> {
System.err.println("Error: " + error.getMessage());
return null;
});Differentiate error types: Handle HttpException (4XX/5XX) differently from RuntimeException (network issues, timeouts).
Don't retry 4XX errors: Client errors usually indicate invalid requests that won't succeed on retry.
Use exponential backoff: When retrying, increase the delay between attempts to avoid overwhelming the server.
Set appropriate timeouts: Configure timeouts based on expected response times to fail fast when appropriate.
Implement circuit breakers: Prevent cascading failures by stopping requests when a service is consistently failing.
Log errors appropriately: Log enough information to diagnose issues but avoid logging sensitive data.
Provide fallbacks: Consider fallback strategies like cached data or default values for non-critical requests.
Monitor error rates: Track error rates in production to identify issues early.
Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-http-client