Skip to content

Unleash Client doesn't handle Spring Boot dev tools hot reloading #315

@espada-edalex

Description

@espada-edalex

Describe the bug

I've configured a basic Unleash client in a Spring Boot application and when the hot reload triggers the background process crashes. The Configuration class is very simple and just setting the parameters (I'm using GitLab feature flags, so there is not api key needed).

I was able to stop the crashing by building our own UnleashScheduledExecutor, but then we had multiple clients running in the background because they weren't being shutdown during the reload.

Steps to reproduce the bug

No response

Expected behavior

No response

Logs, error output, etc.

2025-07-18T05:41:11.031Z  INFO 189 --- [example] [  restartedMain] i.g.repository.FeatureBackupHandlerFile  : Unleash will try to load feature toggle states from temporary backup
2025-07-18T05:41:11.034Z ERROR 189 --- [example] [  restartedMain] i.g.util.UnleashScheduledExecutorImpl    : Unleash background task crashed

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@70ae5d0a[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@32432edc[Wrapped task = io.getunleash.repository.FeatureRepositoryImpl$$Lambda/0x000071686cb72198@344d5c9c]] rejected from java.util.concurrent.ScheduledThreadPoolExecutor@261362f3[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1]
   at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2081) ~[na:na]
   at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:841) ~[na:na]
   at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:340) ~[na:na]
   at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.scheduleAtFixedRate(ScheduledThreadPoolExecutor.java:632) ~[na:na]
   at io.getunleash.util.UnleashScheduledExecutorImpl.setInterval(UnleashScheduledExecutorImpl.java:43) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.repository.FeatureRepositoryImpl.initCollections(FeatureRepositoryImpl.java:125) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.repository.FeatureRepositoryImpl.<init>(FeatureRepositoryImpl.java:92) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.repository.FeatureRepositoryImpl.<init>(FeatureRepositoryImpl.java:65) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.repository.FeatureRepositoryImpl.<init>(FeatureRepositoryImpl.java:51) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.repository.FeatureRepositoryImpl.<init>(FeatureRepositoryImpl.java:39) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.repository.FeatureRepositoryImpl.<init>(FeatureRepositoryImpl.java:34) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.EngineProxyImpl.<init>(EngineProxyImpl.java:38) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.DefaultUnleash.defaultToggleRepository(DefaultUnleash.java:38) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at io.getunleash.DefaultUnleash.<init>(DefaultUnleash.java:42) ~[unleash-client-java-11.0.2.jar:11.0.2]
   at com.example.config.UnleashConfiguration.unleash(UnleashConfiguration.java:39) ~[classes/:na]
   at com.example.config.UnleashConfiguration$$SpringCGLIB$$0.CGLIB$unleash$0(<generated>) ~[classes/:na]
   at com.example.config.UnleashConfiguration$$SpringCGLIB$$FastClass$$1.invoke(<generated>) ~[classes/:na]
   at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.2.8.jar:6.2.8]
   at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:400) ~[spring-context-6.2.8.jar:6.2.8]
   at com.example.config.UnleashConfiguration$$SpringCGLIB$$0.unleash(<generated>) ~[classes/:na]
   at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
   at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
   at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:489) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) ~[spring-beans-6.2.8.jar:6.2.8]
   at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.8.jar:6.2.8]
   at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.8.jar:6.2.8]
   at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.5.3.jar:3.5.3]
   at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.5.3.jar:3.5.3]
   at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.3.jar:3.5.3]
   at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.3.jar:3.5.3]
   at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.5.3.jar:3.5.3]
   at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.5.3.jar:3.5.3]
   at com.example.Application.main(Application.java:10) ~[classes/:na]
   at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
   at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
   at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.5.3.jar:3.5.3]

Screenshots

No response

Additional context

package com.example.config;

import io.getunleash.DefaultUnleash;
import io.getunleash.util.UnleashConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class UnleashConfiguration {

  @Value("${unleash.api-url}")
  private String apiUrl;

  @Value("${unleash.app-name}")
  private String appName;

  @Value("${unleash.instance-id}")
  private String instanceId;

  @Bean
  public DefaultUnleash unleash() {
    UnleashConfig config = UnleashConfig.builder()
      .appName(appName)
      .instanceId(instanceId)
      .unleashAPI(apiUrl)
      .synchronousFetchOnInitialisation(false)
      .build();

    return new DefaultUnleash(config);
  }
}

Unleash version

GitLab feature flags feature - https://docs.gitlab.com/operations/feature_flags/

Subscription type

None

Hosting type

None

SDK information (language and version)

11.0.2

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

For later

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions