Why Adding a Simple Log Triggered NPE in FastJSON Serialization – Lessons Learned

A recent production incident caused by inserting a single log statement revealed how FastJSON's ASM‑generated serializer invokes methods like isChinaName(), leading to a NullPointerException, and the article dissects the root cause, serialization mechanics, and best‑practice annotations to prevent similar bugs.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why Adding a Simple Log Triggered NPE in FastJSON Serialization – Lessons Learned

Online Incident Review

During a recent deployment a developer added a one‑line log statement while reviewing code, assuming it would be harmless. After the release, a flood of alerts appeared, prompting an immediate rollback. The issue was traced to the newly added log causing a NullPointerException during serialization.

Scenario Reconstruction

Define 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("中国");
    }
}
Define 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 throws a NullPointerException because the serializer calls isChinaName() while this.country is still null.

Root Cause Analysis

FastJSON uses ASM to generate a class ASMSerializer_1_CountryDTO that implements the write() method of JavaBeanSerializer. This serializer invokes getter methods (including isXxx()) to obtain field values during serialization, bypassing reflection for performance.

ASM technology can dynamically generate classes to replace Java reflection, reducing overhead.

JavaBeanSerializer Serialization Principle

The serializer calls JavaBeanSerializer.write(), which obtains an ObjectWriter via getObjectWriter(). The critical method is

com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer

, which eventually calls com.alibaba.fastjson.util.TypeUtils#computeGetters to collect getter methods.

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

The algorithm classifies methods into three categories: @JSONField(serialize = false, name = "xxx") annotation to exclude a method.

Methods starting with get (standard getters).

Methods starting with is (boolean getters).

Serialization Flowchart

Serialization flowchart
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()执行!!"); }
    public Boolean isChinaName() { System.out.println("isChinaName()执行!!"); return true; }
    public String getEnglishName() { System.out.println("getEnglishName()执行!!"); return "lucy"; }
    public String getOtherName() { System.out.println("getOtherName()执行!!"); return "lucy"; }
    @JSONField(serialize = false)
    public String getEnglishName2() { System.out.println("getEnglishName2()执行!!"); return "lucy"; }
    public void getEnglishName3() { System.out.println("getEnglishName3()执行!!"); }
    public String isChinaName2() { System.out.println("isChinaName2()执行!!"); return "isChinaName2"; }
}

Running the test produces:

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

Code Standards

Serialization rules are numerous: return types, parameter counts, and annotations like @JSONType and @JSONField affect inclusion. To avoid ambiguity, it is recommended to explicitly mark methods that should not be serialized with @JSONField(serialize = false). The following revised class makes non‑serializable methods obvious:

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

Three Frequently Encountered Serialization Cases

Common serialization cases
Common serialization cases

The overall process follows: discover the issue → analyze the principle → solve the problem → refine coding standards.

Business perspective: solve the problem, choose a good solution, and extend it to multiple systems.

Technical perspective: master the underlying principles by tracing a single issue.

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.

DebuggingJavaserializationannotations
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.