Backend Development 7 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring 6: HTTP Interfaces, RestClient, i18n ProblemDetail, Virtual Threads

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.

backendJavaSpringHTTPVirtual ThreadsRestClientProblemDetail
Spring Full-Stack Practical Cases
Written by

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.

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.