Why a Single Log Triggered a FastJson Crash? Uncovering Java Bean Serialization

A seemingly harmless log addition caused a FastJson alarm, leading to a deep dive into Java bean serialization, ASM‑generated serializers, method invocation rules, and best‑practice guidelines for clean, framework‑agnostic JSON handling in backend development.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Why a Single Log Triggered a FastJson Crash? Uncovering Java Bean Serialization

Online Incident Review

Recently a colleague added a very simple feature and, before the evening release, inserted a single log line while reviewing the code. The log seemed harmless, but after deployment a flood of alerts appeared, forcing an immediate rollback and removal of the log statement.

Scenario Reconstruction

Defined a CountryDTO class:
public class CountryDTO {
    private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return this.country;
    }

    public Boolean isChinaName() {
        return this.country.equals("中国");
    }
}
Defined a test class FastJonTest :
public class FastJonTest {
    @Test
    public void testSerialize() {
        CountryDTO countryDTO = new CountryDTO();
        String str = JSON.toJSONString(countryDTO);
        System.out.println(str);
    }
}

Running the test produced a NullPointerException because the isChinaName() method was invoked during serialization while this.country was still null.

Source Code Analysis

Debugging revealed the call chain leading to ASMSerializer_1_CountryDTO.write, which is generated by FastJson using ASM technology to avoid reflection overhead.

The ASM technique dynamically creates a class to replace Java reflection, improving performance.

JavaBeanSerializer Serialization Principle

The serialization process mainly calls the write() method of JavaBeanSerializer. This serializer obtains an ObjectWriter via getObjectWriter(), which eventually invokes

com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer

and com.alibaba.fastjson.util.TypeUtils#computeGetters.

public static List<FieldInfo> computeGetters(Class<?> clazz, JSONType jsonType, Map<String,String> aliasMap, Map<String,Field> fieldCacheMap, boolean sorted, PropertyNamingStrategy propertyNamingStrategy) {
    // ...
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
        if (method.getReturnType().equals(Void.TYPE)) continue;
        if (method.getParameterTypes().length != 0) continue;
        // ...
        if (methodName.startsWith("get")) {
            // handle getter
        }
        if (methodName.startsWith("is")) {
            // handle boolean getter
        }
    }
}

The analysis shows three relevant cases during serialization: @JSONField(serialize = false, name = "xxx") annotation to exclude a method.

Methods starting with get (standard getters).

Methods starting with is (boolean getters).

Serialization Flowchart

Example Code

/**
 * case1: @JSONField(serialize = false)
 * case2: getXxx() returns void
 * case3: isXxx() returns non‑boolean
 * case4: @JSONType(ignores = "xxx")
 */
@JSONType(ignores = "otherName")
public class CountryDTO {
    private String country;

    public void setCountry(String country) { this.country = country; }
    public String getCountry() { return this.country; }
    public static void queryCountryList() { System.out.println("queryCountryList() executed!!"); }
    public Boolean isChinaName() { System.out.println("isChinaName() executed!!"); return true; }
    public String getEnglishName() { System.out.println("getEnglishName() executed!!"); return "lucy"; }
    public String getOtherName() { System.out.println("getOtherName() executed!!"); return "lucy"; }
    @JSONField(serialize = false)
    public String getEnglishName2() { System.out.println("getEnglishName2() executed!!"); return "lucy"; }
    @JSONField(serialize = false)
    public void getEnglishName3() { System.out.println("getEnglishName3() executed!!"); }
    @JSONField(serialize = false)
    public String isChinaName2() { System.out.println("isChinaName2() executed!!"); return "isChinaName2"; }
}

Running the above produces:

isChinaName() executed!!
getEnglishName() executed!!
{"chinaName":true,"englishName":"lucy"}

Code Guidelines

Serialization rules are numerous: return types, parameter counts, and annotations like @JSONType and @JSONField all affect which methods participate. To avoid ambiguity, explicitly mark non‑serializable methods with @JSONField(serialize = false). This makes the codebase easier to understand and maintain.

Additionally, reduce reliance on heavy logging; log only essential information to prevent excessive I/O, as illustrated by a system that filled 128 GB of disk space with unnecessary logs.

Future work includes removing FastJson‑specific annotations ( @JSONField, @JSONType) to achieve a framework‑agnostic serialization layer.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaserializationJSONfastjson
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.