kryo
Introduction To Kryo
1. Overview
Kryo is a Java serialization framework with a focus on speed, efficiency, and a user-friendly API.
In this article, we’ll explore the key features of the Kryo framework and implement examples to showcase its capabilities.
2. Maven Dependency
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.1</version>
</dependency>
The latest version of this artifact can be found on Maven Central.
3. Kryo Basics
3.1. Introduction
This class orchestrates the serialization process and maps classes to Serializer instances which handle the details of converting an object’s graph to a byte representation.
Once the bytes are ready, they’re written to a stream using an Output object. This way they can be stored in a file, a database or transmitted over the network.
Later, when the object is needed, an Input instance is used to read those bytes and decode them into Java objects.
3.2. Serializing Objects
Before diving into examples, let’s first create a utility method to initialize some variables we’ll use for each test case in this article:
@Before
public void init() {
kryo = new Kryo();
output = new Output(new FileOutputStream("file.dat"));
input = new Input(new FileInputStream("file.dat"));
}
Now, we can look a how easy is to write and read an object using Kryo:
@Test
public void givenObject_whenSerializing_thenReadCorrectly() {
Object someObject = "Some string";
kryo.writeClassAndObject(output, someObject);
output.close();
Object theObject = kryo.readClassAndObject(input);
input.close();
assertEquals(theObject, "Some string");
}
Notice the call to the close() method. This is needed since Output and Input classes inherit from OutputStream and InputStream respectively.
Serializing multiple objects is similarly straightforward:
@Test
public void givenObjects_whenSerializing_thenReadCorrectly() {
String someString = "Multiple Objects";
Date someDate = new Date(915170400000L);
kryo.writeObject(output, someString);
kryo.writeObject(output, someDate);
output.close();
String readString = kryo.readObject(input, String.class);
Date readDate = kryo.readObject(input, Date.class);
input.close();
assertEquals(readString, "Multiple Objects");
assertEquals(readDate.getTime(), 915170400000L);
}
Notice that we’re passing the appropriate class to the readObject() method, this makes our code cast-free.
4. Serializers
4.1. Default Serializers
When Kryo serializes an object, it creates an instance of a previously registered Serializer class to do the conversion to bytes. These are called default serializers and can be used without any setup on our part.
The library already provides several such serializers that process primitives, lists, maps, enums, etc. If no serializer is found for a given class, then a FieldSerializer is used, which can handle almost any type of object.
Let’s see how this looks like. First, lets’ create a Person class:
public class Person {
private String name = "John Doe";
private int age = 18;
private Date birthDate = new Date(933191282821L);
// standard constructors, getters, and setters
}
Now, let’s write an object from this class and then read it back:
@Test
public void givenPerson_whenSerializing_thenReadCorrectly() {
Person person = new Person();
kryo.writeObject(output, person);
output.close();
Person readPerson = kryo.readObject(input, Person.class);
input.close();
assertEquals(readPerson.getName(), "John Doe");
}
Notice that we didn’t have to specify anything to serialize a Person object since a FieldSerializer is created automatically for us.
4.2. Custom Serializers
If we need more control over the serialization process, we have two options; we can write our own Serializer class and register it with Kryo or let the class handle the serialization by itself.
To demonstrate the first option, let’s create a class that extends Serializer:
public class PersonSerializer extends Serializer<Person> {
public void write(Kryo kryo, Output output, Person object) {
output.writeString(object.getName());
output.writeLong(object.getBirthDate().getTime());
}
public Person read(Kryo kryo, Input input, Class<Person> type) {
Person person = new Person();
person.setName(input.readString());
long birthDate = input.readLong();
person.setBirthDate(new Date(birthDate));
person.setAge(calculateAge(birthDate));
return person;
}
private int calculateAge(long birthDate) {
// Some custom logic
return 18;
}
}
Now, let’s put it to test:
@Test
public void givenPerson_whenUsingCustomSerializer_thenReadCorrectly() {
Person person = new Person();
person.setAge(0);
kryo.register(Person.class, new PersonSerializer());
kryo.writeObject(output, person);
output.close();
Person readPerson = kryo.readObject(input, Person.class);
input.close();
assertEquals(readPerson.getName(), "John Doe");
assertEquals(readPerson.getAge(), 18);
}
Notice that the age field is equal to 18, even though we set it previously to 0.
We can also use the @DefaultSerializer annotation to let Kryo know we want to use the PersonSerializer each time it needs to handle a Person object. This helps avoid the call to the register() method:
@DefaultSerializer(PersonSerializer.class)
public class Person implements KryoSerializable {
// ...
}
For the second option, let’s modify our Person class to extend the KryoSerializable interface:
public class Person implements KryoSerializable {
// ...
public void write(Kryo kryo, Output output) {
output.writeString(name);
// ...
}
public void read(Kryo kryo, Input input) {
name = input.readString();
// ...
}
}
Since the test case for this option is equal to a previous one, is not included here. However, you can find it in the source code for this article.
4.3. Java Serializer
In sporadic cases, Kryo won’t be able to serialize a class. If this happens, and writing a custom serializer isn’t an option, we can use the standard Java serialization mechanism using a JavaSerializer. This requires that the class implements the Serializable interface as usual.
Here’s an example that uses the aforementioned serializer:
public class ComplexObject implements Serializable {
private String name = "Bael";
// standard getters and setters
}
@Test
public void givenJavaSerializable_whenSerializing_thenReadCorrectly() {
ComplexClass complexObject = new ComplexClass();
kryo.register(ComplexClass.class, new JavaSerializer());
kryo.writeObject(output, complexObject);
output.close();
ComplexClass readComplexObject = kryo.readObject(input, ComplexClass.class);
input.close();
assertEquals(readComplexObject.getName(), "Bael");
}
5. Conclusion
We serialized multiple simple objects and used the FieldSerializer class to deal with a custom one. We also created a custom serializer and demonstrated how to fallback to the standard Java serialization mechanism if needed.
As always, the complete source code for this article can be found over on Github.