After upgrading Spring Boot from 2.1 to 2.2 our app failed to start.

Previous working configuration: - Spring Boot: 2.1.12.RELEASE - Spring Framework: 5.1.13.RELEASE - EclipseLink: 2.7.6 (Latest)

New failing configuration: - Spring Boot: 2.2.4.RELEASE - Spring Framework: 5.2.3.RELEASE - EclipseLink: 2.7.6 (Latest)

The symptom - error similar to this:

***************************
APPLICATION FAILED TO START
***************************

Description:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    com.company.core.domain.DomainSpecificEntityA._persistence_get_propertyB(DomainSpecificEntityA.java)

The following method did not exist:

    'void com.company.core.domain.DomainSpecificEntityA._persistence_checkFetched(java.lang.String)'

These methods are normally generated during dynamic weaving performed by EclipseLink (JPA implementation). org.eclipse.persistence.internal.jpa.weaving.PersistenceWeaver is that class that performs this weaving.

This PersistenceWeaver is registered in org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo#addTransformer during container entity manager factory creation.

Comparing the list of classes that got instrumented beween Spring Boot 2.1 and 2.2 revealed that the list was shorter in version 2.2.

The difference was due to the fact that some of the JPA entity classes were already loaded by the classloader, earlier than PersistenceWeaver's class transformation was added to the classloader.

I tracked down the location where these classes were getting loaded first and the stack trace was like this:

> java.lang.Class#getDeclaredConstructors   // once this executes the entity class is available in current classloader, excluding it from later instrumentation
>   org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider#findConstructorBindingAnnotatedConstructor
>   org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider#getBindConstructor(org.springframework.boot.context.properties.bind.Bindable<?>, boolean)
>   org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod#forType
>   org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator#validate
>   ...

The bean that was getting validated was similar to this:

// This class is registered as a Spring Bean:
public class ProjectManagerImp implements ProjectManager { ... }

// This is our custom interface extending our another (more generic) interface
public interface ProjectManager extends Service<Project> { ... }

// This is JPA entity extending an abstract JPA MappedSuperclass.
// Both of these get loaded into the classloader, excluding them from further instrumentation
public class Project extends PersistentDomainObjectWithMetaData { ... }

Workaround

Removing org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator made our app start and work as expected. We simply added this to our configuration:

    @Bean
    public BeanDefinitionRegistryPostProcessor offendingValidatorRemovingBeanDefinitionRegistryPostProcessor() {
        return new BeanDefinitionRegistryPostProcessor() {
            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            }

            @Override
            public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
                registry.removeBeanDefinition("org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator");
            }
        };
    }

Additional info - Git history of ConfigurationPropertiesBeanDefinitionValidator - The issue with which ConfigurationPropertiesBeanDefinitionValidator was added in Spring Boot 2.2.0.RC1: https://github.com/spring-projects/spring-boot/issues/17831 - A similar (fixed) bug in Spring Data JPA: Entity classes loaded before EclipseLink LTW is initialized - maybe a similar fix is needed here too?

Comment From: philwebb

@grimsa Are you able to provide a small sample application that reproduces the problem?

Comment From: grimsa

@philwebb Here you go: https://github.com/grimsa/spring-boot-20798-sample I documented my findings in README.md in that repo, hope they help.

Comment From: wilkinsona

Thanks for the sample, @grisma, and for your patience while we found some time to investigate. I've managed to reproduce the problem.

In a nutshell, it isn't safe for a BeanFactoryPostProcessor to load classes or reflect over them when a load-time weaver exists. As you have observed, any class loading that the post-processor performs may then prevent subsequent instrumentation. There are a couple of ways that we could avoid the problem. We could just skip the validation if the context contains a loadTimeWeaver bean. Alternatively, we could defer the validation until the load-time weaving infrastructure is in place. There's no good way to detect this. We'd have to implement LoadTimeWeaverAware and BeanFactoryAware. LoadTimeWeaverAware beans are created early so when the bean is created and setBeanFactory is called, validation could be performed.

Flagging for team attention to see if we can come up with a more elegant solution.