I have a bean that implements Formatter that is supposed to also implement Converter as I need it to be considered for application property binding as well and I can simply forward the call to convert(…) to the parse(…) method of the converter.

Doing that causes the instance not to be registered as Formatter anymore as ApplicationConversionService.addBeans(…) uses an if-else cascade to forward the registration calls. Any particular reason it does not simply use a sequence of ifs?

Comment From: philwebb

I used the if/else cascade because I was worried about beans being registered twice. The Formatter is especially problematic since it's also a Printer and a Parser.

Why do you need to have the Formatter also be a Converter? That's an unusual pattern, especially as the FormattingConversionService adapts them to converters behind the scenes.

Comment From: odrotbohm

I got to this as follows:

  • I have domain type Quantity (an amount plus some metric) for which I have a Formatter implementation to render and parse it in and from Thymeleaf templates.
  • The project introduced a configuration property of the same type. I tried whether the Formatter would already make the binding work. It didn't.
  • I consulted the reference docs, learned about @ConfigurationPropertiesBinding simply added Converter<String, Quantity> to the list of interfaces implemented, added the annotation and delegated the call to convert(…) to the already existing parse(…) method defaulting the Locale to the US one.
  • That makes the properties binding work but breaks the rendering in templates stating that no converter from Quantity to String could be found.

I fixed that be creating a separate class implementing Converter, getting the Formatter implementation injected and delegating to it from the outside.

While this is of course fine, I think it's problematic as it's unusual that adding an interface to a type breaks the functionality plugged in for another implemented interface. The class still is a formatter but it's not considered one anymore. I was able to find out what caused this because I remembered that the beans would be routed into the ConversionService by such an assignment check, but someone not that deep into the internals would've had a hard time to get to the gist of it.

If you think such an arrangement is rare (which I tend to agree with) wouldn't that also mean that your original reason to use else blocks (potential double registration) is guarding a rare case? The auto configuration arrangement currently differentiates between beans of the two relevant types and thus you apparently need to register the functionality twice. The current state requires me to register two beans that could in fact be one.

Comment From: philwebb

I'd like to dig a bit more into why @ConfigurationPropertiesBinding on the Formatter bean didn't work. I feel like GenericConverter, Converter and Formatter are different abstraction levels for the same contract, and it doesn't seem right that the Formatter also needs to be a Converter.

Internally we have CharArrayFormatter and InetAddressFormatter so I'm pretty sure that formatters can work when binding properties. Perhaps there's a bug with the way that the @ConfigurationPropertiesBinding annotation works.

I'll try and create a sample to see if I can replicate the problem.

Comment From: odrotbohm

I can try to tweak the one I have in that direction in a couple of minutes and report back. Before you invest a lot of time in creating one from scratch.

Comment From: odrotbohm

Some more info I could gather: Adding @ConfigurationPropertiesBinding to a Formatter does not cause it to be used for property binding. I assume this is due to ConversionServiceDeducer.Factory only looking up beans of type Converter and GenericConverter (see its constructor).

If you picked up formatters, what Locale would be used to call the parse(…) method? I particularly chose the US one but I can imagine others might want to use a different one and falling back to the system one would potentially change the parsing behavior when moving apps from machines to machines.

Comment From: philwebb

If you picked up formatters, what Locale would be used to call the parse(…) method?

We'd rely on the standard FormattingConversionService behavior which uses LocaleContextHolder.

Comment From: odrotbohm

LocalContextHolder falls back to the system locale in case no explicit default locale is configured. Doesn't this mean that e.g. dates or numbers would be parsed differently when e.g. the application is run on a German localized system vs. a US one in production?

Comment From: philwebb

Yes, I think if your servers are set to different locales then the Formatter would be called with different values. In your example, I suspect if you use unformatted numbers in your properties file then everything will work. Feel free to open a new issue if you think we should offer some way to override the Locale just for configuration properties.