DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • High-Performance Java Serialization to Different Formats
  • Did You Know the Fastest Way of Serializing a Java Field Is Not Serializing It at All?
  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality

Trending

  • ITBench, Part 1: Next-Gen Benchmarking for IT Automation Evaluation
  • The Perfection Trap: Rethinking Parkinson's Law for Modern Engineering Teams
  • Modern Test Automation With AI (LLM) and Playwright MCP
  • Memory-Optimized Tables: Implementation Strategies for SQL Server
  1. DZone
  2. Coding
  3. Languages
  4. Java Serialization Magic Methods and Use Cases

Java Serialization Magic Methods and Use Cases

Want to encrypt/decrypt sensitive info before serializing/de-serializing it? Java's got you covered.

By 
Naresh Joshi user avatar
Naresh Joshi
DZone Core CORE ·
Sep. 10, 19 · Presentation
Likes (8)
Comment
Save
Tweet
Share
20.0K Views

Join the DZone community and get the full member experience.

Join For Free

Image title

Your Java code just needs a little magic

In the previous article Everything You Need to Know About Java Serialization, we discussed how serializability of a class is enabled by implementing the Serializable interface. If our class does not implement Serializable interface, or if it is having a reference to a non-Serializable class, then the JVM will throw NotSerializableException.

You may also like: Serialization and De-Serialization in Java

All subtypes of a serializable class are, themselves, serializable, and the Externalizable interface also extends Serializable. So even if we customize our serialization process using Externalizable, our class is still a Serializable.

The Serializable interface is a marker interface with no methods or fields. It works like a flag for the JVM. The Java serialization process provided by ObjectInputStream and ObjectOutputStream classes are fully controlled by the JVM.

But what if we want to add some additional logic to enhance this normal process? For example, what if we want to encrypt/decrypt our sensitive information before serializing/deserializing it? Java provides us with some additional methods for this, which we are going to discuss in this blog.

writeObject and readObject Methods

Serializable classes that want to customize or add some additional logic to enhance the normal serialization/deserialization process should provide writeObject and readObject methods with these exact signatures:

  • private void writeObject(java.io.ObjectOutputStream out) throws IOException
  • private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException

readObjectNoData Method

As described in Java docs of Serializable class, if we want to initialize the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized, then we should provide writeObject and readObject methods with these exact signatures:

  • private void readObjectNoData() throws ObjectStreamException

This may occur in cases where the receiving party uses a different version of the deserialized instance's class than the sending party, and the receiver's version extends classes that are not extended by the sender's version. This may also occur if the serialization stream has tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a "hostile" or incomplete source stream.

Each serializable class may define its own readObjectNoData method. If a serializable class does not define a readObjectNoData method, then in the circumstances listed above, the fields of the class will be initialized to their default values.

writeReplace and readResolve Methods

Serializable classes that need to designate an alternative object to be used when writing an object to the stream should provide this special method with the exact signature:

  • ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException

And Serializable classes that need to designate a replacement when an instance of it is read from the stream should provide this special method with the exact signature:

  • ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException

Basically, the writeReplace method allows the developer to provide a replacement object that will be serialized instead of the original one. And the readResolve method is used during deserialization process to replace the de-serialized object by another one of our choices.

One of the main usages of writeReplace and readResolve methods is to implement the singleton design pattern with Serialized classes. We know that the deserialization process creates a new object every time, and it can also be used as a method to deeply clone an object, which is not good if we have to make our class singleton.

The method readResolve is called after readObject has returned (conversely writeReplace is called before writeObject and probably on a different object). The object the method returns replaces this object returned to the user of ObjectInputStream.readObject and any further back-references to the object in the stream. We can use the writeReplace method to replace serializing object with null so nothing will be serialized, and then, we can use the readResolve method to replace the deserialized object with the singleton instance.

validateObject Method

If we want to perform certain validations on some of our fields, we can do that by implementing ObjectInputValidation interface and overriding the validateObject method from it.

The method validateObject will automatically get called when we register this validation by calling ObjectInputStream.registerValidation(this, 0) from readObject method. It is very useful to verify that stream has not been tampered with, or that the data makes sense before handing it back to your application.

The following example covers code for all the above methods:

public class SerializationMethodsExample {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Employee emp = new Employee("Naresh Joshi", 25);
        System.out.println("Object before serialization: " + emp.toString());

        // Serialization
        serialize(emp);

        // Deserialization
        Employee deserialisedEmp = deserialize();
        System.out.println("Object after deserialization: " + deserialisedEmp.toString());


        System.out.println();

        // This will print false because both object are separate
        System.out.println(emp == deserialisedEmp);

        System.out.println();

        // This will print false because both `deserialisedEmp` and `emp` are pointing to same object,
        // Because we replaced de-serializing object in readResolve method by current instance
        System.out.println(Objects.equals(emp, deserialisedEmp));
    }

    // Serialization code
    static void serialize(Employee empObj) throws IOException {
        try (FileOutputStream fos = new FileOutputStream("data.obj");
             ObjectOutputStream oos = new ObjectOutputStream(fos))
        {
            oos.writeObject(empObj);
        }
    }

    // Deserialization code
    static Employee deserialize() throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream("data.obj");
             ObjectInputStream ois = new ObjectInputStream(fis))
        {
            return (Employee) ois.readObject();
        }
    }
}

class Employee implements Serializable, ObjectInputValidation {
    private static final long serialVersionUID = 2L;

    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // With ObjectInputValidation interface we get a validateObject method where we can do our validations.
    @Override
    public void validateObject() {
        System.out.println("Validating age.");

        if (age < 18 || age > 70)
        {
            throw new IllegalArgumentException("Not a valid age to create an employee");
        }
    }

    // Custom serialization logic,
    // This will allow us to have additional serialization logic on top of the default one e.g. encrypting object before serialization.
    private void writeObject(ObjectOutputStream oos) throws IOException {
        System.out.println("Custom serialization logic invoked.");
        oos.defaultWriteObject(); // Calling the default serialization logic
    }

    // Replacing de-serializing object with this,
    private Object writeReplace() throws ObjectStreamException {
        System.out.println("Replacing serialising object by this.");
        return this;
    }

    // Custom deserialization logic
    // This will allow us to have additional deserialization logic on top of the default one e.g. performing validations, decrypting object after deserialization.
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        System.out.println("Custom deserialization logic invoked.");

        ois.registerValidation(this, 0); // Registering validations, So our validateObject method can be called.

        ois.defaultReadObject(); // Calling the default deserialization logic.
    }

    // Replacing de-serializing object with this,
    // It will will not give us a full proof singleton but it will stop new object creation by deserialization.
    private Object readResolve() throws ObjectStreamException {
        System.out.println("Replacing de-serializing object by this.");
        return this;
    }

    @Override
    public String toString() {
        return String.format("Employee {name='%s', age='%s'}", name, age);
    }
}


You can find the complete source code for this article on this GitHub Repository, and please feel free to provide your valuable feedback.

Further Reading

Serialization and De-Serialization in Java

Everything You Need to Know About Java Serialization

Java (programming language) Serialization Object (computer science)

Published at DZone with permission of Naresh Joshi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • High-Performance Java Serialization to Different Formats
  • Did You Know the Fastest Way of Serializing a Java Field Is Not Serializing It at All?
  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!