Spring Declarative HTTP Client using @HttpExchange

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:

public interface UserClient {

  Flux<User> getAll();

Spring will provide the interface and exchange implementations in runtime, and we only need to invoke the getAll() method.

UserClient userClient;

    data -> log.info("User: {}", data)

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.


<!-- For reactive support -->

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.
void update(@PathVariable Long id, @RequestBody User user);

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.

User getById(...);

Mono<User> getById(...);

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.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.howtodoinjava.app.web.UserClient;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

public class WebConfig {

  WebClient webClient(ObjectMapper objectMapper) {
    return WebClient.builder()

  UserClient postClient(WebClient webClient) {
    HttpServiceProxyFactory httpServiceProxyFactory =
    return httpServiceProxyFactory.createClient(UserClient.class);

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 Service Interface Example

The following is an example of HTTP interface that interacts with https://jsonplaceholder.typicode.com/users/ endpoint and performs various operations.

import com.howtodoinjava.app.model.User;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.DeleteExchange;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.PutExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@HttpExchange(url = "/users", accept = "application/json", contentType = "application/json")
public interface UserClient {

  Flux<User> getAll();

  Mono<User> getById(@PathVariable("id") Long id);

  Mono<ResponseEntity<Void>> save(@RequestBody User user);

  Mono<ResponseEntity<Void>> update(@PathVariable Long id, @RequestBody User user);

  Mono<ResponseEntity<Void>> delete(@PathVariable Long id);

Note that we have created a record of User type to hold the user information.

public record User(Long id, String name, String username, String email) {}

Now we can inject the UserClient bean into application classes and invoke methods to get the API responses.

UserClient userClient;

//Get All Users
    data -> log.info("User: {}", data)

//Get User By Id
    data -> log.info("User: {}", data)

//Create a New User
userClient.save(new User(null, "Lokesh", "lokesh", "admin@email.com"))
        data -> log.info("User: {}", data)

//Delete User By Id
    data -> log.info("User: {}", data)

6. 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 !!

Sourcecode on Github

Leave a Reply

Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial