Search code examples
javaspringspring-bootthread-safety

Race condition when initialized Spring Bean


I am usingJava Spring Configuration and few beans are referencing one bean. It seems that all beans are trying to initialize properties() bean at same time because getting ConcurrentModificationException on startup of service (not always. getting this error 1 out of 1000 times)-

ERR [main                    ] - org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'genericTaskExecutor' defined in class path resource [com/test/SpringConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.concurrent.Executor]: Factory method 'threadPoolTaskExecutor' threw exception; nested exception is java.util.ConcurrentModificationException
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:483)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:893)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
    at org.springframework.context.support.FileSystemXmlApplicationContext.<init>(FileSystemXmlApplicationContext.java:142)
    at org.springframework.context.support.FileSystemXmlApplicationContext.<init>(FileSystemXmlApplicationContext.java:95)
    at Main.start(Main.java:27)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.concurrent.Executor]: Factory method 'threadPoolTaskExecutor' threw exception; nested exception is java.util.ConcurrentModificationException
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:650)
    ... 16 more
Caused by: java.util.ConcurrentModificationException
    at java.util.Hashtable$Enumerator.next(Hashtable.java:1387)
    at java.util.Hashtable.putAll(Hashtable.java:523)
    at com.test.SpringConfiguration.properties(DependencySpringConfiguration.java:43)
    at com.test.SpringConfiguration.threadPoolTaskExecutor(DependencySpringConfiguration.java:69)
    at com.test.SpringConfiguration$$EnhancerBySpringCGLIB$$c6bbb94b.CGLIB$threadPoolTaskExecutor$0(<generated>)
    at com.test.SpringConfiguration$$EnhancerBySpringCGLIB$$c6bbb94b$$FastClassBySpringCGLIB$$87cff70.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
    at com.test.SpringConfiguration$$EnhancerBySpringCGLIB$$c6bbb94b.threadPoolTaskExecutor(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
    ... 17 more

Following are bean definitions. My code is giving error on "properties.putAll(System.getProperties())" call. I am unable to find what I am missing in Spring Configuration because exception is coming rarely so must be race condition between beans initialization -

@Configuration
@EnableScheduling
public class DependencySpringConfiguration { 
    @Bean
    public static Properties properties() {
        Properties properties = new Properties();
        properties.putAll(System.getProperties());
        //do some formatting on properties data
        return properties;
    }

    @Bean(name = "genericTaskExecutor")
    @Primary
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        
        int corePoolSize = NumberUtils.toInt(properties().getProperty("generic-executor.core-pool.size"), 10);
        log.info("Initial pool size for 'genericTaskExecutor' is {}", corePoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(50);
        executor.setThreadNamePrefix("GenericTaskExecutor-");
        executor.initialize();
        return executor;
    }

    @Bean(name = "legacy")
    public Executor legacyThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int corePoolSize = NumberUtils.toInt(properties().getProperty("legacy-executor.core-pool.size"), 1);
        log.info("Initial pool size for 'legacyTaskExecutor' is {}", corePoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(10);
        executor.setThreadNamePrefix("LegacyTaskExecutor-");
        executor.initialize();
        return executor;
    }

    @Bean(name = "reconTaskExecutor")
    public Executor reconPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int corePoolSize = NumberUtils.toInt(properties().getProperty("recon-executor.core-pool.size"), 2);
        log.info("Initial pool size for 'reconTaskExecutor' is {}", corePoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(10);
        executor.setThreadNamePrefix("ReconTaskExecutor-");
        executor.initialize();
        return executor;
    }

}

Solution

  • System properties can change at any time so your attempt to properties.putAll(System.getProperties()) is throwing ConcurrentModificationException. That is because the putAll method is using an iterator. Try to clone() system properties and use the cloned instance instead.

    properties.putAll(System.getProperties().clone());