Backend Development 12 min read

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.

ByteFE
ByteFE
ByteFE
Comparison of JDK, Fastjson, and Hessian Serialization Protocols and Practical Hessian Serialization

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}耗时:69

Analysis

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/

JavaPerformanceSerializationdeserializationHessianProtocol
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.