javaeclipseserializationdeserializationjava-13

java.io.NotSerializableException even tho the class implements Serializable


I am working on building a music.player and have my music-library stored in a HashMap. The User shall be able to add and delete songs. I want to save this HashMap for when the program is beeing restartet. However did I encounter this warning:

Exception in thread "main" java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: musicplayer.Song

Research showed I have to implement the Serializable Interface in my Song class. Which I did, but with still this warning. My Song class:

package musicplayer;
//Song-Klasse, speichert alle Attribute und Methoden eines Songs. Funktioniert soweit
import java.io.File;
import java.io.IOException;
import java.io.Serializable;

public class Song implements Serializable {
    private static final long serialVersionUID = 4390482518182625971L;
    //Attribute
    File file;
    Clip clip;
    String string;
    //...

The MusicDaten - Class

package musicplayer;


public class MusicDaten implements Serializable {
    
    private static Map<String,Song> all; //= new HashMap<String,Song>();
    private File file = new File("C://Users//ThinkPad T450s//git//testproject//musicplayer//SongInfo.ser");
    
// ...

    public MusicDaten() throws ClassNotFoundException, IOException {
        this.setSavedSongs();
    }
    
    
    public void setSavedSongs() throws IOException, ClassNotFoundException  { //initialisziert HashMap mit den gespeicherten Songs
        FileInputStream fileIn = new FileInputStream(file);
        ObjectInputStream in = new ObjectInputStream(fileIn);
        all = (HashMap<String,Song>) in.readObject();
        in.close();
        fileIn.close();
    }
    public void save() throws IOException {   //Speicher HashMap
        FileOutputStream fileOut = new FileOutputStream(file);
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(all);
        out.close();
        fileOut.close();
        System.out.println("Songinfo saved");
    }

Thank you for the help. (I have edited this question since before it wasn't quite clear)


Solution

  • Implementing Serializable is not sufficient.

    If you attempt to serialize an object, all its non-transient attributes are serialized, too. If any of those attributes is not Serializable, it will not work.

    In your case, Song contains an attribute of type File and File is not serializable. With Clip, you have the same problem.

    In order to get around this, you can do custom serialization.

    Looking at the docs of Serializable, you can find this:

    Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

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

    The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore it. The default mechanism for saving the Object's fields can be invoked by calling out.defaultWriteObject. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.

    The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism for restoring the object's non-static and non-transient fields.

    This means that you can create the methods writeObject and readObject where you specify how to (de)serialize the object.

    If you want to keep the default (de)serialization of the attributes supporting serialization, you can mark all fields not supporting serialization transient and call out.defaultWriteObject/in.defaultReadObject in the writeObject/readObject methods.

    Marking an attribute transient means that serialization ignores it. You can then use your custom logic.


    Note that serialization comes with some problems and you might not want to use it.

    On one hand, it can lead to serious denial of service and even remote code execution vulnerabilities if you deserialize untrusted data. This is also noted in the docs of Serializable:

    Warning: Deserialization of untrusted data is inherently dangerous and should be avoided. Untrusted data should be carefully validated according to the "Serialization and Deserialization" section of the Secure Coding Guidelines for Java SE. Serialization Filtering describes best practices for defensive use of serial filters.

    Another problem of serialization is that it binds your application to a fixed format and makes it difficult to be compatible with old serialized data when update your application if you didn't carefully think it through when initially creating it.

    For more information about this, you may want to consider reading the book Effective Java.