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:

Table 1. HTTP Client Implementations
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")

2. Use Interceptors for Cross-Cutting Concerns

Implement logging, metrics, or tracing as interceptors:

groups.forEachClient((group, clientBuilder) -> {
    clientBuilder.requestInterceptor(new MetricsInterceptor());
    clientBuilder.requestInterceptor(new TracingInterceptor());
});

3. Configure Appropriate Timeouts

Set realistic timeouts based on your SLAs:

# Fast APIs: short timeout
spring.http.serviceclient.cache.read-timeout=500

# External APIs: longer timeout
spring.http.serviceclient.partner.read-timeout=5000

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.