Review HttpMessageConverters semantics in Builder

Prior to this commit, the `HttpMessageConverters` builder API had
methods like "jsonMessageConverter" for configuring a specific converter
for JSON support. This converter would be always configured at a given
position, even if default converters registration is not requested.
On the other hand, `customMessageConverter` would add any converter
ahead of the list, in all cases. This difference was not conveyed as it
should by the API.

This commit makes the following changes:

* builder methods are renamed to `withJsonConverter` and variants, to
  better convey the fact that those are replacing the default converter
  for a given format.
* `customMessageConverter` is renamed to `addCustomConverter` to better
  reflect the additive aspect.
* the JavaDoc has been updated accordingly
* `withJsonConverter` and others are now only effective if the default
  registration of auto-detected converters is requested. This better
  aligns with the behavior in the reactive codecs configuration

Closes gh-35704
This commit is contained in:
Brian Clozel
2025-10-31 17:18:28 +01:00
parent 03c5ea25f5
commit af026c0373
10 changed files with 91 additions and 87 deletions
@@ -338,43 +338,43 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
}
@Override
public ClientBuilder stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
public ClientBuilder withStringConverter(HttpMessageConverter<?> stringMessageConverter) {
setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public ClientBuilder jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
public ClientBuilder withJsonConverter(HttpMessageConverter<?> jsonMessageConverter) {
setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public ClientBuilder xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
public ClientBuilder withXmlConverter(HttpMessageConverter<?> xmlMessageConverter) {
setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public ClientBuilder smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
public ClientBuilder withSmileConverter(HttpMessageConverter<?> smileMessageConverter) {
setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public ClientBuilder cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
public ClientBuilder withCborConverter(HttpMessageConverter<?> cborMessageConverter) {
setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public ClientBuilder yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
public ClientBuilder withYamlConverter(HttpMessageConverter<?> yamlMessageConverter) {
setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public ClientBuilder customMessageConverter(HttpMessageConverter<?> customConverter) {
public ClientBuilder addCustomConverter(HttpMessageConverter<?> customConverter) {
addCustomMessageConverter(customConverter);
return this;
}
@@ -391,20 +391,21 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
this.resourceMessageConverter = new ResourceHttpMessageConverter(false);
detectMessageConverters();
}
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.getCustomConverters());
partConverters.addAll(this.getCoreConverters());
allConverters.addAll(this.getCustomConverters());
allConverters.addAll(this.getBaseConverters());
if (this.resourceMessageConverter != null) {
allConverters.add(this.resourceMessageConverter);
List<HttpMessageConverter<?>> partConverters = new ArrayList<>(this.getCustomConverters());
List<HttpMessageConverter<?>> allConverters = new ArrayList<>(this.getCustomConverters());
if (this.registerDefaults) {
partConverters.addAll(this.getCoreConverters());
allConverters.addAll(this.getBaseConverters());
if (this.resourceMessageConverter != null) {
allConverters.add(this.resourceMessageConverter);
}
}
if (!partConverters.isEmpty() || !allConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
allConverters.addAll(this.getCoreConverters());
if (this.registerDefaults) {
allConverters.addAll(this.getCoreConverters());
}
if (this.configurer != null) {
allConverters.forEach(this.configurer);
}
@@ -422,43 +423,43 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
}
@Override
public ServerBuilder stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
public ServerBuilder withStringConverter(HttpMessageConverter<?> stringMessageConverter) {
setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public ServerBuilder jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
public ServerBuilder withJsonConverter(HttpMessageConverter<?> jsonMessageConverter) {
setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public ServerBuilder xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
public ServerBuilder withXmlConverter(HttpMessageConverter<?> xmlMessageConverter) {
setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public ServerBuilder smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
public ServerBuilder withSmileConverter(HttpMessageConverter<?> smileMessageConverter) {
setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public ServerBuilder cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
public ServerBuilder withCborConverter(HttpMessageConverter<?> cborMessageConverter) {
setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public ServerBuilder yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
public ServerBuilder withYamlConverter(HttpMessageConverter<?> yamlMessageConverter) {
setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public ServerBuilder customMessageConverter(HttpMessageConverter<?> customConverter) {
public ServerBuilder addCustomConverter(HttpMessageConverter<?> customConverter) {
addCustomMessageConverter(customConverter);
return this;
}
@@ -476,24 +477,24 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
this.resourceRegionMessageConverter = new ResourceRegionHttpMessageConverter();
detectMessageConverters();
}
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.getCustomConverters());
partConverters.addAll(this.getCoreConverters());
allConverters.addAll(this.getCustomConverters());
allConverters.addAll(this.getBaseConverters());
if (this.resourceMessageConverter != null) {
allConverters.add(this.resourceMessageConverter);
}
if (this.resourceRegionMessageConverter != null) {
allConverters.add(this.resourceRegionMessageConverter);
List<HttpMessageConverter<?>> partConverters = new ArrayList<>(this.getCustomConverters());
List<HttpMessageConverter<?>> allConverters = new ArrayList<>(this.getCustomConverters());
if (this.registerDefaults) {
partConverters.addAll(this.getCoreConverters());
allConverters.addAll(this.getBaseConverters());
if (this.resourceMessageConverter != null) {
allConverters.add(this.resourceMessageConverter);
}
if (this.resourceRegionMessageConverter != null) {
allConverters.add(this.resourceRegionMessageConverter);
}
}
if (!partConverters.isEmpty() || !allConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
allConverters.addAll(this.getCoreConverters());
if (this.registerDefaults) {
allConverters.addAll(this.getCoreConverters());
}
if (this.configurer != null) {
allConverters.forEach(this.configurer);
}
@@ -21,8 +21,10 @@ import java.util.function.Consumer;
/**
* Utility for building and configuring an immutable collection of {@link HttpMessageConverter}
* instances for {@link #forClient() client} or {@link #forServer() server} usage. You can
* ask to {@link Builder#registerDefaults() register default converters with classpath detection},
* add custom converters and post-process configured converters.
* ask to {@link Builder#registerDefaults() register default converters with classpath detection}
* and {@link Builder#withJsonConverter(HttpMessageConverter) override specific converters} that were detected.
* Custom converters can be independently added in front of default ones.
* Finally, {@link Builder#configureMessageConverters(Consumer) default and custom converters can be configured}.
*
* @author Brian Clozel
* @since 7.0
@@ -79,7 +81,7 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
/**
* Register default converters using classpath detection.
* Manual registrations like {@link #jsonMessageConverter(HttpMessageConverter)} will
* Manual registrations like {@link #withJsonConverter(HttpMessageConverter)} will
* override auto-detected ones.
*/
T registerDefaults();
@@ -90,7 +92,7 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* @param stringMessageConverter the converter instance to use
* @see StringHttpMessageConverter
*/
T stringMessageConverter(HttpMessageConverter<?> stringMessageConverter);
T withStringConverter(HttpMessageConverter<?> stringMessageConverter);
/**
* Override the default Jackson 3.x JSON {@code HttpMessageConverter}
@@ -98,7 +100,7 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* @param jsonMessageConverter the converter instance to use
* @see org.springframework.http.converter.json.JacksonJsonHttpMessageConverter
*/
T jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter);
T withJsonConverter(HttpMessageConverter<?> jsonMessageConverter);
/**
* Override the default Jackson 3.x XML {@code HttpMessageConverter}
@@ -106,7 +108,7 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* @param xmlMessageConverter the converter instance to use
* @see org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter
*/
T xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter);
T withXmlConverter(HttpMessageConverter<?> xmlMessageConverter);
/**
* Override the default Jackson 3.x Smile {@code HttpMessageConverter}
@@ -114,7 +116,7 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* @param smileMessageConverter the converter instance to use
* @see org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter
*/
T smileMessageConverter(HttpMessageConverter<?> smileMessageConverter);
T withSmileConverter(HttpMessageConverter<?> smileMessageConverter);
/**
* Override the default Jackson 3.x CBOR {@code HttpMessageConverter}
@@ -122,7 +124,7 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* @param cborMessageConverter the converter instance to use
* @see org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter
*/
T cborMessageConverter(HttpMessageConverter<?> cborMessageConverter);
T withCborConverter(HttpMessageConverter<?> cborMessageConverter);
/**
* Override the default Jackson 3.x Yaml {@code HttpMessageConverter}
@@ -130,13 +132,13 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* @param yamlMessageConverter the converter instance to use
* @see org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter
*/
T yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter);
T withYamlConverter(HttpMessageConverter<?> yamlMessageConverter);
/**
* Add a custom {@code HttpMessageConverter} to the list of converters.
* Add a custom {@code HttpMessageConverter} to the list of converters, ahead of the default converters.
* @param customConverter the converter instance to add
*/
T customMessageConverter(HttpMessageConverter<?> customConverter);
T addCustomConverter(HttpMessageConverter<?> customConverter);
/**
* Add a consumer for configuring the selected message converters.
@@ -498,7 +498,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
}
else {
if (this.messageConverters != null) {
this.messageConverters.forEach(builder::customMessageConverter);
this.messageConverters.forEach(builder::addCustomConverter);
}
if (this.convertersConfigurer != null) {
this.convertersConfigurer.accept(builder);