Why a Single Log Line Triggered an NPE in FastJson Serialization – A Deep Dive
A seemingly harmless log addition caused a NullPointerException during FastJson serialization, prompting a step‑by‑step investigation of the generated ASM serializer, JavaBeanSerializer workflow, getter detection rules, and practical code‑style guidelines to avoid similar pitfalls.
Incident Review
During a recent deployment a colleague added a single log statement to a new feature. After the release, a flood of alerts forced an immediate rollback. The root cause was a NullPointerException triggered by the added log code.
Scenario Reconstruction
Definition of CountryDTO
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("中国");
}
}Definition of 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 isChinaName() was invoked while this.country was still null.
Why does serialization call isChinaName()?
Which methods are invoked during the serialization process?
Source Code Analysis
Debugging the call stack revealed that FastJson uses ASM to dynamically generate a class named ASMSerializer_1_CountryDTO, which implements the write() method.
ASM technology can generate classes at runtime to replace Java reflection, reducing reflective overhead.
JavaBeanSerializer Serialization Principle
The core of FastJson serialization is the JavaBeanSerializer.write() method. FastJson obtains an object writer via getObjectWriter(), which eventually calls SerializeConfig#createJavaBeanSerializer and 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;
// process @JSONField annotation, get/is prefixes, etc.
}
// ...
}The algorithm classifies getters into three categories: @JSONField(serialize = false, name = "xxx") annotation excludes a method. getXxx() methods (standard getters). isXxx() methods (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 prints:
isChinaName()执行!!
getEnglishName()执行!!
{"chinaName":true,"englishName":"lucy"}Code Guidelines
Serialization rules to watch:
Return type matters – boolean getters must return boolean or Boolean.
Parameter count matters – only zero‑argument methods are considered.
Annotations @JSONType and @JSONField can explicitly include or exclude members.
It is recommended to use @JSONField(serialize = false) to clearly mark methods that should not participate in serialization. This makes the code easier to read and reduces accidental NPEs.
public class CountryDTO {
// ...
@JSONField(serialize = false)
public static void queryCountryList() { /* ... */ }
@JSONField(serialize = false)
public String getOtherName() { /* ... */ }
// other members
}Common High‑Frequency Serialization Cases
The typical workflow is: discover the problem → analyze the principle → implement a solution → codify best practices.
Business perspective: solve the issue, choose a robust solution, and ensure it can be extended to multiple systems.
Technical perspective: resolve the single issue while mastering the underlying serialization mechanism.
The author notes a desire to reduce reliance on FastJson and avoid excessive logging, as overly verbose logs can quickly fill disk space.
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 Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.
