diff --git a/core-java-modules/core-java-httpclient/README.md b/core-java-modules/core-java-httpclient/README.md
new file mode 100644
index 0000000000..24ff7d9941
--- /dev/null
+++ b/core-java-modules/core-java-httpclient/README.md
@@ -0,0 +1,6 @@
+## Java HttpClient
+
+This module contains articles about Java HttpClient
+
+### Relevant articles
+- TODO
diff --git a/core-java-modules/core-java-httpclient/pom.xml b/core-java-modules/core-java-httpclient/pom.xml
new file mode 100644
index 0000000000..57b23e96c1
--- /dev/null
+++ b/core-java-modules/core-java-httpclient/pom.xml
@@ -0,0 +1,58 @@
+
+
+ 4.0.0
+ core-java-httpclient
+ 0.1.0-SNAPSHOT
+ core-java-httpclient
+ jar
+
+
+ com.baeldung
+ parent-modules
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+
+
+ org.mock-server
+ mockserver-netty
+ ${mockserver.version}
+
+
+ org.mock-server
+ mockserver-client-java
+ ${mockserver.version}
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ ${maven.compiler.source.version}
+ ${maven.compiler.target.version}
+
+
+
+
+
+
+ 11
+ 11
+ 3.22.0
+ 5.11.2
+
+
+
\ No newline at end of file
diff --git a/core-java-modules/core-java-httpclient/src/main/java/com/baeldung/httpclient/HttpClientPost.java b/core-java-modules/core-java-httpclient/src/main/java/com/baeldung/httpclient/HttpClientPost.java
new file mode 100644
index 0000000000..d08a7bf183
--- /dev/null
+++ b/core-java-modules/core-java-httpclient/src/main/java/com/baeldung/httpclient/HttpClientPost.java
@@ -0,0 +1,162 @@
+package com.baeldung.httpclient;
+
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+public class HttpClientPost {
+
+ public static HttpResponse sendSynchronousPost(String serviceUrl) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newHttpClient();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(serviceUrl))
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .build();
+
+ HttpResponse response = client
+ .send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response;
+ }
+
+ public static CompletableFuture> sendAsynchronousPost(String serviceUrl) {
+ HttpClient client = HttpClient.newHttpClient();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(serviceUrl))
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .build();
+
+ CompletableFuture> futureResponse = client
+ .sendAsync(request, HttpResponse.BodyHandlers.ofString());
+
+ return futureResponse;
+ }
+
+ public static List>> sendConcurrentPost(List serviceUrls) {
+ HttpClient client = HttpClient.newHttpClient();
+
+ List>> completableFutures = serviceUrls.stream()
+ .map(URI::create)
+ .map(HttpRequest::newBuilder)
+ .map(builder -> builder.POST(HttpRequest.BodyPublishers.noBody()))
+ .map(HttpRequest.Builder::build)
+ .map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
+ .collect(Collectors.toList());
+
+ return completableFutures;
+ }
+
+ public static HttpResponse sendPostWithAuthHeader(String serviceUrl) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newHttpClient();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(serviceUrl))
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .header("Authorization", "Basic " + Base64.getEncoder()
+ .encodeToString(("baeldung:123456").getBytes()))
+ .build();
+
+ HttpResponse response = client
+ .send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response;
+ }
+
+ public static HttpResponse sendPostWithAuthClient(String serviceUrl) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newBuilder()
+ .authenticator(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(
+ "baeldung",
+ "123456".toCharArray());
+ }
+ })
+ .build();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(serviceUrl))
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .build();
+
+ HttpResponse response = client
+ .send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response;
+ }
+
+ public static HttpResponse sendPostWithJsonBody(String serviceUrl) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newHttpClient();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(serviceUrl))
+ .POST(HttpRequest.BodyPublishers.ofString("{\"action\":\"hello\"}"))
+ .build();
+
+ HttpResponse response = client
+ .send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response;
+ }
+
+ public static HttpResponse sendPostWithFormData(String serviceUrl) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newHttpClient();
+
+ Map formData = new HashMap<>();
+ formData.put("username", "baeldung");
+ formData.put("message", "hello");
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(serviceUrl))
+ .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
+ .build();
+
+ HttpResponse response = client
+ .send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response;
+ }
+
+ public static HttpResponse sendPostWithFileData(String serviceUrl, Path file) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newHttpClient();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(serviceUrl))
+ .POST(HttpRequest.BodyPublishers.ofFile(file))
+ .build();
+
+ HttpResponse response = client
+ .send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response;
+ }
+
+ private static String getFormDataAsString(Map formData) {
+ StringBuilder formBodyBuilder = new StringBuilder();
+ for (Map.Entry singleEntry : formData.entrySet()) {
+ if (formBodyBuilder.length() > 0) {
+ formBodyBuilder.append("&");
+ }
+ formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8));
+ formBodyBuilder.append("=");
+ formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8));
+ }
+ return formBodyBuilder.toString();
+ }
+
+}
diff --git a/core-java-modules/core-java-httpclient/src/test/java/com/baeldung/httpclient/HttpClientPostUnitTest.java b/core-java-modules/core-java-httpclient/src/test/java/com/baeldung/httpclient/HttpClientPostUnitTest.java
new file mode 100644
index 0000000000..b43cf08649
--- /dev/null
+++ b/core-java-modules/core-java-httpclient/src/test/java/com/baeldung/httpclient/HttpClientPostUnitTest.java
@@ -0,0 +1,99 @@
+package com.baeldung.httpclient;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.IOException;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.*;
+
+class HttpClientPostUnitTest extends PostRequestMockServer {
+
+ @Test
+ void givenSyncPostRequest_whenServerIsAvailable_thenOkStatusIsReceived() throws IOException, InterruptedException {
+ HttpResponse response = HttpClientPost.sendSynchronousPost(serviceUrl);
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ }
+
+ @Test
+ void givenAsyncPostRequest_whenServerIsAvailable_thenOkStatusIsReceived() throws ExecutionException, InterruptedException {
+ CompletableFuture> futureResponse = HttpClientPost.sendAsynchronousPost(serviceUrl);
+ HttpResponse response = futureResponse.get();
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ }
+
+ @Test
+ void givenConcurrentPostRequests_whenServerIsAvailable_thenOkStatusIsReceived() throws ExecutionException, InterruptedException {
+ List>> completableFutures = HttpClientPost
+ .sendConcurrentPost(List.of(serviceUrl, serviceUrl));
+
+ CompletableFuture>> combinedFutures = CompletableFuture
+ .allOf(completableFutures.toArray(new CompletableFuture[0]))
+ .thenApply(future ->
+ completableFutures.stream()
+ .map(CompletableFuture::join)
+ .collect(Collectors.toList()));
+
+ List> responses = combinedFutures.get();
+ responses.forEach((response) -> {
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ });
+ }
+
+ @Test
+ void givenPostRequestWithAuthClient_whenServerIsAvailable_thenOkStatusIsReceived() throws IOException, InterruptedException {
+ HttpResponse response = HttpClientPost.sendPostWithAuthClient(serviceUrl);
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ }
+
+ @Test
+ void givenPostRequestWithAuthHeader_whenServerIsAvailable_thenOkStatusIsReceived() throws IOException, InterruptedException {
+ HttpResponse response = HttpClientPost.sendPostWithAuthHeader(serviceUrl);
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ }
+
+ @Test
+ void givenPostRequestWithJsonBody_whenServerIsAvailable_thenOkStatusIsReceived() throws IOException, InterruptedException {
+ HttpResponse response = HttpClientPost.sendPostWithJsonBody(serviceUrl);
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ }
+
+ @Test
+ void givenPostRequestWithFormData_whenServerIsAvailable_thenOkStatusIsReceived() throws IOException, InterruptedException {
+ HttpResponse response = HttpClientPost.sendPostWithFormData(serviceUrl);
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ }
+
+ @Test
+ void givenPostRequestWithFileData_whenServerIsAvailable_thenOkStatusIsReceived(@TempDir Path tempDir) throws IOException, InterruptedException {
+ Path file = tempDir.resolve("temp.txt");
+ List lines = Arrays.asList("1", "2", "3");
+ Files.write(file, lines);
+
+ HttpResponse response = HttpClientPost.sendPostWithFileData(serviceUrl, file);
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
+ }
+
+}
diff --git a/core-java-modules/core-java-httpclient/src/test/java/com/baeldung/httpclient/PostRequestMockServer.java b/core-java-modules/core-java-httpclient/src/test/java/com/baeldung/httpclient/PostRequestMockServer.java
new file mode 100644
index 0000000000..fa594897a3
--- /dev/null
+++ b/core-java-modules/core-java-httpclient/src/test/java/com/baeldung/httpclient/PostRequestMockServer.java
@@ -0,0 +1,61 @@
+package com.baeldung.httpclient;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.HttpStatusCode;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.URISyntaxException;
+
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+public abstract class PostRequestMockServer {
+
+ public static ClientAndServer mockServer;
+ public static String serviceUrl;
+
+ private static int serverPort;
+
+ public static final String SERVER_ADDRESS = "127.0.0.1";
+ public static final String PATH = "/test1";
+ public static final String METHOD = "POST";
+
+ @BeforeAll
+ static void startServer() throws IOException, URISyntaxException {
+ serverPort = getFreePort();
+ serviceUrl = "http://" + SERVER_ADDRESS + ":" + serverPort + PATH;
+ mockServer = startClientAndServer(serverPort);
+ mockBasicPostRequest();
+ }
+
+ @AfterAll
+ static void stopServer() {
+ mockServer.stop();
+ }
+
+ private static void mockBasicPostRequest() {
+ new MockServerClient(SERVER_ADDRESS, serverPort)
+ .when(
+ request()
+ .withPath(PATH)
+ .withMethod(METHOD)
+ )
+ .respond(
+ response()
+ .withStatusCode(HttpStatusCode.OK_200.code())
+ .withBody("{\"message\":\"ok\"}")
+ );
+ }
+
+ private static int getFreePort () throws IOException {
+ try (ServerSocket serverSocket = new ServerSocket(0)) {
+ return serverSocket.getLocalPort();
+ }
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index f06c75b3be..5b64baac7f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1318,6 +1318,7 @@
core-java-modules/core-java-networking-3
core-java-modules/multimodulemavenproject
core-java-modules/core-java-strings
+ core-java-modules/core-java-httpclient
ddd-modules
docker
apache-httpclient-2