Spring Boot 4 and Spring Framework 7 introduce powerful enhancements to HTTP Service Interface Clients, enabling developers to call remote REST services using declarative interfaces. This guide demonstrates these features with practical examples covering client registration, configuration, authentication (OAuth2 and HTTP Basic), custom message converters, and global settings.
Introduction
HTTP Service Interface Clients allow you to define REST API clients as Java interfaces annotated with @GetExchange, @PostExchange, and similar annotations. Spring automatically generates implementations at runtime, eliminating boilerplate code and improving maintainability.
The new features in Spring Boot 4 include:
-
Service Groups: Organize clients into logical groups with shared configuration
-
Properties-based Configuration: Configure base URLs, timeouts, and headers via
application.properties -
Customization API: Fine-grained control through
RestClientHttpServiceGroupConfigurer -
OAuth2 Integration: Built-in support via
OAuth2RestClientHttpServiceGroupConfigurer -
Global Settings: Configure HTTP factory choice, timeouts, and other settings globally
This guide uses examples from the spring-http-service-group-examples repository, demonstrating real-world usage patterns.
Project Setup
Before diving into the features, let’s set up a Spring Boot 4 project with the required dependencies.
Maven Dependencies
Add the following dependencies to your pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0</version>
<relativePath/>
</parent>
<dependencies>
<!-- Core web and REST client support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<!-- HTTP client implementations -->
<!-- Use jetty as http library -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
</dependency>
<!-- Use apache http component as http library -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<!-- OAuth2 support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-oauth2-client</artifactId>
</dependency>
</dependencies>
Client Registration
Spring Boot 4 provides two approaches for registering HTTP service clients: annotation-based using @ImportHttpServices and programmatic using AbstractHttpServiceRegistrar.
First, defining HTTP Service Interfaces
Here’s an example HTTP service interface for the JSONPlaceholder API:
public interface JsonPlaceholderClient {
@GetExchange("/posts") (1)
List<Post> getAllPosts();
@GetExchange("/posts/{id}") (2)
Post getPostById(@PathVariable Long id);
@GetExchange("/posts/{postId}/comments")
List<Comment> getCommentsByPostId(@PathVariable Long postId);
@PostExchange("/posts") (3)
Post createPost(@RequestBody Post post);
@PutExchange("/posts/{id}") (4)
Post updatePost(@PathVariable Long id, @RequestBody Post post);
@DeleteExchange("/posts/{id}") (5)
void deletePost(@PathVariable Long id);
}
| 1 | GET request to retrieve all posts |
| 2 | GET request with path variable |
| 3 | POST request to create a resource |
| 4 | PUT request to update a resource |
| 5 | DELETE request to remove a resource |
The interface uses familiar Spring MVC annotations like @PathVariable and @RequestBody, making it intuitive for Spring developers.
Now registering HTTP service clients using @ImportHttpServices
The @ImportHttpServices annotation is the simplest way to register HTTP service clients. You can specify clients by type or by scanning base packages.
@Configuration
@ImportHttpServices(
group = "jph", (1)
types = {JsonPlaceholderClient.class}) (2)
@ImportHttpServices(
group = "github", (3)
basePackages = "com.example.demo.client.github") (4)
@ImportHttpServices(
group = "httpbin",
basePackages = "com.example.demo.client.httpbin")
public class HttpClientConfig {
// Configuration beans...
}
| 1 | Define a service group named "jph" |
| 2 | Register specific client types |
| 3 | Define another service group named "github" |
| 4 | Scan a package for HTTP service interfaces |
Each @ImportHttpServices annotation creates a service group - a collection of HTTP service interfaces that share common configuration. The group attribute identifies the group for later configuration.
Or registering using AbstractHttpServiceRegistrar
For more programmatic control, extend AbstractHttpServiceRegistrar and override the registerHttpServices method:
public class MyHttpServiceRegistrar extends AbstractHttpServiceRegistrar {
@Override
protected void registerHttpServices(
GroupRegistry registry,
AnnotationMetadata metadata) {
// Scan package for HTTP service interfaces
registry.forGroup("ara") (1)
.detectInBasePackages(RestfulApiClient.class); (2)
}
}
| 1 | Create or reference a service group named "ara" |
| 2 | Detect all HTTP service interfaces in the same package as RestfulApiClient |
Then import this registrar in your configuration:
@Configuration
@Import(MyHttpServiceRegistrar.class) (1)
public class HttpClientConfig {
// Configuration beans...
}
| 1 | Import the custom registrar |
This approach is useful when you need conditional registration logic or need to compute group names dynamically.
Client Configuration
Spring Boot 4 offers multiple ways to configure HTTP service clients: through application properties for common settings and programmatically through configurers for advanced customization.
Configuration via application.properties
Configure service groups in application.properties using the pattern spring.http.serviceclient.<group-name>.*:
# JSONPlaceholder service group configuration
spring.http.serviceclient.jph.base-url=https://jsonplaceholder.typicode.com (1)
spring.http.serviceclient.jph.default-header.Accept=application/json (2)
spring.http.serviceclient.jph.default-header.Content-Type=application/json
spring.http.serviceclient.jph.read-timeout=1000 (3)
spring.http.serviceclient.jph.connect-timeout=1000 (4)
# GitHub API service group configuration
spring.http.serviceclient.github.base-url=https://api.github.com
spring.http.serviceclient.github.default-header.Accept=application/vnd.github.v3+json
# HTTP Bin service group configuration
spring.http.serviceclient.httpbin.base-url=https://httpbin.org
spring.http.serviceclient.httpbin.read-timeout=5000
spring.http.serviceclient.httpbin.connect-timeout=5000
| 1 | Base URL for all requests in this group |
| 2 | Default headers sent with every request |
| 3 | Read timeout in milliseconds |
| 4 | Connection timeout in milliseconds |
Configuration via RestClientHttpServiceGroupConfigurer
For advanced customization, implement a RestClientHttpServiceGroupConfigurer bean:
@Bean
public RestClientHttpServiceGroupConfigurer groupConfigurer(
OAuth2AuthorizedClientManager authorizedClientManager) {
return groups -> { (1)
// Configure specific group with custom error handler
groups.filterByName("jph") (2)
.filter(httpServiceGroup -> (3)
httpServiceGroup.httpServiceTypes()
.contains(JsonPlaceholderClient.class))
.forEachClient((group, clientBuilder) -> { (4)
clientBuilder.defaultStatusHandler(
new CustomErrorHandler()); (5)
});
// Add interceptor to another group
groups.filterByName("ara") (6)
.forEachClient((group, clientBuilder) -> {
clientBuilder.requestInterceptor(
new LoggingInterceptor()); (7)
});
// Configure OAuth2 for GitHub group
groups.filterByName("github")
.forEachClient((group, clientBuilder) -> {
var oauth2Interceptor =
new OAuth2ClientHttpRequestInterceptor(
authorizedClientManager); (8)
oauth2Interceptor.setClientRegistrationIdResolver(
request -> "github");
clientBuilder.requestInterceptor(oauth2Interceptor);
});
// Configure HTTP Basic Auth for httpbin group
groups.filterByName("httpbin")
.forEachClient((group, clientBuilder) -> {
clientBuilder.apply(restClientBuilder -> (9)
restClientBuilder.defaultHeaders(headers ->
headers.setBasicAuth(
httpbinUsername,
httpbinPassword)));
// Add custom message converter
clientBuilder.configureMessageConverters(builder -> { (10)
builder.addCustomConverter(
new MyCustomMessageConverter());
});
});
};
}
| 1 | The configurer receives a Groups object to configure |
| 2 | Filter groups by name |
| 3 | Further filter by checking if group contains specific client types |
| 4 | Apply configuration to each client in the filtered groups |
| 5 | Set a custom error handler |
| 6 | Configure a different group with an interceptor |
| 7 | Add a request/response logging interceptor |
| 8 | Add OAuth2 authentication interceptor |
| 9 | Access the underlying RestClient.Builder for advanced configuration |
| 10 | Configure custom message converters |
The RestClientHttpServiceGroupConfigurer provides powerful filtering and configuration options:
-
filterByName()- Filter groups by name -
filter()- Apply custom filtering logic -
forEachClient()- Configure individual clients
Client for OAuth2 Protected Services
Spring Boot 4 provides seamless integration with OAuth2 through the OAuth2RestClientHttpServiceGroupConfigurer.
Configuring OAuth2 in application.properties
First, configure your OAuth2 client registration:
# OAuth2 client registration for GitHub
spring.security.oauth2.client.registration.github.client-id=${GITHUB_OAUTH2_ID} (1)
spring.security.oauth2.client.registration.github.client-secret=${GITHUB_OAUTH2_SECRET} (2)
spring.security.oauth2.client.registration.github.scope=user:email (3)
| 1 | OAuth2 client ID (from environment variable) |
| 2 | OAuth2 client secret (from environment variable) |
| 3 | Requested OAuth2 scopes |
OAuth2 HTTP Service Interface
Define an HTTP service interface for the protected resource:
@HttpExchange("/user") (1)
public interface GithubUserService {
@GetExchange (2)
GithubUser getAuthenticatedUser();
}
| 1 | Base path for all methods in this interface |
| 2 | GET request to /user endpoint |
The model class:
public record GithubUser(String login, int id, String name) {}
Using OAuth2RestClientHttpServiceGroupConfigurer
Configure OAuth2 authentication for the service group:
@Bean
OAuth2RestClientHttpServiceGroupConfigurer securityConfigurer(
OAuth2AuthorizedClientManager manager) {
return OAuth2RestClientHttpServiceGroupConfigurer.from(manager); (1)
}
| 1 | Create the configurer from an OAuth2AuthorizedClientManager |
For more control, you can manually configure the OAuth2AuthorizedClientManager:
@Bean
OAuth2AuthorizedClientManager oauth2AuthorizedClientManager (
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
// Configure OAuth2 provider for authorization code, refresh token,
// and client credentials flows
var authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode() (1)
.refreshToken() (2)
.clientCredentials() (3)
.build();
var authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository,
authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(
authorizedClientProvider);
return authorizedClientManager;
}
| 1 | Support Authorization Code grant type |
| 2 | Support Refresh Token grant type |
| 3 | Support Client Credentials grant type |
Alternative: Manual OAuth2 Configuration
You can also configure OAuth2 manually using RestClientHttpServiceGroupConfigurer:
@Bean
public RestClientHttpServiceGroupConfigurer groupConfigurer(
OAuth2AuthorizedClientManager authorizedClientManager) {
return groups -> {
groups.filterByName("github")
.forEachClient((group, clientBuilder) -> {
var oauth2Interceptor =
new OAuth2ClientHttpRequestInterceptor(
authorizedClientManager); (1)
oauth2Interceptor.setClientRegistrationIdResolver(
request -> "github"); (2)
clientBuilder.requestInterceptor(oauth2Interceptor); (3)
});
};
}
| 1 | Create OAuth2 interceptor with the authorized client manager |
| 2 | Set the client registration ID (matches the name in properties) |
| 3 | Add the interceptor to the client |
Client for HTTP Basic Authentication
HTTP Basic Authentication is simpler than OAuth2 and suitable for internal services or testing.
Configuring Basic Auth Credentials
Store credentials in application.properties:
httpbin.auth.username=mark (1)
httpbin.auth.password=secret (2)
| 1 | Basic auth username |
| 2 | Basic auth password |
HTTP Service Interface for Basic Auth
public interface HttpBinClient {
@GetExchange("/basic-auth/{user}/{password}") (1)
BasicAuthResponse testBasicAuth(
@PathVariable String user,
@PathVariable String password);
}
| 1 | httpbin.org provides a test endpoint that validates credentials |
Configuring Basic Auth with RestClientHttpServiceGroupConfigurer
@Configuration
public class HttpClientConfig {
@Value("${httpbin.auth.username}")
private String httpbinUsername; (1)
@Value("${httpbin.auth.password}")
private String httpbinPassword; (2)
@Bean
public RestClientHttpServiceGroupConfigurer groupConfigurer() {
return groups -> {
groups.filterByName("httpbin")
.forEachClient((group, clientBuilder) -> {
clientBuilder.apply(restClientBuilder -> (3)
restClientBuilder.defaultHeaders(headers ->
headers.setBasicAuth(
httpbinUsername,
httpbinPassword))); (4)
});
};
}
}
| 1 | Inject username from properties |
| 2 | Inject password from properties |
| 3 | Access the underlying RestClient.Builder |
| 4 | Set Basic Auth credentials in default headers |
Now all requests to the "httpbin" group will automatically include the Authorization header with Base64-encoded credentials.
Global Configuration
Global configuration applies to all HTTP clients unless overridden at the group level.
Configuring Global Timeouts
Set default timeouts for all HTTP clients:
spring.http.clients.connect-timeout=2s (1)
spring.http.clients.read-timeout=1s (2)
| 1 | Default connection timeout (2 seconds) |
| 2 | Default read timeout (1 second) |
These values are used unless a specific group overrides them:
# Override for specific group
spring.http.serviceclient.httpbin.read-timeout=5000 (1)
spring.http.serviceclient.httpbin.connect-timeout=5000
| 1 | This group uses 5 seconds instead of the global default |
Choosing HTTP Factory Implementation
Spring Boot supports multiple HTTP client implementations. Choose one based on your needs:
spring.http.clients.imperative.factory=http-components (1)
| 1 | Use Apache HTTP Components Client 5 |
Available options:
-
http-components- Apache HTTP Components Client 5 (recommended for production) -
jetty- Jetty HTTP Client (good for HTTP/2 support) -
jdk- JDK HttpClient (Java 11+, lightweight) -
simple- Simple JDK implementation (basic use cases)
Each implementation has different features and performance characteristics:
| Implementation | HTTP/2 | Connection Pooling | Production Ready |
|---|---|---|---|
http-components |
Yes |
Yes |
Yes |
jetty |
Yes |
Yes |
Yes |
jdk |
Yes |
Yes |
Yes |
simple |
No |
No |
No (testing only) |
Required Dependencies
Make sure to include the dependency for your chosen implementation:
<!-- For Apache HTTP Components -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<!-- For Jetty -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
</dependency>
Spring Boot will automatically detect and configure the available implementation. If multiple implementations are present, use the spring.http.clients.imperative.factory property to select which one to use.
Recommendations
1. Organize Clients into Logical Groups
Group related HTTP service interfaces together:
// Good: Logical grouping
@ImportHttpServices(group = "payment", basePackages = "com.example.payment")
@ImportHttpServices(group = "notification", basePackages = "com.example.notification")
// Avoid: Single group for everything
@ImportHttpServices(group = "default", basePackages = "com.example")
Conclusion
Spring Boot 4’s HTTP Service Interface Clients provide a powerful, declarative approach to building REST clients. The key features include:
-
@ImportHttpServices and AbstractHttpServiceRegistrar for flexible client registration
-
Properties-based configuration for base URLs, timeouts, and headers
-
RestClientHttpServiceGroupConfigurer for programmatic customization
-
Built-in OAuth2 support via OAuth2RestClientHttpServiceGroupConfigurer
-
HTTP Basic Authentication through default headers
-
Custom message converters for special formats
-
Global configuration for timeouts and HTTP factory selection
These features eliminate boilerplate code, improve maintainability, and provide consistent configuration across your application. The complete example code is available at spring-http-service-group-examples.
For more information, consult the official Spring Framework documentation on REST Clients.