Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-17607

@Value Optional<...> field injection fails in case of registered ConversionService

    XMLWordPrintable

    Details

    • Last commented by a User:
      true

      Description

      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.

      For example, we've used annotations such as this:

          @Value("${optional.value:#{null}}")
          private Optional<String> optionalConfiguration;
      

      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
      	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:139)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:419)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1737)
      

       
      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:

      1. DefaultListableBeanFactory.resolveDependency recognized Optional and calls
      2. DefaultListableBeanFactory.createOptionalDependency which then resolves the dependency and, through a couple of methods, ends up calling
      3. 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
      4. ObjectToOptionalConverter, which, naturally, converts the provided property String into Optional<String>.
      5. 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.

        Attachments

          Activity

            People

            Assignee:
            juergen.hoeller Juergen Hoeller
            Reporter:
            ssundell ssundell
            Last updater:
            Spring Issues Spring Issues
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Dates

              Created:
              Updated:
              Resolved:
              Days since last comment:
              3 years, 37 weeks, 1 day ago