Skip to content

RestClient.Builder.configureMessageConverters disables the implicit registration of default converters. #36747

@Rebwon

Description

@Rebwon

In Spring Framework 7, I noticed that calling RestClient.Builder.configureMessageConverters(...) changes how default message converters are registered.

In DefaultRestClientBuilder.initMessageConverters(), defaults are registered only when both messageConverters and convertersConfigurer are null:

private List<HttpMessageConverter<?>> initMessageConverters() {
    HttpMessageConverters.ClientBuilder builder = HttpMessageConverters.forClient();
    if (this.messageConverters == null && this.convertersConfigurer == null) {
        builder.registerDefaults();
    }
    else {
        if (this.messageConverters != null) {
            this.messageConverters.forEach(builder::addCustomConverter);
        }
        if (this.convertersConfigurer != null) {
            this.convertersConfigurer.accept(builder);
        }
    }
    List<HttpMessageConverter<?>> result = new ArrayList<>();
    builder.build().forEach(result::add);
    return result;
}

This means that as soon as configureMessageConverters(...) is called, the implicit registerDefaults() path is skipped. For example:

RestClient client = RestClient.builder()
    .configureMessageConverters(builder -> {
        builder.withJsonConverter(new JacksonJsonHttpMessageConverter(jsonMapper));
    })
    .build();

My initial expectation was that this would keep the default converters and replace/configure the JSON converter. However, since registerDefaults() is not called, the configured JSON converter may not be included in the final converter list unless the user explicitly calls registerDefaults():

RestClient client = RestClient.builder()
    .configureMessageConverters(builder -> {
        builder.registerDefaults();
        builder.withJsonConverter(new JacksonJsonHttpMessageConverter(jsonMapper));
    })
    .build();

Is this the intended behavior?

The part that feels surprising is that the method name configureMessageConverters(...) can be interpreted as “configure the message converters used by the RestClient”, possibly starting from the default converter set. In practice, it appears to mean “fully configure the HttpMessageConverters.ClientBuilder, including deciding whether defaults should be registered”.

I understand that there are valid use cases where users may want to call disableDefaults() or provide a fully custom converter configuration. However, I am wondering why the implicit default registration is disabled merely by the presence of convertersConfigurer, rather than allowing the configurer to operate on the default converter setup by default.

More specifically, I would like to understand the design rationale behind this condition:

if (this.messageConverters == null && this.convertersConfigurer == null) {
    builder.registerDefaults();
}

Would it be possible or desirable for configureMessageConverters(...) to start with defaults unless the user explicitly calls disableDefaults()? Or is the intended contract that any use of configureMessageConverters(...) requires users to explicitly call registerDefaults() when they want to retain the default converters?

This is especially relevant when migrating from the previous list-based messageConverters(...) customization style, where users could modify an already initialized converter list. With the new HttpMessageConverters.ClientBuilder based API, it is easy to accidentally configure a JSON converter while ending up with no default converters at all.

If this is intended, it may be helpful to clarify in the Javadoc that configureMessageConverters(...) disables implicit default registration and that callers should invoke registerDefaults() explicitly when they want to customize the default converter set.

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)status: waiting-for-triageAn issue we've not yet triaged or decided on

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions