1. Introduction

In this tutorial, we’ll talk about the term hydration in the context of programming and dive deep into what it means to hydrate a Java object. 

2. Object Hydration

2.1. Lazy Initialization

Lazy loading or lazy initialization of an object is a common pattern used in software applications. An object in Java is an instance of a class created using the new keyword. Objects are the building blocks of a program, and objects interact with other objects to achieve a desired functionality. 

Objects are generally meant to represent a real-world entity in object-oriented programming paradigm, and hence, objects have several associated properties. Object initialization refers to the process of populating the properties of the object with real data. This is generally done by invoking a class constructor and passing in data as arguments. Initialization can also be done from a data source such as a network, database, or file system. 

Object initializations can, at times, be a resource-intensive operation, especially when the data is being fed from a different data source. Also, objects are not always used by the program immediately upon creation. 

In such scenarios, it is a good practice to defer the object initialization as long as possible until the point it is needed. This pattern is called lazy initialization, as we create the object with empty data at one time and lazily populate the object with relevant data in the future. A conscious delay of the data initialization helps boost code performance and memory utilization.

Let’s create a User class which has several attributes:

public class User {
    private String uId;
    private String firstName;
    private String lastName;
    private String alias;
    // constructor, getter-setters
}

We can create an object of User and keep it in memory without filling its attributes with meaningful data:

User iamUser = new User();

2.2. What Is Hydration?

With lazy initialization, we deliberately delay the initialization process of an object that is already created and exists in memory. The process of populating data to an existing empty object is called hydration.

The User instance we created is a dummy instance. The object does not have any relevant data attributes, as it wasn’t required until this point. To make the object useful, we should fill the object with relevant domain data, which can be done by filling it with data from a source such as a network, database, or file system. 

We perform the hydration of the user instance in the following steps. We first write our hydration logic as a class-level method, which uses the class setters to set the relevant data. In our example, we’ll use our data. However, we can fetch data from a file or a similar source as well:

public void generateMyUser() {
    this.setAlias("007");
    this.setFirstName("James");
    this.setLastName("Bond");
    this.setuId("JB");
}

Now we create an empty instance of User, and when required, we hydrate the same instance by calling the generateMyUser() method:

User jamesBond = new User();
// performing hydration
jamesBond.generateMyUser();

We can validate the results of the hydration by asserting the state of its attributes:

User jamesBond = new User();
Assert.assertNull(jamesBond.getAlias());

jamesBond.generateMyUser();
Assert.assertEquals("007", jamesBond.getAlias());

3. Hydration and Deserialization

Hydration and Deserialization are not synonymous, and we should not use them interchangeably. Deserialization is a process used in programming to revive or recreate an object from its serialized form. We often store or transmit objects over a network. During the process, serialization (the conversion of the object to a stream of bytes) and deserialization (the reverse process of reviving the object) come in very handy. 

We can serialize our User instance into a file or equivalent:

try {
    FileOutputStream fileOut = new FileOutputStream(outputName);
    ObjectOutputStream out = new ObjectOutputStream(fileOut);
    out.writeObject(user);
    out.close();
    fileOut.close();
} catch (IOException e) {
    e.printStackTrace();
}

Similarly, when required, we can recreate the User instance from its serialized form:

try {
    FileInputStream fileIn = new FileInputStream(serialisedFile);
    ObjectInputStream in = new ObjectInputStream(fileIn);
    deserializedUser = (User) in.readObject();
    in.close();
    fileIn.close();
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

It is clear that hydration and deserialization both involve dealing with an object and filling it with data. However, the important difference between the two is that deserialization is mostly a single-step process of creating the instance and populating the attributes.

Hydration, on the other hand, is concerned with only adding the data to the attributes of a pre-formed object. Therefore, deserialization is object instantiation and object hydration in the same step. 

4. Hydration in ORM Frameworks

An ORM (Object Relational Mapping) framework is a software development paradigm that enables us to combine object-oriented programming with relational databases. ORM frameworks facilitate the mapping between the objects in the application code and the tables in a relational database and allow developers to interact with database entities as native objects. 

The idea of object hydration is more prevalent in ORM frameworks, such as Hibernate or JPA.

Let’s consider a JPA Entity class Book and its corresponding Repository class as follows:

@Entity
@Table(name = "books")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "name")
    private String name;
    // other columns and methods
}
public interface BookRepository extends JpaRepository<Book, Long> {
}

Based on ORM principles, the entity Book represents a table in our relational database. The entities’ interactions with the database are abstracted in the form of the BookRepository interface that we have defined above. An instance of the class represents a row in the table. 

When we load an instance of Book from the database by using one of the numerous inbuilt find() methods or using custom queries, the ORM framework performs several steps:

Book aTaleOfTwoCities = bookRepository.findOne(1L);

The framework initializes an empty object, typically by calling the default constructor of the class. Once the object is ready, the framework tries to load the attribute data from a cache store. If there is a cache miss at this point, the framework establishes a connection with the database and queries the table to fetch the row.

Once the ResultSet is obtained, the framework hydrates the aforementioned object aTaleOfTwoCities with the resultset object and finally returns the instance. 

5. Conclusion

In this article, we discussed the meaning of the term hydration in the context of programming. We saw how hydration differs from deserialization. Finally, we explored examples of object hydration in ORM frameworks and plain object models. 

As usual, the code for this article is available over on GitHub.