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.
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-webfluxdependency.
<code><parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
</code> <code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</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
@HttpExchangeand method‑level annotations such as
@PostExchangeand
@GetExchange:
<code>/**
* Define Http interface for remote UmsAdmin service
*/
@HttpExchange
public interface UmsAdminApi {
@PostExchange("admin/login")
CommonResult<LoginInfo> 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<CommonPage<PmsBrand>> list(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize);
@GetExchange("brand/{id}")
CommonResult<PmsBrand> 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
TokenHoldercomponent 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
WebClientbean 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<LoginInfo> login(@RequestParam String username, @RequestParam String password) {
CommonResult<LoginInfo> result = umsAdminApi.login(username, password);
if (result.getData() != null) {
tokenHolder.putToken(result.getData().getTokenHead() + " " + result.getData().getToken());
}
return result;
}
@GetMapping("/brand/list")
public CommonResult<CommonPage<PmsBrand>> 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<PmsBrand> 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
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.