Spring 6: HTTP Interfaces, RestClient, i18n ProblemDetail, Virtual Threads
This guide walks through Spring 6’s new capabilities—including Java 17 baseline, Jakarta namespace migration, HTTP interface proxies with @HttpExchange, WebClient integration via JDK HttpClient, internationalized ProblemDetail handling, the RestClient API, and executing asynchronous tasks on virtual threads—all demonstrated with concise code examples.
Environment: Spring 3.2.0 (Spring 6.1.1) + JDK 21
Spring 6 Baseline
The whole framework codebase targets Java 17 source level.
Servlet, JPA and other namespaces moved from javax to jakarta.
Runtime compatible with Jakarta EE 9 and Jakarta EE 10 APIs.
Compatible with latest web servers: Tomcat 10.1, Jetty 11, Undertow 2.3.
Early compatibility with virtual threads (preview in JDK 19).
1. HTTP Remote Interface Calls
Define HTTP interfaces using @HttpExchange and generate proxy beans to invoke remote endpoints.
<code>@HttpExchange("/demos")
public interface RemoteService {
@GetExchange("/format")
Map<String, Object> format(@RequestParam Map<String, String> params) throws Exception;
}
</code>Configure the proxy bean:
<code>@Configuration
public class RemoteInterfaceConfig {
@Bean
public RemoteService remoteService(WebClient.Builder remoteClient) {
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(
WebClientAdapter.forClient(remoteClient.build())).build();
return factory.createClient(RemoteService.class);
}
}
</code>Use the service:
<code>@Resource
private RemoteService remoteService;
@GetMapping("/index")
public Object index(@RequestParam Map<String, String> params) throws Exception {
return remoteService.format(params);
}
</code>2. HttpClient
JDK 11 introduced HttpClient . In Spring it can be combined with WebClient :
<code>@Bean
public WebClient webClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
ClientHttpConnector connector = new JdkClientHttpConnector(httpClient);
return WebClient.builder().clientConnector(connector).build();
}
</code>3. ProblemDetail Internationalization
ProblemDetail exposes type , title , and detail . ResponseEntityExceptionHandler resolves these fields via MessageSource . Custom ResponseEntityExceptionHandler or ProblemDetailsExceptionHandler bean is required.
Example controller throwing ErrorResponseException :
<code>@GetMapping("/{id}")
public Object index(@PathVariable("id") Long id) {
if (id == 66) {
throw new ErrorResponseException(HttpStatusCode.valueOf(500));
}
return id;
}
</code>Internationalization file (properties):
<code>problemDetail.org.springframework.web.ErrorResponseException=这里是异常详细信息
problemDetail.title.org.springframework.web.ErrorResponseException=发生异常
</code>Handler implementation (excerpt):
<code>@ExceptionHandler({ErrorResponseException.class})
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
// ...
else if (ex instanceof ErrorResponseException subEx) {
return handleErrorResponseException(subEx, subEx.getHeaders(), subEx.getStatusCode(), request);
}
// ...
}
protected ResponseEntity<Object> handleErrorResponseException(
ErrorResponseException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
if (body == null && ex instanceof ErrorResponse errorResponse) {
body = errorResponse.updateAndGetBody(this.messageSource, LocaleContextHolder.getLocale());
}
return createResponseEntity(body, headers, statusCode, request);
}
default ProblemDetail updateAndGetBody(@Nullable MessageSource messageSource, Locale locale) {
if (messageSource != null) {
Object[] arguments = getDetailMessageArguments(messageSource, locale);
String detail = messageSource.getMessage(getDetailMessageCode(), arguments, null, locale);
if (detail != null) {
getBody().setDetail(detail);
}
String title = messageSource.getMessage(getTitleMessageCode(), null, null, locale);
if (title != null) {
getBody().setTitle(title);
}
}
return getBody();
}
</code>4. RestClient
Spring 6.1 adds RestClient , a synchronous HTTP client sharing infrastructure with RestTemplate but offering a WebClient‑like API.
<code>RestClient customClient = RestClient.builder()
.baseUrl("http://localhost:8088")
.defaultUriVariables(Map.of("id", "888"))
.defaultHeader("x-api-token", "aabbcc")
.requestInterceptor(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
System.out.println("拦截器...");
return execution.execute(request, body);
}
})
.requestInitializer(new ClientHttpRequestInitializer() {
@Override
public void initialize(ClientHttpRequest request) {
System.out.println("初始化器...");
request.getHeaders().add("x-version", "1.0.0");
}
})
.build();
Users users = customClient.get()
.uri("/demos/users/{id}")
.retrieve()
.onStatus(HttpStatusCode::isError, (request, response) -> {
throw new RuntimeException(response.getStatusCode().toString() + "请求错误");
})
.body(Users.class);
System.out.println(users);
</code>5. Virtual Thread Asynchronous Tasks
Methods annotated with @Async can run on virtual threads.
<code>@Bean
VirtualThreadTaskExecutor taskExecutor() {
return new VirtualThreadTaskExecutor("pack-vm-");
}
// or
@Bean
SimpleAsyncTaskScheduler taskScheduler() {
SimpleAsyncTaskScheduler scheduler = new SimpleAsyncTaskScheduler();
scheduler.setThreadNamePrefix("pack-vm-");
scheduler.setVirtualThreads(true);
return scheduler;
}
</code>Test method:
<code>@Async
public void task() {
System.out.println(Thread.currentThread().getName() + " - 执行异步任务");
}
</code>For more on virtual threads see the linked article.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.