How do I handle errors and exceptions in Java 11 HttpClient?

In Java 11, the HttpClient API provides a flexible way to handle HTTP requests and responses. When dealing with errors and exceptions, there are several strategies and patterns you can use to ensure your application can handle unexpected issues effectively while making HTTP calls.

1. Exception Types in Java 11 HttpClient

Here are some common exceptions that may occur while using the HttpClient API:

  • java.net.ConnectException: Thrown when a connection cannot be established.
  • java.net.SocketTimeoutException: Thrown if a request times out.
  • java.net.UnknownHostException: Thrown when the host cannot be resolved.
  • java.io.IOException: A general I/O error, such as a failed read/write operation.
  • java.net.http.HttpTimeoutException: Specific to HTTP operations when a request or response timeout is reached.

2. Handling Exceptions with try-catch

You can surround your HTTP client logic with a try-catch block to gracefully handle exceptions. Here is an example:

package org.kodejava.net.http;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;

public class HttpClientErrorHandlingExample {
    public static void main(String[] args) {
        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com"))
                .GET()
                .build();

        try {
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            // Handle successful responses
            if (response.statusCode() == 200) {
                System.out.println("Response received: " + response.body());
            } else {
                System.out.println("Non-OK response: " + response.statusCode());
                System.out.println("Response body: " + response.body());
            }

        } catch (HttpTimeoutException e) {
            System.err.println("Request timed out: " + e.getMessage());
        } catch (UnknownHostException e) {
            System.err.println("Unknown Host: " + e.getMessage());
        } catch (ConnectException e) {
            System.err.println("Connection error: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("I/O error occurred: " + e.getMessage());
        } catch (InterruptedException e) {
            System.err.println("Request interrupted: " + e.getMessage());
            Thread.currentThread().interrupt(); // Restore interrupted status
        }
    }
}

Explanation

  1. try-catch to Isolate Block: Specific exceptions such as HttpTimeoutException or ConnectException are caught and handled to provide more detailed information about what went wrong.
  2. Default IOException: This covers any I/O-related issue not explicitly handled by other exceptions.
  3. Logging Errors: In this case, errors are printed to System.err, but in a production system, you should use a proper logging framework (e.g., SLF4J or Log4j).

3. Timeouts: Configure Proper Timeouts for Error Prevention

Handling timeouts properly can prevent your client from hanging indefinitely due to network issues.

HttpClient httpClient = HttpClient.newBuilder()
        .connectTimeout(java.time.Duration.ofSeconds(10)) // Timeout for connecting
        .build();

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com"))
        .timeout(java.time.Duration.ofSeconds(5)) // Timeout for the request
        .GET()
        .build();
  • connectTimeout(): Sets the maximum time to establish a connection to the server.
  • request.timeout(): Sets the maximum time to wait for the request/response.

4. Handle HTTP Error Responses

Although HTTP error codes like 4xx (client error) or 5xx (server error) do not throw exceptions directly, you can handle them based on the response status code.

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode();
if (statusCode >= 200 && statusCode < 300) {
    System.out.println("Success: " + response.body());
} else if (statusCode >= 400 && statusCode < 500) {
    System.out.println("Client error (4xx): " + response.body());
} else if (statusCode >= 500) {
    System.out.println("Server error (5xx): " + response.body());
} else {
    System.out.println("Unexpected status code: " + statusCode);
}

5. Using Asynchronous API for Better Error Control

The HttpClient supports asynchronous requests using CompletableFuture, which provides an alternative way of handling success and failure scenarios.

HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com"))
        .GET()
        .build();

httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(body -> System.out.println("Response received: " + body))
        .exceptionally(e -> {
            System.err.println("Error occurred: " + e.getMessage());
            return null;
        });
  • thenApply: Process the response body.
  • exceptionally: Handle exceptions such as network failures or timeouts during the asynchronous operation.

6. Best Practices

  • Retry Logic: Implement retry logic for transient errors like timeouts or temporary server unavailability. Libraries like Resilience4j can simplify this.
  • Circuit Breakers: Protect your application from overload by using circuit breakers for repeated failures.
  • Logging and Monitoring: Log error details in a structured format for easy monitoring and debugging.
  • Test Handling: Mock failures using libraries like WireMock to test your error-handling logic.

By combining these strategies, you can handle errors and exceptions comprehensively in Java 11 HttpClient.

How do I work with InetAddress to resolve IP addresses in Java?

In Java, InetAddress is part of the java.net package and is used to handle IP addresses, both IPv4 and IPv6. It provides methods to resolve hostnames to IP addresses and vice versa, as well as to work with local and remote host information. Here’s a detailed explanation of how to work with InetAddress to resolve IP addresses:

Basic Workflow with InetAddress

  • Import the Necessary Package
import java.net.InetAddress;
import java.net.UnknownHostException;
  • Resolve a Hostname to an IP Address Use InetAddress.getByName(String hostname) to find the IP address of a given hostname:
try {
   InetAddress address = InetAddress.getByName("www.example.com");
   System.out.println("Host Name: " + address.getHostName());
   System.out.println("IP Address: " + address.getHostAddress());
} catch (UnknownHostException e) {
   System.err.println("Host not found: " + e.getMessage());
}
  • Get the Local Host Address Use InetAddress.getLocalHost() to retrieve the IP address of your local machine:
try {
   InetAddress localHost = InetAddress.getLocalHost();
   System.out.println("Local Host Name: " + localHost.getHostName());
   System.out.println("Local IP Address: " + localHost.getHostAddress());
} catch (UnknownHostException e) {
   System.err.println("Unable to get local host address: " + e.getMessage());
}
  • Resolve All IP Addresses for a Hostname Some hostnames might resolve to multiple IP addresses (e.g., websites using load balancing). Use InetAddress.getAllByName(String hostname) to get all associated IPs:
try {
   InetAddress[] addresses = InetAddress.getAllByName("www.google.com");
   for (InetAddress address : addresses) {
       System.out.println("IP Address: " + address.getHostAddress());
   }
} catch (UnknownHostException e) {
   System.err.println("Host not found: " + e.getMessage());
}
  • Work with the Loopback Address You can retrieve and work with the default loopback address (127.0.0.1 or ::1):
InetAddress loopback = InetAddress.getLoopbackAddress();
System.out.println("Loopback Address: " + loopback.getHostAddress());
  • Check Reachability Use InetAddress.isReachable(int timeout) to check if an address is reachable (via ICMP ping-like mechanism):
try {
   InetAddress address = InetAddress.getByName("www.example.com");
   if (address.isReachable(5000)) { // Timeout in milliseconds
       System.out.println(address + " is reachable.");
   } else {
       System.out.println(address + " is not reachable.");
   }
} catch (Exception e) {
   System.err.println("Error checking reachability: " + e.getMessage());
}
  • Create an InetAddress from an IP String If you already have an IP address in string form and want to create an InetAddress object:
try {
   InetAddress address = InetAddress.getByName("192.168.0.1");
   System.out.println("Host Name: " + address.getHostName());
   System.out.println("IP Address: " + address.getHostAddress());
} catch (UnknownHostException e) {
   System.err.println("Invalid IP address: " + e.getMessage());
}

Useful Methods in InetAddress

Method Description
getByName(String host) Returns InetAddress for the given hostname or IP address.
getAllByName(String host) Returns all InetAddress objects for the given hostname.
getLocalHost() Fetches the local machine’s InetAddress.
getLoopbackAddress() Returns the loopback address (e.g., 127.0.0.1).
getHostName() Returns the hostname for the IP address.
getHostAddress() Gets the IP address as a string.
isReachable(int timeout) Checks if the address is reachable within a given timeout.
equals(Object obj) Compares two InetAddress objects.
hashCode() Generates the hash code for the InetAddress instance.

Common Use-Case Examples

Example 1: Resolving a Hostname

try {
    InetAddress address = InetAddress.getByName("www.example.com");
    System.out.println("IP Address: " + address.getHostAddress());
} catch (UnknownHostException e) {
    System.err.println("Unable to resolve host: " + e.getMessage());
}

Example 2: Listing All IPs for a Domain

try {
    InetAddress[] addresses = InetAddress.getAllByName("www.google.com");
    for (InetAddress addr : addresses) {
        System.out.println(addr);
    }
} catch (UnknownHostException e) {
    System.err.println("Unable to resolve host: " + e.getMessage());
}

Example 3: Checking Local Host Information

try {
    InetAddress localHost = InetAddress.getLocalHost();
    System.out.println("HostName: " + localHost.getHostName());
    System.out.println("IP Address: " + localHost.getHostAddress());
} catch (UnknownHostException e) {
    System.err.println("Error: " + e.getMessage());
}

Notes:

  • In modern applications, DNS caching and network configurations can affect resolution.
  • InetAddress.getByName internally resolves the hostname using DNS.
  • For non-blocking or asynchronous networking, consider Java’s java.nio package.

With these explanations and examples, you should be able to handle IP address resolution reliably using InetAddress.

How do I convert raw IP address to String?

This example show you how to convert a raw IP address, an array of byte, returned by InetAddress.getAddress() method call to its string representation.

package org.kodejava.net;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class RawIPToString {
    public static void main(String[] args) {
        byte[] ip = new byte[0];
        try {
            InetAddress address = InetAddress.getLocalHost();
            ip = address.getAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        String ipAddress = RawIPToString.getIpAddress(ip);
        System.out.println("IP Address = " + ipAddress);

        try {
            InetAddress address = InetAddress.getByName("google.com");
            ip = address.getAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        ipAddress = RawIPToString.getIpAddress(ip);
        System.out.println("IP Address = " + ipAddress);
    }

    /**
     * Convert raw IP address to string.
     *
     * @param rawBytes raw IP address.
     * @return a string representation of the raw ip address.
     */
    private static String getIpAddress(byte[] rawBytes) {
        int i = 4;
        StringBuilder ipAddress = new StringBuilder();
        for (byte raw : rawBytes) {
            ipAddress.append(raw & 0xFF);
            if (--i > 0) {
                ipAddress.append(".");
            }
        }
        return ipAddress.toString();
    }
}

This example will print something like:

IP Address = 30.30.30.60
IP Address = 142.251.10.113

How do I get a localhost hostname?

package org.kodejava.network;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class HostNameExample {
    public static void main(String[] args) {
        try {
            InetAddress address = InetAddress.getLocalHost();

            System.out.println("Hostname: " + address.getHostName());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

An example of output produce by the code snippet above is:

Hostname: Krakatau