Just a heads-up guys.
I had JRE update 26 pushed onto client machines yesterday and it broke an important client-server app.
This point release changes the serialVersionUID for ImageIcon. The app in question sends images from the server to the cients via a simple writeObject/readObject, which fails at the client end with a
java.io.InvalidClassException: javax.swing.ImageIcon; local class incompatible: stream classdesc serialVersionUID = 532615968316031794, local class serialVersionUID = -962022720109015502

Beware, I don't how many more of these there are in this release

peter_budo commented: Thanx for heads-up +16

Recommended Answers

All 8 Replies

... just by way of clarification, I mean it's incompatible with earlier versions when two versions are mixed in client/server environments. After upgrading the server and re-testing I haven't seen any problems with u26 as such.

I got the same error. Did you file I bug? I don't think this is supposed to break on a dot release.

Just a heads-up guys.
I had JRE update 26 pushed onto client machines yesterday and it broke an important client-server app.
This point release changes the serialVersionUID for ImageIcon. The app in question sends images from the server to the cients via a simple writeObject/readObject, which fails at the client end with a
java.io.InvalidClassException: javax.swing.ImageIcon; local class incompatible: stream classdesc serialVersionUID = 532615968316031794, local class serialVersionUID = -962022720109015502

Beware, I don't how many more of these there are in this release

No I didn't - the doc does say the serialisation may break in a future release, so I guess the lawyers are happy, even if we're not.
But I agree, a pushed-out dot release shouldn't break anything, regardless of the weasel-words in the doc.

If you're using generated serialversionids you can expect that to happen. Those are generated on the fly and the generation can and often will yield different values when using different jvms.
This is well published and there's no excuse for you to not have known this.

It's quite possible a change in a class used by one of your serialised classes caused your serialversionids to change even if the generation algorithm stays the same, and that's something a minor (bugfix) update can quite possibly introduce.

Which is why no serious project/product will ever rely on generated serialversionids except maybe in very early stages of development.

Fair point, but what advice would you give in this case?
I'm just using writeObject/readObject to send an ImageIcon across a socket connection. ImageIcon's serialversionid was changed in u26 causing the serialisation/deserialisation to break with mixed point releases.
Should I subclass every every class I want to serialise in order to override the inbuilt API library behaviour?

you should synch the data between your client and server to ensure both are talking the same language.
There should be no problem if both client and server are using the same JVM version, which in your case apparently is not happening.

That's the main problem using any serialisation system when you rely on external parties to provide the classes you're (de)serialising, when the classes change on one side but not the other things are out of whack.

One solution is to switch to another data transfer system completely. For example you're transferring serialised ImageIcons, you could instead transfer only the images contained in those icons with maybe a message header containing information like sizes, and have the client recreate an ImageIcon of its own. That way you avoid sending core library classes completely (I don't think simply wrapping them would solve anything as there'd still be a serialised ImageIcon in the datastream that needs to be deserialised).

I've never had this problem myself, but then the only stuff I usually send serialised are Strings and maybe some Integers, those are pretty stable.
For more complex data structures it's either webservices or some other mechanism that separates the data from the language specific data structures.

The only "external party" I'm relying on here is Oracle. ImageIcon is a mainstream Java SE class. Yes, I could ignore the standard built-in functionality in the API, but why should I have to? For me this is also the first time I've had any such problem, but once is enough. IMHO when they changed the serialisation of ImageIcon in u26 (which they did because of a security loophole in the old version) they should have done what I have done for my own classes that I serialise - ie in the deserialisation check the version and invoke the appropriate code, even if that's only kept in long enough to ensure a smooth migration.
Anyway, I just wanted to alert people to this issue, which I guess has now been done.

From what I can see, the changes in ImageIcon should not have broken serialization. They didn't add, remove, or change any serializable items. Instead of using an explicit serialVersionUID as is recommended, they have the version computed automatically, which broke serialization when simple a bugfix was applied.

It is a shame as ImageIcon was a easy way to transmit a image over the wire.

Here the new class that I wrote to serialize an image. I guarantee that the serialized version of this data will NEVER change... width, height, and pixels. Even if I fix bugs, or enhance this class, the data will NEVER change.

package org.jbundle.thin.base.screen.util;

import java.awt.Component;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * This module is free software. It may be copied, sold, and modified at will.
 * This module may not be copyrighted by any parties without the permission of the author.
 * @author Don Corley <don@donandann.com>
 *
 */
public class SerializableImage extends Object
    implements Serializable, ImageObserver {

    private static final long serialVersionUID = 1L;

    int width;
    int height;
    int[] pixels;

    /**
     * Creates an Image that can be serialized.
     */
    public SerializableImage() {
    }

    public SerializableImage(Image image) {
        this.setImage(image);
    }

    private void readObject(ObjectInputStream s) throws ClassNotFoundException,
            IOException {
        s.defaultReadObject();

        width = s.readInt();
        height = s.readInt();
        pixels = (int[]) (s.readObject());

    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();

        s.writeInt(width);
        s.writeInt(height);
        s.writeObject(pixels);
    }

    public int getImageWidth() {
        return width;
    }

    public int getImageHeight() {
        return height;
    }

    public Image getImage() {
        Image image = null;

        if (pixels != null) {
            Toolkit tk = Toolkit.getDefaultToolkit();
            ColorModel cm = ColorModel.getRGBdefault();
            image = tk.createImage(new MemoryImageSource(width, height, cm, pixels, 0, width));
        }
        return image;
    }

    public void setImage(Image image) {
        
        loadImage(image);

        width = image.getWidth(this);
        height = image.getHeight(this);
        pixels = image != null ? new int[width * height] : null;

        if (image != null) {
            try {
                PixelGrabber pg = new PixelGrabber(image, 0, 0, width, height, pixels, 0, width);
                pg.grabPixels();
                if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
                    throw new RuntimeException("failed to load image contents");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException("image load interrupted");
            }
        }
    }
    
    static MediaTracker tracker = null;
    static int mediaTrackerID = 0;
    
    /**
     * Loads the image, returning only when the image is loaded.
     * Note: This USUSALLY is a bad idea (to hang this thread until the image loads),
     * but in this case, images are ONLY loaded here when they are initially set which
     * is when they are selected by the user.
     * @param image the image
     */
    protected void loadImage(Image image) {
        MediaTracker mTracker = getTracker();
        synchronized(mTracker) {
            int id = getNextID();

            mTracker.addImage(image, id);
        try {
                mTracker.waitForID(id, 0);
        } catch (InterruptedException e) {
        System.out.println("INTERRUPTED while loading Image");
        }
            //?int loadStatus = mTracker.statusID(id, false);
            mTracker.removeImage(image, id);
    }
    }

    /**
     * Returns an ID to use with the MediaTracker in loading an image.
     */
    private int getNextID() {
        synchronized(getTracker()) {
            return ++mediaTrackerID;
        }
    }

    /**
     * Returns the MediaTracker for the current AppContext, creating a new
     * MediaTracker if necessary.
     */
    private MediaTracker getTracker() {
        // Opt: Only synchronize if trackerObj comes back null?
        // If null, synchronize, re-check for null, and put new tracker
        synchronized(this) {
            if (tracker == null) {
                @SuppressWarnings("serial")
                Component comp = new Component() {};
                tracker = new MediaTracker(comp);
            }
        }
        return (MediaTracker) tracker;
    }

    @Override
    public boolean imageUpdate(Image img, int infoflags, int x, int y,
            int width, int height) {
        // Since I wait for an image to load, I know I have the image.
        return true;
    }
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.