Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Converter annotated with @ConfigurationPropertiesBinding does not get selected if targetType has a static factory method different return type #28592

Closed
DieBauer opened this issue Nov 10, 2021 · 2 comments
Assignees
Labels
type: regression A regression from a previous release
Milestone

Comments

@DieBauer
Copy link

DieBauer commented Nov 10, 2021

I'm upgrading my application from 2.4.10 to 2.5.6 and one of my custom Converters does not get picked up anymore since a specific static factory method (from) is present on the targetType.

After debugging this issue, it looks like in 2.5, the ObjectToObjectConverter wins, and gets called before my custom Converter is picked up.

Even though the return type of the factory method does not fit the targetType!

In my case the factory method returns an Optional.

public class Data {
    final String data;

    public Data(String data) {
        this.data = data;
    }

    public static Optional<Data> from(String s) {
        return Optional.of(new Data(s));
    }
}

while my Properties expects a Data.

In spring-boot 2.4.x this works for me.

The converter gets selected by the Binder, and my property is bound to the properties class.

In spring boot 2.5, the exact same code base fails with ConverterNotFoundException

The resulting error looks like this:

Failed to bind properties under 'my-data' to app.Data:

    Property: my-data
    Value: hello
    Origin: class path resource [application.properties] - 1:8
    Reason: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.util.Optional<?>] to type [app.Data]

In my opinion this is a bug in the priority of the generic ObjectToObjectConverter (not taking into account the targetType) and user-defined custom converters!

Reproduce

A small reproducer is here: https://github.com/DieBauer/spring-boot-propertiesbinding

I have a properties class that gets instantiated with a custom data type like so:

myData=hello

and the corresponding properties class.

@ConfigurationProperties
public class DataProperties {
    Data myData;

    public void setMyData(Data myData) {
        this.myData = myData;
    }

    public Data getMyData() {
        return myData;
    }
}

To make this work, I have a custom converter

public class DataConverter implements Converter<String, Data> {
    @Override
    public Data convert(String source) {
        return new Data(source);
    }
}

Which is annotated like this in my Configuration class.

    @Bean
    @ConfigurationPropertiesBinding
    public DataConverter converter() {
        return new DataConverter();
    }

Potential workarounds

Register the converter manually

I can register my Converter directly in the conversionService bean, following #26294 and the linked SO question

like this:

@Bean
public ConversionService conversionService(@Autowired Set<Converter> converters) {
    ConversionServiceFactoryBean factory = new ConversionServiceFactoryBean();
    factory.setConverters(converters);
    factory.afterPropertiesSet();
    return factory.getObject();
}

rendering the @ConfigurationPropertiesBinding useless.

Rename my static factory method

When the static factory method does not match any of the three terms in the ObjectToObjectConverter (valueOf, of, from) my converter gets picked up.

Add a converter as per suggestion of the error message

Adding a converter of Optional<?> (or whatever the return type of the static factory method is) to your custom type might work.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 10, 2021
@wilkinsona
Copy link
Member

Thanks for the sample. I've reproduced the behaviour that you've described. It appears to be a regression introduced in 2.5.0-RC1 as your sample works fine with 2.5.0-M3. I've yet to verify this, but I suspect that 51c3e18 may be the cause.

@wilkinsona wilkinsona added type: regression A regression from a previous release and removed status: waiting-for-triage An issue we've not yet triaged labels Nov 11, 2021
@wilkinsona wilkinsona added this to the 2.5.x milestone Nov 11, 2021
@wilkinsona wilkinsona added the for: team-meeting An issue we'd like to discuss as a team to make progress label Nov 18, 2021
@philwebb philwebb self-assigned this Nov 22, 2021
@philwebb philwebb removed the for: team-meeting An issue we'd like to discuss as a team to make progress label Dec 1, 2021
@philwebb philwebb modified the milestones: 2.5.x, 2.7.x May 13, 2022
@philwebb philwebb modified the milestones: 2.7.x, 2.6.x Jun 10, 2022
@philwebb philwebb changed the title Custom Converter annotated with @ConfigurationPropertiesBinding does not get selected if targetType has a static factory method from sourceType Custom Converter annotated with @ConfigurationPropertiesBinding does not get selected if targetType has a static factory method different return type Jun 10, 2022
@philwebb
Copy link
Member

I've added a fix in Boot, but I think this is really a framework bug. I've opened spring-projects/spring-framework#28609.

@philwebb philwebb modified the milestones: 2.6.x, 2.6.9 Jun 10, 2022
philwebb added a commit that referenced this issue Jun 10, 2022
Update fix to account for primitive types.

See gh-28592
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: regression A regression from a previous release
Projects
None yet
Development

No branches or pull requests

4 participants