Comparison of JDK, Fastjson, and Hessian Serialization Protocols and Practical Hessian Serialization
This article examines the reasons for using serialization, compares three serialization protocols (JDK built‑in, Fastjson, and Hessian) with performance metrics, and provides a detailed Hessian serialization implementation, including class definitions, test results, hex analysis, and troubleshooting of common issues.
Background
When a program runs, generated data cannot stay in memory forever; it must be stored temporarily or permanently on media such as disks, databases, or files, or transmitted over the network. Retrieval requires deserialization of the binary stream.
Why Use Serialization and Deserialization
Simple scenarios involve basic types; both sides agree on parameter types and the receiver deserializes the binary stream accordingly.
Complex scenarios may involve basic types, wrapper types, custom classes, enums, date types, strings, containers, etc., making simple agreements impossible. A common protocol is needed for both parties.
Three Serialization Protocols and Comparison
Serialization Protocol
Features
jdk (built‑in)
1. Serializes all fields except static and transient.
2. Strongly typed, high safety, type information is included.
3. Deserialization based on field mechanism.
4. Use case: deep copy.
fastjson (third‑party)
1. Good readability, small size.
2. Weakly typed, result does not carry type information, strong readability, some security issues.
3. Deserialization based on field mechanism, compatible with Bean.
4. Use case: messaging, transparent object transfer.
hessian (third‑party)
1. Serializes all fields except static and transient.
2. Strongly typed, compact, cross‑language, type information is included.
3. Deserialization based on field mechanism, compatible with Bean.
4. Use case: RPC.
Comparison
Father father = new Father();
father.name = "厨师";
father.comment = "川菜馆";
father.simpleInt = 1;
father.boxInt = new Integer(10);
father.simpleDouble = 1;
father.boxDouble = new Double(10);
father.bigDecimal = new BigDecimal(11.5);Execution Result
jdk序列化结果长度:626,耗时:55
jdk反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:87
hessian序列化结果长度:182,耗时:56
hessian反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:7
Fastjson序列化结果长度:119,耗时:225
Fastjson反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:69Analysis
JDK serialization is the fastest but its result size is 3–5 times larger than the other two.
Fastjson produces the smallest result size, but its serialization time is about four times longer.
Hessian’s serialization time is comparable to JDK and far lower than Fastjson; its result size is much smaller than JDK, and its deserialization speed is the fastest—about one‑tenth of the others.
Overall, Hessian shows the best performance for both serialization and deserialization.
Hessian Serialization Practical Guide
Experiment Preparation
Parent Class
public class Father implements Serializable {
private static final long serialVersionUID = 1L;
transient int version = 0; // not serialized
public String name; // name
public String comment; // comment
public Integer boxInt; // wrapper type
public int simpleInt; // primitive type
public Double boxDouble; // wrapper type
public double simpleDouble; // primitive type
public BigDecimal bigDecimal; // BigDecimal
public Father() {}
@Override
public String toString() {
return "Father{" +
"version=" + version +
", name='" + name + '\'' +
", comment='" + comment + '\'' +
", boxInt=" + boxInt +
", simpleInt=" + simpleInt +
", boxDouble=" + boxDouble +
", simpleDouble=" + simpleDouble +
", bigDecimal=" + bigDecimal +
'}';
}
}Child Class
public class Son extends Father {
public String name; // same name as parent, overrides
public Attributes attributes; // custom class
public Color color; // enum
public Son() {}
}Custom Class (Attributes)
public class Attributes implements Serializable {
private static final long serialVersionUID = 1L;
public int value;
public String msg;
public Attributes() {}
public Attributes(int value, String msg) {
this.value = value;
this.msg = msg;
}
}Enum (Color)
public enum Color {
RED(1, "red"),
YELLOW(2, "yellow");
public int value;
public String msg;
Color() {}
Color(int value, String msg) {
this.value = value;
this.msg = msg;
}
}Object Initialization
Son son = new Son();
son.name = "厨师"; // only child field is set
son.comment = "川菜馆";
son.simpleInt = 1;
son.boxInt = new Integer(10);
son.simpleDouble = 1;
son.boxDouble = new Double(10);
son.bigDecimal = new BigDecimal(11.5);
son.color = Color.RED;
son.attributes = new Attributes(11, "hello");Result Analysis
Using Hessian serialization, the result is written to a file and examined with a hex viewer (command: %!xxd). The hex dump shows the class definition marker "C", field names, and values according to the Hessian specification.
The serialization rules are:
The class to be serialized must implement Serializable .
Static fields and transient variables are not serialized.
Enum values are stored as their name strings.
The binary structure follows: "C" → class name length + name → field count → each field name length + name → instance marker → field values (recursively for objects).
Deserialization Process
The deserializer reads the "C" marker, loads the class, builds a field‑to‑deserializer map, and then reads each field value in order. Missing fields are ignored (assigned NullFieldDeserializer ), which explains why adding new fields on the serialization side does not break older deserialization code.
Open Issues and Answers
Adding an enum field that does not exist on the deserialization side causes an error because the enum name cannot be resolved via reflection.
When deserializing to a subclass, fields with the same name as the parent are not set because the deserializer matches by field name in the subclass map.
Adding new fields on the serialization side works fine; the deserializer simply skips unknown fields.
Changing a field type from primitive to wrapper (or vice‑versa) can break deserialization because Hessian uses distinct type codes for different primitive ranges.
References:
https://zhuanlan.zhihu.com/p/44787200
https://paper.seebug.org/1131/
Hessian official documentation: http://hessian.caucho.com/doc/hessian-serialization.html#anchor10
ASCII table: http://ascii.911cha.com/
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
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.