Backend Development 9 min read

Mastering @HttpExchange in Spring Boot 3: Real‑World Cases & Advanced Tips

This article compares Feign and Spring 6's @HttpExchange for remote service calls, walks through practical examples—including interface definition, proxy creation, various client options, testing, reactive returns, custom argument resolvers, and error handling—while providing complete code snippets for Spring Boot 3.4.2.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering @HttpExchange in Spring Boot 3: Real‑World Cases & Advanced Tips

1. Introduction

In micro‑service architectures, remote service invocation is essential. Feign and @HttpExchange both enable declarative HTTP calls but differ significantly.

Feign is an open‑source, declarative HTTP client from Netflix, integrated into the Spring Cloud ecosystem. It offers load balancing, circuit breaking, and service discovery, but requires additional third‑party dependencies and configuration.

@HttpExchange is introduced in Spring Framework 6 (used by Spring Boot 3). It defines HTTP services via declarative interfaces without external dependencies, integrates seamlessly with native Spring components such as WebClient , and yields cleaner, higher‑performance code.

This article details how to use @HttpExchange in practice.

2. Practical Cases

2.1 Interface Definition

<code>@HttpExchange("/api")
public interface RemoteService {
  @GetExchange("/{id}")
  public ResponseEntity<User> query(@PathVariable Long id);

  @PostExchange("")
  public ResponseEntity<String> create(@RequestBody User user,
      @RequestHeader(required = false, name = "Authorization") String token);
}
</code>

The annotations work similarly to @RequestMapping or @GetMapping , but the interface‑level @HttpExchange applies a common base path to all methods.

2.2 Creating the Proxy

<code>@Configuration
public class RemoteConfig {

  @Bean
  RemoteService remoteService() {
    WebClient webClient = WebClient.builder()
        .baseUrl("http://localhost:8888")
        .build();
    WebClientAdapter adapter = WebClientAdapter.create(webClient);
    HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
    return factory.createClient(RemoteService.class);
  }
}
</code>

The WebClient is used as the underlying HTTP client. Add the following Maven dependency:

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-webflux&lt;/artifactId&gt;
&lt;/dependency&gt;
</code>

2.3 Other Clients

RestTemplate client

<code>RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("http://localhost:8888"));
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RemoteService service = factory.createClient(RemoteService.class);
</code>

RestClient client

<code>RestClient restClient = RestClient.builder()
    .baseUrl("http://localhost:8888")
    .build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RemoteService service = factory.createClient(RemoteService.class);
</code>

2.4 Test Controller

<code>@RestController
@RequestMapping("/remote")
public class RemoteController {
  private final RemoteService remoteService;

  public RemoteController(RemoteService remoteService) {
    this.remoteService = remoteService;
  }

  @GetMapping("/{id}")
  public ResponseEntity<User> query(@PathVariable Long id) {
    return this.remoteService.query(id);
  }
}
</code>

When invoked, the controller returns the response from the remote service.

2.5 Advanced Features

Reactive Return Types

You can declare methods returning Mono or Flux :

<code>@GetExchange("/{id}")
public Mono<User> query(@PathVariable Long id);
</code>

Custom Argument Resolver

Define a record to hold query parameters:

<code>public record Search(String name, Integer age, String email, String address) {}
</code>

Interface method using the record:

<code>@GetExchange("/search")
public ResponseEntity<String> search(Search search);
</code>

Resolver implementation:

<code>public class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
  @Override
  public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
    if (parameter.getParameterType() == Search.class) {
      Search search = (Search) argument;
      requestValues.addRequestParameter("name", search.name());
      requestValues.addRequestParameter("age", String.valueOf(search.age()));
      requestValues.addRequestParameter("email", search.email());
      requestValues.addRequestParameter("address", search.address());
      return true;
    }
    return false;
  }
}
</code>

The generated request URL will include all fields as query parameters, e.g., http://localhost:8888/api/search?name=pack&age=22&[email protected]&address=xxxooo .

Exception Handling (WebClient example)

<code>@Bean
RemoteService remoteService() {
  WebClient webClient = WebClient.builder()
      .baseUrl("http://localhost:8888")
      .defaultStatusHandler(HttpStatusCode::isError, resp -> {
        return Mono.just(new RuntimeException("Request error"));
      })
      .build();
  // ... create proxy as before
}
</code>

This configures a custom handler that converts HTTP 4xx/5xx responses into a RuntimeException .

All the above demonstrates how to replace Feign with the native @HttpExchange approach, leverage various client adapters, work with reactive types, customize parameter binding, and handle errors in Spring Boot 3.

JavaMicroservicesSpring BootfeignReactiveWebClientRestClienthttp-exchange
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.