I'll copy my answer from https://stackoverflow.com/a/72781591/140707 since I think the two questions are similar (so the answer applies to both).
Existing answers didn't work for me:
- Customizing via
WebMvcConfigurerAdapter.addFormatters
(or simply annotating the converter with @Component
) only works in the WebMvc context and I want my custom converter to be available everywhere, including @Value
injections on any bean.
- Defining a
ConversionService
bean (via ConversionServiceFactoryBean
@Bean
or @Component
) causes Spring Boot to replace the default ApplicationConversionService
on the SpringApplication
bean factory with the custom bean you've defined, which will probably be based on DefaultConversionService
(in AbstractApplicationContext.finishBeanFactoryInitialization
). The problem is that Spring Boot adds some handy converters such as StringToDurationConverter
to the standard set in DefaultConversionService
, so by replacing it you lose those conversions. This may not be an issue for you if you don't use them, but it means that solution won't work for everyone.
I created the following @Configuration
class which did the trick for me. It basically adds custom converters to the ConversionService
instance used by Environment
(which is then passed on to BeanFactory
). This maintains as much backwards compatibility as possible while still adding your custom converter into the conversion services in use.
@Configuration
public class ConversionServiceConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@PostConstruct
public void addCustomConverters() {
ConfigurableConversionService conversionService = environment.getConversionService();
conversionService.addConverter(new MyCustomConverter());
}
}
Obviously you can autowire a list of custom converters into this configuration class and loop over them to add them to the conversion service instead of the hard-coded way of doing it above, if you want the process to be more automatic.
To make sure this configuration class gets run before any beans are instantiated that might require the converter to have been added to the ConversionService
, add it as a primary source in your spring application's run()
call:
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(new Class<?>[] { MySpringBootApplication.class, ConversionServiceConfiguration.class }, args);
}
}
If you don't do this, it might work, or not, depending on the order in which your classes end up in the Spring Boot JAR, which determines the order in which they are scanned. (I found this out the hard way: it worked when compiling locally with an Oracle JDK, but not on our CI server which was using a Azul Zulu JDK.)
Note that for this to work in @WebMvcTest
s, I had to also combine this configuration class along with my Spring Boot application class into a @ContextConfiguration
:
@WebMvcTest(controllers = MyController.class)
@ContextConfiguration(classes = { MySpringBootApplication.class, ConversionServiceConfiguration.class })
@TestPropertySource(properties = { /* ... properties to inject into beans, possibly using your custom converter ... */ })
class MyControllerTest {
// ...
}