Search code examples
javaobjectexceptionserializationclassnotfoundexception

ClassNotFound Exception when deserializing serialized object


I'm trying to serialize and deserialize an object. This object can contain references to other objects, and to ArrayList and HashMap.

When I attempt to execute my code, serialization works fine, but deserialization does not. It causes the following exception:

Exception in thread "main" java.lang.ClassNotFoundException: experiment.Experiment$1
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at javax.crypto.extObjectInputStream.resolveClass(SealedObject.java:490)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2000)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1924)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at javax.crypto.SealedObject.getObject(SealedObject.java:302)
    at experiment.Experiment.main(Experiment.java:52)
Java Result: 1

I have the main class, Experiment, as follows:

public class Experiment {  
    public static void main(String[] args) throws Exception {
        File data = new File("C:\\Users\\Furze\\Desktop\\experiment.dat");   
        // I only execute the following to encrypt the file, which works fine:
        Test test = new Test(new VariableMap<String, String>() {{
            put("Name", "Furze");
        }});   
        Cipher cipher = Cipher.getInstance("Blowfish");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[] {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07}, "Blowfish"));
        SealedObject sealedObject = new SealedObject(test, cipher);
        CipherOutputStream outputStream = new CipherOutputStream(new BufferedOutputStream(new FileOutputStream(data.getPath())), cipher);
        ObjectOutputStream objectOutput = new ObjectOutputStream(outputStream);
        objectOutput.writeObject(sealedObject);     
        objectOutput.close();
        // I then comment out the above code to test the file, which fails.
        Cipher cipher = Cipher.getInstance("Blowfish");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(new byte[] {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07}, "Blowfish"));
        CipherInputStream inputStream = new CipherInputStream(new BufferedInputStream(new FileInputStream(data.getPath())), cipher);
        ObjectInputStream objectInput = new ObjectInputStream(inputStream);
        SealedObject sealedObject = (SealedObject) objectInput.readObject();
        Test test = (Test) sealedObject.getObject(cipher);
        System.out.println(test.variables.get("Name"));
    }
}

Curiously, if I leave the Test test = new Test(...); section intact while reading back, but change the name to something like test_old, it seems to work correctly.

The object classes are as follows:

// The VariableMap class is something I added during debugging to test if HashMap simply isn't serializable. It didn't help. It does have to stay a HashMap (or VariableMap!) however, for my code to operate correctly.
class VariableMap<Name, Value> extends HashMap<String, String> implements java.io.Serializable {
    public VariableMap() {
        super();
    }
}

public class Test implements java.io.Serializable {
    VariableMap<String, String> variables = new VariableMap<>();
    public Test() {}
    public Test(VariableMap<String, String> variables) {
        this.variables = variables;
    }
}

Can anybody explain what could be going wrong with my code here? I've read things about changing the CLASSPATH, but when I attempted that it made no difference.


Solution

  • Situation

    What is Double Brace initialization in Java?

    new VariableMap<String, String>() {{
        put("Name", "Furze");
    }}
    

    creates an anonymous inner class (subclass of the class VariableMap) and creates its object.

    Both class and object are created at the same spot.

    It is still Serializable since all subtypes of a serializable class are themselves serializable.


    Problem

    When you comment it out, the class definition itself vanishes.

    This results in the aforementioned ClassNotFoundException.


    Solutions

    1. Try to avoid Serialization if you can. Implement your own data storage mechanism.
    2. Avoid double brace initialization if you must use Serialization.
    3. Maintain the declaration without using it (test_old way). However it is an ad-hoc solution and bad practice. It is confusing and impossible to remember. Also a possible cause for future bug.