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.
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
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
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.
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.
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!
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.
