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.

@HttpExchange("/demos")
public interface RemoteService {
    @GetExchange("/format")
    Map<String, Object> format(@RequestParam Map<String, String> params) throws Exception;
}

Configure the proxy bean:

@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);
    }
}

Use the service:

@Resource
private RemoteService remoteService;

@GetMapping("/index")
public Object index(@RequestParam Map<String, String> params) throws Exception {
    return remoteService.format(params);
}

2. HttpClient

JDK 11 introduced HttpClient. In Spring it can be combined with WebClient:

@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();
}

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:

@GetMapping("/{id}")
public Object index(@PathVariable("id") Long id) {
    if (id == 66) {
        throw new ErrorResponseException(HttpStatusCode.valueOf(500));
    }
    return id;
}

Internationalization file (properties):

problemDetail.org.springframework.web.ErrorResponseException=这里是异常详细信息
problemDetail.title.org.springframework.web.ErrorResponseException=发生异常

Handler implementation (excerpt):

@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();
}

4. RestClient

Spring 6.1 adds RestClient, a synchronous HTTP client sharing infrastructure with RestTemplate but offering a WebClient‑like API.

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);

5. Virtual Thread Asynchronous Tasks

Methods annotated with @Async can run on virtual threads.

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

Test method:

@Async
public void task() {
    System.out.println(Thread.currentThread().getName() + " - 执行异步任务");
}

For more on virtual threads see the linked article.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaspringHTTPVirtual 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

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.