The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:
Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Overview
AsyncHttpClient (AHC) is a library build on top of Netty, with the purpose of easily executing HTTP requests and processing responses asynchronously.
In this article, weβll present how to configure and use the HTTP client, how to execute a request and process the response using AHC.
2. Setup
The latest version of the library can be found in the Maven repository. We should be careful to use the dependency with the group id org.asynchttpclient and not the one with com.ning:
<dependency>
<groupId>org.asynchttpclient</groupId>
<artifactId>async-http-client</artifactId>
<version>2.2.0</version>
</dependency>
3. HTTP Client Configuration
The most straightforward method of obtaining the HTTP client is by using the Dsl class. The static asyncHttpClient() method returns an AsyncHttpClient object:
AsyncHttpClient client = Dsl.asyncHttpClient();
If we need a custom configuration of the HTTP client, we can build the AsyncHttpClient object using the builder DefaultAsyncHttpClientConfig.Builder:
DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()
This offers the possibility to configure timeouts, a proxy server, HTTP certificates and many more:
DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()
.setConnectTimeout(500)
.setProxyServer(new ProxyServer(...));
AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder);
Once weβve configured and obtained an instance of the HTTP client we can reuse it across out application. We donβt need to create an instance for each request because internally it creates new threads and connection pools, which will lead to performance issues.
Also, itβs important to note that once weβve finished using the client we should call to close() method to prevent any memory leaks or hanging resources.
4. Creating an HTTP Request
There are two methods in which we can define an HTTP request using AHC:
- bound
- unbound
There is no major difference between the two request types in terms of performance. They only represent two separate APIs we can use to define a request. A bound request is tied to the HTTP client it was created from and will, by default, use the configuration of that specific client if not specified otherwise.
For example, when creating a bound request the disableUrlEncoding flag is read from the HTTP client configuration, while for an unbound request this is, by default set to false. This is useful because the client configuration can be changed without recompiling the whole application by using system properties passed as VM arguments:
java -jar -Dorg.asynchttpclient.disableUrlEncodingForBoundRequests=true
A complete list of properties can be found the ahc-default.properties file.
4.1. Bound Request
To create a bound request we use the helper methods from the class AsyncHttpClient that start with the prefix βprepareβ. Also, we can use the prepareRequest() method which receives an already created Request object.
For example, the prepareGet() method will create an HTTP GET request:
BoundRequestBuilder getRequest = client.prepareGet("http://www.baeldung.com");
4.2. Unbound Request
An unbound request can be created using the RequestBuilder class:
Request getRequest = new RequestBuilder(HttpConstants.Methods.GET)
.setUrl("http://www.baeldung.com")
.build();
or by using the Dsl helper class, which actually uses the RequestBuilder for configuring the HTTP method and URL of the request:
Request getRequest = Dsl.get("http://www.baeldung.com").build()
5. Executing HTTP Requests
The name of the library gives us a hint about how the requests can be executed. AHC has support for both synchronous and asynchronous requests.
Executing the request depends on its type. When using a bound request we use the execute() method from the BoundRequestBuilder class and when we have an unbound request weβll execute it using one of the implementations of the executeRequest() method from the AsyncHttpClient interface.
5.1. Synchronously
The library was designed to be asynchronous, but when needed we can simulate synchronous calls by blocking on the Future object. Both execute() and executeRequest() methods return a ListenableFuture<Response> object. This class extends the Java Future interface, thus inheriting the get() method, which can be used to block the current thread until the HTTP request is completed and returns a response:
Future<Response> responseFuture = boundGetRequest.execute();
responseFuture.get();
Future<Response> responseFuture = client.executeRequest(unboundRequest);
responseFuture.get();
Using synchronous calls is useful when trying to debug parts of our code, but itβs not recommended to be used in a production environment where asynchronous executions lead to better performance and throughput.
5.2. Asynchronously
When we talk about asynchronous executions, we also talk about listeners for processing the results. The AHC library provides 3 types of listeners that can be used for asynchronous HTTP calls:
- AsyncHandler
- AsyncCompletionHandler
- ListenableFuture listeners
The AsyncHandler listener offers the possibility to control and process the HTTP call before it has completed. Using it can handle a series of events related to the HTTP call:
request.execute(new AsyncHandler<Object>() {
@Override
public State onStatusReceived(HttpResponseStatus responseStatus)
throws Exception {
return null;
}
@Override
public State onHeadersReceived(HttpHeaders headers)
throws Exception {
return null;
}
@Override
public State onBodyPartReceived(HttpResponseBodyPart bodyPart)
throws Exception {
return null;
}
@Override
public void onThrowable(Throwable t) {
}
@Override
public Object onCompleted() throws Exception {
return null;
}
});
The State enum lets us control the processing of the HTTP request. By returning State.ABORT we can stop the processing at a specific moment and by using State.CONTINUE we let the processing finish.
Itβs important to mention that the AsyncHandler isnβt thread-safe and shouldnβt be reused when executing concurrent requests.
AsyncCompletionHandler inherits all the methods from the AsyncHandler interface and adds the onCompleted(Response) helper method for handling the call completion. All the other listener methods are overridden to return State.CONTINUE, thus making the code more readable:
request.execute(new AsyncCompletionHandler<Object>() {
@Override
public Object onCompleted(Response response) throws Exception {
return response;
}
});
The ListenableFuture interface lets us add listeners that will run when the HTTP call is completed.
Also, it letβs execute the code from the listeners β by using another thread pool:
ListenableFuture<Response> listenableFuture = client
.executeRequest(unboundRequest);
listenableFuture.addListener(() -> {
Response response = listenableFuture.get();
LOG.debug(response.getStatusCode());
}, Executors.newCachedThreadPool());
Besides, the option to add listeners, the ListenableFuture interface lets us transform the Future response to a CompletableFuture.
7. Conclusion
AHC is a very powerful library, with a lot of interesting features. It offers a very simple way to configure an HTTP client and the capability of executing both synchronous and asynchronous requests.
