Description
Problem
I found that after upgrading from Java 8 to Java 11, my Spring boot app intermittently (80% of the time) fails to start due to
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxxRepository' defined in com.xxx.gemfire.repository.CommandRepository defined in @EnableGemfireRepositories declared on XXXXConfiguration: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Region [...] for Domain Type [...] using Repository [...] was not found; You must configure a Region with name [....] in the application context"
Debuging ...
So the Repository classes are found but the adjacent Region classes are not. Debuging soon revealed the issue was caused by @Region
beans not being found by Spring's ClassPathScanningCandidateComponentProvider
. With trace debugging, I found that when the problem occurs 1) the classloader is of type 'jdk.internal.loader.ClassLoaders' and 2)the thread of type 'ForkJoinPool.commonPool-worker'
Root Cause
From there I found spring-projects/spring-boot#6626 and https://stackoverflow.com/questions/49113207/completablefuture-forkjoinpool-set-class-loader which advise that
In Java SE 9, threads that are part of the fork/join common pool will always return the system class loader as their thread context class loader. In previous releases, the thread context class loader may have been inherited from whatever thread causes the creation of the fork/join common pool thread, ...
My understanding is that because SpringBoot puts classes in folder BOOT_INF, the Spring classloader is needed to find them.
Line 212 of https://github.com/spring-projects/spring-data-gemfire/blob/main/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireComponentClassTypeScanner.java requests that all classes be streamed and loaded in parallel and so the ForkJoinPool will attempt and fail to find the Region classes in the SpringBoot jar.
stream(this.spliterator(), true)
Possible fixes
- In the same spring-data-geode class, add new line
componentProvider.setResourceLoader(new DefaultResourceLoader(getEntityClassLoader()));
before the return in method newClassPathScanningCandidateComponentProvider - Change line 212 to
stream(this.spliterator(), false)
- Define a custom ForkJoinWorkerThreadFactory() with Spring Security Context as per the stackoverflow link above.
Notes
In the cases where the app starts, ForkJoinPool is not used at all and the JVM/OS decides to execute the entire stream sequentially using the main thread. I never found that only some of the Regions where loaded.
See also https://stackoverflow.com/questions/72740543/issue-with-spring-boot-gemfire-integration which also describes a problem loading Region beans