17.2 Requests and Responses

Master building and handling HTTP requests and responses.

Building Requests

HttpRequest Builder:

import java.net.http.*;
import java.net.URI;
import java.nio.file.Path;
import java.time.Duration;

/**
 * Comprehensive request building
 */
public class RequestBuilder {

    /**
     * Simple GET request
     */
    public static HttpRequest simpleGet(String url) {
        return HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();
    }

    /**
     * GET with custom headers
     */
    public static HttpRequest getWithHeaders(String url, 
                                            String... headers) {
        HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(url));

        // Headers come in pairs: name, value, name, value, ...
        for (int i = 0; i < headers.length; i += 2) {
            builder.header(headers[i], headers[i + 1]);
        }

        return builder.GET().build();
    }

    /**
     * POST with string body
     */
    public static HttpRequest postWithString(String url, String body) {
        return HttpRequest.newBuilder(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .header("Content-Type", "text/plain")
            .build();
    }

    /**
     * POST with JSON body
     */
    public static HttpRequest postJSON(String url, String jsonBody) {
        return HttpRequest.newBuilder(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
            .header("Content-Type", "application/json")
            .build();
    }

    /**
     * PUT request
     */
    public static HttpRequest putRequest(String url, String body) {
        return HttpRequest.newBuilder(URI.create(url))
            .PUT(HttpRequest.BodyPublishers.ofString(body))
            .header("Content-Type", "application/json")
            .build();
    }

    /**
     * DELETE request
     */
    public static HttpRequest deleteRequest(String url) {
        return HttpRequest.newBuilder(URI.create(url))
            .DELETE()
            .build();
    }

    /**
     * PATCH request (custom method)
     */
    public static HttpRequest patchRequest(String url, String body) {
        return HttpRequest.newBuilder(URI.create(url))
            .method("PATCH", HttpRequest.BodyPublishers.ofString(body))
            .header("Content-Type", "application/json")
            .build();
    }
}

Request Headers

Advanced Header Management:

/**
 * Working with request headers
 */
public class RequestHeaders {

    /**
     * Common headers
     */
    public static HttpRequest buildRequestWithCommonHeaders(String url) {
        return HttpRequest.newBuilder(URI.create(url))
            .header("Accept", "application/json")
            .header("Accept-Language", "en-US")
            .header("User-Agent", "MyApp/1.0")
            .header("Cache-Control", "no-cache")
            .GET()
            .build();
    }

    /**
     * Authorization headers
     */
    public static HttpRequest withBasicAuth(String url, 
                                           String username, 
                                           String password) {
        String credentials = username + ":" + password;
        String encoded = Base64.getEncoder().encodeToString(
            credentials.getBytes(StandardCharsets.UTF_8));

        return HttpRequest.newBuilder(URI.create(url))
            .header("Authorization", "Basic " + encoded)
            .GET()
            .build();
    }

    /**
     * Bearer token
     */
    public static HttpRequest withBearerToken(String url, String token) {
        return HttpRequest.newBuilder(URI.create(url))
            .header("Authorization", "Bearer " + token)
            .GET()
            .build();
    }

    /**
     * Custom headers
     */
    public static HttpRequest withCustomHeaders(String url, 
                                               Map<String, String> headers) {
        HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(url));

        headers.forEach(builder::header);

        return builder.GET().build();
    }

    /**
     * Accept-Encoding for compression
     */
    public static HttpRequest withCompression(String url) {
        return HttpRequest.newBuilder(URI.create(url))
            .header("Accept-Encoding", "gzip, deflate")
            .GET()
            .build();
    }
}

Request Body Publishers

BodyPublisher Types:

/**
 * Different body publisher patterns
 */
public class BodyPublishers {

    /**
     * String body
     */
    public static void stringBody(String url, String content) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofString(content))
            .build();

        HttpClient client = HttpClient.newHttpClient();
        client.send(request, HttpResponse.BodyHandlers.ofString());
    }

    /**
     * Byte array body
     */
    public static void byteArrayBody(String url, byte[] content) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofByteArray(content))
            .build();

        HttpClient client = HttpClient.newHttpClient();
        client.send(request, HttpResponse.BodyHandlers.ofString());
    }

    /**
     * File body
     */
    public static void fileBody(String url, Path filePath) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofFile(filePath))
            .header("Content-Type", "application/octet-stream")
            .build();

        HttpClient client = HttpClient.newHttpClient();
        client.send(request, HttpResponse.BodyHandlers.ofString());
    }

    /**
     * Input stream body
     */
    public static void inputStreamBody(String url, InputStream input) 
            throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofInputStream(() -> input))
            .build();

        HttpClient client = HttpClient.newHttpClient();
        client.send(request, HttpResponse.BodyHandlers.ofString());
    }

    /**
     * Empty body (for GET, DELETE)
     */
    public static HttpRequest noBody(String url) {
        return HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();
    }

    /**
     * Form data
     */
    public static HttpRequest formData(String url, 
                                      Map<String, String> fields) {
        StringBuilder body = new StringBuilder();

        fields.forEach((key, value) -> {
            if (body.length() > 0) {
                body.append("&");
            }

            try {
                body.append(URLEncoder.encode(key, StandardCharsets.UTF_8))
                    .append("=")
                    .append(URLEncoder.encode(value, StandardCharsets.UTF_8));
            } catch (Exception e) {
                // Handle encoding error
            }
        });

        return HttpRequest.newBuilder(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .build();
    }
}

Response Handlers

BodyHandler Types:

/**
 * Different response body handlers
 */
public class ResponseBodyHandlers {

    /**
     * String response
     */
    public static String handleString(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());

        return response.body();
    }

    /**
     * Byte array response
     */
    public static byte[] handleByteArray(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<byte[]> response = client.send(request,
            HttpResponse.BodyHandlers.ofByteArray());

        return response.body();
    }

    /**
     * File response
     */
    public static Path handleFile(String url, Path targetFile) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<Path> response = client.send(request,
            HttpResponse.BodyHandlers.ofFile(targetFile));

        return response.body();
    }

    /**
     * InputStream response
     */
    public static InputStream handleInputStream(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<InputStream> response = client.send(request,
            HttpResponse.BodyHandlers.ofInputStream());

        return response.body();
    }

    /**
     * Discarding response
     */
    public static void handleDiscarding(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<Void> response = client.send(request,
            HttpResponse.BodyHandlers.discarding());

        System.out.println("Status: " + response.statusCode());
    }

    /**
     * Custom body handler
     */
    public static List<String> customLineHandler(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<List<String>> response = client.send(request,
            HttpResponse.BodyHandlers.ofLines()
                .mapping(line -> line.trim())
                .collect(Collectors.toList()));

        return response.body();
    }
}

Streaming Responses

Large Response Handling:

/**
 * Stream large responses efficiently
 */
public class StreamingResponses {

    /**
     * Stream lines from response
     */
    public static void streamLines(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<Stream<String>> response = client.send(request,
            HttpResponse.BodyHandlers.ofLines());

        try (Stream<String> lines = response.body()) {
            lines.filter(line -> !line.isEmpty())
                .forEach(System.out::println);
        }
    }

    /**
     * Process large file
     */
    public static long processLargeFile(String url, Path outputFile) 
            throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        // Stream to file
        HttpResponse<Path> response = client.send(request,
            HttpResponse.BodyHandlers.ofFile(outputFile));

        return Files.size(response.body());
    }

    /**
     * Streaming with input stream
     */
    public static int processInputStream(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<InputStream> response = client.send(request,
            HttpResponse.BodyHandlers.ofInputStream());

        int lineCount = 0;
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(response.body()))) {

            String line;
            while ((line = reader.readLine()) != null) {
                lineCount++;
                // Process line
            }
        }

        return lineCount;
    }
}

Content Negotiation

Content Type Handling:

/**
 * Handle different content types
 */
public class ContentNegotiation {

    /**
     * Request JSON, receive JSON
     */
    public static JsonObject fetchJSON(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .header("Accept", "application/json")
            .GET()
            .build();

        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());

        // Parse JSON (using simple parsing or library)
        String json = response.body();
        return JsonParser.parseObject(json);
    }

    /**
     * Request XML, receive XML
     */
    public static String fetchXML(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .header("Accept", "application/xml")
            .GET()
            .build();

        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());

        return response.body();
    }

    /**
     * Check response content type
     */
    public static String getContentType(HttpResponse<String> response) {
        return response.headers()
            .firstValue("content-type")
            .orElse("text/plain");
    }

    /**
     * Determine if response is JSON
     */
    public static boolean isJSON(HttpResponse<String> response) {
        return getContentType(response).contains("application/json");
    }

    /**
     * Determine if response is XML
     */
    public static boolean isXML(HttpResponse<String> response) {
        return getContentType(response).contains("application/xml");
    }

    /**
     * Handle different response types
     */
    public static String handleDynamicContent(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .header("Accept", "application/json, application/xml")
            .GET()
            .build();

        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());

        String contentType = getContentType(response);

        if (contentType.contains("json")) {
            return parseJSON(response.body());
        } else if (contentType.contains("xml")) {
            return parseXML(response.body());
        } else {
            return response.body();
        }
    }

    private static String parseJSON(String json) {
        // Parse and return structured data
        return "Parsed JSON";
    }

    private static String parseXML(String xml) {
        // Parse and return structured data
        return "Parsed XML";
    }

    // Placeholder for JSON operations
    static class JsonParser {
        static JsonObject parseObject(String json) {
            return new JsonObject();
        }
    }

    static class JsonObject {
    }
}

Chunked Transfer Encoding

Handling Chunked Responses:

/**
 * Process chunked transfer encoded responses
 */
public class ChunkedTransfer {

    /**
     * The client automatically handles chunked encoding
     */
    public static String handleChunkedResponse(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        // Chunked responses are transparently decompressed
        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());

        return response.body();
    }

    /**
     * Stream chunked response efficiently
     */
    public static void streamChunked(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
            .GET()
            .build();

        HttpResponse<InputStream> response = client.send(request,
            HttpResponse.BodyHandlers.ofInputStream());

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(response.body()))) {

            String line;
            while ((line = reader.readLine()) != null) {
                // Process chunk
                System.out.println("Chunk: " + line);
            }
        }
    }

    /**
     * Check if response is chunked
     */
    public static boolean isChunked(HttpResponse<String> response) {
        String transferEncoding = response.headers()
            .firstValue("transfer-encoding")
            .orElse("");

        return transferEncoding.equalsIgnoreCase("chunked");
    }
}

Best Practices

1. Use Appropriate BodyHandler:

// ✓ Good - stream large files
HttpResponse<Path> response = client.send(request, 
    HttpResponse.BodyHandlers.ofFile(path));

// ✗ Bad - loading large file into memory
HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

2. Set Content-Type Header:

HttpRequest request = HttpRequest.newBuilder(uri)
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .header("Content-Type", "application/json")
    .build();

3. Handle Large Responses with Streams:

HttpResponse<InputStream> response = client.send(request,
    HttpResponse.BodyHandlers.ofInputStream());

try (var reader = new BufferedReader(
        new InputStreamReader(response.body()))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // Process line
    }
}

4. Validate Response Status:

HttpResponse<String> response = ...;
if (response.statusCode() >= 400) {
    throw new IOException("HTTP " + response.statusCode());
}

5. Use Content Negotiation:

HttpRequest request = HttpRequest.newBuilder(uri)
    .header("Accept", "application/json")
    .GET()
    .build();

These request and response patterns enable efficient and flexible HTTP communication in Java applications.