This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.0! |
REST Clients
The Spring Framework provides the following choices for making calls to REST endpoints:
-
RestClient
- synchronous client with a fluent API. -
WebClient
- non-blocking, reactive client with fluent API. -
RestTemplate
- synchronous client with template method API. -
HTTP Interface - annotated interface with generated, dynamic proxy implementation.
RestClient
The RestClient
is a synchronous HTTP client that offers a modern, fluent API.
It offers an abstraction over HTTP libraries that allows for convenient conversion from a Java object to an HTTP request, and the creation of objects from an HTTP response.
Creating a RestClient
The RestClient
is created using one of the static create
methods.
You can also use builder()
to get a builder with further options, such as specifying which HTTP library to use (see Client Request Factories) and which message converters to use (see HTTP Message Conversion), setting a default URI, default path variables, default request headers, or uriBuilderFactory
, or registering interceptors and initializers.
Once created (or built), the RestClient
can be used safely by multiple threads.
The following sample shows how to create a default RestClient
, and how to build a custom one.
-
Java
-
Kotlin
RestClient defaultClient = RestClient.create();
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
val defaultClient = RestClient.create()
val customClient = RestClient.builder()
.requestFactory(HttpComponentsClientHttpRequestFactory())
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
Using the RestClient
When making an HTTP request with the RestClient
, the first thing to specify is which HTTP method to use.
This can be done with method(HttpMethod)
or with the convenience methods get()
, head()
, post()
, and so on.
Request URL
Next, the request URI can be specified with the uri
methods.
This step is optional and can be skipped if the RestClient
is configured with a default URI.
The URL is typically specified as a String
, with optional URI template variables.
The following example configures a GET request to example.com/orders/42
:
-
Java
-
Kotlin
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
....
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
...
A function can also be used for more controls, such as specifying request parameters.
String URLs are encoded by default, but this can be changed by building a client with a custom uriBuilderFactory
.
The URL can also be provided with a function or as a java.net.URI
, both of which are not encoded.
For more details on working with and encoding URIs, see URI Links.
Request headers and body
If necessary, the HTTP request can be manipulated by adding request headers with header(String, String)
, headers(Consumer<HttpHeaders>
, or with the convenience methods accept(MediaType…)
, acceptCharset(Charset…)
and so on.
For HTTP requests that can contain a body (POST
, PUT
, and PATCH
), additional methods are available: contentType(MediaType)
, and contentLength(long)
.
The request body itself can be set by body(Object)
, which internally uses HTTP Message Conversion.
Alternatively, the request body can be set using a ParameterizedTypeReference
, allowing you to use generics.
Finally, the body can be set to a callback function that writes to an OutputStream
.
Retrieving the response
Once the request has been set up, the HTTP response is accessed by invoking retrieve()
.
The response body can be accessed by using body(Class)
or body(ParameterizedTypeReference)
for parameterized types like lists.
The body
method converts the response contents into various types – for instance, bytes can be converted into a String
, JSON can be converted into objects using Jackson, and so on (see HTTP Message Conversion).
The response can also be converted into a ResponseEntity
, giving access to the response headers as well as the body.
This sample shows how RestClient
can be used to perform a simple GET
request.
-
Java
-
Kotlin
String result = restClient.get() (1)
.uri("https://example.com") (2)
.retrieve() (3)
.body(String.class); (4)
System.out.println(result); (5)
1 | Set up a GET request |
2 | Specify the URL to connect to |
3 | Retrieve the response |
4 | Convert the response into a string |
5 | Print the result |
val result= restClient.get() (1)
.uri("https://example.com") (2)
.retrieve() (3)
.body<String>() (4)
println(result) (5)
1 | Set up a GET request |
2 | Specify the URL to connect to |
3 | Retrieve the response |
4 | Convert the response into a string |
5 | Print the result |
Access to the response status code and headers is provided through ResponseEntity
:
-
Java
-
Kotlin
ResponseEntity<String> result = restClient.get() (1)
.uri("https://example.com") (1)
.retrieve()
.toEntity(String.class); (2)
System.out.println("Response status: " + result.getStatusCode()); (3)
System.out.println("Response headers: " + result.getHeaders()); (3)
System.out.println("Contents: " + result.getBody()); (3)
1 | Set up a GET request for the specified URL |
2 | Convert the response into a ResponseEntity |
3 | Print the result |
val result = restClient.get() (1)
.uri("https://example.com") (1)
.retrieve()
.toEntity<String>() (2)
println("Response status: " + result.statusCode) (3)
println("Response headers: " + result.headers) (3)
println("Contents: " + result.body) (3)
1 | Set up a GET request for the specified URL |
2 | Convert the response into a ResponseEntity |
3 | Print the result |
RestClient
can convert JSON to objects, using the Jackson library.
Note the usage of URI variables in this sample and that the Accept
header is set to JSON.
-
Java
-
Kotlin
int id = ...;
Pet pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) (1)
.accept(APPLICATION_JSON) (2)
.retrieve()
.body(Pet.class); (3)
1 | Using URI variables |
2 | Set the Accept header to application/json |
3 | Convert the JSON response into a Pet domain object |
val id = ...
val pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) (1)
.accept(APPLICATION_JSON) (2)
.retrieve()
.body<Pet>() (3)
1 | Using URI variables |
2 | Set the Accept header to application/json |
3 | Convert the JSON response into a Pet domain object |
In the next sample, RestClient
is used to perform a POST request that contains JSON, which again is converted using Jackson.
-
Java
-
Kotlin
Pet pet = ... (1)
ResponseEntity<Void> response = restClient.post() (2)
.uri("https://petclinic.example.com/pets/new") (2)
.contentType(APPLICATION_JSON) (3)
.body(pet) (4)
.retrieve()
.toBodilessEntity(); (5)
1 | Create a Pet domain object |
2 | Set up a POST request, and the URL to connect to |
3 | Set the Content-Type header to application/json |
4 | Use pet as the request body |
5 | Convert the response into a response entity with no body. |
val pet: Pet = ... (1)
val response = restClient.post() (2)
.uri("https://petclinic.example.com/pets/new") (2)
.contentType(APPLICATION_JSON) (3)
.body(pet) (4)
.retrieve()
.toBodilessEntity() (5)
1 | Create a Pet domain object |
2 | Set up a POST request, and the URL to connect to |
3 | Set the Content-Type header to application/json |
4 | Use pet as the request body |
5 | Convert the response into a response entity with no body. |
Error handling
By default, RestClient
throws a subclass of RestClientException
when retrieving a response with a 4xx or 5xx status code.
This behavior can be overridden using onStatus
.
-
Java
-
Kotlin
String result = restClient.get() (1)
.uri("https://example.com/this-url-does-not-exist") (1)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { (2)
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); (3)
})
.body(String.class);
1 | Create a GET request for a URL that returns a 404 status code |
2 | Set up a status handler for all 4xx status codes |
3 | Throw a custom exception |
val result = restClient.get() (1)
.uri("https://example.com/this-url-does-not-exist") (1)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> (2)
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } (3)
.body<String>()
1 | Create a GET request for a URL that returns a 404 status code |
2 | Set up a status handler for all 4xx status codes |
3 | Throw a custom exception |
Exchange
For more advanced scenarios, the RestClient
gives access to the underlying HTTP request and response through the exchange()
method, which can be used instead of retrieve()
.
Status handlers are not applied when use exchange()
, because the exchange function already provides access to the full response, allowing you to perform any error handling necessary.
-
Java
-
Kotlin
Pet result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(APPLICATION_JSON)
.exchange((request, response) -> { (1)
if (response.getStatusCode().is4xxClientError()) { (2)
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); (2)
}
else {
Pet pet = convertResponse(response); (3)
return pet;
}
});
1 | exchange provides the request and response |
2 | Throw an exception when the response has a 4xx status code |
3 | Convert the response into a Pet domain object |
val result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange { request, response -> (1)
if (response.getStatusCode().is4xxClientError()) { (2)
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) (2)
} else {
val pet: Pet = convertResponse(response) (3)
pet
}
}
1 | exchange provides the request and response |
2 | Throw an exception when the response has a 4xx status code |
3 | Convert the response into a Pet domain object |
HTTP Message Conversion
Jackson JSON Views
To serialize only a subset of the object properties, you can specify a Jackson JSON View, as the following example shows:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
Multipart
To send multipart data, you need to provide a MultiValueMap<String, Object>
whose values may be an Object
for part content, a Resource
for a file part, or an HttpEntity
for part content with headers.
For example:
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
// send using RestClient.post or RestTemplate.postForEntity
In most cases, you do not have to specify the Content-Type
for each part.
The content type is determined automatically based on the HttpMessageConverter
chosen to serialize it or, in the case of a Resource
, based on the file extension.
If necessary, you can explicitly provide the MediaType
with an HttpEntity
wrapper.
Once the MultiValueMap
is ready, you can use it as the body of a POST
request, using RestClient.post().body(parts)
(or RestTemplate.postForObject
).
If the MultiValueMap
contains at least one non-String
value, the Content-Type
is set to multipart/form-data
by the FormHttpMessageConverter
.
If the MultiValueMap
has String
values, the Content-Type
defaults to application/x-www-form-urlencoded
.
If necessary the Content-Type
may also be set explicitly.
Client Request Factories
To execute the HTTP request, RestClient
uses a client HTTP library.
These libraries are adapted via the ClientRequestFactory
interface.
Various implementations are available:
-
JdkClientHttpRequestFactory
for Java’sHttpClient
-
HttpComponentsClientHttpRequestFactory
for use with Apache HTTP ComponentsHttpClient
-
JettyClientHttpRequestFactory
for Jetty’sHttpClient
-
ReactorNettyClientRequestFactory
for Reactor Netty’sHttpClient
-
SimpleClientHttpRequestFactory
as a simple default
If no request factory is specified when the RestClient
was built, it will use the Apache or Jetty HttpClient
if they are available on the classpath.
Otherwise, if the java.net.http
module is loaded, it will use Java’s HttpClient
.
Finally, it will resort to the simple default.
Note that the SimpleClientHttpRequestFactory may raise an exception when accessing the status of a response that represents an error (for example, 401).
If this is an issue, use any of the alternative request factories.
|
WebClient
WebClient
is a non-blocking, reactive client to perform HTTP requests. It was
introduced in 5.0 and offers an alternative to the RestTemplate
, with support for
synchronous, asynchronous, and streaming scenarios.
WebClient
supports the following:
-
Non-blocking I/O
-
Reactive Streams back pressure
-
High concurrency with fewer hardware resources
-
Functional-style, fluent API that takes advantage of Java 8 lambdas
-
Synchronous and asynchronous interactions
-
Streaming up to or streaming down from a server
See WebClient for more details.
RestTemplate
The RestTemplate
provides a high-level API over HTTP client libraries in the form of a classic Spring Template class.
It exposes the following groups of overloaded methods:
The RestClient offers a more modern API for synchronous HTTP access.
For asynchronous and streaming scenarios, consider the reactive WebClient.
|
Method group | Description |
---|---|
|
Retrieves a representation via GET. |
|
Retrieves a |
|
Retrieves all headers for a resource by using HEAD. |
|
Creates a new resource by using POST and returns the |
|
Creates a new resource by using POST and returns the representation from the response. |
|
Creates a new resource by using POST and returns the representation from the response. |
|
Creates or updates a resource by using PUT. |
|
Updates a resource by using PATCH and returns the representation from the response.
Note that the JDK |
|
Deletes the resources at the specified URI by using DELETE. |
|
Retrieves allowed HTTP methods for a resource by using ALLOW. |
|
More generalized (and less opinionated) version of the preceding methods that provides extra flexibility when needed.
It accepts a These methods allow the use of |
|
The most generalized way to perform a request, with full control over request preparation and response extraction through callback interfaces. |
Initialization
RestTemplate
uses the same HTTP library abstraction as RestClient
.
By default, it uses the SimpleClientHttpRequestFactory
, but this can be changed via the constructor.
See Client Request Factories.
RestTemplate can be instrumented for observability, in order to produce metrics and traces.
See the RestTemplate Observability support section.
|
Body
Objects passed into and returned from RestTemplate
methods are converted to and from HTTP messages with the help of an HttpMessageConverter
, see HTTP Message Conversion.
Migrating from RestTemplate
to RestClient
The following table shows RestClient
equivalents for RestTemplate
methods.
It can be used to migrate from the latter to the former.
RestTemplate method |
RestClient equivalent |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTTP Interface
The Spring Framework lets you define an HTTP service as a Java interface with
@HttpExchange
methods. You can pass such an interface to HttpServiceProxyFactory
to create a proxy which performs requests through an HTTP client such as RestClient
or WebClient
. You can also implement the interface from an @Controller
for server
request handling.
Start by creating the interface with @HttpExchange
methods:
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
Now you can create a proxy that performs requests when methods are called.
For RestClient
:
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
For WebClient
:
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
For RestTemplate
:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/"));
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
@HttpExchange
is supported at the type level where it applies to all methods:
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void updateRepository(@PathVariable String owner, @PathVariable String repo,
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
}
Method Parameters
Annotated, HTTP exchange methods support flexible method signatures with the following method parameters:
Method argument | Description |
---|---|
|
Dynamically set the URL for the request, overriding the annotation’s |
|
Provide a |
|
Dynamically set the HTTP method for the request, overriding the annotation’s |
|
Add a request header or multiple headers. The argument may be a |
|
Add a variable for expand a placeholder in the request URL. The argument may be a
|
|
Provide an |
|
Provide the body of the request either as an Object to be serialized, or a
Reactive Streams |
|
Add a request parameter or multiple parameters. The argument may be a When |
|
Add a request part, which may be a String (form field), |
|
Add a request part from a |
|
Add a cookie or multiple cookies. The argument may be a |
Method parameters cannot be null
unless the required
attribute (where available on a
parameter annotation) is set to false
, or the parameter is marked optional as determined by
MethodParameter#isOptional
.
Return Values
The supported return values depend on the underlying client.
Clients adapted to HttpExchangeAdapter
such as RestClient
and RestTemplate
support synchronous return values:
Method return value | Description |
---|---|
|
Perform the given request. |
|
Perform the given request and return the response headers. |
|
Perform the given request and decode the response content to the declared return type. |
|
Perform the given request and return a |
|
Perform the given request, decode the response content to the declared return type, and
return a |
Clients adapted to ReactorHttpExchangeAdapter
such as WebClient
, support all of above
as well as reactive variants. The table below shows Reactor types, but you can also use
other reactive types that are supported through the ReactiveAdapterRegistry
:
Method return value | Description |
---|---|
|
Perform the given request, and release the response content, if any. |
|
Perform the given request, release the response content, if any, and return the response headers. |
|
Perform the given request and decode the response content to the declared return type. |
|
Perform the given request and decode the response content to a stream of the declared element type. |
|
Perform the given request, and release the response content, if any, and return a
|
|
Perform the given request, decode the response content to the declared return type, and
return a |
|
Perform the given request, decode the response content to a stream of the declared
element type, and return a |
By default, the timeout for synchronous return values with ReactorHttpExchangeAdapter
depends on how the underlying HTTP client is configured. You can set a blockTimeout
value on the adapter level as well, but we recommend relying on timeout settings of the
underlying HTTP client, which operates at a lower level and provides more control.
Error Handling
To customize error response handling, you need to configure the underlying HTTP client.
For RestClient
:
By default, RestClient
raises RestClientException
for 4xx and 5xx HTTP status codes.
To customize this, register a response status handler that applies to all responses
performed through the client:
RestClient restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
For more details and options, such as suppressing error status codes, see the Javadoc of
defaultStatusHandler
in RestClient.Builder
.
For WebClient
:
By default, WebClient
raises WebClientResponseException
for 4xx and 5xx HTTP status codes.
To customize this, register a response status handler that applies to all responses
performed through the client:
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
For more details and options, such as suppressing error status codes, see the Javadoc of
defaultStatusHandler
in WebClient.Builder
.
For RestTemplate
:
By default, RestTemplate
raises RestClientException
for 4xx and 5xx HTTP status codes.
To customize this, register an error handler that applies to all responses
performed through the client:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
For more details and options, see the Javadoc of setErrorHandler
in RestTemplate
and
the ResponseErrorHandler
hierarchy.