Why a Simple Log Caused FastJSON NPE: Inside JavaBeanSerializer Mechanics
The article recounts a production incident where adding a single log line caused a FastJSON NullPointerException, then reconstructs the scenario, analyzes the JavaBeanSerializer source code, presents serialization flowcharts, demonstrates example code with various @JSONField and @JSONType cases, and proposes coding standards to avoid similar serialization pitfalls.
Incident Overview
A colleague added a single log statement to a newly introduced feature and, after deployment, the service generated a flood of alerts. Rolling back the change removed the log line and restored normal operation. The root cause was a NullPointerException (NPE) triggered during FastJSON serialization.
Reproducing the Issue
The problematic code defines a CountryDTO class and a test that serializes an empty instance with FastJSON.
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("中国");
}
} public class FastJonTest {
@Test
public void testSerialize() {
CountryDTO countryDTO = new CountryDTO();
String str = JSON.toJSONString(countryDTO);
System.out.println(str);
}
}Running the test throws an NPE because isChinaName() is invoked while country is still null.
Why Serialization Calls isChinaName()
FastJSON generates a serializer class (e.g., ASMSerializer_1_CountryDTO) using ASM bytecode manipulation. The generated write() method iterates over all getter‑style methods, including those that start with is, which explains why isChinaName() is executed during serialization.
JavaBeanSerializer Internals
The core of FastJSON serialization resides in JavaBeanSerializer. Its write() method delegates to computeGetters to collect methods that should be serialized.
public static List<FieldInfo> computeGetters(Class<?> clazz, /*...*/ ) {
// omitted parts
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.getReturnType().equals(Void.TYPE)) {
continue;
}
if (method.getParameterTypes().length != 0) {
continue;
}
JSONField annotation = TypeUtils.getAnnotation(method, JSONField.class);
if (annotation != null) {
if (!annotation.serialize()) {
continue;
}
if (annotation.name().length() != 0) {
// ...
}
}
if (methodName.startsWith("get")) {
// ...
}
if (methodName.startsWith("is")) {
// ...
}
}
}The selection follows three rules:
@JSONField(serialize = false, name = "xxx") disables serialization for the annotated method.
Methods prefixed with get are treated as property getters.
Methods prefixed with is are also treated as getters (typically for boolean‑like properties).
Serialization Flowchart
Example Cases and Recommended Annotations
The following class illustrates four typical situations and shows how @JSONField(serialize = false) can be used to exclude unwanted members from serialization.
@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";
}
}Serializing an empty instance yields:
isChinaName() executed!!
getEnglishName() executed!!
{"chinaName":true,"englishName":"lucy"}Coding Guidelines
To prevent accidental inclusion of methods in JSON output, the article recommends explicitly annotating such methods with @JSONField(serialize = false). This makes the serialization intent obvious and reduces variance among team members.
High‑Frequency Serialization Scenarios
Three common patterns that frequently cause serialization problems are summarized in the diagram below.
Takeaways
The overall workflow is: detect the issue → analyze the root cause → apply a fix → codify the solution as a coding standard. The author also expresses a desire to decouple the project from FastJSON, allowing the JSON library to be swapped without code changes, and to limit excessive logging that can fill disks quickly.
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.
dbaplus Community
Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.
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.
