Problem

The current design of spring.http.reactiveclient and spring.http.client introduces unnecessary duplication and complexity.

These two configurations serve blocking clients (RestClient) and reactive clients (WebClient) respectively. Their configuration options are nearly identical, with the only difference being the underlying implementations (factory vs connector).

spring:
  http:
    client:
      connect-timeout: 1s
      read-timeout: 10s
      factory: jdk
      service:
        group:
          service-a:
            base-url: service-a:8080
            connect-timeout: 2s
            read-timeout: 30s
    reactiveclient:
      connect-timeout: 1s
      read-timeout: 10s
      connector: jdk
      service:
        group:
          service-b:
            base-url: service-b:8080
            connect-timeout: 2s
            read-timeout: 30s

This configuration structure is unnecessarily verbose and difficult to reason about. We can unify spring.http.reactiveclient and spring.http.client into a single spring.http.client configuration.

Proposed Solution

spring:
  http:
    client:
      connect-timeout: 1s
      read-timeout: 10s
      service:
        group:
          service-a:
            base-url: service-a:8080
            connect-timeout: 2s
            read-timeout: 30s
            client-type: rest_client
          service-b:
            base-url: service-b:8080
            connect-timeout: 2s
            read-timeout: 30s
            client-type: web_client

From a user's perspective, this unified configuration is clearer. The trade-off is that we cannot provide factory/connector configuration in properties, which is acceptable compared to the benefit of clearer configuration. Users can easily provide customization through RestClientCustomizer/WebClientCustomizer beans, making this a worthwhile trade-off.

In terms of implementation, we can merge the spring-boot-restclient and spring-boot-webclient modules into spring-boot-http-client, while keeping the spring-boot-starter-restclient and spring-boot-starter-webclient starters.

Overall, we unify the configuration for blocking and reactive clients, sacrificing the ability to configure factory/connector in exchange for more consistent and ergonomic configuration and cleaner code implementation.

Comment From: bclozel

In terms of implementation, we can merge the spring-boot-restclient and spring-boot-webclient modules into spring-boot-http-client, while keeping the spring-boot-starter-restclient and spring-boot-starter-webclient starters.

I think this means that anyone depending on "spring-boot-http-client" would get both reactive and non-reactive clients in the same application. Modularizing the Spring Boot codebase was driven partly because clients and servers were not enough separated from a user perspective because of classpath signals. Here, this would introduce a new regression. Developers expecting a RestClient would also get WebClient and the entire reactive stack that goes with it.

Overall, we unify the configuration for blocking and reactive clients, sacrificing the ability to configure factory/connector in exchange for more consistent and ergonomic configuration and cleaner code implementation.

Given my comment above, I believe that the tradeoff is clearly in favor of separation. While developer ergonomics is a core value of the project, a consistent dependency management is a must have. Bringing both reactive and traditional stacks with a single dependency is the wrong move.

Comment From: DanielLiu1123

I think this means that anyone depending on "spring-boot-http-client" would get both reactive and non-reactive clients in the same application.

No, both reactive and non-reactive clients are optional (compile-only) dependencies for spring-boot-http-client. Users still use starters: - spring-boot-starter-restclient brings in the dependencies required by non-reactive clients. - spring-boot-starter-webclient brings in the dependencies required by reactive clients.

First, I fully understand why boot wants to split modules, it’s a change I’ve been hoping to see for years, so I would never propose a design where a single dependency includes both reactive and non-reactive, which would obviously be wrong. (BTW, not a fan of reactive.)

Essentially, both non-reactive and reactive clients are implementing the same functionality (HTTP clients), they are just different implementations. Therefore, they should share the same “facade.” I’m not someone who overuses abstraction, but in this case, abstraction is exactly what makes sense.

Comment From: DanielLiu1123

The following configuration is totally enough, it’s simpler and easier to use:

@ConfigurationProperties("spring.http.client")
public class HttpClientProperties extends AbstractHttpClientProperties {

    private Map<String, Group> groups = new LinkedHashMap<>();

    public static class Group extends AbstractHttpClientProperties {
        private ClientType clientType = ClientType.UNSPECIFIED;
    }
}

public abstract class AbstractHttpClientProperties {
    private @Nullable String baseUrl;
    private Map<String, List<String>> defaultHeader = new LinkedHashMap<>();
    private ApiversionProperties apiversion = new ApiversionProperties();
    private @Nullable HttpRedirects redirects;
    private @Nullable Duration connectTimeout;
    private @Nullable Duration readTimeout;
    private final Ssl ssl = new Ssl();
}

This configuration is also very easy to implement, and it allows us to remove many classes that feel redundant. - ClientHttpRequestFactorySettings / ClientHttpConnectorSettings - HttpReactiveClientSettingsProperties - ...

The current approach of separating spring.http.reactiveclient and spring.http.client feels really odd and awkward to use, it honestly gives off a bit of a code smell.