Why Fastjson Breaks SpringBoot Agents and How to Resolve the ClassLoader Conflict

This article analyzes how adding Fastjson to a SpringBoot agent causes classloader conflicts that prevent GenericHttpMessageConverter from loading, explains the underlying parent‑delegation mechanism, and presents solutions such as using Maven Shade to rename packages or switching to a lightweight JSON library like Gson.

Xiao Lou's Tech Notes
Xiao Lou's Tech Notes
Xiao Lou's Tech Notes
Why Fastjson Breaks SpringBoot Agents and How to Resolve the ClassLoader Conflict

Background

After adding a feature that reports exceptions in the agent, a Fastjson dependency was introduced to simplify JSON conversion of objects, which led to various problems.

Environment:

JDK 1.8

SpringBoot 2.0.0.RELEASE

skywalking agent 8.14.0

Initial Problem

2.1 Preliminary定位

Colleagues reported that the application starts locally but fails in the test environment with the agent attached, showing a ClassNotFound error.

First, the presence of GenericHttpMessageConverter in spring-web was confirmed, ruling out a missing dependency.

Then the agent was temporarily disabled, and the application started normally, confirming the agent as the source of the issue.

2.2 Further Investigation

The failing package was downloaded, the agent was attached locally, and the problem was reproduced, allowing debugging.

Note: Starting the application with IDEA (agent attached) initially worked; the reason is explained later.

A conditional breakpoint was set at java.net.URLClassLoader#findClass for GenericHttpMessageConverter. The debugger showed three findClass calls.

The first trigger originated from the third‑party class WebAutoConfig.

Class loading order: BootMessageConverter (third‑party) → FastJsonHttpMessageConverter (fastjson) → GenericHttpMessageConverter (spring‑web)

The relevant code snippet:

@Configuration
public class WebAutoConfig implements WebMvcConfigurer {
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters httpMessageConverter() {
        BootMessageConverter converter = new BootMessageConverter(); // triggers class loading
        ...
    }
}

public class BootMessageConverter extends FastJsonHttpMessageConverter {
    ...
}

public class FastJsonHttpMessageConverter extends AbstractHttpMessageConverter<Object>
        implements GenericHttpMessageConverter<Object> {
    ...
}

The instantiation of BootMessageConverter loads FastJsonHttpMessageConverter, which in turn loads GenericHttpMessageConverter. Because GenericHttpMessageConverter resides only in the application’s spring-web.jar under BOOT-INF/lib, it is loaded by LaunchedURLClassLoader. However, the parent delegation mechanism prevents the AppClassLoader from delegating back to LaunchedURLClassLoader, resulting in a ClassNotFound error.

Class Loading Mechanism

3.1 Parent Delegation

Each classloader (except the Bootstrap ClassLoader) has a parent. When a classloader receives a load request, it first delegates to its parent; only if the parent cannot load the class does the current loader attempt to load it.

Joke: Since there’s only a parent and no mother, it’s more accurate to call it “single‑parent delegation”.

3.1.1 How Dependent Classes Are Loaded

If class A depends on class B, the classloader that loads A will also try to load B, following the same delegation rules.

3.2 SpringBoot Classloader

After packaging, a SpringBoot jar has the following structure:

├─BOOT-INF
│  ├─classes
│  │  └─application code
│  └─lib
│      └─dependency jars
├─META-INF
│  └─MANIFEST.MF
└─org
   └─springframework
       └─boot
           └─loader
               │ JarLauncher.class
               │ LaunchedURLClassLoader.class
               │ ...

The MANIFEST.MF specifies Main-Class: org.springframework.boot.loader.JarLauncher and Start-Class: com.xxx.DemoApplication. When java -jar runs, JarLauncher uses LaunchedURLClassLoader to load classes from BOOT-INF/classes and BOOT-INF/lib, then invokes the Start-Class main method.

Thus, application classes and their dependencies are loaded by LaunchedURLClassLoader, not by the system AppClassLoader.

3.3 How Fastjson Classes Are Found

The loading sequence is:

BootMessageConverter → FastJsonHttpMessageConverter → GenericHttpMessageConverter

Fastjson classes are present both in the application’s BOOT-INF/lib and in the agent’s jar, which is added to the classpath of the AppClassLoader. When BootMessageConverter (loaded by LaunchedURLClassLoader) triggers loading of FastJsonHttpMessageConverter, the request is delegated to the parent AppClassLoader. The AppClassLoader finds the Fastjson class (from the agent) and loads it, but then attempts to load GenericHttpMessageConverter, which exists only in the application’s spring‑web.jar under BOOT-INF/lib. Because the parent delegation cannot go back to LaunchedURLClassLoader, the class is not found, causing the error.

Solution 1: Maven Shade Plugin

To prevent the agent’s Fastjson classes from being loaded by the AppClassLoader, the plugin can rename (shade) the Fastjson packages so that they are isolated.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <shadedArtifactAttached>false</shadedArtifactAttached>
                <createDependencyReducedPom>true</createDependencyReducedPom>
                <createSourcesJar>true</createSourcesJar>
                <shadeSourcesContent>true</shadeSourcesContent>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <manifestEntries>
                            <Premain-Class>xxxxxx.AgentStarter</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </transformer>
                </transformers>
                <relocations>
                    <relocation>
                        <pattern>com.alibaba.fastjson</pattern>
                        <shadedPattern>shade.com.alibaba.fastjson</shadedPattern>
                    </relocation>
                </relocations>
            </configuration>
        </execution>
    </executions>
</plugin>

After packaging, the agent’s Fastjson classes are renamed with the shade. prefix, preventing the AppClassLoader from intercepting them.

Reproducing the Problem

Another application later failed because Jersey’s SPI mechanism discovered the shaded Fastjson provider ( shade.com.alibaba.fastjson.support.jaxrs.FastJsonProvider) from the agent’s META-INF/services directory. The provider depended on MessageBodyReader from jsr311-api.jar, which the agent did not include, leading to another ClassNotFound error.

Decision: Drop Fastjson

To avoid heavy dependencies, the author switched to Google’s Gson, which has no transitive dependencies (except JUnit) and no META-INF/services entries, satisfying the lightweight JSON conversion requirement.

Summary

Prefer using JDK‑built‑in classes in agents to minimize third‑party dependencies.

If third‑party libraries are required, use Maven Shade to rename packages and isolate them from the application.

Pay attention to SPI files; exclude or replace them when shading to avoid unexpected class loading conflicts.

JavamavenclassloaderSpringBootFastJSONGsonDependency Conflict
Xiao Lou's Tech Notes
Written by

Xiao Lou's Tech Notes

Backend technology sharing, architecture design, performance optimization, source code reading, troubleshooting, and pitfall practices

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.