Fixing classloader problems with Spring Devtools and Redisson Node

Embedded Redisson Node is a service where you can execute asynchronous tasks using Redis with Redisson. This library is not bound to Spring whatsoever, so if you want to run your tasks in the Spring context you need to bind it manually. However, using Spring Boot with Devtools you may encounter some class loading problems between Redisson Node and Spring context, what I’ve today. Here is a quick howto what’s this about and how to overcome this
problem.

We start from running the Redisson Node in embedded mode from Spring bean @PostConstruct:

As you can see I expose Scheduler bean in static instance field to have access to both this instance and the Spring Context from my SchedulerTask. This needs to be done in some way because our SchedulerTask recovered from Redisson at the trigger time doesn’t have any access to Spring. Here is the task implementation using this static instance field:

Everything looks working and should work, but it doesn’t… When I use Spring Devtools (org.springframework.boot:spring-boot-devtools dependency) in the project I have NullPointerException when I want to use Scheduler.getInstance() from SchedulerTask. But why?

It turns out it’s the class loader problem. Devtools to support hot restarts uses its own RestartClassLoader. So, when it creates Scheduler class, after which I create Scheduler class instance, the class loader is RestartClassLoader. While for RedissonNode the class loader is AppClassLoader what’s one level up before RestartClassLoader (it’s RestartClassLoader.getParent()). This happens because RedissonNode class comes from a jar library not from the project classes, and is not supported by Devtools hot redeploy feature.

This is a really hard to debug problem and the final culprit of this behavior is this code inside Redisson:

The codec instance created here will be then used to restore classes serialized in Redis. The problem is it uses current classloader (AppClassLoader), not the one I wanted to use creating codec in RedissonConfiguration.redissonCodec(). Even if you pass the right codec instance to this constructor, it will anyway create new codec with the current class loader :/

OK, so how to overcome this problem in Spring Boot app? They are aware of classloading problems with Spring Devtools. It’s possible to redeploy jar library using RestartClassLoader each time Devtools restarts the project. To do this you need to add following file to META-INF/spring-devtools.properties:

restart.include.redisson=redisson-.*.jar

Using this configuration RestartClassLoader will become RedissonNode.class.getClassLoader() and all tasks created by TasksRunnerService will now have an access to static fields of your project classes.