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.
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#createJavaBeanSerializerand 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.
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 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.
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.
