HTTP client abstraction for LangChain4j with synchronous/asynchronous execution and Server-Sent Events (SSE) streaming support
This document provides complete, runnable examples for common HTTP client usage patterns.
import dev.langchain4j.http.client.*;
import java.time.Duration;
public class HttpClientFactory {
public static HttpClient createDefault() {
return HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
}
}public class HttpClientFactory {
public static HttpClient createForRest() {
return HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
}
public static HttpClient createForStreaming() {
return HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(15))
.readTimeout(Duration.ofMinutes(5))
.build();
}
public static HttpClient createForFastAPIs() {
return HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(10))
.build();
}
}import dev.langchain4j.http.client.*;
import dev.langchain4j.exception.HttpException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class RestApiClient {
private final HttpClient client;
private final String baseUrl;
private final String apiKey;
private final ObjectMapper objectMapper;
public RestApiClient(String baseUrl, String apiKey) {
this.client = HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
this.baseUrl = baseUrl;
this.apiKey = apiKey;
this.objectMapper = new ObjectMapper();
}
public <T> T get(String path, Class<T> responseType) throws IOException {
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.GET)
.url(baseUrl, path)
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Accept", "application/json")
.build();
try {
SuccessfulHttpResponse response = client.execute(request);
return objectMapper.readValue(response.body(), responseType);
} catch (HttpException e) {
throw new IOException("API request failed: " + e.getMessage(), e);
}
}
public <T> T post(String path, Object requestBody, Class<T> responseType) throws IOException {
String jsonBody = objectMapper.writeValueAsString(requestBody);
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.POST)
.url(baseUrl, path)
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.body(jsonBody)
.build();
try {
SuccessfulHttpResponse response = client.execute(request);
return objectMapper.readValue(response.body(), responseType);
} catch (HttpException e) {
throw new IOException("API request failed: " + e.getMessage(), e);
}
}
public void delete(String path) throws IOException {
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.DELETE)
.url(baseUrl, path)
.addHeader("Authorization", "Bearer " + apiKey)
.build();
try {
client.execute(request);
} catch (HttpException e) {
throw new IOException("API request failed: " + e.getMessage(), e);
}
}
}
// Usage
RestApiClient api = new RestApiClient("https://api.example.com", "your-api-key");
// GET request
User user = api.get("/users/123", User.class);
// POST request
CreateUserRequest createRequest = new CreateUserRequest("Alice", "alice@example.com");
User createdUser = api.post("/users", createRequest, User.class);
// DELETE request
api.delete("/users/123");import dev.langchain4j.http.client.*;
import dev.langchain4j.http.client.sse.*;
public class SSEClient {
private final HttpClient client;
private final String apiUrl;
private final String apiKey;
public SSEClient(String apiUrl, String apiKey) {
this.client = HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(15))
.readTimeout(Duration.ofMinutes(5))
.build();
this.apiUrl = apiUrl;
this.apiKey = apiKey;
}
public void stream(String prompt, Consumer<String> onChunk, Consumer<Throwable> onError) {
String requestBody = String.format("{\"prompt\":\"%s\",\"stream\":true}", prompt);
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.POST)
.url(apiUrl)
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Accept", "text/event-stream")
.addHeader("Content-Type", "application/json")
.body(requestBody)
.build();
client.execute(request, new ServerSentEventListener() {
@Override
public void onOpen(SuccessfulHttpResponse response) {
System.out.println("Stream connected");
}
@Override
public void onEvent(ServerSentEvent event) {
onChunk.accept(event.data());
}
@Override
public void onError(Throwable throwable) {
onError.accept(throwable);
}
@Override
public void onClose() {
System.out.println("Stream closed");
}
});
}
}
// Usage
SSEClient sseClient = new SSEClient("https://api.example.com/stream", "your-api-key");
sseClient.stream(
"Tell me a story",
chunk -> System.out.print(chunk),
error -> System.err.println("Error: " + error.getMessage())
);public CompletableFuture<String> streamAndAccumulate(String prompt) {
CompletableFuture<String> future = new CompletableFuture<>();
StringBuilder accumulated = new StringBuilder();
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.POST)
.url(apiUrl)
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Accept", "text/event-stream")
.addHeader("Content-Type", "application/json")
.body(String.format("{\"prompt\":\"%s\"}", prompt))
.build();
client.execute(request, new ServerSentEventListener() {
@Override
public void onEvent(ServerSentEvent event) {
accumulated.append(event.data());
}
@Override
public void onError(Throwable throwable) {
future.completeExceptionally(throwable);
}
@Override
public void onClose() {
future.complete(accumulated.toString());
}
});
return future;
}
// Usage
streamAndAccumulate("Hello")
.thenAccept(result -> System.out.println("Complete response: " + result))
.exceptionally(error -> {
System.err.println("Error: " + error.getMessage());
return null;
});import java.nio.file.Files;
import java.nio.file.Path;
public class FileUploadClient {
private final HttpClient client;
private final String uploadUrl;
public FileUploadClient(String uploadUrl) {
this.client = HttpClientFactory.createDefault();
this.uploadUrl = uploadUrl;
}
public String uploadFile(Path filePath) throws IOException {
byte[] fileContent = Files.readAllBytes(filePath);
String fileName = filePath.getFileName().toString();
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
HttpRequest request = HttpRequest.builder()
.method(HttpMethod.POST)
.url(uploadUrl)
.addFormDataFile("file", fileName, contentType, fileContent)
.build();
try {
SuccessfulHttpResponse response = client.execute(request);
return response.body();
} catch (HttpException e) {
throw new IOException("Upload failed: " + e.getMessage(), e);
}
}
public String uploadWithMetadata(Path filePath, Map<String, String> metadata) throws IOException {
byte[] fileContent = Files.readAllBytes(filePath);
String fileName = filePath.getFileName().toString();
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
HttpRequest.Builder builder = HttpRequest.builder()
.method(HttpMethod.POST)
.url(uploadUrl)
.addFormDataFile("file", fileName, contentType, fileContent);
for (Map.Entry<String, String> entry : metadata.entrySet()) {
builder.addFormDataField(entry.getKey(), entry.getValue());
}
try {
SuccessfulHttpResponse response = client.execute(builder.build());
return response.body();
} catch (HttpException e) {
throw new IOException("Upload failed: " + e.getMessage(), e);
}
}
}
// Usage
FileUploadClient uploader = new FileUploadClient("https://api.example.com/upload");
// Simple upload
String result = uploader.uploadFile(Path.of("/path/to/file.jpg"));
// Upload with metadata
Map<String, String> metadata = Map.of(
"title", "My Photo",
"description", "A beautiful sunset"
);
String result2 = uploader.uploadWithMetadata(Path.of("/path/to/file.jpg"), metadata);import java.util.Random;
public class RetryingHttpClient {
private final HttpClient client;
private final int maxRetries;
private final Random random;
public RetryingHttpClient(HttpClient client, int maxRetries) {
this.client = client;
this.maxRetries = maxRetries;
this.random = new Random();
}
public SuccessfulHttpResponse execute(HttpRequest request) {
int attempt = 0;
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;
}
sleep(attempt);
} catch (RuntimeException e) {
attempt++;
if (attempt >= maxRetries) {
throw e;
}
sleep(attempt);
}
}
throw new RuntimeException("Should not reach here");
}
private void sleep(int attempt) {
long baseDelay = 1000L * (1L << attempt); // 1s, 2s, 4s, 8s...
long jitter = random.nextInt(1000);
long delay = baseDelay + jitter;
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during retry", e);
}
}
}
// Usage
RetryingHttpClient retryClient = new RetryingHttpClient(
HttpClientFactory.createDefault(),
3 // max 3 retries
);
SuccessfulHttpResponse response = retryClient.execute(request);import dev.langchain4j.http.client.log.LoggingHttpClient;
public class HttpClientFactory {
public static HttpClient createWithLogging() {
HttpClient baseClient = HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
// Enable logging in development
String env = System.getenv("ENVIRONMENT");
if ("development".equals(env) || "dev".equals(env)) {
return new LoggingHttpClient(baseClient, true, true);
}
return baseClient;
}
public static HttpClient createWithCustomLogging(Logger logger) {
HttpClient baseClient = HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
return new LoggingHttpClient(baseClient, true, true, logger);
}
}import java.util.ArrayList;
import java.util.List;
public class PaginatedApiClient {
private final RestApiClient apiClient;
public PaginatedApiClient(String baseUrl, String apiKey) {
this.apiClient = new RestApiClient(baseUrl, apiKey);
}
public <T> List<T> getAllPages(String path, Class<T[]> arrayType) throws IOException {
List<T> allResults = new ArrayList<>();
int page = 1;
boolean hasMore = true;
while (hasMore) {
String pagedPath = path + "?page=" + page + "&limit=100";
T[] pageResults = apiClient.get(pagedPath, arrayType);
if (pageResults == null || pageResults.length == 0) {
hasMore = false;
} else {
allResults.addAll(Arrays.asList(pageResults));
page++;
}
}
return allResults;
}
}
// Usage
PaginatedApiClient client = new PaginatedApiClient("https://api.example.com", "api-key");
List<User> allUsers = client.getAllPages("/users", User[].class);import java.util.concurrent.ConcurrentHashMap;
public class HttpClientPool {
private static final ConcurrentHashMap<String, HttpClient> clients = new ConcurrentHashMap<>();
public static HttpClient getClient(String profile) {
return clients.computeIfAbsent(profile, HttpClientPool::createClient);
}
private static HttpClient createClient(String profile) {
HttpClientBuilder builder = HttpClientBuilderLoader.loadHttpClientBuilder();
switch (profile) {
case "fast":
return builder
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(10))
.build();
case "streaming":
return builder
.connectTimeout(Duration.ofSeconds(15))
.readTimeout(Duration.ofMinutes(5))
.build();
case "default":
default:
return builder
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
}
}
}
// Usage
HttpClient fastClient = HttpClientPool.getClient("fast");
HttpClient streamingClient = HttpClientPool.getClient("streaming");Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-http-client@1.11.0