Avoid duplicate date-related converters registration

Prior to this commit, `DefaultFormattingConversionService` would
register converters with both `DateTimeFormatterRegistrar` and
`DateFormatterRegistrar`, the former also registering the legace date
converters that the latter contributes.

While we cannot change the behavior for `DateTimeFormatterRegistrar` or
`DateFormatterRegistrar` because of their public contract, we can update
the `DefaultFormattingConversionService` to not use
`DateFormatterRegistrar` and register manually the annotation support
that it contributes.

Closes gh-36951
This commit is contained in:
Brian Clozel
2026-09-19 12:12:10 +02:00
parent b9fa062918
commit b6d7f1d586
2 changed files with 40 additions and 4 deletions
@@ -16,6 +16,8 @@
package org.springframework.format.support;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import org.junit.jupiter.api.AfterEach;
@@ -30,6 +32,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.Formatter;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.number.NumberStyleFormatter;
import static org.assertj.core.api.Assertions.assertThat;
@@ -176,6 +179,28 @@ class FormattingConversionServiceTests {
assertThat(formattingService.convert("1", Integer.class)).isEqualTo(Integer.valueOf(1));
}
@Test // gh-36951
void defaultFormattingConversionServiceRegistersLegacyDateConvertersOnlyOnce() {
DefaultFormattingConversionService defaultService = new DefaultFormattingConversionService();
String convertersDescription = defaultService.toString();
assertThat(convertersDescription)
.containsOnlyOnce("DateFormatterRegistrar$DateToLongConverter")
.containsOnlyOnce("DateFormatterRegistrar$CalendarToDateConverter");
}
@Test // gh-36951
void defaultFormattingConversionServiceStillAppliesDateTimeFormatAnnotationToLegacyDateAndCalendarFields() throws Exception {
DefaultFormattingConversionService defaultService = new DefaultFormattingConversionService();
TypeDescriptor dateDescriptor = new TypeDescriptor(AnnotatedDateBean.class.getDeclaredField("date"));
TypeDescriptor calendarDescriptor = new TypeDescriptor(AnnotatedDateBean.class.getDeclaredField("calendar"));
Date date = (Date) defaultService.convert("2026-09-19", TypeDescriptor.valueOf(String.class), dateDescriptor);
Calendar calendar = (Calendar) defaultService.convert("2026-09-19", TypeDescriptor.valueOf(String.class), calendarDescriptor);
assertThat(defaultService.convert(date, dateDescriptor, TypeDescriptor.valueOf(String.class))).isEqualTo("2026-09-19");
assertThat(defaultService.convert(calendar, calendarDescriptor, TypeDescriptor.valueOf(String.class))).isEqualTo("2026-09-19");
}
static class NullReturningFormatter implements Formatter<Integer> {
@@ -214,4 +239,14 @@ class FormattingConversionServiceTests {
}
}
private static class AnnotatedDateBean {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Calendar calendar;
}
}