Available Hire Me
← All Writing Apache HttpClient Classic APIs

A Quick Guide

Apache HttpClient Classic APIs in Java — complete guide to synchronous HTTP with POST, GET, PUT, PATCH, DELETE, HEAD, and OPTIONS. Response handlers, error handling, and full working examples tested against a REST API.

Apache HttpClient’s classic APIs are my go-to for synchronous HTTP in Java — clear, explicit, and battle-tested. Across numerous integration projects, from Co-op’s third-party pricing data ingestion to Ribby Hall’s REST and SOAP API integrations, I’ve reached for these APIs consistently. This guide demonstrates the full set of HTTP methods with practical code examples, using the Reqres API as a convenient test target.

Why Apache HttpClient?

The classic APIs are synchronous, ideal for straightforward HTTP tasks. They support everything from basic GETs to complex POSTs, with response handlers for clean, reusable code.

Setup

Use the Reqres API (https://reqres.in) for testing. It supports all HTTP methods for CRUD on /api/users.

CRUD Operations

  • Create: POST /api/users
  • Read: GET /api/users/{id} or /api/users?page={n}
  • Update: PUT or PATCH /api/users/{id}
  • Delete: DELETE /api/users/{id}

Response handlers streamline response processing, error handling, and resource cleanup.

HTTP Methods

POST: Create a User

Send a POST request to create a user.

public String createUser(String firstName, String lastName, String email, String avatar)
        throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        List<NameValuePair> formParams = new ArrayList<>();
        formParams.add(new BasicNameValuePair("first_name", firstName));
        formParams.add(new BasicNameValuePair("last_name", lastName));
        formParams.add(new BasicNameValuePair("email", email));
        formParams.add(new BasicNameValuePair("avatar", avatar));
        try (UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8)) {
            HttpHost httpHost = HttpHost.create("https://reqres.in");
            URI uri = new URIBuilder("/api/users/").build();
            HttpPost httpPostRequest = new HttpPost(uri);
            httpPostRequest.setEntity(entity);
            BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
            return httpClient.execute(httpHost, httpPostRequest, handler);
        }
    } catch (Exception e) {
        throw new RequestProcessingException("Failed to create user.", e);
    }
}

Test:


@Test
void executePostRequest() {
    try {
        String createdUser = userHttpRequestHelper.createUser(
                "DummyFirst", "DummyLast", "DummyEmail@example.com", "DummyAvatar");
        assertThat(createdUser).isNotEmpty();
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

GET: Read Users

Fetch a single user or paginated list.

Paginated GET:

public String getPaginatedUsers(Map<String, String> requestParameters)
        throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        HttpHost httpHost = HttpHost.create("https://reqres.in");
        List<NameValuePair> nameValuePairs = requestParameters.entrySet().stream()
                .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
                .toList();
        URI uri = new URIBuilder("/api/users/").addParameters(nameValuePairs).build();
        HttpGet httpGetRequest = new HttpGet(uri);
        BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
        return httpClient.execute(httpHost, httpGetRequest, handler);
    } catch (Exception e) {
        throw new RequestProcessingException("Failed to get paginated users.", e);
    }
}

Test:


@Test
void executeGetPaginatedRequest() {
    try {
        Map<String, String> params = Map.of("page", "1");
        String responseBody = userHttpRequestHelper.getPaginatedUsers(params);
        assertThat(responseBody).isNotEmpty();
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

Specific User GET:

public String getUser(long userId) throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        HttpHost httpHost = HttpHost.create("https://reqres.in");
        HttpGet httpGetRequest = new HttpGet(new URIBuilder("/api/users/" + userId).build());
        BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
        return httpClient.execute(httpHost, httpGetRequest, handler);
    } catch (Exception e) {
        throw new RequestProcessingException(
                MessageFormat.format("Failed to get user for ID: {0}", userId), e);
    }
}

Test:


@Test
void executeGetSpecificRequest() {
    try {
        long userId = 2L;
        String existingUser = userHttpRequestHelper.getUser(userId);
        assertThat(existingUser).isNotEmpty();
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

HEAD: Check Status

Verify a resource’s status without fetching its body.

public Integer getUserStatus(long userId) throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        HttpHost httpHost = HttpHost.create("https://reqres.in");
        URI uri = new URIBuilder("/api/users/" + userId).build();
        HttpHead httpHeadRequest = new HttpHead(uri);
        HttpClientResponseHandler<Integer> handler = HttpResponse::getCode;
        Integer code = httpClient.execute(httpHost, httpHeadRequest, handler);
        return code;
    } catch (Exception e) {
        throw new RequestProcessingException(
                MessageFormat.format("Failed to get user for ID: {0}", userId), e);
    }
}

Test:


@Test
void executeUserStatus() {
    try {
        long userId = 2L;
        Integer userStatus = userHttpRequestHelper.getUserStatus(userId);
        assertThat(userStatus).isEqualTo(HttpStatus.SC_OK);
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

OPTIONS: Discover Methods

Check which HTTP methods the server supports.

public Map<String, String> executeOptions() throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        HttpHost httpHost = HttpHost.create("https://reqres.in");
        URI uri = new URIBuilder("/api/users/").build();
        HttpOptions httpOptionsRequest = new HttpOptions(uri);
        HttpClientResponseHandler<Map<String, String>> handler = response ->
                StreamSupport.stream(
                                Spliterators.spliteratorUnknownSize(response.headerIterator(), Spliterator.ORDERED), false)
                        .collect(Collectors.toMap(Header::getName, Header::getValue));
        return httpClient.execute(httpHost, httpOptionsRequest, handler);
    } catch (Exception e) {
        throw new RequestProcessingException("Failed to execute the request.", e);
    }
}

Test:


@Test
void executeOptions() {
    try {
        Map<String, String> headers = userHttpRequestHelper.executeOptions();
        assertThat(headers.keySet())
                .as("Headers do not contain allow header")
                .containsAnyOf("Allow", "Access-Control-Allow-Methods");
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

PUT: Update a User

Update an entire user record.

public String updateUser(long userId, String firstName, String lastName, String email, String avatar)
        throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        List<NameValuePair> formParams = new ArrayList<>();
        formParams.add(new BasicNameValuePair("first_name", firstName));
        formParams.add(new BasicNameValuePair("last_name", lastName));
        formParams.add(new BasicNameValuePair("email", email));
        formParams.add(new BasicNameValuePair("avatar", avatar));
        try (UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8)) {
            HttpHost httpHost = HttpHost.create("https://reqres.in");
            URI uri = new URIBuilder("/api/users/" + userId).build();
            HttpPut httpPutRequest = new HttpPut(uri);
            httpPutRequest.setEntity(entity);
            BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
            return httpClient.execute(httpHost, httpPutRequest, handler);
        }
    } catch (Exception e) {
        throw new RequestProcessingException("Failed to update user.", e);
    }
}

Test:


@Test
void executePutRequest() {
    try {
        int userId = 2;
        String updatedUser = userHttpRequestHelper.updateUser(
                userId, "UpdatedDummyFirst", "UpdatedDummyLast", "UpdatedDummyEmail@example.com", "UpdatedDummyAvatar");
        assertThat(updatedUser).isNotEmpty();
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

PATCH: Partial Update

Update specific user fields.

public String patchUser(long userId, String firstName, String lastName)
        throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        List<NameValuePair> formParams = new ArrayList<>();
        formParams.add(new BasicNameValuePair("first_name", firstName));
        formParams.add(new BasicNameValuePair("last_name", lastName));
        try (UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8)) {
            HttpHost httpHost = HttpHost.create("https://reqres.in");
            URI uri = new URIBuilder("/api/users/" + userId).build();
            HttpPatch httpPatchRequest = new HttpPatch(uri);
            httpPatchRequest.setEntity(entity);
            BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
            return httpClient.execute(httpHost, httpPatchRequest, handler);
        }
    } catch (Exception e) {
        throw new RequestProcessingException("Failed to patch user.", e);
    }
}

Test:


@Test
void executePatchRequest() {
    try {
        int userId = 2;
        String patchedUser = userHttpRequestHelper.patchUser(userId, "UpdatedDummyFirst", "UpdatedDummyLast");
        assertThat(patchedUser).isNotEmpty();
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

DELETE: Remove a User

Delete a user by ID.

public void deleteUser(long userId) throws RequestProcessingException {
    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        HttpHost httpHost = HttpHost.create("https://reqres.in");
        URI uri = new URIBuilder("/api/users/" + userId).build();
        HttpDelete httpDeleteRequest = new HttpDelete(uri);
        BasicHttpClientResponseHandler handler = new BasicHttpClientResponseHandler();
        httpClient.execute(httpHost, httpDeleteRequest, handler);
    } catch (Exception e) {
        throw new RequestProcessingException("Failed to delete user.", e);
    }
}

Test:


@Test
void executeDeleteRequest() {
    try {
        int userId = 2;
        userHttpRequestHelper.deleteUser(userId);
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

Custom Types

Use POJOs for type-safe requests and responses. Serialize to JSON with Jackson for HTTP entities.

public class DataObjectResponseHandler<T> extends AbstractHttpClientResponseHandler<T> {
    private ObjectMapper objectMapper = new ObjectMapper();
    @NonNull
    private Class<T> realType;

    public DataObjectResponseHandler(@NonNull Class<T> realType) {
        this.realType = realType;
    }

    @Override
    public T handleEntity(HttpEntity httpEntity) throws IOException {
        try {
            return objectMapper.readValue(EntityUtils.toString(httpEntity), realType);
        } catch (ParseException e) {
            throw new ClientProtocolException(e);
        }
    }
}

public class UserTypeHttpRequestHelper extends BaseHttpRequestHelper {
    public User getUser(long userId) throws RequestProcessingException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpHost httpHost = userRequestProcessingUtils.getApiHost();
            URI uri = userRequestProcessingUtils.prepareUsersApiUri(userId);
            HttpGet httpGetRequest = new HttpGet(uri);
            HttpClientResponseHandler<User> handler = new DataObjectResponseHandler<>(User.class);
            return httpClient.execute(httpHost, httpGetRequest, handler);
        } catch (Exception e) {
            throw new RequestProcessingException(
                    MessageFormat.format("Failed to get user for ID: {0}", userId), e);
        }
    }
}

Test:


@Test
void executeGetUser() {
    try {
        long userId = 2L;
        User existingUser = userHttpRequestHelper.getUser(userId);
        ThrowingConsumer<User> responseRequirements = user -> {
            assertThat(user).as("Created user cannot be null.").isNotNull();
            assertThat(user.getId()).as("ID should be positive number.").isEqualTo(userId);
            assertThat(user.getFirstName()).as("First name cannot be null.").isNotEmpty();
            assertThat(user.getLastName()).as("Last name cannot be null.").isNotEmpty();
            assertThat(user.getAvatar()).as("Avatar cannot be null.").isNotNull();
        };
        assertThat(existingUser).satisfies(responseRequirements);
    } catch (Exception e) {
        Assertions.fail("Failed to execute HTTP request.", e);
    }
}

POJOs enhance type safety but add complexity compared to strings.

Conclusion

Apache HttpClient’s classic APIs simplify synchronous HTTP requests for CRUD operations. With response handlers and custom types, you can build robust, maintainable Java applications. Test these examples with Reqres and explore further!

Java Apache HttpClient Reqres API

Samuel Jackson

Samuel Jackson

Senior Java Back End Developer & Contractor

Senior Java Back End Developer — Betfair Exchange API specialist, Spring Boot, AWS, and event-driven architecture. 25+ years delivering high-performance systems across betting, finance, energy, retail, and government. Available for Java contracting.