Course – LS (cat=HTTP Client-Side)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:


1. Overview

The Java HttpClient API was introduced with Java 11. The API implements the client-side of the most recent HTTP standards. It supports HTTP/1.1 and HTTP/2, both synchronous and asynchronous programming models.

We can use it to send HTTP requests and retrieve their responses. Before Java 11, we had to rely on a rudimentary URLConnection implementation or third-party libraries such as Apache HttpClient.

In this tutorial, we’ll look at the sending POST requests using Java HttpClient. We’ll show how to send both synchronous and asynchronous POST requests, as well as concurrent POST requests. In addition, we’ll check how to add authentication parameters and JSON bodies to POST requests.

Finally, we’ll see how to upload files and submit form data. Therefore, we’ll cover most of the common use cases.

2. Preparing a POST Request

Before we can send an HTTP request, we’ll first need to create an instance of an HttpClient.

HttpClient instances can be configured and created from its builder using the newBuilder method. Otherwise, if no configuration is required, we can make use of the newHttpClient utility method to create a default client:

HttpClient client = HttpClient.newHttpClient();

HttpClient will use HTTP/2 by default. It will also automatically downgrade to HTTP/1.1 if the server doesn’t support HTTP/2.

Now we are ready to create an instance of HttpRequest from its builder. We’ll make use of the client instance to send this request later on. The minimum parameters for a POST request are the server URL, request method, and body:

HttpRequest request = HttpRequest.newBuilder()

A request body needs to be supplied via the BodyPublisher class. It’s a reactive-stream publisher that publishes streams of request body on-demand. In our example, we used a body publisher which sends no request body.

3. Sending a POST Request

Now that we’ve prepared a POST request, let’s look at the different options for sending it.

3.1. Synchronously

We can send the prepared request using this default send method. This method will block our code until the response has been received:

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

The BodyHandlers utility implements various useful handlers, such as handling the response body as a String or streaming the response body to a file. Once the response is received, the HttpResponse object will contain the response status, headers, and body:


3.2. Asynchronously

We could send the same request from the previous example asynchronously using the sendAsync method. Instead of blocking our code, this method will immediately return a CompletableFuture instance:

CompletableFuture<HttpResponse<String>> futureResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

The CompletableFuture completes with the HttpResponse once it becomes available:

HttpResponse<String> response = futureResponse.get();

3.3. Concurrently

We can combine Streams with CompletableFutures in order to issue several requests concurrently and await their responses:

List<CompletableFuture<HttpResponse<String>>> completableFutures =
  .map(builder -> builder.POST(HttpRequest.BodyPublishers.noBody()))
  .map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))

Now, let’s wait for all the requests to be complete so that we can process their responses all at once:

CompletableFuture<List<HttpResponse<String>>> combinedFutures = CompletableFuture
  .allOf(completableFutures.toArray(new CompletableFuture[0]))
  .thenApply(future ->

As we’ve combined all the responses using the allOf and join methods, we get a new CompletableFuture that holds our responses:

List<HttpResponse<String>> responses = combinedFutures.get();
responses.forEach((response) -> {

4. Adding Authentication Parameters

We can set an authenticator on the client level for HTTP authentication on all requests:

HttpClient client = HttpClient.newBuilder()
  .authenticator(new Authenticator() {
    protected PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(

However, the HttpClient does not send basic credentials until challenged for them with a WWW-Authenticate header from the server.

To bypass this, we can always create and send the basic authorization header manually:

HttpRequest request = HttpRequest.newBuilder()
  .header("Authorization", "Basic " + 

5. Adding a Body

In the examples so far, we haven’t added any bodies to our POST requests. However, the POST method is commonly used to send data to the server via the request body.

5.1. JSON Body

The BodyPublishers utility implements various useful publishers, such as publishing the request body from a String or a file. We can publish JSON data as String, converted using the UTF-8 character set:

HttpRequest request = HttpRequest.newBuilder()

5.2. Uploading Files

Let’s create a temporary file that we can use for uploading via HttpClient:

Path file = tempDir.resolve("temp.txt");
List<String> lines = Arrays.asList("1", "2", "3");
Files.write(file, lines);

HttpClient provides a separate method, BodyPublishers.ofFile, for adding a file to the POST body. We can simply add our temporary file as a method parameter, and the API takes care of the rest:

HttpRequest request = HttpRequest.newBuilder()

5.3. Submitting Forms

Contrary to files, HttpClient does not provide a separate method for posting form data. Therefore, we’ll again need to make use of the BodyPublishers.ofString method:

Map<String, String> formData = new HashMap<>();
formData.put("username", "baeldung");
formData.put("message", "hello");

HttpRequest request = HttpRequest.newBuilder()
  .header("Content-Type", "application/x-www-form-urlencoded")

However, we’ll need to convert the form data from a Map to a String using a custom implementation:

private static String getFormDataAsString(Map<String, String> formData) {
    StringBuilder formBodyBuilder = new StringBuilder();
    for (Map.Entry<String, String> singleEntry : formData.entrySet()) {
        if (formBodyBuilder.length() > 0) {
        formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8));
        formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8));
    return formBodyBuilder.toString();

6. Conclusion

In this article, we explored sending POST requests using Java HttpClient API introduced in Java 11.

We learned how to create an HttpClient instance and prepare a POST request. We saw how to send prepared requests synchronously, asynchronously, and concurrently. Next, we also saw how to add basic authentication parameters.

Finally, we looked at adding a body to a POST request. We covered JSON payloads, uploading files, and submitting form data.

As always, the complete source code is available over on GitHub.

Course – LS (cat=HTTP Client-Side)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:


res – HTTP Client (eBook) (cat=Http Client-Side)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.