1. Introduction

The HTTP Requests library provides a clean and simplified interface to make HTTP requests.

The spirit of this library is to make it trivial to do easy things with HTTP requests. It can accomplish the most common use cases for an HTTP client, but does not aim to be a complete HTTP client implementation.

At its core, the library defines a framework for making requests. A built-in implementation of this framework based on Java’s built-in HTTP classes is included to make the library extremely lightweight, with its only external dependency being SLF4J. There are also several providers available that use popular HTTP client libraries, such as Jersey and Apache HTTP Components Client. When one of these providers is used, the library serves as a facade, making the use of these popular libraries much easier and your code more concise.

Indeed, the driving philosophy behind this project is to serve as a facade for HTTP requests, so that library authors may perform HTTP operations without dictating what HTTP library is required, much like SLF4J does for logging.

2. Installing

The library is split into several libraries, which can all be found on jCenter and Maven Central. The library contains a core module, which contains the interfaces and base implementations of the library. There are also libraries that provide support for popular HTTP client libraries, as well as various extensions that provide additional functionality. The table below lists out all of the libraries that exist for the HTTP Requests library and what their purposes are.

Table 1. HTTP Requests Library Modules
Module Purpose

http-requests-bom

A bill-of-materials POM for all libraries.

http-requests-core

Contains the core interfaces of the library, in addition to a built-in HTTP client implementation built on top of Java’s HttpURLConnection.

http-requests-jersey1

Adds support for Jersey 1.x.

http-requests-jersey2

Adds support for Jersey 2.x.

http-requests-httpcomponents-client

Adds support for Apache HttpComponents Client.

http-requests-jackson

Adds entity converters for Map and List types using the Jackson library.

http-requests-spring

Enables automatic configuration of the HTTP requests components as Spring beans.

http-requests-groovy

Adds Groovy DSLs to the library and Groovy-specific entity converters.

http-requests-mock

Adds support for mocking HTTP requests in unit tests.

All modules are deployed under the group com.budjb.

As an example, opting to add Jersey 2.x support and Jackson converters, Gradle might be configured like below:

build.gradle
apply plugin: "java"

repositories {
    mavenCentral()
}

dependencies {
    compile "com.budjb:http-requests-jersey2:2.0.7"
    compile "com.budjb:http-requests-jackson:2.0.7"
}

Additionally, the bill of materials POM is available to keep dependencies in sync without the need to specify their version numbers.

build.gradle
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "io.spring.gradle:dependency-management-plugin:1.0.6.RELEASE"
    }
}

apply plugin: "java"
apply plugin: "io.spring.dependency-management"

repositories {
    mavenCentral()
}

dependencyManagement {
    imports { mavenBom "com.budjb:http-requests-bom:2.0.7" }
}

dependencies {
    compile "com.budjb:http-requests-jersey2"
    compile "com.budjb:http-requests-jackson"
}

3. Components

3.1. EntityConverterManager

The EntityConverterManager serves as both a place to aggregate entity converters and as an entry point to object marshalling.

Example Usage
EntityConverterManager converterManager = new EntityConverterManager();

converterManager.add(new ExampleWriter());
converterManager.add(new ExampleReader());

The converter manager may be used directly to marshal objects to and from InputStream instances, but it is intended to be a support component of HttpClient. Therefore, the converter manager is a requirement to build an HttpClientFactory.

More information can be found in the section that covers entity converters.

3.2. HttpClientFactory

An HttpClient is used to make requests, but should not be created directly. HTTP client providers contain implementations of HttpClient specific to the HTTP client being wrapped. In order to ensure that authors do not need to worry about what type of HttpClient needs to be created, an HttpClientFactory should be used to create the client instances using the createHttpClient() method.

The HttpClientFactory interface is implemented by provider libraries and can be created directly. As an example, the Jersey 1.x module’s factory can be used to create a Jersey-specific HttpClient instance.

// Note that EntityConverterManager is required by HttpClientFactory implementations
HttpClientFactory factory = new JerseyHttpClientFactory(entityConverterManager);
HttpClient client = factory.createHttpClient();
The HttpClientFactory can be registered as a Spring bean when applications utilize Spring Framework so that it may be injected as necessary. If Spring is not in use, the HttpClientFactoryHolder may be used as a singleton holder for the HttpClientFactory instance.

Each provider library declares its own HttpClientFactory implementation, which can be looked up in the following table.

Table 2. HttpClientFactory Classes By Provider
Provider HttpClientFactory Class

http-requests-core

com.budjb.httprequests.reference.ReferenceHttpClientFactory

http-requests-jersey1

com.budjb.httprequests.jersey1.JerseyHttpClientFactory

http-requests-jersey2

com.budjb.httprequests.jersey2.JerseyHttpClientFactory

http-requests-httpcomponents-client

com.budjb.httprequests.httpcomponents.client.HttpComponentsClientFactory

3.3. HttpRequest

The HttpRequest object is used to configure an HTTP request. The properties contained within HttpRequest both define a request and influence the behavior of the HTTP client.

Table 3. HttpRequest Properties
Property Type Default Value Description

uri

String

The URI of the request, including the protocol, port, host, and path.

headers

MultiValueMap

Request headers. Individual header names can contain multiple values.

queryParameters

MultiValueMap

Query parameters of the request. Individual query parameters can contain multiple values.

connectionTimeout

Integer

0

The amount of time, in milliseconds, the request should wait for the connection to time out. A value 0 means there is no timeout.

readTimeout

Integer

0

The amount of time, in milliseconds, the request should wait to time out while waiting for content. A value`0` means there is no timeout.

sslValidated

Boolean

true

Whether SSL certification trust chains and host names should be validated.

followRedirects

Boolean

true

Whether the HTTP client should follow redirect responses received from the host.

bufferResponseEntity

Boolean

true

If true, the entity is read completely from the response and the response is closed. This allows the entity to be retrieved multiple times.

filters

Filters to apply to the request.

3.3.1. Building an HttpRequest

The HttpRequest supports a fluent syntax, where calls to setter methods may be chained.

Using the builder syntax, an HttpRequest object may be created like the following:

HttpRequest Example
HttpRequest request = new HttpRequest()
    .setUri("http://example.com")
    .setHeader("Accept", "application/json")
    .setFollowRedirects(false);

3.3.2. Behavior Modification

As mentioned before, some of the HttpRequest properties influence the behavior of the HTTP client. This section will identify those properties and their behavior.

followRedirects

By default, the HTTP client will follow redirect responses sent by the server by repeating the request against the new HTTP URI. If this behavior should be avoided, it can be disabled by setting followRedirects to false.

When this happens, the HttpResponse object will likely not contain much information other than the HTTP status code.
bufferResponseEntity

When this is enabled, the response entity (if there is one) is automatically read from the HTTP connection’s InputStream into a byte array stored in the HttpResponse object, and the stream is closed. This allows the entity to be retrieved multiple times. When disabled, the response InputStream may typically only be read only once.

It is very important that the response is closed if this functionality is disabled, or the underlying system resources may not be freed up. This is true even when an HTTP status exception is thrown via the HttpStatusExceptionFilter, which includes the HttpResponse in the thrown exception.
sslValidated
Disabling this functionality makes HTTP requests inherently insecure and is not recommended in situations where host identity verification is important, such as production environments.

By default, all SSL and TLS connections go through the standard validation steps, and may throw an IOException if SSL validation fails. Sometimes, authors may wish to bypass this functionality when they know SSL validation will fail but they still trust the remote host. When this property is set to true, SSL validation will not occur and the request should succeed unless there’s a more serious SSL issue encountered.

Disabling SSL validation is a great tool for quickly deploying development environments with self-signed certificates that are not stored in Java keystores.

3.4. HttpEntity

An HttpEntity is the container object for an entity to be used for a request or returned with a response. The HttpEntity class itself contains an InputStream, which contains the actual content of the entity, and optionally a content type and character set.

3.5. HttpClient

The HttpClient contains many methods used to initiate requests. The HttpClient should not be created directly, but rather the use of the createHttpClient method in HttpClientFactory.

Methods in the client are named after the HTTP verb or method to be used for the request.

Supported HTTP Methods
  • GET

  • POST

  • PUT

  • DELETE

  • TRACE

  • OPTIONS

  • HEAD

  • PATCH

3.5.1. Client Methods

Each HTTP verb has a corresponding method in HttpClient. Each method takes either an HttpRequest, or a String for simple requests. All methods in the client have the following methods.

HttpClient Common Method Structure
/**
 * Perform an HTTP request.
 *
 * @param request Request properties to use with the HTTP request.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException When an underlying IO exception occurs.
 */
HttpResponse method(HttpRequest request) throws IOException;

/**
 * Perform an HTTP request.
 *
 * @param uri URI of the request.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException        When an underlying IO exception occurs.
 * @throws URISyntaxException When the syntax of the request is incorrect.
 */
HttpResponse method(String uri) throws IOException, URISyntaxException;

Some HTTP verbs support request entities (or bodies). Specifically, all HTTP verbs except GET and TRACE support request entities. Entities may be provided with a few different types.

Certain HTTP verbs, such as PATCH and DELETE, have limited support both in provider HTTP clients and in HTTP servers.
Table 4. Request Entity Types
Type Description

HttpEntity

Contains an object that has already been serialized into an InputStream, and optionally content type information.

InputStream

Contains an object that has already been serialized, with no content type information.

ConvertingHttpEntity

Contains an object that will be serialized with the EntityConverterManager, and optionally content type information.

Object

An object that will be serialized with the EntityConverterManager.

HTTP verbs that support entities have additional methods defined in the HttpClient, with the following structure.

/**
 * Perform an HTTP request with the given request entity.
 *
 * @param request Request properties to use with the HTTP request.
 * @param entity  An HTTP entity.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException When an underlying IO exception occurs.
 */
HttpResponse method(HttpRequest request, HttpEntity entity) throws IOException;

/**
 * Perform an HTTP request with the given request entity.
 *
 * @param uri    URI of the request.
 * @param entity An HTTP entity.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException        When an underlying IO exception occurs.
 * @throws URISyntaxException When the syntax of the request is incorrect.
 */
HttpResponse method(String uri, HttpEntity entity) throws IOException, URISyntaxException;

/**
 * Perform an HTTP request with the given input stream.
 *
 * @param request     Request properties to use with the HTTP request.
 * @param inputStream An {@link InputStream} containing the response body.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException When an underlying IO exception occurs.
 */
HttpResponse method(HttpRequest request, InputStream inputStream) throws IOException;

/**
 * Perform an HTTP request with the given input stream.
 *
 * @param uri         URI of the request.
 * @param inputStream An {@link InputStream} containing the response body.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException        When an underlying IO exception occurs.
 * @throws URISyntaxException When the syntax of the request is incorrect.
 */
HttpResponse method(String uri, InputStream inputStream) throws IOException, URISyntaxException;

/**
 * Perform an HTTP request with the given entity.
 *
 * The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
 * writer can be found, an {@link UnsupportedConversionException} will be thrown.
 *
 * @param request Request properties to use with the HTTP request.
 * @param entity  Request entity.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException                    When an underlying IO exception occurs.
 * @throws UnsupportedConversionException When an error in entity conversion occurs.
 */
HttpResponse method(HttpRequest request, ConvertingHttpEntity entity) throws IOException, UnsupportedConversionException;

/**
 * Perform an HTTP request with the given entity.
 *
 * The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
 * writer can be found, an {@link UnsupportedConversionException} will be thrown.
 *
 * @param uri    URI of the request.
 * @param entity Request entity.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException                    When an underlying IO exception occurs.
 * @throws URISyntaxException             When the syntax of the request is incorrect.
 * @throws UnsupportedConversionException When an error in entity conversion occurs.
 */
HttpResponse method(HttpRequest request, ConvertingHttpEntity entity) throws IOException, UnsupportedConversionException;

/**
 * Perform an HTTP request with the given entity.
 *
 * The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
 * writer can be found, an {@link UnsupportedConversionException} will be thrown.
 *
 * @param request Request properties to use with the HTTP request.
 * @param entity  Request entity.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException                    When an underlying IO exception occurs.
 * @throws UnsupportedConversionException When an error in entity conversion occurs.
 */
HttpResponse method(HttpRequest request, Object entity) throws IOException, UnsupportedConversionException;

/**
 * Perform an HTTP request with the given entity.
 *
 * The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
 * writer can be found, an {@link UnsupportedConversionException} will be thrown.
 *
 * @param uri    URI of the request.
 * @param entity Request entity.
 * @return A {@link HttpResponse} object containing the properties of the server response.
 * @throws IOException                    When an underlying IO exception occurs.
 * @throws URISyntaxException             When the syntax of the request is incorrect.
 * @throws UnsupportedConversionException When an error in entity conversion occurs.
 */
HttpResponse method(String uri, Object entity) throws IOException, URISyntaxException, UnsupportedConversionException;
Example Without Request Entity
HttpResponse response = client.get("http://example.com/foo/bar");
Example With Request Entity
HttpResponse response = client.post("http://example.com/foo/bar", "content");

3.6. Handling Responses

HttpClient request methods return an HttpResponse object that contains information about the response.

3.6.1. HttpResponse Properties

Property Type Description

status

Integer

HTTP status code of the response.

headers

MultiValueMap

Response headers.

entity

HttpEntity

Response entity (may be null).

To ensure that all resources used during the request are released, it is critical that the response is closed via the HttpResponse.close() method once the response is no longer needed.

3.6.2. Response Entities

The response entity can be retrieved by using the getEntity() method of the HttpResponse, which return an HttpEntity containing the entity. By default, the response entity, if available, is buffered and the response is closed. Subsequent calls to retrieve the response entity will reproduce the entity an unlimited amount of times. If the request properties disabled this functionality, the response’s entity can be read only once, but it does not require the memory space to buffer the entity in the HttpResponse object. Additionally, it is important to close the response when this functionality is disabled.

When bufferResponseEntity is false, it is very important that the HttpResponse is closed when it is no longer needed. If it is not closed, underlying system resources may not be freed up. The close() method can be called on the HttpResponse to properly clean up the request.

The HttpResponse supports entity conversion using the getEntity(Class<?> type) method. If an entity reader is registered that supports the requested type, it will be converted and returned.

Retrieving the Response Entity as an InputStream
HttpResponse response = client.get("https://example.com/api/foo_resource");

try {
    InputStream inputStream = response.getEntity();
    // do some work with the input stream
}
finally {
    response.close()
}
Retrieving the Response Entity as a String
HttpResponse response = client.get("https://example.com/api/foo_resource");

try {
    String inputStream = response.getEntity(String.class);
}
finally {
    response.close();
}
The above example assumes that an entity converter that can read the entity into a String is registered with the EntityConverterManager that the HttpClientFactory was built with.

4. Filters

Filters may be added to the HttpRequest to modify or react to certain events in the lifecycle of an HTTP request. There are various filter types available, each providing an injection point into the request lifecycle, as explained below.

HTTP Request Filter Chain
Figure 1. HTTP Request Filter Chain

4.1. RequestFilter

Applications may need to intercept an HTTP request directly before the request is actually made to modify the request in some manner. A great example of a use of this filter is the built-in BasicAuthFilter

// Adding a BasicAuthFilter to the request will automatically apply the required
// headers for Basic Authentication.
HttpRequest request = new HttpRequest("https://example.com");
request.addFilter(new BasicAuthFilter('username', 'password'));

4.2. OutputStreamFilter

This filter allows applications to modify the OutputStream sent to the actual HTTP connection before it is sent. An example of a built-in filter that uses this functionality is the GZIPFilter, which wraps the output stream in a GZIPOutputStream.

4.3. HttpClientResponseFilter

The response filter is called after a response has been received from the HTTP request. This filter provides an opportunity to modify the HttpResponse before it is returned.

4.4. RetryFilter

The retry filter allows applications to determine if a request should be retried based on some criteria. If any one retry filter determines that the request should be retried, the request will be submitted again until no more retry filters request a retry.

A use case for this functionality is authentication schemes that provide an authentication token that can be reused until it expires. A retry filter might be created for this type of authentication scheme that inspects the response of a call to the service that requires the authentication token, and re-authenticates if the previous authentication token has expired. Once a new authentication token is obtained, the original request can be retried with the updated authentication token.

There is a built-in AuthenticationTokenHeaderFilter class that creates a framework for this type of authentication scheme.

4.5. LifecycleFilter

This filter provides several integration points related to the lifecycle of the request. The interfaces includes default, empty implementations of each available method so that only the methods a filter need only override the methods it requires. Each of the methods in this interface as passed an HttpContext, which contains the request, method, request entity, the response, and a property repository. This allows filters to maintain some state during the course of the request. The methods available are as follows.

  • onStart is called before any processing of the request is started.

  • onRequest is called directly before the request is executed, and is the last chance to modify the request before it is sent over the wire.

  • onResponse is similar in purpose to onRequest, but is called once a response has been received.

  • onComplete is called once the HTTP request has completed and no further retries have been requested. This is the final filter opportunity before the response is returned to the caller.

4.6. Built-In Filters

The library includes several built-in filters for common use cases.

4.6.1. BasicAuthFilter

The basic authentication filter provides simple basic auth functionality with a username and a password.

4.6.2. GZIPFilter

The GZIP filter wraps the output of the request with a GZIPOutputStream so that the entity’s contents are compressed with the GZIP algorithm. The appropriate Content-Encoding header is set.

4.6.3. DeflateFilter

The deflate filter wraps the output of the request with a DeflateOutputStream so that the entity’s contents are compressed with the deflate algorithm. The appropriate Content-Encoding header is set.

4.6.4. AuthenticationTokenHeaderFilter

This filter is abstract and provides a framework for HTTP requests that require token authentication and follow the flow:

  1. If no authentication token is known, authenticate.

  2. Make the HTTP request.

  3. If HTTP status 401 is received, and no retries have been attempted, re-authenticate and request a retry.

The filter provides integration points so that authentication logic and authentication token header names can be provided by concrete implementations of the filter.

4.6.5. LoggingFilter

The logging filter captures the contents of the request and the response and logs it. The LoggingFilter itself is an abstract class, and there are a couple bundled implementations.

ConsoleLoggingFilter

The ConsoleLoggingFilter is a concrete implementation of LoggingFilter that prints the contents of the log to the Java console.

Slf4jLoggingFilter

The Slf4jLoggingFilter is a concrete implementation of LoggingFilter that logs the contents of the log to an SLF4J logger. The filter can be configured with a logger name to use, otherwise the fully-qualified class name of the logging filter will be used. By default, the log is configured to log at the TRACE level. The filter can be configured to log at a different level if desired.

4.6.6. HttpStatusExceptionFilter

The HttpStatusExceptionFilter filter modifies how the response status is treated. When an HTTP status that is not considered successful (that is, not in the 200-299 range), an exception representing that status code is thrown. For example, if an HTTP 404 is received, an HttpNotFoundException will be thrown.

Use this functionality to create different error handling for expected error cases.

If no appropriate exception exists for the HTTP status code that is received, an HttpStatusException is thrown instead. This exception is actually the superclass of all HTTP status specific exceptions.

These exceptions contain the following information about the response:

Table 5. HttpStatusException Properties
Property Description

status

The HTTP status code of the response, as an Integer.

response

The HttpResponse object containing details about the received response.

5. Entity Converters

Entity converters are responsible for marshalling request and response entities to and from a byte stream. This prevents applications from having to make separate calls to helper methods or classes to do the conversion.

Entity converters are registered with an EntityConverterManager, which is a requirement to create an HttpClientFactory. This allows the HttpClient to marshal objects to use as request entities. The manager is subsequently passed to HttpResponse objects so that it too may marshal response objects.

5.1. Entity Readers

Entity readers are responsible for marshalling the byte stream of a response entity into some other type of object. Applications can create a new entity reader for a custom object type that contains the logic to read relevant information from the byte stream and create the custom object from it, bound with the data from the response.

Entity readers should implement the EntityReader interface.

5.2. Entity Writers

Entity writers are responsible for marshalling an object into a byte stream for use in a request. Much like entity readers, entity writers provide applications a method to convert custom objects for use in a request without having to handle that logic separately from the request call.

Entity writers should implement the EntityWriter interface.

Entity writers have the option to provide a default content type if it was able to marshal the entity. The default content type is only used if one was not otherwise provided with the entity.

5.3. Built-In Converters

The library contains several out of the box entity converters that may be used without including any other dependencies.

  • ByteArrayEntityReader

  • ByteArrayEntityWriter

  • FormDataEntityWriter

  • StringEntityReader

  • StringEntityWriter

5.4. Jackson Converters

The http-requests-jackson library adds entity converters for List and Map objects using the Jackson[https://github.com/FasterXML/jackson] library. The following entity converters are available:

  • JacksonListReader

  • JacksonMapReader

  • JacksonMapListWriter

5.5. Groovy Converters

The http-requests-groovy library adds entity converters specific to Groovy class types. The following entity converters are available:

  • GStringEntityWriter

  • JsonEntityWriter

  • JsonEntityReader

  • XmlSlurperEntityReader

If the Groovy JSON converters are in use, the Jackson library is redundant. The groovy variants utilize JsonSlurper and JsonBuilder.

6. Spring Boot Integration

The http-requests-spring is available to automatically configure the various library components to the application context as Spring beans. It supports automatic entity converter registry for all entity converters that are registered as beans, and it automatically configures the HttpClientFactory instance as a bean depending on which provider is present on the classpath.

Auto configuration must be enabled for the HTTP requests components to be automatically registered. This can be enabled with the EnableAutoConfiguration annotation on the main application class or a Configuration class. Alternatively, the HttpRequestsAutoConfiguration configuration class may be configured as a bean to accomplish automatic configuration of the library.

With the Spring support added, other beans may inject the HttpClientFactory (with the name httpClientFactory) bean as a dependency.

7. Examples

7.1. Using the Library

This example walks through creating the various components of the library and how they are used. The example is admittedly verbose, so that each component is elaborated upon. In real world applications, the initialization code and requests are much more likely to be separated.

// The entity converter manager is built and contains any entity converters
// needed by the application.
EntityConverterManager entityConverterManager = new EntityConverterManager();
entityConverterManager.add(new StringEntityReader());

// The HttpClientFactory is built, using the built-in Java client.
// The converter manager is passed in as a dependency.
HttpClientFactory httpClientFactory = ReferenceHttpClientFactory(entityConverterManager);

// The factory may be stored in the holder if other parts of the application need it.
//HttpClientFactoryHolder.setHttpClientFactory(httpClientFactory);

// An HttpClient is created from the factory.
HttpClient client = httpClientFactory.createHttpClient();

try {
    // Make the HTTP request, and capture the response.
    HttpResponse response = client.get("https://reqres.in/api/users");

    // Marshal the response entity as a String (since the StringEntityReader was registered
    // with the EntityConverterManager above).
    String result = response.getEntity(String.class);

    // Print the result.
    System.out.println("HTTP response is:\n" + result);
}
catch (Exception e) {
    System.out.println("unexpected exception occurred");
    e.printStackTrace();
}

7.2. Using Spring Integration

Using the Spring Boot integration removes the need for applications to bootstrap the HttpClientFactory, due to the automatic configuration of the library’s beans. The above example is significantly simplified in the Spring Boot example below. Notice that the code does not create an EntityConverterManager, register entity converters with it, or create the HttpClientFactory. This is all handled via the Spring library.

@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }

    /**
     * The command line runner is a simple way to run some code just as the application
     * has started up.
     *
     * For this example, the commandLineRunner bean simply takes the httpClientFactory
     * bean as a dependency and makes a request.
     */
    @Bean
    CommandLineRunner commandLineRunner(HttpClientFactory httpClientFactory) {
        return args -> {
            // Create the client.
            HttpClient client = httpClientFactory.createHttpClient();

            // Make the request.
            HttpResponse response = client.get("https://reqres.in/api/users");

            // Marshal the response entity as a String.
            String result = response.getEntity(String.class);

            // Print the result.
            System.out.println("HTTP response is:\n" + result);
        };
    }
}

7.3. Additional Samples

More samples can be found in the samples project on GitHub.

8. Testing

Since it is not ideal to make actual HTTP requests while performing unit tests, a mock library exists that allows tests to inject responses for expected HTTP requests made by the code being tested. Using the built-in request mocking functionality avoids the need to use dynamically generated mocking and stubbing libraries such as Mockito or Spock.

8.1. Request Mocks

A request mock contains the details about a potential HTTP request that should have a mocked response. The RequestMock object contains most of the same properties that a typical HttpRequest does, such as request URI, headers, and query parameters, as well as some additional properties, such as the HTTP method that should match.

It also includes properties about the response, such as response status code, headers, and the response entity.

The minimal requirement for a mock to match a request is that the URI must match the request. If a mock has other details set, such as request headers, HTTP method, or query parameters, those also must match. If they are not specified in the mock, they are not considered as part of the match criteria. This allows tests to be as specific or not as they need to be.

Mocks that match requests will have their counters incremented each time a match occurs. The RequestMock object exposes these details via the called and getCalledCount methods.

If a no matching mock is found for an HTTP request, and UnmatchedRequestMockException will be thrown.

8.2. MockHttpClientFactory

The MockHttpClientFactory class may be used in tests and injected into those objects being tested. This factory implementation exposes the same interface as the other provider libraries, and adds some additional methods specific to testing. Request mocks are created via the createMock method, which returns and tracks an empty mock which may be configured.

The factory exposes a method allMocksCalled that will return whether all created mocks have been called at least once.

8.3. Example

Below is a simple Java object that makes an API request and returns the payload, which is expected to be a simple String.

/**
* A test object that makes use of the HTTP requests library to fetch
* a string result from the API located at http://localhost/api.
*/
class TestObject {
    private final HttpClientFactory httpClientFactory;

    public TestObject(HttpClientFactory httpClientFactory) {
        this.httpClientFactory = httpClientFactory;
    }

    public String makeApiCall() {
        return httpClientFactory.createHttpClient.get("http://localhost/api").getEntity(String.class);
    }
}

This class may be tested using a framework, such as Junit or Spock. A Spock example follows:

class FunctionalSpec extends Specification {
    /**
     * Mock HTTP client factory.
     */
    MockHttpClientFactory httpClientFactory

    /**
     * Entity converter manager.
     */
    EntityConverterManager entityConverterManager

    /**
     * Configure the environment before each test.
     */
    def setup() {
        // Create an entity converter manager with the Groovy JSON converters
        entityConverterManager = new EntityConverterManager([
            new JsonEntityWriter(),
            new JsonEntityReader()
        ])

        // Create the mock HTTP client factory with the entity converter manager above
        httpClientFactory = new MockHttpClientFactory(entityConverterManager)
    }

    def 'A simple mocked GET request returns the expected response entity'() {
        setup:
        // Create a mock for a specific URL with a response payload
        RequestMock mock = httpClientFactory
            .createMock()
            .setRequestUri('http://localhost/foo/bar')
            .setResponseEntity([
                foo: 'bar',
                baz: 'boz'
            ])

        TestObject object = new TestObject(httpClientFactory)

        when:
        // Make the API call through the test object
        def response = object.makeApiCall()

        then:
        // The mock should have been called at least once
        mock.called()

        // Since the above mock is the only one that was created, all mocks
        // should have been called from the client factory.
        httpClientFactory.allMocksCalled()

        // The response should have been returned and converted properly
        response == [
            foo: 'bar',
            baz: 'boz'
        ]
    }
}

9. Changelog

The changelog will be updated with release notes for each release of the library.

2.0.7
  • Ensure that a Content-Type specified in the request is preferred over the one provided in the HTTP entity. This ensures that any Content-Type provided by a converter will not take precedence over a user-specified one.

  • Add http-requests-mock library to enable easier testing of code using the http-requests libraries.

  • Fix NullPointerException when sending a request with a null entity (#14).

  • Write a test to ensure that retrieving an entity when the response does not contain an entity does not result in a NullPointerException (#13).

  • Throw a URISyntaxException instead of NullPointerException when a URI is not absolute (#12).

2.0.6
  • Make the build compatible with JDK 8.

  • Fix publishing for the jersey version 1 library.

2.0.5
  • Add handling for null values in multi-valued maps (which affects request headers, query parameters, and form fields).

2.0.4
  • Update Gradle version and build dependencies.

  • Update library dependencies to latest.

  • Make EntityConverterManager immutable. Converters need to be passed into its constructor going forward.

  • Introduce entity converter ordering to ensure that certain converters have higherh or lower priority than others.

2.0.3
  • Fix a bug where the reference HTTP client threw an IOException when an error HTTP status code was returned with no response entity.

  • Fix a bug where calling HttpResponse.getEntity(Class) threw a NullPointerException when no response entity was returned.

2.0.2
  • Add Closeable support for filters. Any filter that implements Closeable are guaranteed to be closed.

  • Fix bug where the LoggingFilter threw a NullPointerException when no request entity is present.

  • Do not apply a port if none was specified using the reference HTTP client.

2.0.1
  • Close the underlying Apache HTTP client when the HttpResponse is closed.

2.0.0

The version 2.0 upgrade involved refactoring of the components of the library, with the aim of simplifying the codebase and making the project more maintainable. Due to this, the change log for this version is relatively lengthy.

  • The library has been ported from Groovy to Java. This was done to remove a required dependency and widen the audience that might find value in the library. The Groovy functionality has been moved into its own library.

  • Changed the Groovy DSL to use a new function-based delegate, bringing it in line with how Groovy DSLs typically behave. Before, properties of the HttpRequest were assigned as properties. With the change, properties are exposed as methods.

  • The EntityConverterManager is now a required dependency to HttpClientFactory. The same manager instance is passed along to the HttpClient and HttpResponse objects to support object marshalling throughout the stack. This differs from version 1.0 , where the set of entity converters may be modified in both the HttpClientFactory and HttpClient separately.

  • Filters are no longer registered with the HttpClientFactory or HttpClient, but rather with an HttpRequest. Since filters act on a request, it brings the registration of filters closer to to the context in which they are used.

  • Properties that align to specific headers have been removed from the HttpRequest and HttpResponse. Of course, these headers may be added to the request just as any other header would be. In particular, the following have been removed:

    • contentType

    • accept

    • allow

  • Introduced the http-requests-groovy library, which contains the Groovy DSL extensions to HttpRequest and Groovy specific entity converters.

  • Introduced the http-requests-spring library, which adds automatic configuration for the various libraries contained in the http-requests project.

  • Introduced the http-requests-jackson library, which adds Map and List entity converters using Jackson.

  • Added ConvertingHttpEntity for objects that should be marshalled, so that a content type may also be provided.

  • Added a samples project in GitHub with a collection of example applications demonstrating various aspects of the library.

  • Removed the Grails 3 plugin.

  • Add support for HTTP PATCH. Whether the verb works or not is dependent on the underlying HTTP client.

  • Add entity support to HTTP DELETE. Whether this works or not is dependent on the underlying HTTP client and the HTTP server the request is sent to.

  • All artifacts are now deployed to both jcenter and Maven Central.

  • Added query parameter support to the LoggingFilter (#10).

  • Fixed HttpRequest clone issue where paths with special characters would cause a URISyntaxException (#7).

1.0.2
  • Remove default character set from the HttpRequest.

  • Change default conversion character set from UTF-8 to ISO-8859-1 (per HTTP 1.1 spec).

1.0.1
  • Add missing @DelegatesTo annotation to a method taking a closure.

  • If a retry filter is registered with the client, the request entity is automatically buffered so that it can be resent with subsequent requests.

  • Add a reference implementation of the client to avoid the need to include other provider implementation modules.

  • Made the Grails 3.x plugin compatible with JDK 1.7.

  • Modify Grails 3.x plugin so that the classpath scanner prefers the non-built-in HttpClientFactory if another provider is found.

1.0.0
  • Initial non-beta release of the library.