/spring-boot-rest-client

A Rest Client for Spring Boot

Primary LanguageJavaMIT LicenseMIT

Spring Boot Rest Client

Build Status Maven Central GitHub issues GitHub license

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 host
  • method() - The HTTP method (GET is the default)
  • produces() - Value of the Accept header
  • consumes() - Value of the Content-Type header
  • headers() - 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 a AsyncRestTemplate 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 optional url() 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