Spring 6: Declarative HTTP Client Interfaces

One of the most common developer tasks in today’s day to day business is writing http clients to query data from REST web services. Traditionally, this is done with the Spring Framework by using either the RestTemplate or the reactive WebClient to call the http methods of a service and transform the result into a Java object. This logic is usually encapsulated in a separate Java class for each web service called a client. If the web service has a lot of resources to call and also implements all http methods for these, writing the clients can be quite time consuming.

To avoid writing your own clients, Spring 6 introduced http client interfaces to simply write an interface that specifies the http methods you want to call of the web service and the framework will do the implementation for you automatically. No need to use RestTemplates or WebClients anymore to call a web services. To use new functionality you will need a Spring Boot 3.X project and the Spring Reactive Web dependency when you create your Spring Boot project in the Spring Initializer.

Let’s use this new feature and create a client interface to call a REST endpoint.

First, we will need an endpoint that we want to call from our application and some dummy data to return for it. A quite handy website to provide some basic JSON data for demo applications is JSONPlaceholder. The site offers a simple REST web service endpoint that you can call to get some JSON data if you don’t have any service of your own set up. The root resource you can query is called Posts, which returns a list of blog posts objects in JSON format. The URL of the Posts endpoint is:

https://jsonplaceholder.typicode.com/posts

If you hit this endpoint with a simple get, it returns an array of 100 blog post objects that have an id, a userId, a title and a body property.

[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati ...",
    "body": "nostrum rerum est autem sunt rem eveniet architecto ..."
  },
  ... // more posts here ommited
  ]

The endpoint also supports other http methods for the Post resource like post, put and delete and it also has some subresources that you can query, which I won’t use in this article.

Next, we will need a domain object that can hold the data from the Posts endpoint. Let’s use a simple record called Post for this:

public record Post (Integer userId, Integer id, String title, String body ){}

To start with our declarative http client, we need a Java interface that specifies the methods of the client calls we want the Spring framework to implement for us. With Spring 6, we have some new annotations to mark a method in an interface as a call to an http endpoint. The annotations to specify a call for the basic CRUD operations are:

  • @GetExchange
  • @PostExchange
  • @PutExchange
  • @DeleteExchange
  • @PatchExchange

Each of the annotations takes a String parameter that tells Spring which path of the web service you want to call and it is placed on a method of the interface. The syntax is quite similar to writing your own http endpoint with Spring MVC, but instead of e.g. using the @GetMapping annotation to create an endpoint, you use the @GetExchange annotation to call one. Since you only write an interface, you won’t need to implement it. Spring will do the implementation for you later.

To pass parameters in the URL to the web service, you can use the well known Spring MVC syntax to define a path parameter by putting the placeholder name into curly braces into the URL like e.g. {id}. To connect a method parameter with the parameter in the URL simply use the @PathVariable annotation. To specify the body of a POST or PUT request use the @RequestBody annotation and to add a query parameter to the end of the URL use the @RequestParam annotation. All these are the same annotation we use to specify an endpoint with Spring MVC.

Here is the example client interface to access the Posts endpoint:

import org.springframework.web.service.annotation.*;

public interface PostsClient {
    
    @GetExchange("/posts/{id}")
    public Post getPost(@PathVariable Integer id);

    @GetExchange("/posts")
    public List<Post> getAllPosts();

    @DeleteExchange("/posts/{id}")
    public void deletePost(@PathVariable Integer id);

    @PostExchange("/posts")
    public Post createPost(@RequestBody Post post);
}

Notice that we only use the relative path of the resource we want to call as the parameter of the exchange annotations. The absolute URL will be provided later, when we create an implementation of the client.

Next, we need to get our hands on the implementation of the PostsClient interface. We will need to create two more Spring beans to create one:

  • A WebClient to call the web service behind the scenes
  • A HttpServiceProxyFactory to create an implementation of our interface

To create the WebClient use the following bean definition:

private static final String POST_SERVICE_BASE_URL 
  = "https://jsonplaceholder.typicode.com/";

@Bean
public WebClient createWebClient(){
  WebClient webClient = WebClient.builder()
    .baseUrl(serviceUrl)
    .build();
  return webClient;
}

The WebClient is passed the root URL of the service you want to call, which is defined in a static final String in the beginning of the snippet. Since we only call a single service in this example, we only need to create a single WebClient. If you were to call multiple web services with different base URLs from your code, you would need to create several WebClients with different qualifiers.

With the WebClient created, we can use it to create a HttpServiceProxyFactory:

@Bean
public HttpServiceProxyFactory createProxyFactory(WebClient webClient){
  return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
}

Finally, we can use the HttpServiceProxyFactory to create an implementation of our interface:

@Bean
public PostsClient createPostsClient(HttpServiceProxyFactory httpServiceProxyFactory ){
  return httpServiceProxyFactory.createClient(PostsClient.class);
}

That’s all we need to do to create our client. We can now inject it in any Spring bean to call the post web service.

Since we are responsible developers, we will test our client by a Spring Boot integration test:

@SpringBootTest
public class PostsClientTest {

  @Autowired
  private PostsClient postsClient;

  @Test
  public void testPostsClientGet() {
    List<Post> posts = postsClient.getAllPosts();
    assertNotNull(posts);
  }
  
  @Test
  public void testGetSinglePost() {
    Post postNo5 = postsClient.getPost(5);
    assertTrue(postNo5.id()==5);
  }

  @Test
  public void testDeleteSinglePost() {
    postsClient.deletePost(5);
  }

  @Test
  public void testCreatePost() {
    Post newPost = new Post(1,null,"new title", "new body");
    Post post = postsClient.createPost(newPost);
    assertEquals("new title", post.title());
  }
}

If you need to get access to the HTTP status code or the headers of the response, you can also use ResponseEntity as a return type in the client interface, which we already know from Spring MVC RestControllers. Since we are using a WebClient behind the scenes to call the service, the reactive return types (Mono<T> and Flux<T>) are also supported as return types in your client. Of course the reactive return types should only be used if the web service also returns the data reactively.

Here is a quick example how a client with reactive return types would looks like:

public interface ReactivePostsClient {

    @GetExchange("/posts/{id}")
    public Mono<Post> getPost(@PathVariable Integer id);

    @GetExchange("/posts")
    public Flux<Post> getAllPosts();
    
}

That is pretty much all you need to know about the new declarative http client interfaces. They are quite easy to use and you only need the Spring Reactive Web dependency for it. Of course you could define all client interfaces in a separate dependency and simply include them in your main application for use. That way separate teams could also easily share the client specification of their web services with each other by exchanging the client interface dependency.

If you want to try out the snippets yourself, you can find the source code in this github project.

If you are already using Spring 3.2 or greater the RestClient is another interesting alternative to call an http endpoint. If you want to know how the RestClient works, just check out this post.


Beitrag veröffentlicht

in

,

von

Schlagwörter:

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert