Easy HTTP Client with Spring 6.x
Tags: spring, http, java, programming
Published: 2023-10-30 19:55:28

##### TLDR;

Spring 6 brings `HttpServiceProxyFactory` class allowing you to easily generate HTTP client based on interfaces and annotations. 

# Introduction 

Before Spring 6.x, creating an HTTP client consisted of implementing each calls using either the old `RestTemplate`, the reactive `WebClient`, or the new `RestClient` introduced in Spring 6.1.
This leads to a lot of boilerplate code. 

Nevertheless, some alternatives exist and aim to ease the development of rest client such as OpenFEIGN or the Eclipse MicroProfile RestClient specification. 

In its last major release, Spring introduced the `HttpServiceProxyFactory` class as well as a new blocking rest client `RestClient` (in 6.1). 

Thanks to this factory, you can declare your client using a simple interface annotated with `@HttpExchange` annotations (similar to Spring Data Repositories). The factory will then take care of generating an implementation of your interface.

# Giving it a try

Let's consider the following beer API (based on Brewery OpenDB)

```
GET /api/v1/beer 
GET /api/v1/beer/{beerId}
POST /api/v1/beer
```

In order to create a client, we need to create an interface and specify annotation in order to declare: 

* Path
* HTTP verb
* Headers
* Cookies
* Path and Query parameters

```java
package org.dev.example;

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

public interface BeerClient {

    @GetExchange("/api/v1/beer")
    public List<Beer> listBeers();

    @GetExchange("/api/v1/beer/{beerId}")
    public Beer getBeer(@PathVariable String beerId);

    @PostExchange("/api/v1/beer")
    public Beer createBeer(@RequestBody Beer beer);
}
```

It is possible to "simplify" paths using `@HttpExchange` directly on the interface

```java
package org.dev.example;

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

@HttpExchange(url = "api/v1/beer", contentType = "application/json")
public interface BeerClient {

    @GetExchange
    public List<Beer> listBeers();

    @GetExchange("/{beerId}")
    public Beer getBeer(@PathVariable String beerId);

    @PostExchange
    public Beer createBeer(@RequestBody Beer beer);
}
```

Once the interface is created, creating a new client consists of using `HttpServiceProxyFactory`, setting the http client we would like to use and then creating the client implementation.

```java
public BeerClient buildBeerRestClient(WebClient webClient) {
    HttpServiceProxyFactory clientFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
    return clientFactory.createClient(BeerClient.class);
}
```

This can be integrated in a bean using a simple configuration class:

```java
@Configuration
public class BeerClientConfiguration() {

    @Bean
    public BeerClient buildBeerClient(WebClient.Builder webClientBuilder) {
        WebClient webClient = webClientBuilder.build();
        HttpServiceProxyFactory clientFactory = 
HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
        return clientFactory.createClient(BeerClient.class);
    }
}
```

This bean can then be injected as any other bean. 

# Customizing the WebClient

Usually, in most client, we need to inject some headers, add authentication tokens, set loggers, ...

This can be done directly in the WebClient or RestClient (introduced in Spring 6.1) builders using `defaultHeaders(...)` method or using filters (interceptors when using `RestClient` or `RestTemplate`). For example, you can easily register a filter that add an authentication header based on the currently logged in user.

# What about non blocking types ? 

Types such as `Mono` or `Flux`, can be directly specified as return type in the interface such as 

```java
package org.dev.example;

import org.springframework.web.service.annotation.*;
import reactor.core.publisher.*;

@HttpExchange(url = "api/v1/beer", contentType = "application/json")
public interface BeerClient {

    @GetExchange
    public Flux<Beer> listBeers();

    @GetExchange("/{beerId}")
    public Mono<Beer> getBeer(@PathVariable String beerId);

    @PostExchange
    public Mono<Beer> createBeer(@RequestBody Beer beer);
}

```

The `HttpServiceProxyFactory` also supports returning `ResponseEntity` type (which can also be combined with `Mono` and `Flux`).

# What about testing ?

Testing can be done in different ways:

* By overriding the `exchangeFunction` of the `WebClient` used by the `HttpServiceProxyFactory`
* If your client is exposed as bean, it can be mocked as any other bean by your favorite mocking library.