Backend Development 10 min read

Master Spring Boot 3 Http Interface: Declarative Remote Calls Made Easy

This article explains how Spring Boot 3's Http Interface feature enables declarative HTTP service calls by defining Java interfaces, covering dependency setup, interface definitions, token handling, WebClient configuration, controller integration, testing with Postman, and discusses its reliance on WebFlux.

macrozheng
macrozheng
macrozheng
Master Spring Boot 3 Http Interface: Declarative Remote Calls Made Easy

Introduction

Http Interface, introduced in Spring Boot 3.0, lets you 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 (required by Spring Boot 3).

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;relativePath/&gt; <!-- lookup parent from repository -->
&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

Prepare a remote service (mall‑tiny‑swagger) and configure its base URL in

application.yml

:

<code>remote:
  baseUrl: http://localhost:8088/
</code>

Define HTTP interfaces with

@HttpExchange

and method‑level annotations such as

@PostExchange

and

@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>

Create a

TokenHolder

component to store the authentication token in the session:

<code>@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>

Configure a

WebClient

bean and create proxy beans for the 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>

Inject the proxies into a controller and call the remote endpoints:

<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(value = "pageNum", defaultValue = "1") Integer pageNum,
                                                          @RequestParam(value = "pageSize", 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 remote login API, obtain a token, and then successfully request the brand list API.

Conclusion

Http Interface allows remote HTTP calls without writing implementation code, but it depends on WebFlux’s WebClient, which may cause friction when using traditional Spring MVC.

Reference: Spring Framework integration documentation .

Source code: https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-http-interface

Javabackend developmentSpring BootWebClientHttp InterfaceDeclarative HTTP
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.