1123 lines
35 KiB
Plaintext
1123 lines
35 KiB
Plaintext
[[rest-client-access]]
|
||
= REST Clients
|
||
|
||
The Spring Framework provides the following choices for making calls to REST endpoints:
|
||
|
||
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] - synchronous client with a fluent API.
|
||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] - non-blocking, reactive client with fluent API.
|
||
* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] - synchronous client with template method API.
|
||
* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] - annotated interface with generated, dynamic proxy implementation.
|
||
|
||
|
||
[[rest-restclient]]
|
||
== `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 <<rest-request-factories>>) and which message converters to use (see <<rest-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.
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||
----
|
||
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")
|
||
.requestInterceptor(myCustomInterceptor)
|
||
.requestInitializer(myCustomInitializer)
|
||
.build();
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||
----
|
||
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")
|
||
.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 `https://example.com/orders/42`:
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||
----
|
||
int id = 42;
|
||
restClient.get()
|
||
.uri("https://example.com/orders/{id}", id)
|
||
....
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||
----
|
||
val id = 42
|
||
restClient.get()
|
||
.uri("https://example.com/orders/{id}", id)
|
||
...
|
||
----
|
||
======
|
||
|
||
A function can also be used for more controls, such as specifying xref:web/webmvc/mvc-uri-building.adoc[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 xref:web/webmvc/mvc-uri-building.adoc[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 <<rest-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 <<rest-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.
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||
----
|
||
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
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||
----
|
||
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`:
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||
----
|
||
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
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||
----
|
||
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.
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||
----
|
||
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
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||
----
|
||
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.
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||
----
|
||
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.
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||
----
|
||
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`.
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||
----
|
||
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
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||
----
|
||
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.
|
||
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||
----
|
||
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
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||
----
|
||
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
|
||
======
|
||
|
||
|
||
[[rest-message-conversion]]
|
||
=== HTTP Message Conversion
|
||
|
||
xref:web/webmvc/message-converters.adoc#message-converters[See the supported HTTP message converters in the dedicated section].
|
||
|
||
==== Jackson JSON Views
|
||
|
||
To serialize only a subset of the object properties, you can specify a {baeldung-blog}/jackson-json-view-annotation[Jackson JSON View], as the following example shows:
|
||
|
||
[source,java,indent=0,subs="verbatim"]
|
||
----
|
||
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:
|
||
|
||
[source,java,indent=0,subs="verbatim"]
|
||
----
|
||
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.
|
||
|
||
[[rest-request-factories]]
|
||
=== 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's `HttpClient`
|
||
* `HttpComponentsClientHttpRequestFactory` for use with Apache HTTP Components `HttpClient`
|
||
* `JettyClientHttpRequestFactory` for Jetty's `HttpClient`
|
||
* `ReactorNettyClientRequestFactory` for Reactor Netty's `HttpClient`
|
||
* `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.
|
||
|
||
TIP: Note that the `SimpleClientHttpRequestFactory` may raise an exception when accessing the status of a response that represents an error (e.g. 401).
|
||
If this is an issue, use any of the alternative request factories.
|
||
|
||
[[rest-webclient]]
|
||
== `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 xref:web/webflux-webclient.adoc[WebClient] for more details.
|
||
|
||
|
||
|
||
|
||
[[rest-resttemplate]]
|
||
== `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:
|
||
|
||
NOTE: The xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] offers a more modern API for synchronous HTTP access.
|
||
For asynchronous and streaming scenarios, consider the reactive xref:web/webflux-webclient.adoc[WebClient].
|
||
|
||
[[rest-overview-of-resttemplate-methods-tbl]]
|
||
.RestTemplate methods
|
||
[cols="1,3"]
|
||
|===
|
||
| Method group | Description
|
||
|
||
| `getForObject`
|
||
| Retrieves a representation via GET.
|
||
|
||
| `getForEntity`
|
||
| Retrieves a `ResponseEntity` (that is, status, headers, and body) by using GET.
|
||
|
||
| `headForHeaders`
|
||
| Retrieves all headers for a resource by using HEAD.
|
||
|
||
| `postForLocation`
|
||
| Creates a new resource by using POST and returns the `Location` header from the response.
|
||
|
||
| `postForObject`
|
||
| Creates a new resource by using POST and returns the representation from the response.
|
||
|
||
| `postForEntity`
|
||
| Creates a new resource by using POST and returns the representation from the response.
|
||
|
||
| `put`
|
||
| Creates or updates a resource by using PUT.
|
||
|
||
| `patchForObject`
|
||
| Updates a resource by using PATCH and returns the representation from the response.
|
||
Note that the JDK `HttpURLConnection` does not support `PATCH`, but Apache HttpComponents and others do.
|
||
|
||
| `delete`
|
||
| Deletes the resources at the specified URI by using DELETE.
|
||
|
||
| `optionsForAllow`
|
||
| Retrieves allowed HTTP methods for a resource by using ALLOW.
|
||
|
||
| `exchange`
|
||
| More generalized (and less opinionated) version of the preceding methods that provides extra flexibility when needed.
|
||
It accepts a `RequestEntity` (including HTTP method, URL, headers, and body as input) and returns a `ResponseEntity`.
|
||
|
||
These methods allow the use of `ParameterizedTypeReference` instead of `Class` to specify
|
||
a response type with generics.
|
||
|
||
| `execute`
|
||
| 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 <<rest-request-factories>>.
|
||
|
||
NOTE: `RestTemplate` can be instrumented for observability, in order to produce metrics and traces.
|
||
See the xref:integration/observability.adoc#http-client.resttemplate[RestTemplate Observability support] section.
|
||
|
||
[[rest-template-body]]
|
||
=== Body
|
||
|
||
Objects passed into and returned from `RestTemplate` methods are converted to and from HTTP messages with the help of an `HttpMessageConverter`, see <<rest-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.
|
||
|
||
.RestClient equivalents for RestTemplate methods
|
||
[cols="1,1", options="header"]
|
||
|===
|
||
| `RestTemplate` method | `RestClient` equivalent
|
||
|
||
| `getForObject(String, Class, Object...)`
|
||
| `get()
|
||
.uri(String, Object...)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
| `getForObject(String, Class, Map)`
|
||
| `get()
|
||
.uri(String, Map)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
| `getForObject(URI, Class)`
|
||
| `get()
|
||
.uri(URI)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
|
||
| `getForEntity(String, Class, Object...)`
|
||
| `get()
|
||
.uri(String, Object...)
|
||
.retrieve()
|
||
.toEntity(Class)`
|
||
|
||
| `getForEntity(String, Class, Map)`
|
||
| `get()
|
||
.uri(String, Map)
|
||
.retrieve()
|
||
.toEntity(Class)`
|
||
|
||
| `getForEntity(URI, Class)`
|
||
| `get()
|
||
.uri(URI)
|
||
.retrieve()
|
||
.toEntity(Class)`
|
||
|
||
|
||
| `headForHeaders(String, Object...)`
|
||
| `head()
|
||
.uri(String, Object...)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getHeaders()`
|
||
|
||
| `headForHeaders(String, Map)`
|
||
| `head()
|
||
.uri(String, Map)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getHeaders()`
|
||
|
||
| `headForHeaders(URI)`
|
||
| `head()
|
||
.uri(URI)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getHeaders()`
|
||
|
||
|
||
| `postForLocation(String, Object, Object...)`
|
||
| `post()
|
||
.uri(String, Object...)
|
||
.body(Object).retrieve()
|
||
.toBodilessEntity()
|
||
.getLocation()`
|
||
|
||
| `postForLocation(String, Object, Map)`
|
||
| `post()
|
||
.uri(String, Map)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getLocation()`
|
||
|
||
| `postForLocation(URI, Object)`
|
||
| `post()
|
||
.uri(URI)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getLocation()`
|
||
|
||
|
||
| `postForObject(String, Object, Class, Object...)`
|
||
| `post()
|
||
.uri(String, Object...)
|
||
.body(Object)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
| `postForObject(String, Object, Class, Map)`
|
||
| `post()
|
||
.uri(String, Map)
|
||
.body(Object)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
| `postForObject(URI, Object, Class)`
|
||
| `post()
|
||
.uri(URI)
|
||
.body(Object)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
|
||
| `postForEntity(String, Object, Class, Object...)`
|
||
| `post()
|
||
.uri(String, Object...)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(Class)`
|
||
|
||
| `postForEntity(String, Object, Class, Map)`
|
||
| `post()
|
||
.uri(String, Map)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(Class)`
|
||
|
||
| `postForEntity(URI, Object, Class)`
|
||
| `post()
|
||
.uri(URI)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(Class)`
|
||
|
||
|
||
| `put(String, Object, Object...)`
|
||
| `put()
|
||
.uri(String, Object...)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toBodilessEntity()`
|
||
|
||
| `put(String, Object, Map)`
|
||
| `put()
|
||
.uri(String, Map)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toBodilessEntity()`
|
||
|
||
| `put(URI, Object)`
|
||
| `put()
|
||
.uri(URI)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toBodilessEntity()`
|
||
|
||
|
||
| `patchForObject(String, Object, Class, Object...)`
|
||
| `patch()
|
||
.uri(String, Object...)
|
||
.body(Object)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
| `patchForObject(String, Object, Class, Map)`
|
||
| `patch()
|
||
.uri(String, Map)
|
||
.body(Object)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
| `patchForObject(URI, Object, Class)`
|
||
| `patch()
|
||
.uri(URI)
|
||
.body(Object)
|
||
.retrieve()
|
||
.body(Class)`
|
||
|
||
|
||
| `delete(String, Object...)`
|
||
| `delete()
|
||
.uri(String, Object...)
|
||
.retrieve()
|
||
.toBodilessEntity()`
|
||
|
||
| `delete(String, Map)`
|
||
| `delete()
|
||
.uri(String, Map)
|
||
.retrieve()
|
||
.toBodilessEntity()`
|
||
|
||
| `delete(URI)`
|
||
| `delete()
|
||
.uri(URI)
|
||
.retrieve()
|
||
.toBodilessEntity()`
|
||
|
||
|
||
| `optionsForAllow(String, Object...)`
|
||
| `options()
|
||
.uri(String, Object...)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getAllow()`
|
||
|
||
| `optionsForAllow(String, Map)`
|
||
| `options()
|
||
.uri(String, Map)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getAllow()`
|
||
|
||
| `optionsForAllow(URI)`
|
||
| `options()
|
||
.uri(URI)
|
||
.retrieve()
|
||
.toBodilessEntity()
|
||
.getAllow()`
|
||
|
||
|
||
| `exchange(String, HttpMethod, HttpEntity, Class, Object...)`
|
||
| `method(HttpMethod)
|
||
.uri(String, Object...)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(Class)` footnote:http-entity[`HttpEntity` headers and body have to be supplied to the `RestClient` via `headers(Consumer<HttpHeaders>)` and `body(Object)`.]
|
||
|
||
| `exchange(String, HttpMethod, HttpEntity, Class, Map)`
|
||
| `method(HttpMethod)
|
||
.uri(String, Map)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(Class)` footnote:http-entity[]
|
||
|
||
| `exchange(URI, HttpMethod, HttpEntity, Class)`
|
||
| `method(HttpMethod)
|
||
.uri(URI)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(Class)` footnote:http-entity[]
|
||
|
||
|
||
| `exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Object...)`
|
||
| `method(HttpMethod)
|
||
.uri(String, Object...)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(ParameterizedTypeReference)` footnote:http-entity[]
|
||
|
||
| `exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Map)`
|
||
| `method(HttpMethod)
|
||
.uri(String, Map)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(ParameterizedTypeReference)` footnote:http-entity[]
|
||
|
||
| `exchange(URI, HttpMethod, HttpEntity, ParameterizedTypeReference)`
|
||
| `method(HttpMethod)
|
||
.uri(URI)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(ParameterizedTypeReference)` footnote:http-entity[]
|
||
|
||
|
||
| `exchange(RequestEntity, Class)`
|
||
| `method(HttpMethod)
|
||
.uri(URI)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(Class)` footnote:request-entity[`RequestEntity` method, URI, headers and body have to be supplied to the `RestClient` via `method(HttpMethod)`, `uri(URI)`, `headers(Consumer<HttpHeaders>)` and `body(Object)`.]
|
||
|
||
| `exchange(RequestEntity, ParameterizedTypeReference)`
|
||
| `method(HttpMethod)
|
||
.uri(URI)
|
||
.headers(Consumer<HttpHeaders>)
|
||
.body(Object)
|
||
.retrieve()
|
||
.toEntity(ParameterizedTypeReference)` footnote:request-entity[]
|
||
|
||
|
||
| `execute(String, HttpMethod, RequestCallback, ResponseExtractor, Object...)`
|
||
| `method(HttpMethod)
|
||
.uri(String, Object...)
|
||
.exchange(ExchangeFunction)`
|
||
|
||
| `execute(String, HttpMethod, RequestCallback, ResponseExtractor, Map)`
|
||
| `method(HttpMethod)
|
||
.uri(String, Map)
|
||
.exchange(ExchangeFunction)`
|
||
|
||
| `execute(URI, HttpMethod, RequestCallback, ResponseExtractor)`
|
||
| `method(HttpMethod)
|
||
.uri(URI)
|
||
.exchange(ExchangeFunction)`
|
||
|
||
|===
|
||
|
||
|
||
[[rest-http-interface]]
|
||
== 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:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
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`:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
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`:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
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`:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
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:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
@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);
|
||
|
||
}
|
||
----
|
||
|
||
|
||
[[rest-http-interface-method-parameters]]
|
||
=== Method Parameters
|
||
|
||
Annotated, HTTP exchange methods support flexible method signatures with the following
|
||
method parameters:
|
||
|
||
[cols="1,2", options="header"]
|
||
|===
|
||
| Method argument | Description
|
||
|
||
| `URI`
|
||
| Dynamically set the URL for the request, overriding the annotation's `url` attribute.
|
||
|
||
| `UriBuilderFactory`
|
||
| Provide a `UriBuilderFactory` to expand the URI template and URI variables with.
|
||
In effect, replaces the `UriBuilderFactory` (and its base URL) of the underlying client.
|
||
|
||
| `HttpMethod`
|
||
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
|
||
|
||
| `@RequestHeader`
|
||
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
|
||
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
|
||
individual value. Type conversion is supported for non-String values. This overrides
|
||
the annotation's `headers` attribute.
|
||
|
||
| `@PathVariable`
|
||
| Add a variable for expand a placeholder in the request URL. The argument may be a
|
||
`Map<String, ?>` with multiple variables, or an individual value. Type conversion
|
||
is supported for non-String values.
|
||
|
||
| `@RequestAttribute`
|
||
| Provide an `Object` to add as a request attribute. Only supported by `RestClient`
|
||
and `WebClient`.
|
||
|
||
| `@RequestBody`
|
||
| Provide the body of the request either as an Object to be serialized, or a
|
||
Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported
|
||
through the configured `ReactiveAdapterRegistry`.
|
||
|
||
| `@RequestParam`
|
||
| Add a request parameter or multiple parameters. The argument may be a `Map<String, ?>`
|
||
or `MultiValueMap<String, ?>` with multiple parameters, a `Collection<?>` of values, or
|
||
an individual value. Type conversion is supported for non-String values.
|
||
|
||
When `"content-type"` is set to `"application/x-www-form-urlencoded"`, request
|
||
parameters are encoded in the request body. Otherwise, they are added as URL query
|
||
parameters.
|
||
|
||
| `@RequestPart`
|
||
| Add a request part, which may be a String (form field), `Resource` (file part),
|
||
Object (entity to be encoded, e.g. as JSON), `HttpEntity` (part content and headers),
|
||
a Spring `Part`, or Reactive Streams `Publisher` of any of the above.
|
||
|
||
| `MultipartFile`
|
||
| Add a request part from a `MultipartFile`, typically used in a Spring MVC controller
|
||
where it represents an uploaded file.
|
||
|
||
| `@CookieValue`
|
||
| Add a cookie or multiple cookies. The argument may be a `Map<String, ?>` or
|
||
`MultiValueMap<String, ?>` with multiple cookies, a `Collection<?>` of values, or an
|
||
individual value. Type conversion is supported for non-String values.
|
||
|
||
|===
|
||
|
||
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
|
||
{spring-framework-api}/core/MethodParameter.html#isOptional()[`MethodParameter#isOptional`].
|
||
|
||
|
||
|
||
[[rest-http-interface-return-values]]
|
||
=== Return Values
|
||
|
||
The supported return values depend on the underlying client.
|
||
|
||
Clients adapted to `HttpExchangeAdapter` such as `RestClient` and `RestTemplate`
|
||
support synchronous return values:
|
||
|
||
[cols="1,2", options="header"]
|
||
|===
|
||
| Method return value | Description
|
||
|
||
| `void`
|
||
| Perform the given request.
|
||
|
||
| `HttpHeaders`
|
||
| Perform the given request and return the response headers.
|
||
|
||
| `<T>`
|
||
| Perform the given request and decode the response content to the declared return type.
|
||
|
||
| `ResponseEntity<Void>`
|
||
| Perform the given request and return a `ResponseEntity` with the status and headers.
|
||
|
||
| `ResponseEntity<T>`
|
||
| Perform the given request, decode the response content to the declared return type, and
|
||
return a `ResponseEntity` with the status, headers, and the decoded body.
|
||
|
||
|===
|
||
|
||
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`:
|
||
|
||
[cols="1,2", options="header"]
|
||
|===
|
||
| Method return value | Description
|
||
|
||
| `Mono<Void>`
|
||
| Perform the given request, and release the response content, if any.
|
||
|
||
| `Mono<HttpHeaders>`
|
||
| Perform the given request, release the response content, if any, and return the
|
||
response headers.
|
||
|
||
| `Mono<T>`
|
||
| Perform the given request and decode the response content to the declared return type.
|
||
|
||
| `Flux<T>`
|
||
| Perform the given request and decode the response content to a stream of the declared
|
||
element type.
|
||
|
||
| `Mono<ResponseEntity<Void>>`
|
||
| Perform the given request, and release the response content, if any, and return a
|
||
`ResponseEntity` with the status and headers.
|
||
|
||
| `Mono<ResponseEntity<T>>`
|
||
| Perform the given request, decode the response content to the declared return type, and
|
||
return a `ResponseEntity` with the status, headers, and the decoded body.
|
||
|
||
| `Mono<ResponseEntity<Flux<T>>`
|
||
| Perform the given request, decode the response content to a stream of the declared
|
||
element type, and return a `ResponseEntity` with the status, headers, and the decoded
|
||
response body stream.
|
||
|
||
|===
|
||
|
||
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.
|
||
|
||
|
||
[[rest-http-interface-exceptions]]
|
||
=== 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:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
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:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
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:
|
||
|
||
[source,java,indent=0,subs="verbatim,quotes"]
|
||
----
|
||
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.
|
||
|