skip to content

Declarative HTTP Interfaces

Declaring an interface with @HttpExchange methods and letting Spring generate the implementation gives you a typed client with no boilerplate. A good answer when asked how you would keep a growing set of remote calls readable.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is a Spring HTTP Interface (declarative HTTP client), and how do you declare one with @HttpExchange?

level: juniorimportance: must knowfreq 55%

answer

  1. interface + @GetExchange = declarative call
  2. @HttpExchange type-level shared config
  3. params: @PathVariable/@RequestParam/@RequestBody
  4. proxy from HttpServiceProxyFactory
  5. like Feign, built into Spring 6

basics

~10 s

You define a Java interface with methods annotated like @GetExchange("/users/{id}"). Spring generates a proxy that turns each call into a real HTTP request, so you never write HTTP plumbing by hand.

solid answer

~40 s

An HTTP Interface is a plain Java/Kotlin interface where each method describes a remote HTTP call using annotations: @HttpExchange on the type for shared config (base path, content type) and @GetExchange / @PostExchange / @PutExchange / @DeleteExchange / @PatchExchange on methods. Method parameters map to request parts via @PathVariable, @RequestParam, @RequestBody, @RequestHeader. Spring's HttpServiceProxyFactory creates a dynamic proxy implementing that interface, backed by an adapter over a RestClient, WebClient, or RestTemplate. Calling a method executes the HTTP request, deserializes the response into the return type, and hands it back. It's the declarative equivalent of hand-writing RestClient calls — similar in spirit to OpenFeign, but built into Spring Framework 6+ with no extra dependency.

code

java · 15 lines
java
@HttpExchange(url = "/users", accept = "application/json")
public interface UserClient {

    @GetExchange("/{id}")
    User getById(@PathVariable long id);

    @GetExchange
    List<User> search(@RequestParam String name);

    @PostExchange
    User create(@RequestBody NewUser body);

    @DeleteExchange("/{id}")
    void delete(@PathVariable long id);
}

go deeper

for a junior

Know it's a declarative interface with @GetExchange-style methods that Spring turns into HTTP calls.

for a middle

Know the full annotation set and parameter binding (@PathVariable/@RequestParam/@RequestBody).

for a senior

Explain proxy creation via HttpServiceProxyFactory and adapter choice, plus return-type options.

for a principal

Weigh it against Feign/RestClient directly, discuss registration strategy, resilience, and testing across a codebase.

## What it is An **HTTP Interface** is Spring Framework's declarative HTTP client, introduced in Spring 6.0. Instead of imperatively building requests with `RestTemplate` or `RestClient`, you declare an **interface** whose methods each describe one remote endpoint. Spring generates a runtime **dynamic proxy** that implements the interface; each method invocation is translated into an actual HTTP request/response. ## The annotations - **`@HttpExchange`** — placed on the interface (type level) to hold shared settings such as a base `url`, default `contentType`, and `accept`. It can also be used directly on a method with an explicit `method = "GET"`. - Method-level shortcuts, each a meta-annotation of `@HttpExchange` with the HTTP method pre-set: - `@GetExchange` - `@PostExchange` - `@PutExchange` - `@DeleteExchange` - `@PatchExchange` - Their key attributes: `url` (path, appended to the type-level url), `contentType`, `accept`. ## Parameter binding Method parameters are mapped to parts of the request with familiar Spring MVC-style annotations: - `@PathVariable` — fills `{placeholders}` in the url. - `@RequestParam` — query parameters (or form data for form content type). - `@RequestBody` — serialized request payload (JSON by default via the underlying message converters/codecs). - `@RequestHeader`, `@CookieValue`, `@RequestPart` (multipart). - Certain **special parameter types** are handled automatically: a `URI` argument overrides the url, an `HttpMethod` argument overrides the method, and `MultipartFile` / `MultiValueMap` support form and multipart uploads. ## How the proxy is made (preview — deeper in other questions) You don't `new` the interface. You obtain an implementation from `HttpServiceProxyFactory`, configured with an **exchange adapter** (`RestClientAdapter`, `WebClientAdapter`, or `RestTemplateAdapter`) that wraps the real HTTP client: ```java RestClient restClient = RestClient.create("https://api.example.com"); HttpServiceProxyFactory factory = HttpServiceProxyFactory .builderFor(RestClientAdapter.create(restClient)).build(); UserClient client = factory.createClient(UserClient.class); ``` ## Return types - With a **blocking adapter** (RestClient / RestTemplate): the method can return the deserialized body (`User`), `ResponseEntity<User>`, `void`, a collection, etc. - With the **reactive** `WebClientAdapter`: it can additionally return `Mono<T>` / `Flux<T>`. ## Why use it - Removes boilerplate: no manual URL building, header setting, or response extraction. - Type-safe and testable: the interface is easy to mock in unit tests of your service code. - Consistent: reuses Spring's message converters, so JSON (Jackson) serialization "just works." - Comparable to Netflix/OpenFeign, but native to Spring with no external library. ## Gotchas - It is **not** a Spring bean automatically in older setups — you must register the proxy as a bean (or use Boot's newer auto-registration support) before injecting it. - The interface only *describes* calls; all real behavior (timeouts, base URL, interceptors, auth) is configured on the **underlying** RestClient/WebClient, not the interface. - Don't confuse it with `@FeignClient` (that's Spring Cloud OpenFeign, a separate project).

  • Which annotation carries shared settings for the whole interface, and what can it set?
    `@HttpExchange` at the type level. It supplies a base `url` prepended to method urls, plus default `contentType` and `accept`. Method annotations like `@GetExchange` inherit/override these.
  • How is this different from Spring Cloud OpenFeign's @FeignClient?
    HTTP Interfaces are core Spring Framework 6+, using HttpServiceProxyFactory over RestClient/WebClient/RestTemplate, no extra dependency. @FeignClient is a separate Spring Cloud project wrapping OpenFeign. They solve the same declarative-client problem but are distinct libraries.

saying these in an interview costs you the question

  • Thinking you implement the interface yourself with `new` or an `@Component`.
  • Confusing @HttpExchange with @FeignClient / assuming it needs Spring Cloud.
  • Believing the annotations configure timeouts or base URL (those live on the RestClient/WebClient).

context

open as a page

How do you turn an @HttpExchange interface into a usable client using HttpServiceProxyFactory and an adapter?

level: middleimportance: must knowfreq 50%

basics

~10 s

Wrap a configured RestClient (or WebClient) in an adapter, pass it to HttpServiceProxyFactory.builderFor(adapter).build(), then call factory.createClient(MyInterface.class) to get a proxy you register as a bean.

open as a page

How does error handling work for HTTP Interface calls, and how would you unit/integration test a declarative client?

level: seniorimportance: should knowfreq 32%

basics

~10 s

Errors surface as exceptions from the underlying client (e.g. RestClient throws HttpClientErrorException/HttpServerErrorException on 4xx/5xx). Customize via the client's status handlers. Test by pointing the client at a MockWebServer or mocking the interface directly.

open as a page

When do you back an HTTP Interface with the RestClientAdapter versus the WebClientAdapter, and how does that affect return types?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Use RestClientAdapter for blocking/servlet apps — methods return plain bodies or ResponseEntity. Use WebClientAdapter in reactive apps to additionally return Mono/Flux. RestTemplateAdapter exists for legacy code.

open as a page

At scale, how do you register many HTTP Interface clients cleanly, share configuration, and when would you NOT use declarative HTTP interfaces?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Share a pre-configured RestClient.Builder and register each interface as an @Bean via one factory (or use Spring's group registration). Skip declarative interfaces when calls are highly dynamic, need per-call streaming/low-level control, or when one-off imperative RestClient calls are simpler.

open as a page