Why Spring Boot Devtools Triggers ClassCastException and How to Fix It
This article explains why adding spring-boot-devtools can trigger a ClassCastException due to its dual classloader mechanism, demonstrates how to reproduce the issue in a Maven multi‑module project, and provides two practical solutions: customizing the RestartClassLoader or removing the devtools dependency.
1. Why use spring-boot-devtools?
Spring Boot provides an additional set of tools to improve the development experience. The spring-boot-devtools module can be added to any project to enable development‑time features such as automatic restarts.
Typical Maven inclusion:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>2. Then the login service crashed...
Our project uses Dubbo, separating services and calls. A DarkPortalHeader class is shared via a common module and accessed by both consumer and provider. The provider extends Dubbo's Filter to retrieve header data.
public class DarkPortalFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String dpHeaderJson = invocation.getAttachment(DP_HEADER_KEY);
if (!StringUtils.isBlank(dpHeaderJson)) {
DarkPortalHeader darkPortalHeader = JSON.parseObject(dpHeaderJson, DarkPortalHeader.class);
DarkPortalHeaderHolder.set(darkPortalHeader);
}
try {
return invoker.invoke(invocation);
} finally {
DarkPortalHeaderHolder.remove();
}
}
}One day a java.lang.ClassCastException occurred:
java.lang.ClassCastException: com.ziroom.xxx.DarkPortalHeader cannot be cast to com.ziroom.xxx.DarkPortalHeaderThe cause is that the same class was loaded by two different classloaders: the standard AppClassLoader and Spring Boot Devtools' RestartClassLoader. The restart technology uses a base classloader for unchanged classes (e.g., third‑party JARs) and a restart classloader for classes under active development. When the application restarts, the restart classloader is discarded and recreated, leading to the mismatch.
Thus, JSON.class from a third‑party JAR is loaded by AppClassLoader, while DarkPortalHeader from the project is loaded by RestartClassLoader, causing the ClassCastException.
3. Reproducing the problem
Create a Maven multi‑module project with the following structure:
└── common
└── src/main/java/com/ziroom/model/City.java
└── pom.xml
└── demo
└── src/main/java/com/ziroom/Application.java
└── pom.xml
pom.xml (parent)Parent pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ziroom</groupId>
<artifactId>sample</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>common</module>
<module>demo</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>common/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sample</artifactId>
<groupId>com.ziroom</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
</dependencies>
</project>demo/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sample</artifactId>
<groupId>com.ziroom</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>demo</artifactId>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.41</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>com.gracier</groupId>
<artifactId>basic</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>City.java
package com.ziroom.model;
import lombok.Data;
@Data
public class City {
private String name;
public City() {}
public City(String name) { this.name = name; }
}Application.java
package com.ziroom;
import com.alibaba.fastjson.JSON;
import com.ziroom.model.City;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@EventListener
public void onApplicationStarted(ApplicationStartedEvent event) {
City city = new City("北京");
// should throw java.lang.ClassCastException
String json = JSON.toJSONString(city);
}
}Running the application prints the following exception:
java.lang.ClassCastException: com.ziroom.model.City cannot be cast to com.ziroom.model.CityHow to solve?
Solution 1: Customize the RestartClassLoader by creating a META-INF/spring-devtools.properties file with the following content:
restart.include.fastjson=/fastjson-[\\w\\d-\.]+\.jarSave the file and rerun the program; the exception disappears.
Solution 2: Remove the spring-boot-devtools dependency from pom.xml.
5. Summary
For a long time there has been no perfect hot‑reload solution for backend development. Spring Boot Devtools offers benefits such as faster restarts, but it also introduces classloader‑related issues that are hard to predict. Its use should be limited to the development phase, and developers should understand its internals before relying on it.
6. References
https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html
https://github.com/spring-projects/spring-boot/issues/3316
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
