Starting Spring 6 and Spring Boot 3, Spring framework supports proxying a remote HTTP service as a Java interface with annotated methods for HTTP exchanges. Similar libraries, like OpenFeign and Retrofit, can still be used, but HttpServiceProxyFactory adds native support to Spring framework.
1. What is a Declarative Http Interface?
A declarative HTTP interface is a Java interface that helps reduce the boilerplate code, generates a proxy implementing this interface, and performs the exchanges at the framework level.
For example, if we want to consume an API at URL https://server-address.com/api/resource/id then we need to create and configure either a RestTemplate or WebClient bean and use its exchange methods for invoking the API, parsing response and handling the errors.
Most often, the code to create and configure the beans and invoke remote APIs is very similar and thus can be abstracted by the framework, so we do not need to write this code again and again in every application. We can simply express the remote API details using the annotations on an interface and let the framework create an implementation under the hood.
For example, if we want to consume a HTTP GET /users API then we can simply write:
Spring will provide the interface and exchange implementations in runtime, and we only need to invoke the getAll() method.
2. Maven
The declarative HTTP interface functionality is part of the spring-web dependency that is transitively pulled in when we include either spring-boot-starter-web or spring-boot-starter-webflux. If we want to add the reactive support then include the later dependency.
3. Creating an HTTP Service Interface
In Spring, an HTTP service interface is a Java interface with @HttpExchange methods. The annotated method is treated as an HTTP endpoint, and the details are defined statically through annotation attributes as well as through the input method argument types.
3.1. Exchange Methods
We can use the following annotations to mark a method as HTTP service endpoint:
- @HttpExchange: is the generic annotation to specify an HTTP endpoint. When used at the interface level, it applies to all methods.
- @GetExchange: specifies @HttpExchange for HTTP GET requests.
- @PostExchange: specifies @HttpExchange for HTTP POST requests.
- @PutExchange: specifies @HttpExchange for HTTP PUT requests.
- @DeleteExchange: specifies @HttpExchange for HTTP DELETE requests.
- @PatchExchange: specifies @HttpExchange for HTTP PATCH requests.
3.2. Method Arguments
The exchange methods support the following method parameters in the method signature:
- URI: sets the URL for the request.
- @PathVariable: replaces a value with a placeholder in the request URL.
- @RequestBody: provides the body of the request.
- @RequestParam: add the request parameter(s). 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.
- @RequestHeader: adds the request header names and values.
- @RequestPart: can be used to add a request part (form field, resource or HttpEntity etc).
- @CookieValue: adds cookies to the request.
3.3. Return Values
An HTTP exchange method can return values that are:
- either blocking or reactive (
Mono
/Flux
). - only the specific response information, such as status code and/or response headers.
void
if the method is treated as execute only.
For a blocking exchange method, we should generally return ResponseEntity, and for reactive methods, we can return the Mono/Flux types.
4. Building HttpServiceProxyFactory
The HttpServiceProxyFactory is a factory to create a client proxy from an HTTP service interface. Use its HttpServiceProxyFactory.builder(client).build() method to get an instance of the proxy bean.
Notice we have set the remote API’s base URL in the WebClient bean, so we need to use only the relative paths in the exchange methods.
5. HTTP Request Headers
We can set the request headers in all outgoing requests in two ways: headers specific to a request or global headers send with every outgoing request automatically.
5.1. Headers Specific to a Request
To send a header, specific to a request, we must add it to the method signature using the @RequestHeader annotation as follows:
Next, we need to set the header value when we execute the request.
5.2. Set Static Global Header to WebClient Bean
To set the request headers for every outgoing request, such as trace ID or authentication, we can set the headers in the WebClient bean itself.
In the following example, we are setting the basic authentication with each outgoing request. Additionally, we have set the enableLoggingRequestDetails(true) to verify the headers in outgoing requests. Comment this code in production.
Now, when we execute the getAll() API written, we can see that both, local and global, headers are present in the request.
5.3. Set Dynamic Global Header with ExchangeFilterFunction
If we want to set a global header whose value changes for each outgoing request, we can use the ExchangeFilterFunction. The ExchangeFilterFunction represents a filter that, once registered, is executed for each request-response interaction with the server.
It should be noted ClientRequest object is immutable and we cannot modify this once it has been fully initialized. So in the ExchangeFilterFunction.filter(), we need to create a new ClientRequest with the details from the existing request, add our new headers with dynamic values and build a new request for further processing.
Next, register the DynamicHeaderFilter with the WebClient so it is invoked for each outgoing request.
Let us test the above filter with a simple request.
6. HTTP Service Interface Example
The following is an example of HTTP interface that interacts with https://jsonplaceholder.typicode.com/users/ endpoint and performs various operations.
Note that we have created a record of User type to hold the user information.
Now we can inject the UserClient bean into application classes and invoke methods to get the API responses.
7. Conclusion
In this Spring tutorial, we learned to create and use the declarative HTTP client interface using examples. We learned to create the exchange methods in the interface and then invoke them using the proxy implementation created by the Spring framework. We also learned to create the interface proxy using the HttpServiceProxyFactory and WebClient beans.
Happy Learning !!
Comments