DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Spring Boot 3.2: Replace Your RestTemplate With RestClient
  • Serverless Patterns: Web
  • Building REST API Backend Easily With Ballerina Language
  • Translating OData Queries to MongoDB in Java With Jamolingo

Trending

  • Monitoring Spring Boot Applications with Prometheus and Grafana
  • Working With Cowork: Don’t Be Confused
  • From APIs to Event-Driven Systems: Modern Java Backend Design
  • Architectural Evidence in Enterprise Java: Making Domain-Driven Design Visible
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Spring REST API Client Flavors: From RestTemplate to RestClient

Spring REST API Client Flavors: From RestTemplate to RestClient

Client-server synchronous communication via REST, focusing on the client while presenting two distinct implementations with RestTemplate and RestClient.

By 
Horatiu Dan user avatar
Horatiu Dan
DZone Core CORE ·
Sep. 22, 25 · Analysis
Likes (5)
Comment
Save
Tweet
Share
3.4K Views

Join the DZone community and get the full member experience.

Join For Free

Just as humans have always preferred co-existing and communicating ideas, looking for and providing pieces of advice from and to their fellow humans, applications nowadays find themselves in the same situation, where they need to exchange data in order to collaborate and fulfill their purposes.

At a very high level, applications’ interactions are carried out either conversationally (the case of REST APIs), where the information is exchanged synchronously by asking and responding, or asynchronously via notifications (the case of event-driven APIs), where data is sent by producers and picked up by consumers as it becomes available and they are ready.

This article is an analysis of the synchronous communication between a client and a server via REST, with a focus on the client part. Its main purpose is to present how a Spring REST API client can be implemented, first using the RestTemplate, then the newer RestClient and seamlessly accomplish the same interaction.

A Brief History

RestTemplate was introduced in Spring Framework version 3.0, and according to the API reference, it’s a “synchronous client to perform HTTP requests, exposing a simple, template method API over underlying HTTP client libraries.” Flexible and highly configurable, it’s been for a long time the best choice when a fully-fledged synchronous but blocking HTTP client was implemented as part of a Spring application. As time has passed, its lack of non-blocking capabilities, the use of the old-fashioned template pattern, and the pretty cumbersome API significantly contributed to the emergence of a new, more modern HTTP client library, one that may also handle non-blocking and asynchronous calls.

Spring Framework version 5.0 introduced WebClient, “a fluent, reactive API, over underlying HTTP client libraries.” It was especially designed for the WebFlux stack and by following the modern and functional API style, it was much cleaner and easier to use by developers. Nevertheless, for blocking scenarios, WebClient‘s ease of use benefit came with an extra cost – the need to add an additional library dependency into the project.

Starting with Spring Framework version 6.1 and Spring Boot version 3.2, a new component is available — RestClient — which “offers a more modern API for synchronous HTTP access.”

The evolution has been quite significant, developers nowadays may choose among these three options, RestTemplate, WebClient and RestClient, depending on the application needs and particularities.

Implementation

As stated above, the proof of concept in this article experiments with both RestTemplate and RestClient, leaving the WebClient aside as the communication here is conversational, that is synchronous.

There are two simple actors involved, two applications:

  • figure-service – the server that exposes the REST API and allows managing Figures
  • figure-client – the client that consumes the REST API and actually manages the Figures

Both are custom-made and use Java 21, Spring Boot version 3.5.3, and Maven version 3.9.9.

A Figure is a generic entity that could denote a fictional character, a superhero, or a Lego mini-figure, for instance.

The Server

figure-service is a small service that allows performing common CRUD operations on simple entities that represent figures.

As the focus in this article is on the client, server characteristics are only highlighted. The implementation is done in a standard, straightforward manner in accordance with the common best practices.

The service exposes a REST API to manage figures:

  • read all – GET /api/v1/figures
  • read one – GET /api/v1/figures/{id}
  • read a random one – GET /api/v1/figures/random
  • create one – POST /api/v1/figures
  • update one – PUT /api/v1/figures/{id}
  • delete one – DELETE /api/v1/figures/{id}

The operations are secured at a minimum with an API key that shall be available as a request header

Plain Text
 
"x-api-key": the api key


Figure entities are stored in an in-memory H2 database, described by a unique identifier, a name, and a code, and modelled as below: 

Java
 
@Entity
@Table(name = "figures")
public class Figure {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @Column(name = "name", unique = true, nullable = false)
    private String name;
 
    @Column(name = "code", nullable = false)
    private String code;
     
    ...
}


While the id and the name are visible to the outside world, the code is considered business domain information and kept private. Thus, the used DTOs look as below: 

Java
 
public record FigureRequest(String name) {}
 
public record FigureResponse(long id, String name) {}


All server exceptions are handled generically in a single ResponseEntityExceptionHandler and sent back to the client in the following form, with the corresponding HTTP status:

JSON
 
{  
  "title": "Bad Request",
  "status": 400,
  "detail": "Figure not found.",
  "instance": "/api/v1/figures/100"
}
Java
 
public record ErrorResponse(String title, int status, String detail, String instance) {}


Basically, in this service implementation, a client receives either the aimed response (if any) or one highlighting the error (detailed at point 4), in case there is a service one.

This resource contains the figure-service source code, it may be browsed for additional details.

The Client

Let’s assume figure-client is an application that uses Figure entities as part of its business operations. As these are managed and exposed by the figure-service, the client needs to communicate via REST with the server, but also to comply with the contract and the requirements of the service provider.

In this direction, a few considerations are needed prior to the actual implementation.

Contract

Since the synchronous communication is first implemented using a RestTemplate, then modified to use the RestClient, the client operations are outlined in the interface below.

Java
 
public interface FigureClient {
 
    List<Figure> allFigures();
 
    Optional<Figure> oneFigure(long id);
 
    Figure createFigure(FigureRequest figure);
 
    Figure updateFigure(long id, FigureRequest figureRequest);
 
    void deleteFigure(long id);
 
    Figure randomFigure();
}


In this manner, the implementation change is isolated and does not impact other application parts. 

Authentication

As the server access is secured, a valid API key is needed. Once available, it is stored as an environment variable and used via the application.properties into a ClientHttpRequestInterceptor. According to the API reference, such a component defines the contract to intercept client-side HTTP requests and allows implementers to modify the outgoing request and/or incoming response.

For this use case, all requests are intercepted, and the configured API key is set as the x-api-key header, then the execution is resumed.

Java
 
@Component
public class AuthInterceptor implements ClientHttpRequestInterceptor {
 
    private final String apiKey;
 
    public AuthInterceptor(@Value("${figure.service.api.key}") String apiKey) {
        this.apiKey = apiKey;
    }
 
    @Override
    public ClientHttpResponse intercept(HttpRequest request,
                                        byte[] body,
                                        ClientHttpRequestExecution execution) throws IOException {
        request.getHeaders()
                .add("x-api-key", apiKey);
        return execution.execute(request, body);
    }
}


The AuthInterceptor is used in the RestTemplate configuration. 

Data Transfer Objects (DTOs)

Particularly in this POC, as the Figure entities are trivial in terms of the attributes that describe them, the DTOs used in the operations of interest and by the RestTemplate are simple as well.

Java
 
public record FigureRequest(String name) {}
 
public record Figure(long id, String name) {}


Since once read, Figure objects might be further used, their name was simplified, although they denote response DTOs. 

Exception Handling

RestTemplate (and then RestClient) allows setting a ResponseErrorHandler implementation during its configuration, a strategy interface used to determine whether a particular response has errors or not, and permits custom handling.

In this POC, as the figure-service sends all errors in the same form, it is very convenient and easy to adopt a generic handling manner.

Java
 
@Component
public class CustomResponseErrorHandler implements ResponseErrorHandler {
 
    private static final Logger log = LoggerFactory.getLogger(CustomResponseErrorHandler.class);
 
    private final ObjectMapper objectMapper;
 
    public CustomResponseErrorHandler() {
        objectMapper = new ObjectMapper();
    }
 
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        return response.getStatusCode().isError();
    }
 
    @Override
    public void handleError(URI url, HttpMethod method,
                            ClientHttpResponse response) throws IOException {
        HttpStatusCode statusCode = response.getStatusCode();
        String body = new String(response.getBody().readAllBytes());
 
        if (statusCode.is4xxClientError()) {
            throw new CustomException("Client error.", statusCode, body);
        }
 
        String message = null;
        try {
            message = objectMapper.readValue(body, ErrorResponse.class).detail();
        } catch (JsonProcessingException e) {
            log.error("Failed to parse response body: {}", e.getMessage(), e);
        }
 
        throw new CustomException(message, statusCode, body);
    }
 
    @JsonIgnoreProperties(ignoreUnknown = true)
    private record ErrorResponse(String detail) {}
}


The logic here is the following:

  • Both client and server errors are considered and handled — see hasError() method.
  • All errors result in a custom RuntimeException decorated with an HTTP status code and a detail, the default being the general Internal Server Error and the raw response body, respectively.
Java
 
public class CustomException extends RuntimeException {
 
    private final HttpStatusCode statusCode;
    private final String detail;
 
    public CustomException(String message) {
        super(message);
        this.statusCode = HttpStatusCode.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        this.detail = null;
    }
 
    public CustomException(String message, HttpStatusCode statusCode, String detail) {
        super(message);
        this.statusCode = statusCode;
        this.detail = detail;
    }
 
    public HttpStatusCode getStatusCode() {
        return statusCode;
    }
 
    public String getDetail() {
        return detail;
    }
}


In case of recoverable errors, all methods declared in FigureClient are throwing CustomExceptions, thus providing a simple exception handling mechanism.

  • An extraction of the detail provided by the figure-service in the response body is first attempted and if possible, included in the CustomException, otherwise, the body is set as such

Useful to Have

Although not required, especially during development, but not only, it proves very useful to be able to see the requests and the responses exchanged in the logs of the client application. In order to accomplish this, a LoggingInterceptor is added to the RestTemplate configuration.

Java
 
@Component
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
 
    private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
 
    @Override
    public ClientHttpResponse intercept(HttpRequest request,
                                        byte[] body,
                                        ClientHttpRequestExecution execution) throws IOException {
        logRequest(body);
 
        ClientHttpResponse response = execution.execute(request, body);
 
        logResponse(response);
        return response;
    }
 
    private void logRequest(byte[] body) {
        var bodyContent = new String(body);
        log.debug("Request body : {}", bodyContent);
    }
 
    private void logResponse(ClientHttpResponse response) throws IOException {
        var bodyContent = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset());
        log.debug("Response body: {}", bodyContent);
    }
}


Here, only the request and response bodies are logged, although other items might be of interest as well (headers, response statuses, etc.). Although useful, there is a gotcha worth explaining that needs to be taken into account.

As it can be depicted, when the response is logged in the above interceptor, it is basically read and the stream “consumed”, which determines the client to eventually end up with an empty body. To prevent this, a BufferingClientHttpRequestFactory component shall be used, a component that allows buffering the stream content into memory and thus be able to read the response twice. The response availability is now resolved, but buffering the entire response body into memory might not be a good idea when its size is significant. Before jumping into blindly using it out of the box, developers should analyze the possible performance impact and adapt, particularly for each application.

Configuration

Having clarified the figure-service contract and requirements, and moreover, having already implemented certain “pieces,” the RestTemplate can now be configured.

Java
 
@Bean
public RestOperations restTemplate(LoggingInterceptor loggingInterceptor,
                                   AuthInterceptor authInterceptor,
                                   CustomResponseErrorHandler customResponseErrorHandler) {
                                    
    RestTemplateCustomizer customizer = restTemplate -> restTemplate.getInterceptors()
            .addAll(List.of(loggingInterceptor, authInterceptor));
 
    return new RestTemplateBuilder(customizer)
            .requestFactory(() -> new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
            .errorHandler(customResponseErrorHandler)
            .build();
}


A RestTemplateBuilder is used, the LoggingInterceptor, AuthInterceptor are added via a RestTemplateCustomizer, while the error handler is set to a CustomResponseErrorHandler instance.

RestTemplate Implementation

Once the RestTemplate instance is constructed, it can be injected into the actual FigureClient implementation and used to communicate with the figure-service.

Java
 
@Service
public class FigureRestTemplateClient implements FigureClient {
 
    private final String url;
    private final RestOperations restOperations;
 
    public FigureRestTemplateClient(@Value("${figure.service.url}") String url,
                                    RestOperations restOperations) {
        this.url = url;
        this.restOperations = restOperations;
    }
 
    @Override
    public List<Figure> allFigures() {
        ResponseEntity<Figure[]> response = restOperations.exchange(url,
                HttpMethod.GET, null, Figure[].class);
 
        Figure[] figures = response.getBody();
        if (figures == null) {
            throw new CustomException("Could not get the figures.");
        }
        return List.of(figures);
    }
 
    @Override
    public Optional<Figure> oneFigure(long id) {
        ResponseEntity<Figure> response = restOperations.exchange(url + "/{id}",
                HttpMethod.GET, null, Figure.class, id);
 
        Figure figure = response.getBody();
        if (figure == null) {
            return Optional.empty();
        }
        return Optional.of(figure);
    }
 
    @Override
    public Figure createFigure(FigureRequest figureRequest) {
        HttpEntity<FigureRequest> request = new HttpEntity<>(figureRequest);
        ResponseEntity<Figure> response = restOperations.exchange(url,
                HttpMethod.POST, request, Figure.class);
 
        Figure figure = response.getBody();
        if (figure == null) {
            throw new CustomException("Could not create figure.");
        }
        return figure;
    }
 
    @Override
    public Figure updateFigure(long id, FigureRequest figureRequest) {
        HttpEntity<FigureRequest> request = new HttpEntity<>(figureRequest);
        ResponseEntity<Figure> response = restOperations.exchange(url + "/{id}",
                HttpMethod.PUT, request, Figure.class, id);
 
        Figure figure = response.getBody();
        if (figure == null) {
            throw new CustomException("Could not update figure.");
        }
        return figure;
    }
 
    @Override
    public void deleteFigure(long id) {
        restOperations.exchange(url + "/{id}",
                HttpMethod.DELETE, null, Void.class, id);
    }
 
    @Override
    public Figure randomFigure() {
        ResponseEntity<Figure> response = restOperations.exchange(url + "/random",
                HttpMethod.GET, null, Figure.class);
 
        Figure figure = response.getBody();
        if (figure == null) {
            throw new CustomException("Could not get a random figure.");
        }
        return figure;
    }
}


In order to observe how this solution works end-to-end, first, the figure-service is started. A CommandLineRunner is configured there, so that a few Figure entities are persisted into the database. 

Java
 
@Bean
public CommandLineRunner initDatabase(FigureService figureService) {
    return args -> {
        log.info("Loading data...");
        figureService.create(new Figure("Lloyd"));
        figureService.create(new Figure("Jay"));
        figureService.create(new Figure("Kay"));
        figureService.create(new Figure("Cole"));
        figureService.create(new Figure("Zane"));
 
        log.info("Available figures:");
        figureService.findAll()
                .forEach(figure -> log.info("{}", figure));
    };
}


Then, as part of the figure-client application, a FigureRestTemplateClient instance is injected into the following integration test. 

Java
 
@SpringBootTest
class FigureClientTest {
 
    @Autowired
    private FigureRestTemplateClient figureClient;
 
    @Test
    void allFigures() {
        List<Figure> figures = figureClient.allFigures();
        Assertions.assertFalse(figures.isEmpty());
    }
 
    @Test
    void oneFigure() {
        long id = figureClient.allFigures().stream()
                .findFirst()
                .orElseThrow(() -> new RuntimeException("No figures found"))
                .id();
 
        Optional<Figure> figure = figureClient.oneFigure(id);
        Assertions.assertTrue(figure.isPresent());
    }
 
    @Test
    void createFigure() {
        var request  = new FigureRequest( "Fig " + UUID.randomUUID());
 
        Figure figure = figureClient.createFigure(request);
        Assertions.assertNotNull(figure);
        Assertions.assertTrue(figure.id() > 0L);
        Assertions.assertEquals(request.name(), figure.name());
 
        CustomException ex = Assertions.assertThrows(CustomException.class,
                () -> figureClient.createFigure(request));
        Assertions.assertEquals("A Figure with the same 'name' already exists.", ex.getMessage());
        Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), ex.getStatusCode().value());
        Assertions.assertEquals("""
                {"title":"Bad Request","status":400,"detail":"A Figure with the same 'name' already exists.","instance":"/api/v1/figures"}""", ex.getDetail());
    }
 
    @Test
    void updateFigure() {
        List<Figure> figures = figureClient.allFigures();
        long id = figures.stream()
                .findFirst()
                .orElseThrow(() -> new RuntimeException("No figures found"))
                .id();
 
        var updatedRequest = new FigureRequest("Updated Fig " + UUID.randomUUID());
        Figure updatedFigure = figureClient.updateFigure(id, updatedRequest);
        Assertions.assertNotNull(updatedFigure);
        Assertions.assertEquals(id, updatedFigure.id());
        Assertions.assertEquals(updatedRequest.name(), updatedFigure.name());
 
        Figure otherExistingFigure = figures.stream()
                .filter(f -> f.id() != id)
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Not enough figures"));
 
        var updateExistingRequest = new FigureRequest(otherExistingFigure.name());
        CustomException ex = Assertions.assertThrows(CustomException.class,
                () -> figureClient.updateFigure(id, updateExistingRequest));
        Assertions.assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getStatusCode().value());
    }
 
    @Test
    void deleteFigure() {
        long id = figureClient.allFigures().stream()
                .findFirst()
                .orElseThrow(() -> new RuntimeException("No figures found"))
                .id();
 
        figureClient.deleteFigure(id);
 
        CustomException ex = Assertions.assertThrows(CustomException.class,
                () -> figureClient.deleteFigure(id));
        Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), ex.getStatusCode().value());
        Assertions.assertEquals("Figure not found.", ex.getMessage());
    }
 
    @Test
    void randomFigure() {
        CustomException ex = Assertions.assertThrows(CustomException.class,
                () -> figureClient.randomFigure());
        Assertions.assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getStatusCode().value());
        Assertions.assertEquals("Not implemented yet.", ex.getMessage());
    }
}


When running, for instance, the above createFigure() test, the RestTemplate and the LoggingInterceptor contribute to clearly describe what’s happening and display it in the client log: 

Plain Text
 
[main] DEBUG RestTemplate#HTTP POST http://localhost:8082/api/v1/figures
[main] DEBUG InternalLoggerFactory#Using SLF4J as the default logging framework
[main] DEBUG RestTemplate#Accept=[application/json, application/*+json]
[main] DEBUG RestTemplate#Writing [FigureRequest[name=Fig 6aa854a5-ba7a-4bbf-8160-70adf7d3e59b]] with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
[main] DEBUG LoggingInterceptor#Request body : {"name":"Fig 6aa854a5-ba7a-4bbf-8160-70adf7d3e59b"}
[main] DEBUG LoggingInterceptor#Response body: {"id":8,"name":"Fig 6aa854a5-ba7a-4bbf-8160-70adf7d3e59b"}
[main] DEBUG RestTemplate#Response 201 CREATED
[main] DEBUG RestTemplate#Reading to [com.hcd.figureclient.service.dto.Figure]
[main] DEBUG RestTemplate#HTTP POST http://localhost:8082/api/v1/figures
[main] DEBUG RestTemplate#Accept=[application/json, application/*+json]
[main] DEBUG RestTemplate#Writing [FigureRequest[name=Fig 6aa854a5-ba7a-4bbf-8160-70adf7d3e59b]] with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
[main] DEBUG LoggingInterceptor#Request body : {"name":"Fig 6aa854a5-ba7a-4bbf-8160-70adf7d3e59b"}
[main] DEBUG LoggingInterceptor#Response body: {"title":"Bad Request","status":400,"detail":"A Figure with the same 'name' already exists.","instance":"/api/v1/figures"}
[main] DEBUG RestTemplate#Response 400 BAD_REQUEST


And with that, a client implementation using RestTemplate is complete.

RestClient Implementation

The aim here, as stated from the beginning, is to be able to accomplish the same, but instead of using RestTemplate, to use a RestClient instance.

As the LoggingInterceptor, AuthInterceptor and the CustomResponseErrorHandler can be reused, they are not changed, and the RestClient configured as below.

Java
 
@Bean
public RestClient restClient(@Value("${figure.service.url}") String url,
                             LoggingInterceptor loggingInterceptor,
                             AuthInterceptor authInterceptor,
                             CustomResponseErrorHandler customResponseErrorHandler) {
    return RestClient.builder()
            .baseUrl(url)
            .requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
            .requestInterceptor(loggingInterceptor)
            .requestInterceptor(authInterceptor)
            .defaultStatusHandler(customResponseErrorHandler)
            .build();
}


Then, the instance is injected into a new FigureClient implementation. 

Java
 
@Service
public class FigureRestClient implements FigureClient {
 
    private final RestClient restClient;
 
    public FigureRestClient(RestClient restClient) {
        this.restClient = restClient;
    }
 
    @Override
    public List<Figure> allFigures() {
        var figures = restClient.get()
                .retrieve()
                .body(Figure[].class);
 
        if (figures == null) {
            throw new CustomException("Could not get the figures.");
        }
        return List.of(figures);
    }
 
    @Override
    public Optional<Figure> oneFigure(long id) {
        var figure = restClient.get()
                .uri("/{id}", id)
                .retrieve()
                .body(Figure.class);
 
        return Optional.ofNullable(figure);
    }
 
    @Override
    public Figure createFigure(FigureRequest figureRequest) {
        var figure = restClient.post()
                .contentType(MediaType.APPLICATION_JSON)
                .body(figureRequest)
                .retrieve()
                .body(Figure.class);
 
        if (figure == null) {
            throw new CustomException("Could not create figure.");
        }
        return figure;
    }
 
    @Override
    public Figure updateFigure(long id, FigureRequest figureRequest) {
        var figure = restClient.put()
                .uri("/{id}", id)
                .contentType(MediaType.APPLICATION_JSON)
                .body(figureRequest)
                .retrieve()
                .body(Figure.class);
 
        if (figure == null) {
            throw new CustomException("Could not update figure.");
        }
        return figure;
    }
 
    @Override
    public void deleteFigure(long id) {
        restClient.delete()
                .uri("/{id}", id)
                .retrieve()
                .toBodilessEntity();
    }
 
    @Override
    public Figure randomFigure() {
        var figure = restClient.get()
                .uri("/random")
                .retrieve()
                .body(Figure.class);
 
        if (figure == null) {
            throw new CustomException("Could not get a random figure.");
        }
        return figure;
    }
}


In addition to these, there is only one important step left: to test the client-server integration. In order to fulfill that, it is enough to replace the FigureRestTemplateClient instance with the FigureRestClient one above in the previous FigureClientTest. 

Java
 
@SpringBootTest
class FigureClientTest {
 
    @Autowired
    private FigureRestClient figureClient;
     
    ...
}


If running, for instance, the same createFigure() test, the client output is similar. Apparently, RestClient is not as generous (or verbose) as RestTemplate when it comes to logging, but there is room for improvement as part of the custom LoggingInterceptor. 

Plain Text
 
[main] DEBUG DefaultRestClient#Writing [FigureRequest[name=Fig 1155fd2c-91fe-486d-aaa3-35bf682629d4]] as "application/json" with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
[main] DEBUG LoggingInterceptor#Request body : {"name":"Fig 1155fd2c-91fe-486d-aaa3-35bf682629d4"}
[main] DEBUG LoggingInterceptor#Response body: {"id":9,"name":"Fig 1155fd2c-91fe-486d-aaa3-35bf682629d4"}
[main] DEBUG DefaultRestClient#Reading to [com.hcd.figureclient.service.dto.Figure]
[main] DEBUG DefaultRestClient#Writing [FigureRequest[name=Fig 1155fd2c-91fe-486d-aaa3-35bf682629d4]] as "application/json" with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
[main] DEBUG LoggingInterceptor#Request body : {"name":"Fig 1155fd2c-91fe-486d-aaa3-35bf682629d4"}
[main] DEBUG LoggingInterceptor#Response body: {"title":"Bad Request","status":400,"detail":"A Figure with the same 'name' already exists.","instance":"/api/v1/figures"}


That’s it, the migration from RestTemplate to RestClient is now complete. 

Conclusions

When it comes to new synchronous API client implementations, I find RestClient the best choice mostly for its functional and fluent API style.

For older projects, which had been started before Spring Framework version 6.1 (Spring Boot 3.2, respectively), introduced RestClient and most probably are still using RestTemplate, I consider the migration worth planning and doing (more details in [Resource 4]). Moreover, the possibility of reusing existing components (ClientHttpRequestInterceptors, ResponseErrorHandlers, etc.) is another incentive for such a migration.

Ultimately, as a last resort, it is even possible to create a RestClient instance using the already configured RestTemplate and go from there, although I find this solution pretty tangled.

Resources

  1. RestTemplate Spring Framework API Reference
  2. WebClient Spring Framework API Reference
  3. RestClient Spring Framework API Reference
  4. Migrating from RestTemplate to RestClient
  5. figure-service source code
  6. figure-client source code
  7. The picture was taken in Bucharest, Romania.
API REST resttemplate Data Types

Published at DZone with permission of Horatiu Dan. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Boot 3.2: Replace Your RestTemplate With RestClient
  • Serverless Patterns: Web
  • Building REST API Backend Easily With Ballerina Language
  • Translating OData Queries to MongoDB in Java With Jamolingo

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook