Apache HttpClient

Mature HTTP client library for Java developed by Apache Software Foundation. Proven track record in enterprise environments with full HTTP/1.1 implementation, authentication, proxy support, SSL/TLS, and connection management. Enterprise-grade reliability and stability.

HTTP ClientJavaApacheEnterpriseFeature-RichSync/Async

GitHub Overview

apache/httpcomponents-client

Mirror of Apache HttpClient

Stars1,514
Watchers103
Forks982
Created:May 21, 2009
Language:Java
License:Apache License 2.0

Topics

httpcomponents

Star History

apache/httpcomponents-client Star History
Data as of: 10/22/2025, 04:10 AM

Library

Apache HttpClient

Overview

Apache HttpClient is developed as "a comprehensive HTTP client library for Java" - a long-established open-source project by the Apache Software Foundation. Designed for robust HTTP communication in enterprise environments, it provides extensive authentication mechanisms, proxy support, SSL/TLS processing, connection management, and caching functionality. With over 20 years of proven track record as a Java HTTP client library, it has been adopted by numerous enterprise systems and large-scale applications.

Details

Apache HttpClient 2025 edition has achieved further maturity as the HTTP communication foundation for enterprise systems, maintaining its important position in the Java ecosystem. Through complete HTTP/1.1 support, advanced SSL/TLS processing, diverse authentication protocols (Basic, Digest, NTLM, Kerberos), and detailed connection pool management, it ensures stability in mission-critical systems. With thread-safe design, extensive configuration options, and extensible architecture, it continues to evolve as a feature-rich HTTP client capable of handling complex enterprise requirements.

Key Features

  • Enterprise-Grade Functionality: Extensive authentication and security features required by enterprise systems
  • Connection Management: Advanced connection pooling and lifecycle management
  • Diverse Authentication: Wide range of authentication methods including Basic, Digest, NTLM, Kerberos
  • SSL/TLS Support: Detailed SSL configuration and client certificate support
  • Proxy Support: Complete support for HTTP, HTTPS, and SOCKS proxies
  • Thread Safety: Safe usage in concurrent processing environments

Pros and Cons

Pros

  • High reliability and stability with over 20 years of proven track record
  • Extensive authentication and security features required in enterprise environments
  • Flexible customization through detailed configuration options
  • Thread-safe design for secure concurrent processing
  • Continuous maintenance and support by Apache Foundation
  • Connection pool functionality for efficient management of large numbers of simultaneous connections

Cons

  • High learning cost and complexity for beginners
  • Verbose API design compared to modern Java libraries
  • Limited HTTP/2 support with slower adoption of latest specifications
  • Performance may be inferior to modern libraries like OkHttp in some cases
  • Limited asynchronous processing capabilities compared to other libraries
  • Difficulty in proper configuration due to excessive configuration options

Reference Pages

Code Examples

Installation and Basic Setup

<!-- Maven (pom.xml) -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3</version>
</dependency>

<!-- Logging functionality (recommended) -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5-fluent</artifactId>
    <version>5.3</version>
</dependency>

<!-- Caching functionality (optional) -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5-cache</artifactId>
    <version>5.3</version>
</dependency>
// Gradle (build.gradle)
dependencies {
    implementation 'org.apache.httpcomponents.client5:httpclient5:5.3'
    implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.3'
    implementation 'org.apache.httpcomponents.client5:httpclient5-cache:5.3' // Optional
}

Basic Requests (GET/POST/PUT/DELETE)

import org.apache.hc.client5.http.classic.methods.*;
import org.apache.hc.client5.http.impl.classic.*;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.entity.*;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;

public class ApacheHttpClientExample {
    
    // Basic GET request
    public void basicGetRequest() throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet request = new HttpGet("https://api.example.com/users");
            
            // Set headers
            request.setHeader("Accept", "application/json");
            request.setHeader("User-Agent", "MyApp/1.0 (Apache HttpClient)");

            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println("Status: " + response.getCode());
                System.out.println("Content-Type: " + response.getFirstHeader("Content-Type"));
                
                // Get response body
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    String responseBody = EntityUtils.toString(entity);
                    System.out.println("Response: " + responseBody);
                }
            }
        }
    }

    // POST request (sending JSON)
    public void postJsonRequest() throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost request = new HttpPost("https://api.example.com/users");
            
            // Set JSON data
            String jsonData = "{\"name\": \"John Doe\", \"email\": \"[email protected]\"}";
            StringEntity entity = new StringEntity(jsonData, ContentType.APPLICATION_JSON);
            request.setEntity(entity);
            
            // Set headers
            request.setHeader("Authorization", "Bearer your-jwt-token");
            request.setHeader("Accept", "application/json");

            try (CloseableHttpResponse response = httpClient.execute(request)) {
                if (response.getCode() >= 200 && response.getCode() < 300) {
                    String responseBody = EntityUtils.toString(response.getEntity());
                    System.out.println("Created user: " + responseBody);
                } else {
                    System.out.println("POST request failed: " + response.getCode());
                }
            }
        }
    }

    // PUT request (update)
    public void putRequest() throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPut request = new HttpPut("https://api.example.com/users/123");
            
            String jsonData = "{\"name\": \"Jane Doe\", \"email\": \"[email protected]\"}";
            StringEntity entity = new StringEntity(jsonData, ContentType.APPLICATION_JSON);
            request.setEntity(entity);
            
            request.setHeader("Authorization", "Bearer your-jwt-token");

            try (CloseableHttpResponse response = httpClient.execute(request)) {
                if (response.getCode() == 200) {
                    System.out.println("User updated successfully");
                }
            }
        }
    }

    // DELETE request
    public void deleteRequest() throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpDelete request = new HttpDelete("https://api.example.com/users/123");
            request.setHeader("Authorization", "Bearer your-jwt-token");

            try (CloseableHttpResponse response = httpClient.execute(request)) {
                if (response.getCode() == 204) {
                    System.out.println("User deleted successfully");
                }
            }
        }
    }

    // Form data submission
    public void submitFormData() throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost request = new HttpPost("https://api.example.com/login");
            
            // Set form parameters
            List<NameValuePair> parameters = new ArrayList<>();
            parameters.add(new BasicNameValuePair("username", "testuser"));
            parameters.add(new BasicNameValuePair("password", "secret123"));
            parameters.add(new BasicNameValuePair("remember", "true"));
            
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters);
            request.setEntity(entity);

            try (CloseableHttpResponse response = httpClient.execute(request)) {
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Login response: " + responseBody);
            }
        }
    }

    // Using Fluent API (concise syntax)
    public void fluentApiExample() throws Exception {
        import org.apache.hc.client5.http.fluent.Request;
        
        // Concise GET request
        String response = Request.get("https://api.example.com/users")
                .addHeader("Accept", "application/json")
                .execute()
                .returnContent()
                .asString();
        
        System.out.println("Fluent API response: " + response);
        
        // Concise POST request
        String postResponse = Request.post("https://api.example.com/users")
                .bodyString("{\"name\": \"John Doe\"}", ContentType.APPLICATION_JSON)
                .addHeader("Authorization", "Bearer token")
                .execute()
                .returnContent()
                .asString();
        
        System.out.println("Fluent POST: " + postResponse);
    }
}

Advanced Configuration and Customization (Headers, Authentication, Timeout, etc.)

import org.apache.hc.client5.http.auth.*;
import org.apache.hc.client5.http.config.*;
import org.apache.hc.client5.http.impl.auth.*;
import org.apache.hc.client5.http.impl.classic.*;
import org.apache.hc.client5.http.impl.io.*;
import org.apache.hc.client5.http.socket.*;
import org.apache.hc.client5.http.ssl.*;
import org.apache.hc.core5.http.config.*;
import org.apache.hc.core5.pool.*;
import org.apache.hc.core5.util.Timeout;

public class AdvancedHttpClientExample {
    
    // Create custom HttpClient instance with custom settings
    public CloseableHttpClient createCustomHttpClient() {
        // Connection pool configuration
        PoolingHttpClientConnectionManager connectionManager = 
                PoolingHttpClientConnectionManagerBuilder.create()
                .setMaxConnTotal(100)           // Maximum total connections
                .setMaxConnPerRoute(20)         // Maximum connections per route
                .setConnectionTimeToLive(TimeValue.ofMinutes(10)) // Connection time to live
                .build();

        // Timeout configuration
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(Timeout.ofSeconds(5))  // Connection request timeout
                .setConnectTimeout(Timeout.ofSeconds(10))           // Connection timeout
                .setResponseTimeout(Timeout.ofSeconds(30))          // Response timeout
                .setCookieSpec(StandardCookieSpec.STRICT)           // Cookie specification
                .setRedirectsEnabled(true)                          // Allow redirects
                .setMaxRedirects(5)                                 // Maximum redirects
                .build();

        // Build HTTP client
        return HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setRetryStrategy(DefaultHttpRequestRetryStrategy.INSTANCE)
                .setRedirectStrategy(DefaultRedirectStrategy.INSTANCE)
                .setUserAgent("MyApp/1.0 (Apache HttpClient 5.3)")
                .build();
    }

    // Basic authentication setup
    public void basicAuthenticationExample() throws Exception {
        // Store authentication credentials
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
                new AuthScope("api.example.com", 443),
                new UsernamePasswordCredentials("username", "password".toCharArray())
        );

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultCredentialsProvider(credentialsProvider)
                .build()) {
            
            HttpGet request = new HttpGet("https://api.example.com/protected");
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Authenticated response: " + responseBody);
            }
        }
    }

    // Digest authentication setup
    public void digestAuthenticationExample() throws Exception {
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
                AuthScope.ANY,
                new UsernamePasswordCredentials("username", "password".toCharArray())
        );

        // Digest authentication configuration
        AuthSchemeProvider digestSchemeProvider = new DigestSchemeProvider();
        Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
                .register(StandardAuthScheme.DIGEST, digestSchemeProvider)
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultCredentialsProvider(credentialsProvider)
                .setDefaultAuthSchemeRegistry(authSchemeRegistry)
                .build()) {
            
            HttpGet request = new HttpGet("https://api.example.com/digest-auth");
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println("Digest auth response: " + response.getCode());
            }
        }
    }

    // Proxy configuration
    public void proxyExample() throws Exception {
        // HTTP proxy configuration
        HttpHost proxy = new HttpHost("proxy.example.com", 8080, "http");
        
        // Proxy authentication (if required)
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
                new AuthScope(proxy),
                new UsernamePasswordCredentials("proxy-user", "proxy-pass".toCharArray())
        );

        RequestConfig requestConfig = RequestConfig.custom()
                .setProxy(proxy)
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultCredentialsProvider(credentialsProvider)
                .setDefaultRequestConfig(requestConfig)
                .build()) {
            
            HttpGet request = new HttpGet("https://api.example.com/data");
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response via proxy: " + responseBody);
            }
        }
    }

    // SSL/TLS configuration
    public void sslConfiguration() throws Exception {
        // Custom SSL context
        SSLContext sslContext = SSLContexts.custom()
                .loadTrustMaterial(null, (certificate, authType) -> true) // Trust all certificates (development only)
                .build();

        // SSL configuration
        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
                sslContext,
                new String[]{"TLSv1.2", "TLSv1.3"},
                null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier()
        );

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", sslSocketFactory)
                .register("http", new PlainConnectionSocketFactory())
                .build();

        PoolingHttpClientConnectionManager connectionManager = 
                PoolingHttpClientConnectionManagerBuilder.create()
                .setConnectionSocketFactoryRegistry(socketFactoryRegistry)
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build()) {
            
            HttpGet request = new HttpGet("https://secure-api.example.com/data");
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println("SSL communication success: " + response.getCode());
            }
        }
    }

    // Cookie handling
    public void cookieHandling() throws Exception {
        // Create cookie store
        CookieStore cookieStore = new BasicCookieStore();
        
        // Add custom cookie
        BasicClientCookie cookie = new BasicClientCookie("session_id", "abc123");
        cookie.setDomain("api.example.com");
        cookie.setPath("/");
        cookieStore.addCookie(cookie);

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultCookieStore(cookieStore)
                .build()) {
            
            // Cookies are automatically sent
            HttpGet request = new HttpGet("https://api.example.com/session-data");
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response with cookies: " + responseBody);
                
                // Check received cookies
                for (Cookie receivedCookie : cookieStore.getCookies()) {
                    System.out.println("Received cookie: " + receivedCookie.getName() + "=" + receivedCookie.getValue());
                }
            }
        }
    }
}

Error Handling and Retry Functionality

import org.apache.hc.client5.http.HttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.TimeValue;

public class ErrorHandlingExample {
    
    // Comprehensive error handling
    public String safeHttpRequest(String url) {
        try (CloseableHttpClient httpClient = createRobustHttpClient()) {
            HttpGet request = new HttpGet(url);
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                int statusCode = response.getCode();
                
                if (statusCode >= 200 && statusCode < 300) {
                    return EntityUtils.toString(response.getEntity());
                } else {
                    handleHttpError(response);
                    return null;
                }
                
            } catch (ConnectTimeoutException e) {
                System.err.println("Connection timeout: " + e.getMessage());
                System.err.println("Please check your network connection");
            } catch (SocketTimeoutException e) {
                System.err.println("Read timeout: " + e.getMessage());
                System.err.println("Server response is delayed");
            } catch (UnknownHostException e) {
                System.err.println("Host not found: " + e.getMessage());
                System.err.println("Please check URL or network connection");
            } catch (IOException e) {
                System.err.println("Network error: " + e.getMessage());
            }
            
        } catch (Exception e) {
            System.err.println("Unexpected error: " + e.getMessage());
            e.printStackTrace();
        }
        
        return null;
    }

    private void handleHttpError(CloseableHttpResponse response) throws IOException {
        int statusCode = response.getCode();
        String reasonPhrase = response.getReasonPhrase();
        String errorBody = EntityUtils.toString(response.getEntity());

        System.err.println("HTTP Error: " + statusCode + " " + reasonPhrase);
        
        switch (statusCode) {
            case 400:
                System.err.println("Bad Request: Please check parameters");
                break;
            case 401:
                System.err.println("Unauthorized: Please check authentication");
                break;
            case 403:
                System.err.println("Forbidden: Please check permissions");
                break;
            case 404:
                System.err.println("Not Found: Resource does not exist");
                break;
            case 429:
                String retryAfter = response.getFirstHeader("Retry-After").getValue();
                System.err.println("Rate Limited: " + (retryAfter != null ? "Retry after " + retryAfter + " seconds" : "Try again later"));
                break;
            case 500:
                System.err.println("Internal Server Error");
                break;
            case 502:
                System.err.println("Bad Gateway: Server temporarily unavailable");
                break;
            case 503:
                System.err.println("Service Unavailable: Server overloaded");
                break;
            default:
                System.err.println("HTTP Error details: " + errorBody);
        }
    }

    // Custom retry strategy
    public static class CustomRetryStrategy implements HttpRequestRetryStrategy {
        private final int maxRetries;
        private final TimeValue retryInterval;

        public CustomRetryStrategy(int maxRetries, TimeValue retryInterval) {
            this.maxRetries = maxRetries;
            this.retryInterval = retryInterval;
        }

        @Override
        public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) {
            if (execCount > maxRetries) {
                return false;
            }

            // Retry on specific exceptions
            if (exception instanceof ConnectTimeoutException ||
                exception instanceof SocketTimeoutException ||
                exception instanceof NoHttpResponseException) {
                
                System.out.println("Retrying (" + execCount + "/" + maxRetries + "): " + exception.getMessage());
                return true;
            }

            return false;
        }

        @Override
        public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) {
            if (execCount > maxRetries) {
                return false;
            }

            int statusCode = response.getCode();
            
            // Retry on specific status codes
            if (statusCode == 429 || // Too Many Requests
                statusCode == 500 || // Internal Server Error
                statusCode == 502 || // Bad Gateway
                statusCode == 503 || // Service Unavailable
                statusCode == 504) { // Gateway Timeout
                
                System.out.println("HTTP Status retry (" + execCount + "/" + maxRetries + "): " + statusCode);
                return true;
            }

            return false;
        }

        @Override
        public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) {
            // Check Retry-After header
            Header retryAfterHeader = response.getFirstHeader("Retry-After");
            if (retryAfterHeader != null) {
                try {
                    int retryAfterSeconds = Integer.parseInt(retryAfterHeader.getValue());
                    return TimeValue.ofSeconds(retryAfterSeconds);
                } catch (NumberFormatException e) {
                    // Use default value if parsing fails
                }
            }

            // Exponential backoff
            long delayMillis = retryInterval.toMilliseconds() * (long) Math.pow(2, execCount - 1);
            return TimeValue.ofMilliseconds(Math.min(delayMillis, 30000)); // Maximum 30 seconds
        }
    }

    // Create robust HttpClient
    private CloseableHttpClient createRobustHttpClient() {
        return HttpClients.custom()
                .setRetryStrategy(new CustomRetryStrategy(3, TimeValue.ofSeconds(1)))
                .setDefaultRequestConfig(RequestConfig.custom()
                        .setConnectTimeout(Timeout.ofSeconds(10))
                        .setResponseTimeout(Timeout.ofSeconds(30))
                        .build())
                .build();
    }

    // Asynchronous error handling
    public void asyncErrorHandling() throws Exception {
        CloseableHttpAsyncClient asyncClient = HttpAsyncClients.createDefault();
        asyncClient.start();

        try {
            HttpGet request = new HttpGet("https://api.example.com/data");
            
            Future<SimpleHttpResponse> future = asyncClient.execute(
                    SimpleRequestBuilder.copy(request).build(),
                    new FutureCallback<SimpleHttpResponse>() {
                        @Override
                        public void completed(SimpleHttpResponse response) {
                            System.out.println("Async request completed: " + response.getCode());
                        }

                        @Override
                        public void failed(Exception ex) {
                            System.err.println("Async request failed: " + ex.getMessage());
                        }

                        @Override
                        public void cancelled() {
                            System.out.println("Async request was cancelled");
                        }
                    }
            );

            // Wait for result (with timeout)
            SimpleHttpResponse response = future.get(30, TimeUnit.SECONDS);
            System.out.println("Async response: " + response.getBodyText());
            
        } finally {
            asyncClient.close();
        }
    }
}

Concurrent Processing and Asynchronous Requests

import org.apache.hc.client5.http.async.methods.*;
import org.apache.hc.client5.http.impl.async.*;
import org.apache.hc.core5.concurrent.FutureCallback;
import java.util.concurrent.*;
import java.util.stream.Collectors;

public class ConcurrentRequestsExample {
    
    // Parallel synchronous requests
    public List<String> fetchMultipleUrlsSync(List<String> urls) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // Parallel processing using ExecutorService
            ExecutorService executor = Executors.newFixedThreadPool(10);
            
            try {
                List<Future<String>> futures = urls.stream()
                        .map(url -> executor.submit(() -> {
                            try {
                                HttpGet request = new HttpGet(url);
                                try (CloseableHttpResponse response = httpClient.execute(request)) {
                                    if (response.getCode() == 200) {
                                        return EntityUtils.toString(response.getEntity());
                                    } else {
                                        return "Error: " + response.getCode();
                                    }
                                }
                            } catch (Exception e) {
                                return "Exception: " + e.getMessage();
                            }
                        }))
                        .collect(Collectors.toList());

                // Collect results
                return futures.stream()
                        .map(future -> {
                            try {
                                return future.get(30, TimeUnit.SECONDS);
                            } catch (Exception e) {
                                return "Timeout or error: " + e.getMessage();
                            }
                        })
                        .collect(Collectors.toList());
                        
            } finally {
                executor.shutdown();
            }
        } catch (IOException e) {
            throw new RuntimeException("HTTP client creation error", e);
        }
    }

    // Asynchronous parallel requests
    public CompletableFuture<List<String>> fetchMultipleUrlsAsync(List<String> urls) {
        CloseableHttpAsyncClient asyncClient = HttpAsyncClients.createDefault();
        asyncClient.start();

        List<CompletableFuture<String>> futures = urls.stream()
                .map(url -> {
                    CompletableFuture<String> future = new CompletableFuture<>();
                    
                    SimpleHttpRequest request = SimpleRequestBuilder.get(url).build();
                    
                    asyncClient.execute(request, new FutureCallback<SimpleHttpResponse>() {
                        @Override
                        public void completed(SimpleHttpResponse response) {
                            try {
                                if (response.getCode() == 200) {
                                    future.complete(response.getBodyText());
                                } else {
                                    future.complete("Error: " + response.getCode());
                                }
                            } catch (Exception e) {
                                future.complete("Response error: " + e.getMessage());
                            }
                        }

                        @Override
                        public void failed(Exception ex) {
                            future.complete("Failed: " + ex.getMessage());
                        }

                        @Override
                        public void cancelled() {
                            future.complete("Cancelled");
                        }
                    });
                    
                    return future;
                })
                .collect(Collectors.toList());

        // Wait for all async processes to complete
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                .thenApply(v -> futures.stream()
                        .map(CompletableFuture::join)
                        .collect(Collectors.toList()))
                .whenComplete((result, throwable) -> {
                    try {
                        asyncClient.close();
                    } catch (IOException e) {
                        System.err.println("Async client shutdown error: " + e.getMessage());
                    }
                });
    }

    // Pagination support
    public List<String> fetchAllPages(String baseUrl, int maxPages) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            ExecutorService executor = Executors.newFixedThreadPool(5);
            List<Future<String>> futures = new ArrayList<>();

            for (int page = 1; page <= maxPages; page++) {
                final int currentPage = page;
                Future<String> future = executor.submit(() -> {
                    try {
                        String url = baseUrl + "?page=" + currentPage + "&limit=20";
                        HttpGet request = new HttpGet(url);
                        
                        try (CloseableHttpResponse response = httpClient.execute(request)) {
                            if (response.getCode() == 200) {
                                String responseBody = EntityUtils.toString(response.getEntity());
                                System.out.println("Page " + currentPage + " completed");
                                return responseBody;
                            } else {
                                System.err.println("Page " + currentPage + " error: " + response.getCode());
                                return null;
                            }
                        }
                    } catch (Exception e) {
                        System.err.println("Page " + currentPage + " exception: " + e.getMessage());
                        return null;
                    }
                });
                futures.add(future);
            }

            List<String> results = new ArrayList<>();
            for (Future<String> future : futures) {
                try {
                    String result = future.get(60, TimeUnit.SECONDS);
                    if (result != null) {
                        results.add(result);
                    }
                } catch (TimeoutException e) {
                    System.err.println("Page fetch timeout");
                }
            }

            executor.shutdown();
            return results;
        }
    }

    // Streaming file download
    public void downloadLargeFile(String url, String filename) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet request = new HttpGet(url);
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                if (response.getCode() != 200) {
                    throw new IOException("Download failed: " + response.getCode());
                }

                HttpEntity entity = response.getEntity();
                long contentLength = entity.getContentLength();
                
                System.out.println("File size: " + contentLength + " bytes");

                try (InputStream inputStream = entity.getContent();
                     FileOutputStream outputStream = new FileOutputStream(filename);
                     BufferedOutputStream bufferedOut = new BufferedOutputStream(outputStream)) {
                    
                    byte[] buffer = new byte[8192];
                    long downloaded = 0;
                    int bytesRead;

                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        bufferedOut.write(buffer, 0, bytesRead);
                        downloaded += bytesRead;

                        if (contentLength > 0) {
                            int progress = (int) ((downloaded * 100) / contentLength);
                            System.out.print("\rDownload progress: " + progress + "%");
                        }
                    }
                    
                    System.out.println("\nDownload completed: " + filename);
                }
            }
        }
    }

    // Rate-limited parallel processing
    public void processWithRateLimit(List<String> urls, int requestsPerSecond) throws Exception {
        Semaphore rateLimiter = new Semaphore(requestsPerSecond);
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        
        // Replenish semaphore every second
        scheduler.scheduleAtFixedRate(() -> {
            rateLimiter.release(requestsPerSecond - rateLimiter.availablePermits());
        }, 1, 1, TimeUnit.SECONDS);

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            CountDownLatch latch = new CountDownLatch(urls.size());
            
            for (String url : urls) {
                new Thread(() -> {
                    try {
                        rateLimiter.acquire(); // Wait for rate limit
                        
                        HttpGet request = new HttpGet(url);
                        try (CloseableHttpResponse response = httpClient.execute(request)) {
                            System.out.println("Processed: " + url + " (" + response.getCode() + ")");
                        }
                    } catch (Exception e) {
                        System.err.println("Processing error: " + url + " - " + e.getMessage());
                    } finally {
                        latch.countDown();
                    }
                }).start();
            }
            
            // Wait for all processing to complete
            latch.await(300, TimeUnit.SECONDS);
        } finally {
            scheduler.shutdown();
        }
    }
}

Framework Integration and Practical Examples

// Spring Framework integration
@Service
public class HttpClientService {
    private final CloseableHttpClient httpClient;
    
    @Autowired
    public HttpClientService() {
        this.httpClient = HttpClients.custom()
                .setConnectionManager(createConnectionManager())
                .setDefaultRequestConfig(createRequestConfig())
                .build();
    }
    
    private PoolingHttpClientConnectionManager createConnectionManager() {
        return PoolingHttpClientConnectionManagerBuilder.create()
                .setMaxConnTotal(200)
                .setMaxConnPerRoute(50)
                .build();
    }
    
    private RequestConfig createRequestConfig() {
        return RequestConfig.custom()
                .setConnectTimeout(Timeout.ofSeconds(10))
                .setResponseTimeout(Timeout.ofSeconds(30))
                .build();
    }
    
    public String callExternalApi(String url, Map<String, String> headers) {
        try {
            HttpGet request = new HttpGet(url);
            
            // Set headers
            headers.forEach(request::setHeader);
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                if (response.getCode() == 200) {
                    return EntityUtils.toString(response.getEntity());
                } else {
                    throw new RuntimeException("API call error: " + response.getCode());
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("HTTP communication error", e);
        }
    }
    
    @PreDestroy
    public void cleanup() {
        try {
            httpClient.close();
        } catch (IOException e) {
            System.err.println("HTTP client shutdown error: " + e.getMessage());
        }
    }
}

// HTTP client with caching functionality
public class CachingHttpClientExample {
    private final CloseableHttpClient cachingClient;
    
    public CachingHttpClientExample() {
        // Cache configuration
        CacheConfig cacheConfig = CacheConfig.custom()
                .setMaxCacheEntries(1000)
                .setMaxObjectSize(8192)
                .setHeuristicCachingEnabled(true)
                .setHeuristicDefaultLifetime(TimeValue.ofMinutes(10))
                .build();
        
        this.cachingClient = CachingHttpClients.custom()
                .setCacheConfig(cacheConfig)
                .setHttpCacheStorage(new BasicHttpCacheStorage(cacheConfig))
                .build();
    }
    
    public String getCachedData(String url) throws Exception {
        HttpGet request = new HttpGet(url);
        request.setHeader("Cache-Control", "max-age=300"); // 5 minute cache
        
        try (CloseableHttpResponse response = cachingClient.execute(request)) {
            Header cacheStatus = response.getFirstHeader("X-Cache-Status");
            if (cacheStatus != null) {
                System.out.println("Cache status: " + cacheStatus.getValue());
            }
            
            return EntityUtils.toString(response.getEntity());
        }
    }
}

// Multipart file upload
public class FileUploadExample {
    
    public String uploadFile(File file, Map<String, String> metadata) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost uploadRequest = new HttpPost("https://api.example.com/upload");
            
            // Build multipart entity
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            
            // Add file
            builder.addBinaryBody("file", file, ContentType.DEFAULT_BINARY, file.getName());
            
            // Add metadata
            metadata.forEach((key, value) -> 
                builder.addTextBody(key, value, ContentType.TEXT_PLAIN));
            
            HttpEntity multipartEntity = builder.build();
            uploadRequest.setEntity(multipartEntity);
            
            // Authentication header
            uploadRequest.setHeader("Authorization", "Bearer your-token");
            
            try (CloseableHttpResponse response = httpClient.execute(uploadRequest)) {
                if (response.getCode() == 200) {
                    return EntityUtils.toString(response.getEntity());
                } else {
                    throw new IOException("Upload failed: " + response.getCode());
                }
            }
        }
    }
    
    // Upload with progress tracking
    public void uploadWithProgress(File file, ProgressCallback callback) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost request = new HttpPost("https://api.example.com/upload");
            
            // Entity with progress tracking functionality
            FileEntity fileEntity = new FileEntity(file, ContentType.DEFAULT_BINARY) {
                @Override
                public void writeTo(OutputStream outStream) throws IOException {
                    try (FileInputStream inStream = new FileInputStream(file);
                         ProgressOutputStream progressOut = new ProgressOutputStream(outStream, file.length(), callback)) {
                        
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = inStream.read(buffer)) != -1) {
                            progressOut.write(buffer, 0, bytesRead);
                        }
                    }
                }
            };
            
            request.setEntity(fileEntity);
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                if (response.getCode() == 200) {
                    callback.onComplete(EntityUtils.toString(response.getEntity()));
                } else {
                    callback.onError(new IOException("Upload failed: " + response.getCode()));
                }
            }
        }
    }
    
    public interface ProgressCallback {
        void onProgress(long bytesTransferred, long totalBytes);
        void onComplete(String response);
        void onError(Exception error);
    }
    
    private static class ProgressOutputStream extends OutputStream {
        private final OutputStream target;
        private final long totalBytes;
        private final ProgressCallback callback;
        private long bytesTransferred = 0;
        
        public ProgressOutputStream(OutputStream target, long totalBytes, ProgressCallback callback) {
            this.target = target;
            this.totalBytes = totalBytes;
            this.callback = callback;
        }
        
        @Override
        public void write(int b) throws IOException {
            target.write(b);
            bytesTransferred++;
            callback.onProgress(bytesTransferred, totalBytes);
        }
        
        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            target.write(b, off, len);
            bytesTransferred += len;
            callback.onProgress(bytesTransferred, totalBytes);
        }
    }
}