Spring Boot Rest Client
This library was born as an effort to avoid boilerplate code and making use of Spring Boot's auto-configuration features.
It is based on Spring Cloud Feign but it uses RestTemplate instead of Netflix's Feign and Spring MVC annotations.
As an additional feature, spring-boot-rest-client supports Spring Retry so that HTTP requests can be retried upon either specific HTTP statuses and/or defined Exceptions.
Usage
@EnableRestClients
@SpringBootApplication
public class FooApplication {
public static void main(String... args) {
SpringApplication.run(FooApplication.class, args);
}
@RestClient("foo")
interface FooClient {
@RequestMapping
Foo getFoo();
}
}
spring:
rest:
client:
services:
foo: http://foo.bar.se
You can later use @Autowired
(or constructor injection) and just call fooClient.getFoo()
which will make an HTTP GET
call to http://foo.bar.se
@Component
public class RestClientConsumer {
private final FooClient fooClient;
RestClientConsumer(FooClient fooClient) {
this.fooClient = fooClient;
}
public Foo getFoo() {
return fooClient.getFoo();
}
}
Structure
@RequestMapping
values have the following correspondence to the resulting HTTP call:
value()
- Path appended to the hostmethod()
- The HTTP method (GET is the default)produces()
- Value of the Accept headerconsumes()
- Value of the Content-Type headerheaders()
-String[]
of key-value pairs of headers separated by ':'
All HTTP REST methods are supported (GET, POST, PUT, PATCH and DELETE) as well as the following annotations on parameters:
A method parameter with no annotation is expected to be the request body (payload) of the request if @RequestBody
is not specified.
In addition to @RequestMapping
, composed variants introduced in Spring MVC 4.3 can also be used. Check this for more details.
Async
Spring Boot Rest Template can be also be configured to be used for asynchronous REST calls for which it will instead use an AsyncRestTemplate
bean. It supports both Oracle's CompletableFuture as well as Spring's ListenableFuture.
@RestClient("foo")
interface FooClient {
@RequestMapping("/{id}")
ListenableFuture<String> foo(@PathVariable("id") String id, @RequestParam("query") String query);
@RequestMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
CompletableFuture<Foo> getFoo(@PathVariable("id") String id);
}
Please note that retry functionality is currently not supported for asynchronous requests.
Generics
Generic declarations are supported as long as the "implementing" interface contains a concrete class.
Working example:
interface FooBaseClient<T> {
@GetMapping(value = "/{id}")
T getParameterized(@PathVariable("id") String id);
}
@RestClient("foo")
interface FooClient extends FooBaseClient<Foo> {
}
HTTP Entities
If for some reason you do not wish to have the body extracted from your response, you can wrap your response type in either a ResponseEntity as well as an HttpEntity.
@RestClient("foo")
interface FooClient {
@GetMapping
ResponseEntity<String> getEntity();
@GetMapping
HttpEntity<String> getHttpEntity();
}
JDK 8 Support
If you wrap your response type in Oracle's JDK 8 Optional, Spring Boot Rest Client will return an Optional.empty()
upon a HTTP 404 NOT FOUND
response code.
@RestClient("foo")
interface FooClient {
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
Optional<Foo> getOptional();
}
Please note that default methods in interfaces declaring @RestClient
are currently not supported.
HATEOAS Support
Specially useful for HATEOAS, you can use the @PostForLocation
annotation to indicate that a POST request should return the Location
HTTP header as an URI
.
@RestClient("foo")
interface FooClient {
@PostForLocation("/postForLocation")
URI postForLocation(String body);
}
Additionally, by including Spring HATEOAS as a dependency, you can use Spring HATEOAS resource support:
public class FooResource extends ResourceSupport {
}
@RestClient("foo")
interface FooClient {
@GetMapping(value = "/foo/{id}", produces = MediaTypes.HAL_JSON_VALUE)
FooResource getFoo(@PathVariable("id") String id);
@GetMapping(value = "/foo/{id}", produces = MediaTypes.HAL_JSON_VALUE)
Resource<Foo> getFooWrapped(@PathVariable("id") String id);
@GetMapping(value = "/foos", produces = MediaTypes.HAL_JSON_VALUE)
Resources<FooResource> getFoos();
@GetMapping(value = "/foos", produces = MediaTypes.HAL_JSON_VALUE)
PagedResources<FooResource> getPagedFoos();
}
Retry
The rest client library can be used with Spring Retry. Just by adding the org.springframework.retry:spring-retry
library as a dependency and @EnableRetry
in your configuration, the retry functionality will be enabled. By default, calls are retried on HTTP 503 SERVICE UNAVAILABLE
and IOException
but you can configure your own:
@RestClient(
value = "foo",
retryOn = {HttpStatus.SERVICE_UNAVAILABLE, HttpStatus.BAD_GATEWAY},
retryOnException = SocketTimeoutException.class)
interface FooClient {
@RestClient("/foos")
List<Foo> getFooList();
}
Furthermore, global retry settings can be configured by adding values to application.yml
. Below, the default values are shown:
spring:
rest:
client:
services:
foo: http://foo.bar.se
retry:
max-attempts: 3
back-off:
delay: 1000
max-delay: 0
multiplier: 0.0
random: false
Refer to Spring Retry for more information about what the values refer to.
Miscellaneous
- The library will create a
RestTemplate
and aAsyncRestTemplate
Spring beans if not already present using a RestTemplateBuilder - The library is non-intrusive. That means that if you want the spring-retry functionality you'll need to include it and all of its dependencies
@RestClient
also accepts an optionalurl()
parameter which can be either a hardcoded value or a SpEL expression
TODO
- Add option to disable retry on either clients or specific methods
- Support
@Recover
method as specified in Spring Retry when retries are exhausted