Spring Boot WebClient POST Example

Spring WebClient provides a fluent API for sending HTTP requests and handling the responses in a Spring and Spring Boot-based application. WebClient follows the reactive (non-blocking) approach, and so it is preferred over its blocking counterpart RestTemplate.

This Spring Boot WebClient tutorial discusses different ways to send HTTP POST requests and handle their responses or errors.

1. Setting Up WebClient in Spring Boot

To use WebClient, make sure we have included it using the spring-boot-starter-webflux dependency:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Also, in a @Configuration class, we must create a bean to initialize the WebClient with appropriate settings.

@Configuration
public class WebConfig {

  @Bean
  public WebClient webClient() {

    WebClient webClient = WebClient.builder()
      .baseUrl("http://localhost:3000")
      .defaultCookie("cookie-name", "cookie-value")
      .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
      .build();
  }
}

Since Spring 5.3, the exchange() method is deprecated due to potential memory and connection leaks. Using exchangeToMono(), exchangeToFlux(), or retrieve() are preferred methods.

2. Using WebClient to Call a POST Request and Handle Response

In these examples, we are writing code for a service that prepares the data, sends it to a remote API for creating a resource, and finally handles the response. Only two parties are involved here.

Spring WebClient Interaction between Client and Server

2.1. Remote API returns the Location Header and Empty Response Body

This is the most common usecase where we create a new resource and the server responds with the Location header of the newly created resource.

The object, for which the resource has to be created, is sent as the serialized JSON/XML string in the request body and setting the appropriate content type. The serialization is handled by the Spring framework automatically.

In the following code snippet, the bodyValue() function sets the request body and contentType() is set to APPLICATION_JSON. The retrieve() method triggers the request and returns a ResponseEntity without a body.

Employee newEmployee = ...;  //Create a new employee object

webClient.post()
  .uri("/employees")
  .bodyValue(BodyInserters.fromValue(newEmployee))
  .retrieve()
  .toBodilessEntity() 
  .subscribe(
    responseEntity -> {
      // Handle success response here
      HttpStatusCode status = responseEntity.getStatusCode();
      URI location = responseEntity.getHeaders().getLocation();
      // handle response as necessary
    },
    error -> {
      // Handle the error here
      if (error instanceof WebClientResponseException) {
        WebClientResponseException ex = (WebClientResponseException) error;
        HttpStatusCode status = ex.getStatusCode();
        System.out.println("Error Status Code: " + status.value());
        //...
      } else {
        // Handle other types of errors
        System.err.println("An unexpected error occurred: " + error.getMessage());
      }
    }
  );

2.2. Remote API returns Success Response with Response Body

If there is a response body and we want to fetch it from the API response, we need to replace toBodilessEntity() to toEntity(Employee.class) method.

webClient.post()
  .uri("/employees")
  .bodyValue(BodyInserters.fromValue(newEmployee))
  .retrieve()
  .toEntity(Employee.class)   //Change here
  .subscribe(
    responseEntity -> {
      // Handle success response here
      HttpStatusCode status = responseEntity.getStatusCode();
      URI location = responseEntity.getHeaders().getLocation();
      Employee createdEmployee = responseEntity.getBody();    // Response body
      // handle response as necessary
    },
    error -> {
      // Handle the error here
      if (error instanceof WebClientResponseException) {
        WebClientResponseException ex = (WebClientResponseException) error;
        HttpStatusCode status = ex.getStatusCode();
        System.out.println("Error Status Code: " + status.value());
        //...
      } else {
        // Handle other types of errors
        System.err.println("An unexpected error occurred: " + error.getMessage());
      }
    }
  );

3. Using WebClient to Call a POST Request and Pass Response to API Client

Many times, we will write code for an intermediary service that accepts requests from API/UI clients, sends them to remote APIs and returns the response to the API/UI clients. In such cases, after making the request, we must return a Mono or Flux to the API/UI client to keep the whole flow truly reactive.

Spring WebClient Interaction between UI, API Client and Server.png

Generally, these solutions will create a REST controller and a service class. The controller will handle the requests from UI and invoke the methods in Service, which in turn will execute the remote APIs.

3.1. Response with Location Header and Empty Response Body

The Service class can be written as:

@Service
public class EmployeeService {

  private final WebClient webClient;

  @Autowired
  public EmployeeService(WebClient webClient) {
    this.webClient = webClient;
  }

  public Mono<ResponseEntity<Void>> createEmployee(Employee newEmployee) {

    return webClient.post()
      .uri("/employees")
      .contentType(MediaType.APPLICATION_JSON)
      .bodyValue(newEmployee)
      .retrieve()
      .toBodilessEntity();
  }
}

Next, we call this API in some other class such as API controller. It returns a response with a status code of 201 (Created) and a Location header indicating the location of the newly created resource.

@PostMapping("/create")
public Mono<ResponseEntity<String>> createEmployee(@RequestBody Employee newEmployee) {

  return employeeService.createEmployee(newEmployee)
    .map(responseEntity -> {
      if (responseEntity.getStatusCode().is2xxSuccessful()) {

          String employeeId = createdEmployee.getId();
          
          String locationUri = ServletUriComponentsBuilder
                  .fromCurrentRequest()
                  .path("/{id}")
                  .buildAndExpand(employeeId)
                  .toUriString();

          return ResponseEntity
                  .status(HttpStatus.CREATED)
                  .header("Location", locationUri)
                  .body("Employee created successfully. Location: " + locationUri);
      } else {
          return ResponseEntity.status(responseEntity.getStatusCode()).body("Failed to create employee");
      }
    });
}

3.2. Success Response with Response Body

The service class code will be:

public Mono<ResponseEntity<Employee>> createEmployee(Employee newEmployee) {

  return webClient.post()
    .uri("/employees")
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue(newEmployee)
    .retrieve()
    .toEntity(Employee.class);
}

Next, we can use this API in the controller class as follows:

@PostMapping("/create")
public Mono<ResponseEntity<?>> createEmployee(@RequestBody Employee newEmployee) {

  return employeeService.createEmployee(newEmployee)
    .map(responseEntity -> {
      if (responseEntity.getStatusCode().is2xxSuccessful()) {
        return ResponseEntity.ok(responseEntity.getBody());
      } else {
        return ResponseEntity.status(responseEntity.getStatusCode())
          .body("Failed to create employee");
      }
    })
    .onErrorResume(exception -> {
      return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
        .body("Internal Server Error: " + exception.getMessage()));
    });
}

4. Using WebClient to Submit Form Data

Another common usecase for WebClient is to post data in the MVC form style. In this case, we use BodyInserters.fromFormData() to create the form data by adding key-value pairs where keys are form fields and values are input field values.

The added fields are posted to the API URL as URL-encoded form data.

@Service
public class EmployeeService {

  private final WebClient webClient;

  @Autowired
  public EmployeeService(WebClient webClient) {
      this.webClient = webClient;
  }

  public Mono<Employee> createEmployee(Map<String, String> formParams) {
    return webClient.post()
      .uri("/employees")
      .body(BodyInserters.fromFormData("id", formParams.get("id"))
        .with("name", formParams.get("name"))
        .with("status", formParams.get("status")))
      .retrieve()
      .onStatus(HttpStatus::is4xxClientError, clientResponse -> {
          // Handle 4xx client errors here
      })
      .onStatus(HttpStatus::is5xxServerError, clientResponse -> {
          // Handle 5xx server errors here
      })
      .toEntity(Employee.class)
      .flatMap(responseEntity -> Mono.justOrEmpty(responseEntity.getBody()));
  }
}

From the controller, we can call this method as follows:

@PostMapping("/create-employee")
public Mono<ResponseEntity<Employee>> createEmployee(@RequestBody Map<String, String> formParams) {
  return employeeService.createEmployee(formParams)
          .map(employee -> ResponseEntity.ok(employee));
}

5. Using WebClient to Upload a Multipart File

To submit a form with multipart form data and simple fields, we can use the MultipartBodyBuilder class to add different parts of the input form.

MultipartBodyBuilder builder = new MultipartBodyBuilder();

builder.part("file", new FileSystemResource("c:/temp/file-name.txt"));
builder.part("id", "190001", MediaType.TEXT_PLAIN);
builder.part("name", "Lokesh", MediaType.TEXT_PLAIN);
builder.part("status", "active", MediaType.TEXT_PLAIN);

Then we can submit the multipart form data by using the method BodyInserters.fromMultipartData(builder.build()) and send a normal request as in the previous examples.

webClient.post()
    .uri("/employees")
    .contentType(MediaType.MULTIPART_FORM_DATA)
    .body(BodyInserters.fromMultipartData(builder.build()))
    .retrieve()
    .toEntity(Employee.class)
    .doOnError(WriteTimeoutException.class, ex -> {
      System.err.println("WriteTimeout");
    })
    .subscribe(responseEntity -> {
      System.out.println("Status: " + responseEntity.getStatusCode().value());
      System.out.println("Location URI: " + responseEntity.getHeaders().getLocation().toString());
      System.out.println("Created New Employee : " + responseEntity.getBody());
    });

6. Sending URL/Query Parameters using WebClient

Generally, there is no need to send the query parameters in a POST request. We should add all the needed information in the request body itself.

But if in any case, we need to send the URL parameters in any POST request, we can use UriComponentsBuilder to build the request URI with the parameters in it.

String endpoint = "/employees";
        
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromPath(endpoint)
    .queryParam("param1", "value1")
    .queryParam("param2", "value2");

webClient.post()
        .uri(uriBuilder.build().toUri())
        .bodyValue(new Employee(...))
        .retrieve()
        ...

7. Sending Request Headers using WebClient

The custom request headers are set using the header() method while building the WebClient request. The headers can be needed for many reasons such as authorization information, user agent, API version information, preferred data center location etc.

webClient.get()
    .uri("/employees")
    .bodyValue(new Employee(...))
    .header("Authorization", "Bearer auth-token")
    .header("User-Agent", "Mobile App 1.0")
    .retrieve()
    ...

8. Conclusion

In this short Spring Boot WebClient example, we learned to send HTTP POST requests to REST APIs and handle the response status, headers and body.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.