Backend Development 10 min read

Master Spring Boot 3 Http Interface: Declarative Remote Calls with WebFlux

This article explains how Spring Boot 3's Http Interface feature enables declarative HTTP service calls by defining Java interfaces, covering dependency setup, interface definition, token handling, configuration, controller integration, testing with Postman, and a brief discussion of its WebFlux dependency.

macrozheng
macrozheng
macrozheng
Master Spring Boot 3 Http Interface: Declarative Remote Calls with WebFlux

Introduction

Http Interface, introduced in Spring Boot 3.0, allows you to declare HTTP services as Java interfaces, generating proxy implementations based on WebFlux's WebClient.

Usage

Dependency Integration

Set Spring Boot version 3.0.0 in

pom.xml

.

Use JDK 17.

Add

spring-boot-starter-webflux

dependency.

<code>&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;3.0.0&lt;/version&gt;
&lt;/parent&gt;</code>
<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>

Basic Usage

Define an interface for the remote service using

@HttpExchange

and method‑level annotations such as

@PostExchange

or

@GetExchange

.

<code>/**
 * Define Http interface for remote UmsAdmin service
 */
@HttpExchange
public interface UmsAdminApi {
    @PostExchange("admin/login")
    CommonResult&lt;LoginInfo&gt; login(@RequestParam("username") String username,
                                 @RequestParam("password") String password);
}</code>
<code>/**
 * Define Http interface for remote PmsBrand service
 */
@HttpExchange
public interface PmsBrandApi {
    @GetExchange("brand/list")
    CommonResult&lt;CommonPage&lt;PmsBrand&gt;&gt; list(@RequestParam("pageNum") Integer pageNum,
                                            @RequestParam("pageSize") Integer pageSize);
    @GetExchange("brand/{id}")
    CommonResult&lt;PmsBrand&gt; detail(@PathVariable("id") Long id);
    @PostExchange("brand/create")
    CommonResult create(@RequestBody PmsBrand pmsBrand);
    @PostExchange("brand/update/{id}")
    CommonResult update(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrand);
    @GetExchange("brand/delete/{id}")
    CommonResult delete(@PathVariable("id") Long id);
}</code>

Token Storage

Implement a

TokenHolder

component that stores the authentication token in the HTTP session.

<code>/**
 * Login token storage (in Session)
 */
@Component
public class TokenHolder {
    public void putToken(String token) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
        request.getSession().setAttribute("token", token);
    }
    public String getToken() {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
        Object token = request.getSession().getAttribute("token");
        return token != null ? (String) token : null;
    }
}</code>

Configuration

Create a configuration class that provides a

WebClient

bean with default headers and a filter that adds the token to each request, then creates proxy beans for the declared interfaces.

<code>@Configuration
public class HttpInterfaceConfig {
    @Value("${remote.baseUrl}")
    private String baseUrl;
    @Autowired
    private TokenHolder tokenHolder;

    @Bean
    WebClient webClient() {
        return WebClient.builder()
            .defaultHeader("source", "http-interface")
            .filter((request, next) -> {
                ClientRequest filtered = ClientRequest.from(request)
                    .header("Authorization", tokenHolder.getToken())
                    .build();
                return next.exchange(filtered);
            })
            .baseUrl(baseUrl)
            .build();
    }

    @Bean
    UmsAdminApi umsAdminApi(WebClient client) {
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
        return factory.createClient(UmsAdminApi.class);
    }

    @Bean
    PmsBrandApi pmsBrandApi(WebClient client) {
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
        return factory.createClient(PmsBrandApi.class);
    }
}</code>

Controller

Inject the generated proxy beans and call them from a REST controller to perform login, list brands, get brand details, create, update, and delete operations.

<code>@RestController
@RequestMapping("/remote")
public class HttpInterfaceController {
    @Autowired
    private UmsAdminApi umsAdminApi;
    @Autowired
    private PmsBrandApi pmsBrandApi;
    @Autowired
    private TokenHolder tokenHolder;

    @PostMapping("/admin/login")
    public CommonResult&lt;LoginInfo&gt; login(@RequestParam String username, @RequestParam String password) {
        CommonResult&lt;LoginInfo&gt; result = umsAdminApi.login(username, password);
        if (result.getData() != null) {
            tokenHolder.putToken(result.getData().getTokenHead() + " " + result.getData().getToken());
        }
        return result;
    }

    @GetMapping("/brand/list")
    public CommonResult&lt;CommonPage&lt;PmsBrand&gt;&gt; listBrand(@RequestParam(defaultValue = "1") Integer pageNum,
                                                             @RequestParam(defaultValue = "3") Integer pageSize) {
        return pmsBrandApi.list(pageNum, pageSize);
    }

    @GetMapping("/brand/{id}")
    public CommonResult&lt;PmsBrand&gt; brand(@PathVariable("id") Long id) {
        return pmsBrandApi.detail(id);
    }

    @PostMapping("/brand/create")
    public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
        return pmsBrandApi.create(pmsBrand);
    }

    @PostMapping("/brand/update/{id}")
    public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrand) {
        return pmsBrandApi.update(id, pmsBrand);
    }

    @GetMapping("/delete/{id}")
    public CommonResult deleteBrand(@PathVariable("id") Long id) {
        return pmsBrandApi.delete(id);
    }
}</code>

Testing

Use Postman to call the login endpoint, obtain the token, and then invoke the authenticated brand‑list endpoint successfully.

Conclusion

Http Interface lets you call remote HTTP services by simply defining interfaces, but it relies on WebFlux’s WebClient, which may cause friction when using traditional Spring MVC.

JavaSpring BootWebFluxRemote CallHttp InterfaceDeclarative REST
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

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.