- Use the modern HttpClient API (Java 11+)
- Make synchronous and asynchronous HTTP requests
- Send different HTTP methods with body data
- Handle responses and headers properly
HTTP Client API
Java 11 introduced a modern HTTP Client API (java.net.http) replacing HttpURLConnection. Supports HTTP/1.1 and HTTP/2, sync and async, cleaner API. Recommended for new HTTP client code.
Key Classes
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
// HttpClient - sends requests, receives responses
// HttpRequest - represents an HTTP request
// HttpResponse - represents an HTTP response
Creating an HttpClient
// Simple client with defaults
HttpClient client = HttpClient.newHttpClient();
// Customized client
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // Prefer HTTP/2
.followRedirects(HttpClient.Redirect.NORMAL) // Follow redirects
.connectTimeout(Duration.ofSeconds(10)) // Connection timeout
.build();
Making GET Requests
Synchronous GET
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Accept", "application/json")
.GET() // Default, can be omitted
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
Asynchronous GET
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join(); // Wait for completion
With CompletableFuture
CompletableFuture<HttpResponse<String>> future =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
future.thenAccept(response -> {
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}).exceptionally(e -> {
System.err.println("Error: " + e.getMessage());
return null;
});
Making POST Requests
POST with JSON Body
String json = "{\"name\": \"John\", \"email\": \"john@example.com\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
if (response.statusCode() == 201) {
System.out.println("Created: " + response.body());
}
POST with Form Data
String formData = "username=john&password=secret";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/login"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(formData))
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
POST with File
Path filePath = Path.of("data.txt");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/upload"))
.header("Content-Type", "text/plain")
.POST(HttpRequest.BodyPublishers.ofFile(filePath))
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
Other HTTP Methods
// PUT
HttpRequest putRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/1"))
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(json))
.build();
// DELETE
HttpRequest deleteRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/1"))
.DELETE()
.build();
// Custom method
HttpRequest patchRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/1"))
.method("PATCH", HttpRequest.BodyPublishers.ofString(json))
.build();
Response Body Handlers
// As String
HttpResponse<String> stringResponse = client.send(
request, HttpResponse.BodyHandlers.ofString());
// As byte array
HttpResponse<byte[]> bytesResponse = client.send(
request, HttpResponse.BodyHandlers.ofByteArray());
// Save to file
HttpResponse<Path> fileResponse = client.send(
request, HttpResponse.BodyHandlers.ofFile(Path.of("output.txt")));
// As stream of lines
HttpResponse<Stream<String>> streamResponse = client.send(
request, HttpResponse.BodyHandlers.ofLines());
streamResponse.body().forEach(System.out::println);
// Discard body
HttpResponse<Void> discardResponse = client.send(
request, HttpResponse.BodyHandlers.discarding());
Working with Headers
Request Headers
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Accept", "application/json")
.header("Authorization", "Bearer token123")
.header("User-Agent", "Java HttpClient")
.headers("X-Custom-1", "value1", "X-Custom-2", "value2")
.build();
Response Headers
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
// Get all headers
HttpHeaders headers = response.headers();
// Get specific header
Optional<String> contentType = headers.firstValue("Content-Type");
List<String> cookies = headers.allValues("Set-Cookie");
// Print all headers
headers.map().forEach((name, values) -> {
System.out.println(name + ": " + values);
});
Timeouts
// Client-level timeout
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
// Request-level timeout
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/slow"))
.timeout(Duration.ofSeconds(30))
.build();
try {
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
} catch (HttpTimeoutException e) {
System.err.println("Request timed out!");
}
Handling Redirects
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS) // Follow all redirects
.build();
// Redirect policies:
// NEVER - never follow redirects
// ALWAYS - always follow redirects
// NORMAL - follow redirects except HTTPS to HTTP
Concurrent Requests
HttpClient client = HttpClient.newHttpClient();
List<URI> uris = List.of(
URI.create("https://api.example.com/users/1"),
URI.create("https://api.example.com/users/2"),
URI.create("https://api.example.com/users/3")
);
List<CompletableFuture<String>> futures = uris.stream()
.map(uri -> HttpRequest.newBuilder(uri).build())
.map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
.map(future -> future.thenApply(HttpResponse::body))
.collect(Collectors.toList());
// Wait for all and collect results
List<String> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
Authentication
Basic Authentication
HttpClient client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"username",
"password".toCharArray()
);
}
})
.build();
Bearer Token
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/protected"))
.header("Authorization", "Bearer " + token)
.build();
Complete Example
public class HttpClientExample {
private final HttpClient client;
public HttpClientExample() {
this.client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
}
public String get(String url) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(30))
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException("HTTP error: " + response.statusCode());
}
return response.body();
}
public String post(String url, String json) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
public CompletableFuture<String> getAsync(String url) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
}
HttpClient sends requests, HttpRequest builds them, HttpResponse accesses data. BodyPublishers create request bodies, BodyHandlers process responses. Use HttpClient.newBuilder() for configured clients. send() for sync, sendAsync() for async. Set timeouts at client or request level. Reuse HttpClient instances—they're thread-safe.
