We've used Optional fields in our project to handle configurable features where configuration properties can be defined or left out. However, after upgrading from Springboot 1.5.3 to Springboot 2.1 and Spring Framework 5.x value injection fails.
This initializes fine, but when you actually try to use the Optional, it fails with an exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nameOfTheBean': Invocation of init method failed; nested exception is java.lang.ClassCastException: java.util.Optional cannot be cast to java.lang.String
In other words: the type of the field is evaluated twice, and as a result, the post processor injects the intializing bean with Optional<Optional<String>> instead of Optional<String>.
The flow seems to be like this:
DefaultListableBeanFactory.resolveDependency recognized Optional and calls
DefaultListableBeanFactory.createOptionalDependency which then resolves the dependency and, through a couple of methods, ends up calling
TypeConverterDelegate.convertIfNecessary with both requiredType == String and field's TypeDescriptor, which then identifies the type as Optional<String>. This ignores the requiredType enough so that it ends up calling
ObjectToOptionalConverter, which, naturally, converts the provided property String into Optional<String>.
This brings us back to DefaultListableBeanFactory.createOptionalDependency, which now has Optional<String> created in step 4 in its hands.... and it wraps it inside another Optional by calling Optional.ofNullable.
I haven't managed to reproduce this: All test case variants that I tried resolve fine for me, with and without actual conversion needs in an Optional<...> structure. Please try to narrow it down a bit, ideally providing a test case for it...
Thanks, that helped! I was trying to isolate a core framework test case, and it is Boot's default ConversionService that really triggers this behavior. As per your analysis above, ObjectToOptionalConverter is involved here which only appears in case of a ConversionService registered with the application context... which is indeed the case in Boot 2.
This turns out to be specific to the use of @Value on fields in combination with a ConversionService. Nesting is properly adapted for method/constructor parameters but unfortunately not for fields here. I'll fix this for 5.1.4, to be backported to 5.0.12 and 4.3.22.
DependencyDescriptor fully supports TypeDescriptor resolution for fields now. This allows for proper nested type conversion in @Value Optional fields analogous to method parameters, through a new TypeDescriptor-based method in the TypeConverter SPI in 5.1.4. As an additional and less involved measure that is worth backporting, DefaultListableBeanFactory defensively checks for pre-converted Optional wrappers.