Which Java Serialization Framework Wins? A Deep Performance and Compatibility Comparison
This article evaluates seven popular Java serialization frameworks—JDK Serializable, FST, Kryo, Protocol Buffers, Thrift, Hessian, and Avro—by comparing their universality, ease of use, extensibility, performance, and support for Java data types and syntax, providing concrete benchmark results and code examples.
Background
Serialization and deserialization are essential for data persistence and network transmission, but the plethora of Java serialization frameworks makes it hard to choose the right one for a given scenario. This article compares open‑source serialization frameworks across five dimensions: universality, ease of use, extensibility, performance, and Java data‑type support.
Evaluation Criteria
Universality : Does the framework support cross‑language or cross‑platform usage?
Ease of Use : How simple is the API and configuration?
Extensibility : Can the framework handle schema evolution and field changes?
Performance : Serialization size and time cost for both encoding and decoding.
Java Data‑Type Support : Compatibility with primitive types, collections, custom classes, enums, inner classes, lambdas, etc.
1. JDK Serializable
JDK Serializable is the built‑in Java mechanism; it requires implementing java.io.Serializable or java.io.Externalizable and uses ObjectOutputStream / ObjectInputStream for I/O.
Universality : Java‑only.
Ease of Use : No external dependencies, but the API is verbose and low‑level.
Extensibility : Controlled by serialVersionUID; mismatched versions throw java.io.InvalidClassException. Defining a constant serialVersionUID enables field addition.
Performance : Serializing a test MessageInfo object (432 bytes) takes 38,952 ms for 10 million encodings and 96,508 ms for decodings.
Java Data‑Type Support : Fully supports all primitive types, collections, custom classes, enums, and inner classes (static, non‑static, local, anonymous). Lambdas need explicit casting to Serializable.
public static byte[] encoder(Object ob) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(ob);
oos.close();
return bos.toByteArray();
}
public static <T> T decoder(byte[] bytes) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
T obj = (T) ois.readObject();
ois.close();
return obj;
}2. FST (Fast‑Serialization)
FST is a Java‑compatible framework that can be up to 10× faster than JDK serialization and produces roughly one‑third the size.
Universality : Java‑only.
Ease of Use : Simple API; FSTConfiguration provides thread‑safe methods.
Extensibility : Uses @Version annotations for field evolution; deleting fields can break backward compatibility.
Performance : Raw serialization size 172 bytes (≈ 1/3 of JDK). 10 million encodings take 13,587 ms, decodings 19,031 ms. After optimization (disable reference sharing, register classes) size grows to 191 bytes, but time improves to 7,609 ms (encode) and 17,792 ms (decode).
Java Data‑Type Support : Mirrors JDK support; all primitive types, collections, custom classes, enums, and inner classes are supported.
private static final ThreadLocal<FSTConfiguration> conf = ThreadLocal.withInitial(() -> {
return FSTConfiguration.createDefaultConfiguration();
});
public static byte[] encoder(Object obj) {
return conf.get().asByteArray(obj);
}
public static <T> T decoder(byte[] bytes) {
return (T) conf.get().asObject(bytes);
}3. Kryo
Kryo is a high‑performance binary serializer that relies on ASM for bytecode generation.
Universality : Primarily Java; cross‑language support is limited and complex.
Ease of Use : Concise API with Kryo, Output, and Input. Configurable serializers add complexity.
Extensibility : Default FieldSerializer does not support field addition; custom serializers are required.
Performance : Raw size 172 bytes, 10 million encodings 13,550 ms, decodings 14,315 ms. With class registration and disabled reference sharing, size drops to 120 bytes, encoding 11,799 ms, decoding 11,584 ms.
Java Data‑Type Support : Supports most collections, but requires a no‑arg constructor; static inner classes work, non‑static inner classes do not.
private static final ThreadLocal<Kryo> kryoLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false);
return kryo;
});
public static byte[] encoder(Object obj) {
Output output = new Output();
kryoLocal.get().writeObject(output, obj);
output.flush();
return output.toBytes();
}
public static <T> T decoder(byte[] bytes) {
Input input = new Input(bytes);
return (T) kryoLocal.get().readClassAndObject(input);
}4. Protocol Buffers
Protobuf is a language‑neutral, platform‑independent schema‑based serialization framework.
Universality : Supports many languages (Java, Python, C++, Go, C#, etc.).
Ease of Use : Requires a .proto file; the protoc compiler generates Java classes with built‑in encode/decode methods.
Extensibility : Adding fields with new tag numbers is safe; removing fields requires reserving old tags.
Performance : Serialized size 192 bytes; 10 million encodings take 14,235 ms, decodings 306 ms (note: protobuf decoding is very fast but slightly slower than FST/Kryo for this test).
Java Data‑Type Support : Supports primitive types (except byte/short/char), collections via repeated, custom classes, enums. Does not support Java methods.
// proto definition (MessageInfo.proto)
syntax = "proto3";
option java_package = "com.yjz.serialization.protobuf3";
message MessageInfo {
string username = 1;
string password = 2;
int32 age = 3;
map<string, string> params = 4;
}
// Generated Java usage
byte[] bytes = MessageInfo.newBuilder()
.setUsername("abc")
.setPassword("123")
.setAge(27)
.putAllParams(map)
.build()
.toByteArray();
MessageInfo msg = MessageInfo.parseFrom(bytes);5. Thrift
Thrift, originally from Facebook, is an RPC framework that also provides serialization via IDL.
Universality : Supports many languages (C++, Java, Python, PHP, Ruby, etc.).
Ease of Use : Requires writing a .thrift file, generating Java code, then using TSerializer and TDeserializer.
Extensibility : Field addition, deletion, and type changes are handled via numeric tags; required fields need default values.
Performance : Serialized size 257 bytes; 10 million encodings take 28,634 ms, decodings 20,722 ms.
Java Data‑Type Support : Supports basic primitives, List, Set, Map, custom structs, enums, and byte arrays. Does not support Java methods.
// Thrift IDL (MessageInfo.thrift)
namespace java com.yjz.serialization.thrift
struct MessageInfo {
1: string username;
2: string password;
3: i32 age;
4: map<string, string> params;
}
// Java usage
public static byte[] encoder(MessageInfo msg) throws Exception {
TSerializer serializer = new TSerializer();
return serializer.serialize(msg);
}
public static MessageInfo decoder(byte[] bytes) throws Exception {
TDeserializer deserializer = new TDeserializer();
MessageInfo msg = new MessageInfo();
deserializer.deserialize(msg, bytes);
return msg;
}6. Hessian
Hessian is a lightweight binary RPC protocol that uses self‑describing serialization.
Universality : Multi‑language support (Java, Flash/Flex, Python, C++, .NET, etc.).
Ease of Use : No IDL required; simply implement Serializable and use Hessian2Output / Hessian2Input.
Extensibility : Field changes are handled automatically; no serialVersionUID constraints.
Performance : Size 178 bytes with Hessian 2.0; 10 million encodings 38,823 ms, decodings 17,682 ms (Hessian 2.0 is faster than 1.0).
Java Data‑Type Support : Supports all Java primitive types, collections, custom classes, enums, and most inner class variations.
public static <T> byte[] encoder2(T obj) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(bos);
out.writeObject(obj);
out.close();
return bos.toByteArray();
}
public static <T> T decoder2(byte[] bytes) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
Hessian2Input in = new Hessian2Input(bis);
Object obj = in.readObject();
in.close();
return (T) obj;
}7. Avro
Avro is an Apache Hadoop project that uses JSON‑based schemas for data serialization.
Universality : Supports Java, C, C++, C#, Python, PHP, Ruby, etc.
Ease of Use : Requires an .avsc schema; Java code can be generated with avro-tools. Dynamic languages can use the schema directly.
Extensibility : Fields must have default values; adding fields is safe, but field types and names cannot be changed without breaking compatibility.
Performance : Serialized size 111 bytes; 10 million encodings 26,565 ms, decodings 45,383 ms.
Java Data‑Type Support : Supports primitive types (except byte/short/char), collections via array and map, custom records, enums, unions, and fixed types. Does not allow Java methods in schema.
// Avro schema (MessageInfo.avsc)
{
"namespace": "com.yjz.serialization.avro",
"type": "record",
"name": "MessageInfo",
"fields": [
{"name": "username", "type": "string"},
{"name": "password", "type": "string"},
{"name": "age", "type": "int"},
{"name": "params", "type": {"type": "map", "values": "string"}}
]
}
// Java usage
public static byte[] encoder(MessageInfo obj) throws Exception {
DatumWriter<MessageInfo> writer = new SpecificDatumWriter<>(MessageInfo.class);
ByteArrayOutputStream out = new ByteArrayOutputStream();
BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(out, null);
writer.write(obj, encoder);
return out.toByteArray();
}
public static MessageInfo decoder(byte[] bytes) throws Exception {
DatumReader<MessageInfo> reader = new SpecificDatumReader<>(MessageInfo.class);
BinaryDecoder decoder = DecoderFactory.get().directBinaryDecoder(new ByteArrayInputStream(bytes), null);
return reader.read(new MessageInfo(), decoder);
}Summary
The comparison shows that Protocol Buffers and Thrift lead in cross‑language universality, while FST and Kryo excel in raw performance. JDK Serializable is the most compatible with Java language features but lags in speed and size. Avro offers the smallest payload but requires more complex schema management. Choosing a framework depends on the trade‑off between portability, developer productivity, schema evolution needs, and performance requirements.
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.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
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.
