Building REST Clients with Spring 6 RestClient
Building REST Clients with Spring 6 RestClient
Spring Framework has always been at the forefront of making REST client development simple and intuitive. With the release of Spring 6 and Spring Boot 3.x, the team introduced a new, modern HTTP client: RestClient. This synchronous client is designed to replace the aging RestTemplate and provide a fluent, functional API that aligns with the reactive WebClient while remaining synchronous.
In this post, I’ll walk you through everything you need to know about building REST clients with Spring 6’s RestClient—from basic setup to advanced error handling and testing.
Why RestClient?
Before diving into code, let’s understand why RestClient exists. RestTemplate has been the go-to synchronous HTTP client for Spring applications since Spring 3.0. However, it has several drawbacks:
- Deprecated since Spring 5.0: While still functional, it’s not getting new features.
- Verbose API: Requires manual handling of headers, parameters, and body serialization.
- Error-prone: Exceptions are unchecked, leading to less predictable error handling.
- Tight coupling: Hard to mock and test in isolation.
RestClient addresses all these issues by providing:
- A fluent, builder-based API
- Built-in support for JSON/XML serialization via
HttpMessageConverter - Clear separation of request preparation and response handling
- First-class error handling with
StatusHandler - Easy integration with Spring’s testing utilities
Setting Up RestClient
To use RestClient, you need Spring Boot 3.x (which ships with Spring 6). Add the following dependency to your pom.xml:
1 | <dependency> |
That’s it. The RestClient is part of spring-web, which is included transitively.
Creating a RestClient Bean
While you can create RestClient instances on the fly, it’s better to define a bean for consistent configuration:
1 |
|
Spring Boot automatically provides a RestClient.Builder bean pre-configured with sensible defaults (like Jackson for JSON, connection timeouts, etc.). You can customize it further using RestClientCustomizer beans or application properties.
Making GET Requests
Let’s start with the most common operation—fetching data via GET.
Simple GET with Response Body
1 |
|
This is clean and readable. The URI template {id} is automatically expanded, and the response body is deserialized to User using Jackson.
GET with Query Parameters
1 | public List<User> searchUsers(String name, int page) { |
Notice the use of ParameterizedTypeReference to handle generic types like List<User>. This avoids unchecked casts.
Making POST Requests
Creating resources is just as straightforward:
1 | public User createUser(User newUser) { |
The .body() method accepts any object; Jackson serializes it automatically. You can also send MultiValueMap for form data or raw strings.
POST with Custom Headers
1 | public User createUserWithAuth(User newUser, String token) { |
PUT and DELETE Requests
Updating a Resource (PUT)
1 | public User updateUser(Long id, User updatedUser) { |
Deleting a Resource
1 | public void deleteUser(Long id) { |
toBodilessEntity() returns a ResponseEntity<Void>, which is useful when you don’t expect a response body.
Error Handling with StatusHandler
One of the biggest improvements over RestTemplate is the declarative error handling. Instead of catching HttpClientErrorException or HttpServerErrorException, you can define handlers for specific HTTP status codes:
1 | public User getUserByIdSafe(Long id) { |
You can also define a default error handler:
1 | .onStatus(HttpStatus::isError, (request, response) -> { |
This pattern makes error handling predictable and testable.
Working with ResponseEntity
Sometimes you need access to the full HTTP response, including headers and status code:
1 | public ResponseEntity<User> getUserWithResponse(Long id) { |
This returns a ResponseEntity<User> containing status, headers, and body.
Exchange Methods for Advanced Scenarios
If you need to intercept or modify the response before deserialization (e.g., to handle streaming or custom parsing), use the exchange method:
1 | public String getRawResponse(Long id) { |
This gives you full control over the ClientHttpResponse.
Timeouts and Connection Management
You can configure timeouts globally via application properties:
1 | spring: |
Or programmatically using a RestClientCustomizer:
1 |
|
Testing RestClient
Spring Boot provides excellent support for testing RestClient using MockRestServiceServer from spring-boot-starter-test:
1 |
|
This approach allows you to test your service logic without hitting real endpoints, making tests fast and reliable.
Migrating from RestTemplate
If you have existing RestTemplate code, migration is straightforward. Here’s a quick reference:
| RestTemplate | RestClient |
|---|---|
restTemplate.getForObject(url, Class) |
restClient.get().uri(url).retrieve().body(Class) |
restTemplate.postForObject(url, request, Class) |
restClient.post().uri(url).body(request).retrieve().body(Class) |
restTemplate.exchange(url, HttpMethod, entity, Class) |
restClient.method(HttpMethod).uri(url).headers(headers).body(body).retrieve().toEntity(Class) |
restTemplate.execute(...) |
restClient.method(HttpMethod).uri(url).exchange(...) |
Real-World Example: Complete Service
Let’s put it all together with a realistic example—a service that interacts with a paginated API:
1 |
|
Best Practices
- Always define a base URL in the bean configuration to avoid duplication.
- Use
ParameterizedTypeReferencefor generic collections. - Handle errors explicitly with
onStatusrather than relying on runtime exceptions. - Inject
RestClient.Builderinstead ofRestClientto allow customization. - Test with
MockRestServiceServerto validate request/response flows. - Configure timeouts globally in application properties.
- Use
exchangesparingly—only when you need low-level access.
Key Takeaways
- Spring 6’s
RestClientis the modern replacement forRestTemplate, offering a fluent, functional API. - Fluent builder pattern makes request construction intuitive and readable.
- Declarative error handling with
onStatussimplifies exception management and testing. - Seamless integration with Jackson, Spring Boot auto-configuration, and testing utilities.
- Migration from
RestTemplateis straightforward, with one-to-one mapping for common operations. - Best practices include using
ParameterizedTypeReference, configuring timeouts, and leveragingMockRestServiceServerfor unit tests.
Whether you’re starting a new project or maintaining a legacy codebase, RestClient is the way forward for synchronous HTTP communication in Spring applications. Its clean API and robust error handling make it a joy to work with—and your future self will thank you for making the switch.