Backend Development 14 min read

Master Java HTTP Clients: From HttpClient to OkHttp and WebClient

This article introduces four Java HTTP client libraries—Java HttpClient, Apache HttpComponents, OkHttp, and Spring WebClient—explains their key features, provides async GET and sync POST code examples for each, and offers guidance on selecting the right client for Spring Boot and plain Java projects.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Java HTTP Clients: From HttpClient to OkHttp and WebClient

Hypertext Transfer Protocol (HTTP) is an application‑layer protocol used to transfer hypermedia documents (HTML) and standard formats such as JSON and XML. It is the most common protocol for REST API communication, and Java applications need an HTTP client to call other services.

1. HTTP Client Options

The article reviews four major Java HTTP client libraries:

Java 11+ built‑in HttpClient

Apache HttpComponents HttpClient

Square's OkHttpClient

Spring WebFlux WebClient

Each client is demonstrated with an asynchronous GET request and a synchronous POST request against two sample endpoints ( /list and /save ) defined in a simple Spring Boot controller.

2. Sample Controller

<code>@RestController
@RequestMapping("/api")
public class ApiController {
  private static List<User> datas = new ArrayList<>();
  static {
    datas.addAll(List.of(
        new User(1L, "狗蛋"),
        new User(2L, "観月あかね")
    ));
  }
  @GetMapping("/list")
  public List<User> list() { return datas; }
  @PostMapping("/save")
  public User save(@RequestBody User user) { datas.add(user); return user; }
}
</code>

3. Java HttpClient

Async GET

<code>public static void invoke() throws Exception {
  HttpClient client = HttpClient.newBuilder()
      .version(Version.HTTP_2)
      .followRedirects(Redirect.NORMAL)
      .build();
  HttpRequest request = HttpRequest.newBuilder()
      .uri(new URI(URLConstants.LIST))
      .GET()
      .timeout(Duration.ofSeconds(5))
      .build();
  client.sendAsync(request, BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println)
    .join();
}
</code>

Result:

<code>[{"id":1,"name":"狗蛋"},{"id":2,"name":"観月あかね"}]
</code>

Sync POST

<code>public static void invokePost() {
  try {
    String requestBody = prepareRequest();
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(URLConstants.SAVE))
        .POST(HttpRequest.BodyPublishers.ofString(requestBody))
        .header(HttpHeaders.CONTENT_TYPE, "application/json")
        .header(HttpHeaders.ACCEPT, "application/json")
        .build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    System.out.printf("Response: %s%n", response.body());
  } catch (Exception e) { e.printStackTrace(); }
}
private static String prepareRequest() throws Exception {
  var mapper = new ObjectMapper();
  return mapper.writeValueAsString(new User(666L, "莉莉"));
}
</code>

Result:

<code>{"id":666,"name":"莉莉"}
</code>

4. Apache HttpComponents

Async GET

<code>public static void invoke() {
  try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
    client.start();
    SimpleHttpRequest request = SimpleRequestBuilder.get()
        .setUri(URLConstants.LIST)
        .build();
    Future<SimpleHttpResponse> future = client.execute(request, new FutureCallback<SimpleHttpResponse>() {
      public void completed(SimpleHttpResponse result) {
        String resp = new String(result.getBodyBytes(), StandardCharsets.UTF_8);
        System.out.printf("result: %s%n", resp);
      }
      public void failed(Exception ex) { System.out.printf("error: %s%n", ex); }
      public void cancelled() {}
    });
    HttpResponse response = future.get();
    System.out.printf("code: %s, reason: %s%n", response.getCode(), response.getReasonPhrase());
  }
}
</code>

Result:

<code>code: 200, reason: OK
result: [{"id":1,"name":"狗蛋"},{"id":2,"name":"観月あかね"},{"id":666,"name":"莉莉"}]
</code>

Sync POST

<code>public static void invokePost() throws Exception {
  StringEntity entity = new StringEntity(prepareRequest());
  HttpPost post = new HttpPost(URLConstants.SAVE);
  post.setEntity(entity);
  post.setHeader("Accept", "application/json");
  post.setHeader("Content-type", "application/json");
  try (CloseableHttpClient http = HttpClients.createDefault()) {
    String result = http.execute(post, response -> {
      System.out.printf("code: %s, reason: %s%n", response.getCode(), response.getReasonPhrase());
      return EntityUtils.toString(response.getEntity());
    });
    System.out.printf("result: %s%n", result);
  }
}
private static String prepareRequest() throws Exception {
  var mapper = new ObjectMapper();
  return mapper.writeValueAsString(new User(666L, "Heyzo"));
}
</code>

Result:

<code>{"id":666,"name":"Heyzo"}
</code>

5. OkHttpClient

Async GET

<code>public static void invoke() throws Exception {
  OkHttpClient client = new OkHttpClient.Builder()
      .readTimeout(1000, TimeUnit.MILLISECONDS)
      .writeTimeout(1000, TimeUnit.MILLISECONDS)
      .build();
  Request request = new Request.Builder().url(URLConstants.LIST).get().build();
  client.newCall(request).enqueue(new Callback() {
    public void onResponse(Call call, Response response) throws IOException {
      System.out.printf("result: %s%n", response.body().string());
    }
    public void onFailure(Call call, IOException e) {}
  });
}
</code>

Sync POST

<code>public static void invokePost() throws Exception {
  OkHttpClient client = new OkHttpClient.Builder()
      .readTimeout(1000, TimeUnit.MILLISECONDS)
      .writeTimeout(1000, TimeUnit.MILLISECONDS)
      .build();
  String bodyStr = prepareRequest();
  RequestBody body = RequestBody.create(bodyStr, MediaType.parse("application/json"));
  Request request = new Request.Builder().url(URLConstants.SAVE).post(body).build();
  Response response = client.newCall(request).execute();
  System.out.printf("result: %s%n", response.body().string());
}
</code>

Result:

<code>{"id":666,"name":"Heyzo"}
</code>

6. Spring WebClient (WebFlux)

Async GET

<code>public static void invoke() {
  WebClient client = WebClient.create();
  client.get()
    .uri(URLConstants.LIST)
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(System.out::println);
}
</code>

Result:

<code>[{"id":1,"name":"狗蛋"},{"id":2,"name":"観月あかね"},{"id":666,"name":"莉莉"}]
</code>

Sync POST

<code>public static void invokePost() throws Exception {
  WebClient client = WebClient.create();
  String result = client.post()
    .uri(URLConstants.SAVE)
    .contentType(MediaType.APPLICATION_JSON)
    .body(BodyInserters.fromValue(prepareRequest()))
    .retrieve()
    .bodyToMono(String.class)
    .block();
  System.out.printf("result: %s%n", result);
}
</code>

Result:

<code>{"id":666,"name":"Heyzo"}
</code>

7. How to Choose the Right Client

If you prefer no external dependencies and run on Java 11+, the built‑in HttpClient is the default choice.

For Spring Boot projects that use reactive APIs, WebClient offers the most seamless integration.

When you need extensive configurability and the richest ecosystem of examples, Apache HttpClient is a solid option.

If you want a lightweight, highly configurable library with excellent Android support, choose Square’s OkHttpClient .

JavaSpring BootWebClientOkHttpHTTP ClientApache HttpClient
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.