Skip to content

Binder can fail with POJOs that have generic types #16821

@Clank84

Description

@Clank84

This year I'm upgrading my application from Java 8 to OpenJDK 11.0.2 so that necessitated upgrading Spring Boot from v1.5.3 to 2.1.4. After I did that the POJO to store my YAML properties failed.

For convenience I had created a simple POJO to hold any key and value type, this allowed for complex YAML tag configurations without having to create a lot of specialized POJO classes.

public class GenericProperty <K,V> {
	private K key;
	private V value;
	
	public GenericProperty() {
		//Default
	}
	
	/**
	 * Creates an instance of SimpleProperty
	 * @param aKey Represents a property key
	 * @param aValue Represents a property value
	 */
	public GenericProperty(K aKey, V aValue) {
		key = aKey;
		value = aValue;
	}
}
examples:
# Map<Integer, GenericProperty<Integer,String>>
  pojo-map: {14: {key: 23, value: "hello"}, 98: {key: 85, value: "world"}}  

# Maping with Boolean as Key, and Value is list of Objects 
# Map<Boolean,List<GenericProperty<String,String>>>
  pojo-map-list: {
    true: [
       {key: "hello", value: "hola"},
       {key: "night", value: "nochas"}
    ], 
    false: [
       {key: "blue", value: "azul"},
       {key: "white", value: "blanco"}
    ]
  }  

In v1.5.3 this worked fine when the YAML was mapped to attributes on a POJO like this:

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix="examples")//Root level within .yml config file
public class YamlPojo {

	private Map<Integer,GenericProperty<Integer,String>> pojoMap;
	
	/** Only with SpringBoot 1.5.x can reuse GenericProperty again */
	private Map<Boolean,List<GenericProperty<String,String>>> pojoMapList;

	public YamlPojo() {
		//Default
	}
}
# To String Output after YAML loaded and mapped to Object
YamlPojo[
Pojo Map:,
	98=GenericProperty[Key=85,Value=world],
	14=GenericProperty[Key=23,Value=hello],
Pojo Map2:,
	false=[GenericProperty[Key=blue,Value=azul], GenericProperty[Key=white,Value=blanco]],
	true=[GenericProperty[Key=hello,Value=hola], GenericProperty[Key=night,Value=nochas]]]
]

But when switching to v2.1.4 reusing the same GenericPropery.java on a different attribute failed. I think that's because the library used a binder on the first attribute, pojoMap, for <Integer,String> and did not create another binder for <String,String> on the second attribute, pojoMapList.

Caused by: org.springframework.boot.context.properties.ConfigurationPropertiesBindException: 
  Error creating bean with name 'yamlPojo': 
  Could not bind properties to 'YamlPojo' : 
  prefix=examples, ignoreInvalidFields=false, ignoreUnknownFields=true; 
  nested exception is org.springframework.boot.context.properties.bind.BindException: 
    Failed to bind properties under 
    'examples.pojo-map-list[true][0].key' to java.lang.Integer
	at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:110) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:93) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:414) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]

My only workaround is to create another Class to handle mapping the second attribute. I've attached a project (yaml-generics.zip) so you can test the scenario. My pom.xml is using the latest code, so if you try both JUnit classes then the test case that tries to reuse GenericProperty.java twice will fail. If you modify the pom.xml to revert to the older release, v1.5, then both JUnit cases work. Was that just a "happy accident" under v1.5 that using generics with a Collection was handled, or is this a bug with the latest version?

This might be similar to these issues: #11408, #13376

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions