From 7be318d15dacf506d1d32cb218bc412090c636a6 Mon Sep 17 00:00:00 2001 From: "hayden.rear" Date: Thu, 18 Dec 2025 19:07:33 -0500 Subject: [PATCH 1/2] Closes gh-36043 Adds ability to add message converter at specific index to builders, and propagates through to java.util.List.add(idx, ...) Signed-off-by: Hayden Rear hayden.rear@gmail.com --- .../DefaultHttpMessageConverters.java | 19 ++++++++++++++++++- .../http/converter/HttpMessageConverters.java | 8 ++++++++ .../DefaultHttpMessageConvertersTests.java | 16 ++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java index f955acc53857..d55b4ba539c0 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java +++ b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java @@ -217,6 +217,11 @@ private void checkConverterSupports(HttpMessageConverter converter, MediaType throw new IllegalArgumentException("converter should support '" + mediaType + "'"); } + void addCustomMessageConverter(int index, HttpMessageConverter customConverter) { + Assert.notNull(customConverter, "'customConverter' must not be null"); + this.customConverters.add(index, customConverter); + } + void addCustomMessageConverter(HttpMessageConverter customConverter) { Assert.notNull(customConverter, "'customConverter' must not be null"); this.customConverters.add(customConverter); @@ -432,7 +437,13 @@ public ClientBuilder withYamlConverter(HttpMessageConverter yamlConverter) { @Override public ClientBuilder addCustomConverter(HttpMessageConverter customConverter) { - addCustomMessageConverter(customConverter); + addCustomConverter(customConverter); + return this; + } + + @Override + public ClientBuilder addCustomConverter(int index, HttpMessageConverter customConverter) { + addCustomMessageConverter(index, customConverter); return this; } @@ -533,6 +544,12 @@ public ServerBuilder addCustomConverter(HttpMessageConverter customConverter) return this; } + @Override + public ServerBuilder addCustomConverter(int index, HttpMessageConverter customConverter) { + addCustomMessageConverter(index, customConverter); + return this; + } + @Override public ServerBuilder configureMessageConverters(Consumer> configurer) { addMessageConverterConfigurer(configurer); diff --git a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java index 61bc33ab9bc4..48be5baad26c 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java +++ b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java @@ -166,6 +166,14 @@ interface Builder> { */ T addCustomConverter(HttpMessageConverter customConverter); + + /** + * Add a custom {@code HttpMessageConverter} to the list of converters, at the specified index, pushing all existing message converters forward one. + * @param customConverter the converter instance to add + * @param index index at which the specified element is to be inserted + */ + T addCustomConverter(int index, HttpMessageConverter customConverter); + /** * Add a consumer for configuring the selected message converters. * @param configurer the configurer to use diff --git a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java index f939e9b227ac..ba6f388bc2f1 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java @@ -147,6 +147,22 @@ void registerCustomMessageConverter() { assertThat(converters).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class); } + @Test + void registerCustomMessageConverterAtIndex() { + var clientBuilder = HttpMessageConverters.forClient() + .addCustomConverter(0, new CustomHttpMessageConverter()); + var converters = clientBuilder.build(); + + assertThat(converters).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class); + + clientBuilder.addCustomConverter(0, new CustomHttpMessageConverter()); + converters = clientBuilder.build(); + + assertThat(converters).hasExactlyElementsOfTypes( + CustomHttpMessageConverter.class, CustomHttpMessageConverter.class, + AllEncompassingFormHttpMessageConverter.class); + } + @Test void registerCustomMessageConverterAheadOfDefaults() { var converters = HttpMessageConverters.forClient().registerDefaults() From aeae7b97bf4b60d99da93a3280e91a7d2fd5706c Mon Sep 17 00:00:00 2001 From: "hayden.rear" Date: Thu, 18 Dec 2025 20:46:02 -0500 Subject: [PATCH 2/2] Closes gh-36043 Adds ability to change message converters list at before it's returned, using configureMessageConvertersList Signed-off-by: Hayden Rear hayden.rear@gmail.com --- .../DefaultHttpMessageConverters.java | 37 ++++++---- .../http/converter/HttpMessageConverters.java | 13 ++-- .../DefaultHttpMessageConvertersTests.java | 68 ++++++++++++++----- 3 files changed, 81 insertions(+), 37 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java index d55b4ba539c0..dcf9d44e5923 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java +++ b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java @@ -122,6 +122,8 @@ abstract static class DefaultBuilder { @Nullable Consumer> configurer; + @Nullable Consumer>> convertersListConfigurer; + @Nullable HttpMessageConverter kotlinJsonConverter; @Nullable HttpMessageConverter jsonConverter; @@ -217,11 +219,6 @@ private void checkConverterSupports(HttpMessageConverter converter, MediaType throw new IllegalArgumentException("converter should support '" + mediaType + "'"); } - void addCustomMessageConverter(int index, HttpMessageConverter customConverter) { - Assert.notNull(customConverter, "'customConverter' must not be null"); - this.customConverters.add(index, customConverter); - } - void addCustomMessageConverter(HttpMessageConverter customConverter) { Assert.notNull(customConverter, "'customConverter' must not be null"); this.customConverters.add(customConverter); @@ -231,6 +228,10 @@ void addMessageConverterConfigurer(Consumer> configurer) this.configurer = (this.configurer != null) ? configurer.andThen(this.configurer) : configurer; } + void addMessageConvertersListConfigurer(Consumer>> configurer) { + this.convertersListConfigurer = (this.convertersListConfigurer != null) ? convertersListConfigurer.andThen(this.convertersListConfigurer) : configurer; + } + List> getBaseConverters() { List> converters = new ArrayList<>(); if (this.byteArrayConverter != null) { @@ -437,19 +438,19 @@ public ClientBuilder withYamlConverter(HttpMessageConverter yamlConverter) { @Override public ClientBuilder addCustomConverter(HttpMessageConverter customConverter) { - addCustomConverter(customConverter); + addCustomMessageConverter(customConverter); return this; } @Override - public ClientBuilder addCustomConverter(int index, HttpMessageConverter customConverter) { - addCustomMessageConverter(index, customConverter); + public ClientBuilder configureMessageConverters(Consumer> configurer) { + addMessageConverterConfigurer(configurer); return this; } @Override - public ClientBuilder configureMessageConverters(Consumer> configurer) { - addMessageConverterConfigurer(configurer); + public ClientBuilder configureMessageConvertersList(Consumer>> configurer) { + addMessageConvertersListConfigurer(configurer); return this; } @@ -477,6 +478,10 @@ public HttpMessageConverters build() { if (this.configurer != null) { allConverters.forEach(this.configurer); } + if (this.convertersListConfigurer != null) { + this.convertersListConfigurer.accept(allConverters); + } + return new DefaultHttpMessageConverters(allConverters); } } @@ -545,14 +550,14 @@ public ServerBuilder addCustomConverter(HttpMessageConverter customConverter) } @Override - public ServerBuilder addCustomConverter(int index, HttpMessageConverter customConverter) { - addCustomMessageConverter(index, customConverter); + public ServerBuilder configureMessageConverters(Consumer> configurer) { + addMessageConverterConfigurer(configurer); return this; } @Override - public ServerBuilder configureMessageConverters(Consumer> configurer) { - addMessageConverterConfigurer(configurer); + public ServerBuilder configureMessageConvertersList(Consumer>> configurer) { + addMessageConvertersListConfigurer(configurer); return this; } @@ -584,6 +589,10 @@ public HttpMessageConverters build() { if (this.configurer != null) { allConverters.forEach(this.configurer); } + if (this.convertersListConfigurer != null) { + this.convertersListConfigurer.accept(allConverters); + } + return new DefaultHttpMessageConverters(allConverters); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java index 48be5baad26c..68a6a859cbfc 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java +++ b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java @@ -16,6 +16,7 @@ package org.springframework.http.converter; +import java.util.List; import java.util.function.Consumer; /** @@ -166,19 +167,17 @@ interface Builder> { */ T addCustomConverter(HttpMessageConverter customConverter); - /** - * Add a custom {@code HttpMessageConverter} to the list of converters, at the specified index, pushing all existing message converters forward one. - * @param customConverter the converter instance to add - * @param index index at which the specified element is to be inserted + * Add a consumer for configuring the selected message converters. + * @param configurer the configurer to use */ - T addCustomConverter(int index, HttpMessageConverter customConverter); + T configureMessageConverters(Consumer> configurer); /** - * Add a consumer for configuring the selected message converters. + * Add a consumer for configuring the message converters list just before it's returned. * @param configurer the configurer to use */ - T configureMessageConverters(Consumer> configurer); + T configureMessageConvertersList(Consumer>> configurer); /** * Build and return the {@link HttpMessageConverters} instance configured by this builder. diff --git a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java index ba6f388bc2f1..8e15bbe703a3 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java @@ -17,9 +17,11 @@ package org.springframework.http.converter; import java.io.IOException; +import java.util.Comparator; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.assertj.core.util.Lists; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -147,22 +149,6 @@ void registerCustomMessageConverter() { assertThat(converters).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class); } - @Test - void registerCustomMessageConverterAtIndex() { - var clientBuilder = HttpMessageConverters.forClient() - .addCustomConverter(0, new CustomHttpMessageConverter()); - var converters = clientBuilder.build(); - - assertThat(converters).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class); - - clientBuilder.addCustomConverter(0, new CustomHttpMessageConverter()); - converters = clientBuilder.build(); - - assertThat(converters).hasExactlyElementsOfTypes( - CustomHttpMessageConverter.class, CustomHttpMessageConverter.class, - AllEncompassingFormHttpMessageConverter.class); - } - @Test void registerCustomMessageConverterAheadOfDefaults() { var converters = HttpMessageConverters.forClient().registerDefaults() @@ -227,6 +213,31 @@ void shouldConfigureConverter() { assertThat(customConverter.processed).isTrue(); } + @Test + void shouldConfigureConverterOrder() { + var customConverter = new CustomHttpMessageConverter(); + var converted = HttpMessageConverters.forClient() + .addCustomConverter(customConverter) + .configureMessageConvertersList(converter -> { + converter.sort(Comparator.comparing(s -> s.getClass().equals(CustomHttpMessageConverter.class) ? 1 : -1)); + }).build(); + + var messageConvertersBack = Lists.newArrayList(converted); + assertThat(messageConvertersBack.size()).isGreaterThan(1); + assertThat(messageConvertersBack.get(messageConvertersBack.size() - 1).getClass()).isEqualTo(CustomHttpMessageConverter.class); + + var convertedFront = HttpMessageConverters.forClient() + .addCustomConverter(customConverter) + .configureMessageConvertersList(converter -> { + converter.sort(Comparator.comparing(s -> s.getClass().equals(CustomHttpMessageConverter.class) ? -1 : 1)); + }).build(); + + var messageConvertersFront = Lists.newArrayList(convertedFront); + assertThat(messageConvertersFront.get(0).getClass()).isEqualTo(CustomHttpMessageConverter.class); + assertThat(messageConvertersFront.size()).isGreaterThan(1); + + } + } @@ -335,6 +346,31 @@ void shouldConfigureConverter() { assertThat(customConverter.processed).isTrue(); } + + @Test + void shouldConfigureConverterOrder() { + var customConverter = new CustomHttpMessageConverter(); + var converted = HttpMessageConverters.forServer() + .addCustomConverter(customConverter) + .configureMessageConvertersList(converter -> { + converter.sort(Comparator.comparing(s -> s.getClass().equals(CustomHttpMessageConverter.class) ? 1 : -1)); + }).build(); + + var messageConvertersBack = Lists.newArrayList(converted); + assertThat(messageConvertersBack.size()).isGreaterThan(1); + assertThat(messageConvertersBack.get(messageConvertersBack.size() - 1).getClass()).isEqualTo(CustomHttpMessageConverter.class); + + var convertedFront = HttpMessageConverters.forServer() + .addCustomConverter(customConverter) + .configureMessageConvertersList(converter -> { + converter.sort(Comparator.comparing(s -> s.getClass().equals(CustomHttpMessageConverter.class) ? -1 : 1)); + }).build(); + + var messageConvertersFront = Lists.newArrayList(convertedFront); + assertThat(messageConvertersFront.size()).isGreaterThan(1); + assertThat(messageConvertersFront.get(0).getClass()).isEqualTo(CustomHttpMessageConverter.class); + + } } @SuppressWarnings("unchecked")