close up shot of keyboard buttons

Different ways to call REST APIs in Spring Boot without additional libraries

close up shot of keyboard buttons
Photo by Miguel Á. Padriñán on Pexels.com

In today’s world most modern web apps talk to each other through HTTP APIs.

REST API is a popular standard for these HTTP APIs.

So calling a REST API from your backend app is a critical functionality.

Spring Boot provides multiple abstractions to do this in a simple way.

Here are five different ways:

Using RestClient:

Rest Client was recently introduced by Spring.

It offers a fluent API so it is intuitive to write the code.

It is also synchronous.

You first create the rest client using create() method and then call any of the HTTP methods GET/POST/PUT/DELETE.

Here is an example of making a GET request:

RestClient restClient = RestClient.create();

String result = restClient.get()
  .uri("https://thirdpartyapi.com/resource")
  .retrieve()
  .body(String.class);

The code is easy to read and write and is superior to the other ways of making REST API calls!

In the above example we are firing a GET request to the REST API hosted at https://thirdpartyapi.com/resource.

You specify the uri using uri() method ,

You get the response from the API using retrieve() method

And you map the response to the required data type using body() method.

In the above example we just get the response body.

If we need the response status and headers as well you can use toEntity() method instead of body() method like below:

ResponseEntity<String> result = restClient.get()
  .uri("https://thirdpartyapi.com/resource")
  .retrieve()
  .toEntity(String.class);

System.out.println("Response status: " + result.getStatusCode());
System.out.println("Response headers: " + result.getHeaders());
System.out.println("Contents: " + result.getBody());

Here is a sample POST request :

Product product = ..
ResponseEntity<String> response = restClient.post()
  .uri("https://ecommerce.example.com/products")
  .contentType(APPLICATION_JSON)
  .body(product)
  .retrieve()
  .body(String.class);

In the above example we are posting a new product to a REST API ,

The request body is specified using body() method and the product object is passed as an argument to it.

The response body is also specified using body() method and it is mapped to String class (assuming the above REST API returns a string response)

If the REST API does not return any response you can use toBodilessEntity() instead of body(String.class) in the above code.

Error handling:

By default if any error happens while calling a REST API , RestClientException is thrown.

But if you want to throw custom exceptions you can do it this way:

String result = restClient.get()
  .uri("https://test.com/non-existing-api")
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  })
  .body(String.class);

As you see in the above case on receiving a 404 error or any error code starting with 400 we throw a custom exception.

Customized API calls:

Sometimes you may want to do more customization while making API calls.

You may want to use a default URL configured for the Rest Client object instead of specifying it in every call.

You may want to pass some header values in the request.

You may want to intercept the requests.

You may want to convert the response messages in a customized way.

You may want to tell Spring which HTTP client to use in the background ( by default spring uses Apache HTTP client)

Here is a generic example:


RestClient customClient = RestClient.builder()
  .requestFactory(new HttpComponentsClientHttpRequestFactory()) //this is the default request factory which uses Apache HTTP client
  .messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
  .baseUrl("https://test.com")
  .defaultUriVariables(Map.of("variable", "foo"))
  .defaultHeader("My-Header", "Foo")
  .requestInterceptor(myCustomInterceptor)
  .requestInitializer(myCustomInitializer)
  .build();

You may not want to configure all of the above and can use those what you need.

Once the custom client is created then you can use it to make HTTP calls (GET/POST etc)

Using REST Template:

This is the oldest and the standard way to make REST API calls in Spring Boot.

It provides more generic methods like exchange() and execute() where you need to specify which HTTP method to invoke in addition to the other details like the URL , request etc.

And also it provides more specific methods like getForEntity() , postForEntity() etc for specific HTTP methods.

Here are few examples:

@Autowired
private RestTemplate restTemplate;

// GET request
public ResponseEntity<String> getExample(String url) {
    return restTemplate.getForEntity(url, String.class);
}


// POST request
public ResponseEntity<String> postExample(String url, Object request) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<Object> entity = new HttpEntity<>(request, headers);
    return restTemplate.postForEntity(url, entity, String.class);
}


// PUT request
public void putExample(String url, Object request) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<Object> entity = new HttpEntity<>(request, headers);
    restTemplate.put(url, entity);
}


// DELETE request
public void deleteExample(String url) {
    restTemplate.delete(url);
}

In these examples, the RestTemplate is used to send HTTP requests for different methods (GET, POST, PUT, and DELETE).

For POST and PUT requests, an HTTP entity is created with a request object and headers, and for DELETE requests, only the URL is required.

The response for GET and POST requests is returned as a ResponseEntity, which contains both the response body and the response headers.

For PUT and DELETE requests, no response is returned, as these methods are used to modify resources on the server, not to retrieve data.

Rest Template calls are synchronous .

This class is also in maintenance mode as given in Spring Docs so you better avoid using them!

Using Spring WebClient:

Spring WebClient supports both synchronous and asynchronous calls and supports reactive programming.

The library is written in fluent API style and is more readable than REST Template.

Here are few examples:

@Autowired
private WebClient webClient;
// GET request
public Mono<String> getExample(String url) {
    return webClient.get().uri(url).retrieve().bodyToMono(String.class);
}


// POST request
public Mono<String> postExample(String url, Object request) {
    return webClient.post().uri(url).contentType(MediaType.APPLICATION_JSON).bodyValue(request).retrieve().bodyToMono(String.class);
}


// PUT request
public Mono<Void> putExample(String url, Object request) {
    return webClient.put().uri(url).contentType(MediaType.APPLICATION_JSON).bodyValue(request).retrieve().bodyToMono(Void.class);
}


// DELETE request
public Mono<Void> deleteExample(String url) {
    return webClient.delete().uri(url).retrieve().bodyToMono(Void.class);
}

In these examples, the WebClient is used to send HTTP requests for different methods (GET, POST, PUT, and DELETE).

For POST and PUT requests, the request body is set with the bodyValue method, and for DELETE requests, only the URL is required.

The response for GET and POST requests is returned as a Mono, which is a reactive stream that represents a single value or an error.

For PUT and DELETE requests, a Mono<Void> is returned, indicating that no response body is expected.

For detailed examples check here:

Spring WebClient was created more for reactive programming than for making synchronous API calls so it is better to use RestClient.

Using Spring OpenFeign:

Spring Open Feign provides an even higher level of abstraction to invoke REST APIs.

It follows a declarative approach where you just declare the APIs you need to call in an interface. This interface is annotated with @FeignClient annotation provided by Spring which will supply the background implementation at run time.

Here are few examples:

@FeignClient(name = "example", url = "http://example.com")
public interface ExampleClient {
    @RequestMapping(method = RequestMethod.GET, value = "/example")
    String getExample();

    @RequestMapping(method = RequestMethod.POST, value = "/example")
    String postExample(@RequestBody Object request);

    @RequestMapping(method = RequestMethod.PUT, value = "/example")
    void putExample(@RequestBody Object request);

    @RequestMapping(method = RequestMethod.DELETE, value = "/example")
    void deleteExample();

}
// In another class:
@Autowired
private ExampleClient exampleClient;
// GET request
public String getExample() {
    return exampleClient.getExample();
}

// POST request
public String postExample(Object request) {
    return exampleClient.postExample(request);
}

// PUT request
public void putExample(Object request) {
    exampleClient.putExample(request);
}

// DELETE request
public void deleteExample() {
    exampleClient.deleteExample();
}

In these examples, an interface ExampleClient is created with @FeignClient annotation, which is a simple HTTP client used to make HTTP requests to a remote server.

The URI is specified on top of the interface.

The interface defines methods for different HTTP methods (GET, POST, PUT, and DELETE) using the @RequestMapping annotation, and the actual implementation of the interface is provided by OpenFeign.

The methods in the ExampleClient interface can be called like regular Java methods, and the responses for GET and POST requests are returned as regular Java objects.

For PUT and DELETE requests, no response is returned, as these methods are used to modify resources on the server, not to retrieve data.

If you don’t want much customization you can go with Spring OpenFeign.

Here is a detailed explanation of the implementation:

Using Java 11 Http Client:

Making HTTP calls in Java was quite an arduous task until Java 11 (without using a framework like Spring).

You had to use an external library like Apache HTTPClient to make those calls.

Thanks to HTTPClient library introduced in Java 11 , making these calls just got easier.

The API is written in fluid style just like RestClient provided by Spring but is a bit more complex.

Here are few examples:

HttpClient client = HttpClient.newBuilder().build();

// GET request
public String getExample(String url) throws IOException, InterruptedException {

    HttpRequest request = HttpRequest.newBuilder()
            .GET()
            .uri(URI.create(url))
            .build();

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

    return response.body();
}

// POST request
public String postExample(String url, Object request) throws IOException, InterruptedException {

    HttpRequest httpRequest = HttpRequest.newBuilder()
            .POST(HttpRequest.BodyPublishers.ofString(new Gson().toJson(request)))
            .header("Content-Type", "application/json")
            .uri(URI.create(url))
            .build();

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

    return response.body();
}

// PUT request
public void putExample(String url, Object request) throws IOException, InterruptedException {

    HttpRequest httpRequest = HttpRequest.newBuilder()
            .PUT(HttpRequest.BodyPublishers.ofString(new Gson().toJson(request)))
            .header("Content-Type", "application/json")
            .uri(URI.create(url))
            .build();

    client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
}

// DELETE request
public void deleteExample(String url) throws IOException, InterruptedException {

    HttpRequest request = HttpRequest.newBuilder().DELETE()
            .uri(URI.create(url))
            .build();

    client.send(request, HttpResponse.BodyHandlers.ofString());
}

In these examples, the HttpClient is used to send HTTP requests for different methods (GET, POST, PUT, and DELETE).

For POST and PUT requests, the request body is created using the HttpRequest.BodyPublishers.ofString method, and for DELETE requests, only the URL is required.

The response for GET and POST requests is returned as a String, which contains the response body.

For PUT and DELETE requests, no response is returned, as these methods are used to modify resources on the server, not to retrieve data. The Gson library is used to convert the request body to a JSON string.

Here is a detailed explanation of HTTP Client :

That’s it!


Posted

in

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading