Java HttpClient

Standard HTTP client API provided from Java 11+. Modern and fluent API design with HTTP/2 support and both asynchronous/synchronous capabilities. Built into JDK with no external dependencies, provides WebSocket support and CompletableFuture-based async processing.

HTTP ClientJavaStandard LibraryHTTP/2AsynchronousSecure

GitHub Overview

openjdk/jdk

JDK main-line development https://openjdk.org/projects/jdk

Stars21,352
Watchers355
Forks6,084
Created:September 17, 2018
Language:Java
License:GNU General Public License v2.0

Topics

javajvmopenjdk

Star History

openjdk/jdk Star History
Data as of: 7/18/2025, 01:39 AM

Library

Java HttpClient

Overview

Java HttpClient is the standard HTTP client library in the "java.net.http" package officially introduced in Java 11. First appearing as an incubation feature in Java 9, it supports both HTTP/1.1 and HTTP/2, corresponding to synchronous and asynchronous programming models as a modern HTTP communication solution. As the official successor to the traditional HttpURLConnection, it features reactive stream support, builder pattern adoption, and complete SSL/TLS support, enabling production-quality HTTP communication without external dependencies.

Details

Java HttpClient 2025 edition provides maturity and stability as a modern HTTP implementation within the Java standard library. Complete support for the HTTP/2 protocol enables efficient multiplexed communication, comprehensively providing CompletableFuture-based asynchronous APIs, reactive stream body processing, and automatic redirect, authentication, and proxy support. Being included in the JDK standard eliminates the need for external dependencies, making adoption easy in enterprise environments. It is positioned as the recommended HTTP client in major frameworks such as Spring Boot and Quarkus.

Key Features

  • JDK Standard Inclusion: Available without external dependencies from Java 11 onwards
  • Complete HTTP/2 Support: Multiplexing, server push, and header compression support
  • Synchronous & Asynchronous Support: Flexible programming model through CompletableFuture
  • Reactive Streams: Efficient request and response body processing
  • Builder Pattern: Intuitive and type-safe API design
  • Comprehensive Security: Complete support for SSL/TLS, authentication, and certificate verification

Pros and Cons

Pros

  • JDK standard library with no external dependencies and long-term support guarantee
  • Performance improvement in high-performance parallel communication and resource efficiency through HTTP/2 support
  • Natural asynchronous programming experience through CompletableFuture
  • Type-safe and maintainable builder pattern API
  • Easy adoption in enterprise environments, passes security audits
  • Recommended client in major frameworks such as Spring Boot

Cons

  • Limited to Java 11 and later, unavailable in legacy environments
  • Limited high-level features compared to third-party libraries
  • Complex detailed settings such as HTTPS proxy configuration
  • Somewhat low-level streaming processing API
  • Basic debugging and logging output features
  • Requires custom implementation for retry, circuit breaker, etc.

Reference Pages

Code Examples

Basic Setup and Dependencies

// No additional dependencies required for Java 11 and later
// module-info.java (when using module system)
module myapp {
    requires java.net.http;
}
# Java environment verification
java -version  # Confirm Java 11 or later

# Project creation example (Maven case)
mvn archetype:generate -DgroupId=com.example.httpclient \
  -DartifactId=java-httpclient-demo \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DinteractiveMode=false

# Configure JDK 11 or later (pom.xml)
<!-- pom.xml -->
<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

Basic HTTP Requests (GET, POST, PUT, DELETE)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;

public class BasicHttpClientExample {
    
    public static void main(String[] args) throws Exception {
        // HttpClient creation
        HttpClient client = HttpClient.newHttpClient();
        
        // Basic GET request
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/users"))
                .GET()
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Status Code: " + response.statusCode());
        System.out.println("Headers: " + response.headers().map());
        System.out.println("Body: " + response.body());
    }
    
    // HttpClient creation with custom settings
    public static void customHttpClientExample() throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)  // Prefer HTTP/2
                .followRedirects(HttpClient.Redirect.NORMAL)
                .connectTimeout(Duration.ofSeconds(20))
                .build();
        
        // GET request with query parameters
        String url = "https://api.example.com/users?page=1&limit=10&sort=created_at";
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Accept", "application/json")
                .header("User-Agent", "MyApp/1.0 (Java HttpClient)")
                .timeout(Duration.ofMinutes(1))
                .GET()
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        if (response.statusCode() == 200) {
            System.out.println("Success: " + response.body());
        } else {
            System.out.println("Error: " + response.statusCode());
        }
    }
    
    // POST request (JSON sending)
    public static void postJsonExample() throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        
        String jsonBody = """
                {
                    "name": "John Doe",
                    "email": "[email protected]",
                    "age": 30
                }
                """;
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/users"))
                .header("Content-Type", "application/json")
                .header("Authorization", "Bearer your-token")
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        if (response.statusCode() == 201) {
            System.out.println("User creation completed: " + response.body());
        } else {
            System.out.println("Error: " + response.statusCode() + " - " + response.body());
        }
    }
    
    // PUT and DELETE requests
    public static void putDeleteExample() throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        
        // PUT request (data update)
        String updatedJsonBody = """
                {
                    "name": "Jane Doe",
                    "email": "[email protected]",
                    "age": 31
                }
                """;
        
        HttpRequest putRequest = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/users/123"))
                .header("Content-Type", "application/json")
                .header("Authorization", "Bearer your-token")
                .PUT(HttpRequest.BodyPublishers.ofString(updatedJsonBody))
                .build();
        
        HttpResponse<String> putResponse = client.send(putRequest, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Update status: " + putResponse.statusCode());
        
        // DELETE request
        HttpRequest deleteRequest = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/users/123"))
                .header("Authorization", "Bearer your-token")
                .DELETE()
                .build();
        
        HttpResponse<String> deleteResponse = client.send(deleteRequest, 
                HttpResponse.BodyHandlers.ofString());
        
        if (deleteResponse.statusCode() == 204) {
            System.out.println("User deletion completed");
        }
    }
}

Asynchronous Requests and CompletableFuture

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.List;
import java.util.stream.Collectors;

public class AsyncHttpClientExample {
    
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        
        // Single asynchronous request
        asyncSingleRequest(client);
        
        // Multiple parallel asynchronous requests
        asyncMultipleRequests(client);
        
        // Asynchronous chained processing
        asyncChainedRequests(client);
    }
    
    // Single asynchronous request
    public static void asyncSingleRequest(HttpClient client) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/users"))
                .header("Accept", "application/json")
                .GET()
                .build();
        
        CompletableFuture<HttpResponse<String>> futureResponse = 
                client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
        
        // Wait for asynchronous processing result
        HttpResponse<String> response = futureResponse.get();
        System.out.println("Async response: " + response.statusCode());
        
        // Or process with callback
        futureResponse.thenAccept(resp -> {
            System.out.println("Callback processing: " + resp.body());
        }).join();
    }
    
    // Multiple parallel asynchronous requests
    public static void asyncMultipleRequests(HttpClient client) throws Exception {
        List<String> urls = List.of(
                "https://api.example.com/users",
                "https://api.example.com/posts",
                "https://api.example.com/comments",
                "https://api.example.com/categories"
        );
        
        // Start parallel requests
        List<CompletableFuture<HttpResponse<String>>> futures = urls.stream()
                .map(url -> HttpRequest.newBuilder()
                        .uri(URI.create(url))
                        .header("Accept", "application/json")
                        .GET()
                        .build())
                .map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
                .collect(Collectors.toList());
        
        // Wait for all completions
        CompletableFuture<List<HttpResponse<String>>> allFutures = 
                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                        .thenApply(v -> futures.stream()
                                .map(CompletableFuture::join)
                                .collect(Collectors.toList()));
        
        List<HttpResponse<String>> responses = allFutures.get();
        
        for (int i = 0; i < responses.size(); i++) {
            HttpResponse<String> response = responses.get(i);
            System.out.println("URL " + urls.get(i) + " - Status: " + response.statusCode());
        }
    }
    
    // Asynchronous chained processing
    public static void asyncChainedRequests(HttpClient client) throws Exception {
        // Initial request
        HttpRequest initialRequest = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/auth/login"))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString("""
                        {"username": "user", "password": "pass"}
                        """))
                .build();
        
        // Chain processing: login → token acquisition → access protected resource
        CompletableFuture<String> result = client
                .sendAsync(initialRequest, HttpResponse.BodyHandlers.ofString())
                .thenCompose(loginResponse -> {
                    if (loginResponse.statusCode() == 200) {
                        // Login successful, extract token (JSON parsing required in practice)
                        String token = extractTokenFromResponse(loginResponse.body());
                        
                        // Access protected resource using token
                        HttpRequest protectedRequest = HttpRequest.newBuilder()
                                .uri(URI.create("https://api.example.com/protected-resource"))
                                .header("Authorization", "Bearer " + token)
                                .GET()
                                .build();
                        
                        return client.sendAsync(protectedRequest, HttpResponse.BodyHandlers.ofString());
                    } else {
                        // Login failure handling
                        return CompletableFuture.failedFuture(
                                new RuntimeException("Login failed: " + loginResponse.statusCode())
                        );
                    }
                })
                .thenApply(protectedResponse -> {
                    if (protectedResponse.statusCode() == 200) {
                        return protectedResponse.body();
                    } else {
                        throw new RuntimeException("Protected resource access failed: " + 
                                protectedResponse.statusCode());
                    }
                })
                .exceptionally(throwable -> {
                    System.err.println("Chain processing error: " + throwable.getMessage());
                    return "Error occurred";
                });
        
        String finalResult = result.get();
        System.out.println("Chain processing result: " + finalResult);
    }
    
    private static String extractTokenFromResponse(String body) {
        // In practice, parse using JSON library
        // Hard-coded here for sample purposes
        return "sample-jwt-token";
    }
}

Authentication, Security, and Custom Configuration

import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Base64;
import javax.net.ssl.SSLContext;

public class SecurityHttpClientExample {
    
    public static void main(String[] args) throws Exception {
        // Basic authentication example
        basicAuthenticationExample();
        
        // Bearer Token authentication
        bearerTokenExample();
        
        // Proxy configuration
        proxyConfigurationExample();
        
        // SSL/TLS configuration
        sslConfigurationExample();
        
        // Custom authentication
        customAuthenticationExample();
    }
    
    // Basic authentication
    public static void basicAuthenticationExample() throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .authenticator(new Authenticator() {
                    @Override
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication("username", "password".toCharArray());
                    }
                })
                .build();
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/private"))
                .GET()
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Basic authentication status: " + response.statusCode());
        
        // Or manually set Basic authentication header
        String auth = "username:password";
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
        
        HttpRequest manualBasicRequest = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/private"))
                .header("Authorization", "Basic " + encodedAuth)
                .GET()
                .build();
        
        HttpResponse<String> manualResponse = client.send(manualBasicRequest, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Manual Basic authentication status: " + manualResponse.statusCode());
    }
    
    // Bearer Token authentication
    public static void bearerTokenExample() throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        
        String jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // Actual JWT token
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/protected"))
                .header("Authorization", "Bearer " + jwtToken)
                .header("Accept", "application/json")
                .header("User-Agent", "MyApp/1.0 (Java HttpClient)")
                .GET()
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Bearer authentication status: " + response.statusCode());
        
        // API key authentication example
        HttpRequest apiKeyRequest = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/data"))
                .header("X-API-Key", "your-api-key-here")
                .header("Accept", "application/json")
                .GET()
                .build();
        
        HttpResponse<String> apiKeyResponse = client.send(apiKeyRequest, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("API key authentication status: " + apiKeyResponse.statusCode());
    }
    
    // Proxy configuration
    public static void proxyConfigurationExample() throws Exception {
        // HTTP proxy configuration
        HttpClient client = HttpClient.newBuilder()
                .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
                .build();
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/data"))
                .GET()
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Proxy via status: " + response.statusCode());
        
        // Authenticated proxy (combined with Authenticator)
        HttpClient authProxyClient = HttpClient.newBuilder()
                .proxy(ProxySelector.of(new InetSocketAddress("authenticated-proxy.example.com", 8080)))
                .authenticator(new Authenticator() {
                    @Override
                    protected PasswordAuthentication getPasswordAuthentication() {
                        if (getRequestorType() == RequestorType.PROXY) {
                            return new PasswordAuthentication("proxyuser", "proxypass".toCharArray());
                        }
                        return null;
                    }
                })
                .build();
    }
    
    // SSL/TLS configuration
    public static void sslConfigurationExample() throws Exception {
        // Use default SSLContext
        HttpClient defaultSslClient = HttpClient.newBuilder()
                .sslContext(SSLContext.getDefault())
                .build();
        
        // Custom SSLContext (example: specify specific TLS version)
        try {
            SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
            sslContext.init(null, null, null);
            
            HttpClient customSslClient = HttpClient.newBuilder()
                    .sslContext(sslContext)
                    .build();
            
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://secure-api.example.com/data"))
                    .header("Accept", "application/json")
                    .GET()
                    .build();
            
            HttpResponse<String> response = customSslClient.send(request, 
                    HttpResponse.BodyHandlers.ofString());
            
            System.out.println("Custom SSL status: " + response.statusCode());
            
        } catch (NoSuchAlgorithmException e) {
            System.err.println("TLSv1.3 not available: " + e.getMessage());
        }
    }
    
    // Custom authentication and headers
    public static void customAuthenticationExample() throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(30))
                .build();
        
        // Custom headers and timeout configuration
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://secure-api.example.com/data"))
                .header("Accept", "application/json")
                .header("Accept-Language", "en-US,ja-JP")
                .header("X-API-Version", "v2")
                .header("X-Request-ID", java.util.UUID.randomUUID().toString())
                .header("Authorization", "Bearer your-jwt-token")
                .header("User-Agent", "MyApp/1.0 (Java " + System.getProperty("java.version") + ")")
                .timeout(Duration.ofSeconds(60))
                .GET()
                .build();
        
        try {
            HttpResponse<String> response = client.send(request, 
                    HttpResponse.BodyHandlers.ofString());
            
            System.out.println("Custom authentication status: " + response.statusCode());
            System.out.println("Response headers: " + response.headers().map());
            
            // Cookie handling example
            response.headers().allValues("Set-Cookie").forEach(cookie -> {
                System.out.println("Received cookie: " + cookie);
            });
            
        } catch (Exception e) {
            System.err.println("Request error: " + e.getMessage());
        }
    }
}

Error Handling and Retry Functionality

import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpConnectTimeoutException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;

public class ErrorHandlingHttpClientExample {
    
    private static final HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .followRedirects(HttpClient.Redirect.NORMAL)
            .build();
    
    public static void main(String[] args) {
        // Basic error handling
        basicErrorHandling();
        
        // Request with retry functionality
        retryableRequest();
        
        // Asynchronous error handling
        asyncErrorHandling();
        
        // Circuit breaker pattern
        circuitBreakerExample();
    }
    
    // Basic error handling
    public static void basicErrorHandling() {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/data"))
                .timeout(Duration.ofSeconds(30))
                .GET()
                .build();
        
        try {
            HttpResponse<String> response = client.send(request, 
                    HttpResponse.BodyHandlers.ofString());
            
            // Status code specific processing
            switch (response.statusCode()) {
                case 200:
                    System.out.println("Success: " + response.body());
                    break;
                case 401:
                    System.out.println("Authentication error: Please check your token");
                    break;
                case 403:
                    System.out.println("Permission error: Access denied");
                    break;
                case 404:
                    System.out.println("Not found: Resource does not exist");
                    break;
                case 429:
                    System.out.println("Rate limit: Please wait before retrying");
                    break;
                case 500:
                case 502:
                case 503:
                case 504:
                    System.out.println("Server error: " + response.statusCode());
                    break;
                default:
                    System.out.println("Unexpected status: " + response.statusCode());
            }
            
        } catch (HttpConnectTimeoutException e) {
            System.err.println("Connection timeout: " + e.getMessage());
        } catch (HttpTimeoutException e) {
            System.err.println("Request timeout: " + e.getMessage());
        } catch (ConnectException e) {
            System.err.println("Connection error: Cannot connect to server - " + e.getMessage());
        } catch (SocketTimeoutException e) {
            System.err.println("Socket timeout: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("IO error: " + e.getMessage());
        } catch (InterruptedException e) {
            System.err.println("Processing was interrupted: " + e.getMessage());
            Thread.currentThread().interrupt(); // Restore interrupt status
        } catch (Exception e) {
            System.err.println("Unexpected error: " + e.getMessage());
        }
    }
    
    // Request with retry functionality
    public static void retryableRequest() {
        String url = "https://api.example.com/unstable-endpoint";
        int maxRetries = 3;
        Duration baseDelay = Duration.ofMillis(500);
        
        for (int attempt = 0; attempt <= maxRetries; attempt++) {
            try {
                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) {
                    System.out.println("Retry success (attempt " + (attempt + 1) + "): " + response.body());
                    return; // Exit on success
                } else if (isRetryableStatus(response.statusCode()) && attempt < maxRetries) {
                    System.out.println("Attempt " + (attempt + 1) + " failed (status: " + 
                            response.statusCode() + "). Retrying...");
                } else {
                    System.err.println("Finally failed (status: " + response.statusCode() + ")");
                    return;
                }
                
            } catch (Exception e) {
                if (attempt < maxRetries && isRetryableException(e)) {
                    System.out.println("Exception in attempt " + (attempt + 1) + ": " + e.getMessage() + ". Retrying...");
                } else {
                    System.err.println("Finally failed: " + e.getMessage());
                    return;
                }
            }
            
            // Wait with exponential backoff
            if (attempt < maxRetries) {
                try {
                    Duration delay = baseDelay.multipliedBy((long) Math.pow(2, attempt));
                    // Add jitter to distribute server load
                    long jitter = ThreadLocalRandom.current().nextLong(0, delay.toMillis() / 4);
                    Thread.sleep(delay.toMillis() + jitter);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    System.err.println("Interrupted during retry wait");
                    return;
                }
            }
        }
    }
    
    private static boolean isRetryableStatus(int statusCode) {
        return statusCode == 429 || // Too Many Requests
               statusCode == 500 || // Internal Server Error
               statusCode == 502 || // Bad Gateway
               statusCode == 503 || // Service Unavailable
               statusCode == 504;   // Gateway Timeout
    }
    
    private static boolean isRetryableException(Exception e) {
        return e instanceof HttpTimeoutException ||
               e instanceof HttpConnectTimeoutException ||
               e instanceof ConnectException ||
               e instanceof SocketTimeoutException;
    }
    
    // Asynchronous error handling
    public static void asyncErrorHandling() {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/async-data"))
                .timeout(Duration.ofSeconds(30))
                .GET()
                .build();
        
        CompletableFuture<String> result = client
                .sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(response -> {
                    if (response.statusCode() == 200) {
                        return response.body();
                    } else {
                        throw new RuntimeException("HTTP Error: " + response.statusCode());
                    }
                })
                .exceptionally(throwable -> {
                    if (throwable.getCause() instanceof HttpTimeoutException) {
                        System.err.println("Async request timeout");
                        return "TIMEOUT_ERROR";
                    } else if (throwable.getCause() instanceof ConnectException) {
                        System.err.println("Async connection error");
                        return "CONNECTION_ERROR";
                    } else {
                        System.err.println("Async processing error: " + throwable.getMessage());
                        return "UNKNOWN_ERROR";
                    }
                })
                .orTimeout(Duration.ofSeconds(45)); // Overall timeout
        
        try {
            String responseBody = result.get();
            System.out.println("Async result: " + responseBody);
        } catch (Exception e) {
            System.err.println("Async processing exception: " + e.getMessage());
        }
    }
    
    // Simple circuit breaker pattern
    static class SimpleCircuitBreaker {
        private int failureCount = 0;
        private long lastFailureTime = 0;
        private final int threshold;
        private final Duration timeout;
        private boolean isOpen = false;
        
        public SimpleCircuitBreaker(int threshold, Duration timeout) {
            this.threshold = threshold;
            this.timeout = timeout;
        }
        
        public boolean canExecute() {
            if (!isOpen) {
                return true;
            }
            
            // Try reset after timeout
            if (System.currentTimeMillis() - lastFailureTime > timeout.toMillis()) {
                isOpen = false;
                failureCount = 0;
                return true;
            }
            
            return false;
        }
        
        public void recordSuccess() {
            failureCount = 0;
            isOpen = false;
        }
        
        public void recordFailure() {
            failureCount++;
            lastFailureTime = System.currentTimeMillis();
            
            if (failureCount >= threshold) {
                isOpen = true;
            }
        }
        
        public boolean isOpen() {
            return isOpen;
        }
    }
    
    public static void circuitBreakerExample() {
        SimpleCircuitBreaker circuitBreaker = new SimpleCircuitBreaker(3, Duration.ofMinutes(1));
        
        for (int i = 0; i < 10; i++) {
            if (!circuitBreaker.canExecute()) {
                System.out.println("Circuit breaker is open. Skipping request.");
                continue;
            }
            
            try {
                HttpRequest request = HttpRequest.newBuilder()
                        .uri(URI.create("https://api.example.com/unreliable"))
                        .timeout(Duration.ofSeconds(5))
                        .GET()
                        .build();
                
                HttpResponse<String> response = client.send(request, 
                        HttpResponse.BodyHandlers.ofString());
                
                if (response.statusCode() == 200) {
                    circuitBreaker.recordSuccess();
                    System.out.println("Success: " + response.body());
                } else {
                    circuitBreaker.recordFailure();
                    System.out.println("Failed (status: " + response.statusCode() + ")");
                }
                
            } catch (Exception e) {
                circuitBreaker.recordFailure();
                System.err.println("Exception occurred: " + e.getMessage());
            }
            
            // Wait a bit before next request
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

File Upload, Download, and Streaming

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Flow;

public class FileStreamingHttpClientExample {
    
    private static final HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(30))
            .build();
    
    public static void main(String[] args) throws Exception {
        // File upload
        fileUploadExample();
        
        // File download
        fileDownloadExample();
        
        // Streaming upload
        streamingUploadExample();
        
        // Streaming download
        streamingDownloadExample();
    }
    
    // File upload
    public static void fileUploadExample() throws Exception {
        Path filePath = Path.of("upload-file.txt");
        
        // Create file for upload
        Files.writeString(filePath, "This is a test file for upload.\nMultiple lines content...");
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/post"))
                .header("Content-Type", "text/plain; charset=UTF-8")
                .header("Authorization", "Bearer your-token")
                .POST(HttpRequest.BodyPublishers.ofFile(filePath))
                .timeout(Duration.ofMinutes(5))
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("File upload status: " + response.statusCode());
        System.out.println("Response: " + response.body());
        
        // Multipart file upload
        multipartFileUpload();
    }
    
    // Multipart file upload
    public static void multipartFileUpload() throws Exception {
        Path filePath = Path.of("multipart-file.txt");
        Files.writeString(filePath, "Multipart upload test file");
        
        String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
        
        String multipartBody = "--" + boundary + "\r\n" +
                "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n" +
                "Content-Type: text/plain\r\n\r\n" +
                Files.readString(filePath) + "\r\n" +
                "--" + boundary + "\r\n" +
                "Content-Disposition: form-data; name=\"description\"\r\n\r\n" +
                "Test file description\r\n" +
                "--" + boundary + "--\r\n";
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/post"))
                .header("Content-Type", "multipart/form-data; boundary=" + boundary)
                .POST(HttpRequest.BodyPublishers.ofString(multipartBody))
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Multipart upload status: " + response.statusCode());
    }
    
    // File download
    public static void fileDownloadExample() throws Exception {
        Path downloadPath = Path.of("downloaded-file.txt");
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/bytes/1024"))
                .header("Accept", "*/*")
                .GET()
                .build();
        
        // Download directly to file
        HttpResponse<Path> response = client.send(request, 
                HttpResponse.BodyHandlers.ofFile(downloadPath));
        
        System.out.println("Download status: " + response.statusCode());
        System.out.println("File saved to: " + response.body());
        System.out.println("File size: " + Files.size(downloadPath) + " bytes");
        
        // Asynchronous download
        asyncFileDownload();
    }
    
    // Asynchronous file download
    public static void asyncFileDownload() throws Exception {
        Path asyncDownloadPath = Path.of("async-downloaded-file.dat");
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/bytes/2048"))
                .GET()
                .build();
        
        CompletableFuture<Path> downloadFuture = client
                .sendAsync(request, HttpResponse.BodyHandlers.ofFile(asyncDownloadPath))
                .thenApply(HttpResponse::body);
        
        // Wait for download completion
        Path downloadedFile = downloadFuture.get();
        System.out.println("Async download completed: " + downloadedFile);
        System.out.println("File size: " + Files.size(downloadedFile) + " bytes");
    }
    
    // Streaming upload
    public static void streamingUploadExample() throws Exception {
        // Custom BodyPublisher for streaming
        HttpRequest.BodyPublisher streamingPublisher = new HttpRequest.BodyPublisher() {
            @Override
            public long contentLength() {
                return -1; // Unknown size
            }
            
            @Override
            public void subscribe(Flow.Subscriber<? super java.nio.ByteBuffer> subscriber) {
                // Streaming data transmission logic
                subscriber.onSubscribe(new Flow.Subscription() {
                    private boolean cancelled = false;
                    private int sentChunks = 0;
                    
                    @Override
                    public void request(long n) {
                        if (cancelled) return;
                        
                        try {
                            for (int i = 0; i < n && sentChunks < 5; i++, sentChunks++) {
                                String chunk = "Streaming chunk " + sentChunks + "\n";
                                subscriber.onNext(java.nio.ByteBuffer.wrap(chunk.getBytes()));
                            }
                            
                            if (sentChunks >= 5) {
                                subscriber.onComplete();
                            }
                        } catch (Exception e) {
                            subscriber.onError(e);
                        }
                    }
                    
                    @Override
                    public void cancel() {
                        cancelled = true;
                    }
                });
            }
        };
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/post"))
                .header("Content-Type", "text/plain")
                .header("Transfer-Encoding", "chunked")
                .POST(streamingPublisher)
                .build();
        
        HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Streaming upload status: " + response.statusCode());
    }
    
    // Streaming download
    public static void streamingDownloadExample() throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/stream/5"))
                .GET()
                .build();
        
        // Custom BodyHandler for streaming processing
        HttpResponse<Void> response = client.send(request, 
                HttpResponse.BodyHandlers.fromSubscriber(new Flow.Subscriber<java.util.List<java.nio.ByteBuffer>>() {
                    private Flow.Subscription subscription;
                    private int receivedChunks = 0;
                    
                    @Override
                    public void onSubscribe(Flow.Subscription subscription) {
                        this.subscription = subscription;
                        subscription.request(1); // Request first chunk
                    }
                    
                    @Override
                    public void onNext(java.util.List<java.nio.ByteBuffer> buffers) {
                        receivedChunks++;
                        int totalBytes = buffers.stream().mapToInt(java.nio.ByteBuffer::remaining).sum();
                        
                        System.out.println("Chunk " + receivedChunks + " received: " + totalBytes + " bytes");
                        
                        // Data processing (output content in this example)
                        buffers.forEach(buffer -> {
                            byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            System.out.print(new String(bytes));
                        });
                        
                        subscription.request(1); // Request next chunk
                    }
                    
                    @Override
                    public void onError(Throwable throwable) {
                        System.err.println("Streaming error: " + throwable.getMessage());
                    }
                    
                    @Override
                    public void onComplete() {
                        System.out.println("\nStreaming completed (total chunks: " + receivedChunks + ")");
                    }
                }));
        
        System.out.println("Streaming download status: " + response.statusCode());
    }
    
    // Download with progress display
    public static void downloadWithProgress() throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/bytes/10240")) // 10KB
                .GET()
                .build();
        
        HttpResponse<InputStream> response = client.send(request, 
                HttpResponse.BodyHandlers.ofInputStream());
        
        if (response.statusCode() == 200) {
            Path outputPath = Path.of("progress-download.dat");
            
            try (InputStream inputStream = response.body()) {
                long totalSize = response.headers().firstValueAsLong("Content-Length").orElse(-1L);
                long downloadedBytes = 0;
                byte[] buffer = new byte[1024];
                int bytesRead;
                
                Files.deleteIfExists(outputPath);
                
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    Files.write(outputPath, buffer, 0, bytesRead, 
                            StandardOpenOption.CREATE, StandardOpenOption.APPEND);
                    downloadedBytes += bytesRead;
                    
                    if (totalSize > 0) {
                        double progress = (double) downloadedBytes / totalSize * 100;
                        System.out.printf("\rDownload progress: %.1f%% (%d/%d bytes)", 
                                progress, downloadedBytes, totalSize);
                    } else {
                        System.out.printf("\rDownload: %d bytes", downloadedBytes);
                    }
                }
                
                System.out.println("\nDownload completed: " + outputPath);
            }
        }
    }
}