[MM’s] Boot Notes — Spring’s HTTP Client Revolution: Goodbye Feign, Hello @HttpExchange

Discover @HttpExchange and HttpService Registry for native, declarative client design.

Simpler, Smarter HTTP Clients in Spring

For years, Spring developers have reached for RestTemplate or OpenFeign to make HTTP calls between services. Both have served well — but each came with trade-offs.
RestTemplate was synchronous and verbose; Feign was declarative but external.

With Spring Framework 7, the game changes. HTTP client support finally becomes native, declarative and type-safe — no third-party dependencies, no boilerplate factories and full Spring Boot integration.

⏩ TL;DR (Quick Recap)

  • Native & Declarative: @HttpExchange brings Feign-like clients directly into Spring.
  • Auto-Configured: @ImportHttpServices groups and registers them automatically.
  • Integrated: Works seamlessly with Boot, Cloud and Security.
  • Future-Ready: Built on RestClient / WebClient, supports API versioning and Virtual Threads.
  • Goodbye Feign: No external dependency, full Spring support.

1. The Problem with Traditional HTTP Clients

For more than a decade, RestTemplate was the default way to call remote APIs. But the template-style API began to show its age:

  • Only synchronous execution
  • Verbose configuration and limited extensibility
  • Incompatible with new features like API versioning
  • AsyncRestTemplate already deprecated

OpenFeign, popularized through Spring Cloud, fixed some of this by letting developers declare interfaces for remote services. But it was never truly Spring-native. It lived in its own dependency world, requiring extra configuration, version alignment and adapter glue.

Spring developers have long wanted the declarative simplicity of Feign, without the external baggage.

2. Enter Spring’s Declarative Model

At the heart of the new approach lies the @HttpExchange annotation—a way to define HTTP clients as plain Java interfaces.

@HttpExchange was introduced in Spring 6, enhanced in Spring 7.

public interface ProductService {
@GetExchange("/objects")
List<Product> getAllProducts();
@GetExchange("/objects/{id}")
Product getProductById(@PathVariable String id);
}

Creating a live, type-safe proxy for this interface takes just a few lines:

var client = RestClient.create("https://api.restful-api.dev");
var factory = HttpServiceProxyFactory
.builderFor(RestClientAdapter.create(client))
.build();
var service = factory.createClient(ProductService.class);

That’s it — no Feign, no custom bean setup. Your interface doubles as both documentation and client.

This design reuses the same underlying HTTP infrastructure as RestClient or WebClient, so you choose whichever client suits your app—imperative or reactive—without rewriting logic.

3. Organizing Clients with the Registry

Declaring one or two clients is easy; managing dozens quickly becomes repetitive.
Spring 7 introduces the Http Service Registry, which automates proxy creation and configuration.

Instead of declaring each client bean manually, you group related interfaces and let Spring register them for you.

@Configuration
@ImportHttpServices(group = "product",
types = { ProductService.class })
public class HttpServiceConfig { }

Each group shares the same configuration (base URL, timeouts, authentication, etc.).
Behind the scenes, Spring creates and wires all the necessary proxy beans.

You can even fine-tune groups programmatically:

@Bean
RestClientHttpServiceGroupConfigurer groupConfigurer() {
return groups -> groups
.filterByName("product")
.forEachClient((_, builder) ->
builder.baseUrl("https://api.restful-api.dev"));
}

Or move configuration entirely to properties:

spring.http.client.service.group.product.base-url=https://api.restful-api.dev
spring.http.client.service.read-timeout=2s
spring.http.client.service.connect-timeout=5s

No more repetitive factory beans — the registry scales automatically with your service list.

4. Built-in Integration: Boot, Cloud, and Security

The new registry model is tightly woven into the Spring ecosystem:

  • Spring Boot 4.0 automatically configures RestClient or WebClient for each group.
  • Spring Cloud 2025.1 adds load-balancing and circuit-breaking at the group level.
  • Spring Security 7.0 detects @ClientRegistrationId annotations for OAuth2 token propagation.

In other words, you get end-to-end service-to-service communication with almost no glue code.

5. Customization and Error Handling

Error handling can now be defined once — at the adapter level — and shared across all clients.

var restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError,
(request, response) -> {
throw new CustomApiException(response.getStatusCode());
})
.build();
var factory = HttpServiceProxyFactory
.builderFor(RestClientAdapter.create(restClient))
.build();

You can also extend parameter resolution with custom logic:

static class SearchQueryResolver implements HttpServiceArgumentResolver {
@Override
public boolean resolve(Object arg, MethodParameter param,
HttpRequestValues.Builder values) {
if (param.getParameterType().equals(Search.class)) {
var search = (Search) arg;
values.addRequestParameter("owner", search.owner());
values.addRequestParameter("query", search.query());
return true;
}
return false;
}
}

6. Choosing the Right HTTP Client

Spring 7 clarifies which client to use for which scenario:

  • Client Traditional MVC apps needing synchronous calls -> RestClient
  • Reactive, non-blocking, streaming workloads -> WebClient
  • Multiple organized APIs with declarative configuration -> @HttpExchange interfaces + Registry

Pro tip: RestClient pairs beautifully with Java 21’s Virtual Threads and Structured Concurrency, giving you high concurrency without reactive complexity.

7. From Feign to Spring-Native

If you’ve used Feign before, the transition feels familiar:

  • @FeignClient("users") → @HttpExchange +@ImportHttpServices(group="users")
  • Custom configuration per client → Group-level configuration via properties or Configurer
  • Separate dependency (spring-cloud-openfeign) → Built into Spring Framework core
  • Manual setup for OAuth2 or load-balancing → Automatic via Boot, Cloud, and Security

In short, Spring 7 turns Feign’s best ideas into native Spring features — with less friction, less configuration, and better long-term support.

8. Migrating from RestTemplate or Feign

Migration is straightforward:

→ From RestTemplate:
Switch to RestClient for a fluent, modern API.
You can wrap existing RestTemplate instances temporarily while you migrate.

→ From Feign:
Replace Feign interfaces with @HttpExchange equivalents.
Method signatures and annotations remain nearly identical.

Most Feign clients can be ported in minutes. Once migrated, you get first-class support for Boot configuration, Cloud integration and future enhancements like API versioning.

9. Beyond the Basics: New Testing and Extensibility

Spring 7 also introduces the RestTestClient, a companion for integration testing.
It allows you to test controllers and routes — either against a live server or mock endpoints — using the same fluent API as RestClient.

This unifies the testing experience across reactive and traditional stacks and is a likely successor to TestRestTemplate.

10. The Future of HTTP Clients in Spring

Spring 7 marks a major milestone:

  • RestTemplate begins its deprecation path (announcement in 7.0, deprecated in 7.1, removed in 8.0).
  • RestClient becomes the default for synchronous code.
  • WebClient remains the reactive option.
  • @HttpExchange and the Http Service Registry unify both worlds under a single, declarative model.

By Spring 8, all HTTP client usage will revolve around these unified, modern APIs. It’s cleaner, faster, and — finally — Spring-native.

Why It Matters

This evolution isn’t just about new APIs. It’s about consistency and simplicity.

You define interfaces.
Spring builds proxies.
Boot, Cloud, and Security wire everything else.

HTTP clients in Spring now feel like they always belonged there.
And for every developer who’s ever thought, “I wish Feign were just part of Spring,” — that wish just came true.

You can find all the code on GitHub.

Originally posted on marconak-matej.medium.com.