CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-http-client

HTTP client abstraction for LangChain4j with synchronous/asynchronous execution and Server-Sent Events (SSE) streaming support

Overview
Eval results
Files

common-patterns.mddocs/examples/

Common Patterns and Examples

This document provides complete, runnable examples for common HTTP client usage patterns.

Client Configuration

Default Configuration

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();
    }
}

Multiple Client Types

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();
    }
}

REST API Client

Complete REST Client

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");

Server-Sent Events (SSE) Streaming

Basic SSE Client

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())
);

SSE with Accumulation

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;
    });

File Upload

Simple File Upload

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);

Retry Logic

Retry with Exponential Backoff

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);

Logging

Logging Client

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);
    }
}

Pagination

Paginated API Client

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);

Pooled Client Management

Client Pool

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");

Related Documentation

  • Advanced Patterns: Advanced Examples
  • API Reference: Core APIs
  • Guides: Synchronous Requests | SSE Streaming

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-http-client

docs

index.md

installation.md

quick-start.md

tile.json