0

A NullPointerException is thrown when trying to serialize a TableModel object while the associated JTable object is visible in a JFrame. I created a minimal, reproducible Example and will also include the Stack-Trace.

This is the NullPointerException reason:

Cannot invoke "javax.swing.CellRendererPane.paintComponent(java.awt.Graphics, java.awt.Component, java.awt.Container, int, int, int, int, boolean)" because "this.rendererPane" is null

I hope someone can help. Maybe the stack trace below helps to understand it better.

I am using AdoptOpenJDK14 (14.0.2.12-hotspot)

Here is the minimal, reproducible Example:

Serialization.java (Entrypoint of the Program)

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;

import com.formdev.flatlaf.FlatDarkLaf;

public class Serialization {

    enum SERIALIZATION_THREAD {
        MAIN,
        THREAD,
        EDT
    }

    public static void main(String[] args) throws Exception {

        // Create GUI with frame and model
        UI ui = new UI();

        // Serialize the table model
        System.out.println("SERIALIZING ON MAIN THREAD");
        sleep(1000);
        serialize(ui.model, SERIALIZATION_THREAD.MAIN); // DOESN'T WORK

        System.out.println("SERIALIZING ON SEPARATE THREAD");
        sleep(1000);
        serialize(ui.model, SERIALIZATION_THREAD.THREAD); // DOESN'T WORK

        System.out.println("SERIALIZING ON EDT");
        sleep(1000);
        serialize(ui.model, SERIALIZATION_THREAD.EDT); // WORKS
    }

    public static void serialize(Serializable object, SERIALIZATION_THREAD thread) {
        switch (thread) {
            case MAIN:
                serialize0(object);
                break;
            case THREAD:
                new Thread(() -> serialize0(object)).start();
                break;
            case EDT:
                SwingUtilities.invokeLater(() -> serialize0(object));
                break;
            default:
                break;
        }
    }

    public static void serialize0(Serializable object) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("some.dat"))) {
            oos.writeObject(object);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void sleep(int ms){
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class UI {

    JFrame frame;
    MyAbstractModel model;

    public UI() throws InvocationTargetException, InterruptedException {

        SwingUtilities.invokeAndWait(() -> {
            FlatDarkLaf.setup(); // When using FlatLaf, it causes a NullPointer when serializing.

            model = new MyAbstractModel();
            frame = new JFrame();
            frame.add(new JTable(model));
            frame.setSize(300, 300);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setVisible(true);

            System.out.println("Done Building GUI");

        });
    }
}

class MyAbstractModel extends AbstractTableModel {

    private List<String> list = new ArrayList<>();

    public MyAbstractModel() {
        list = Collections.synchronizedList(new ArrayList<>());
        list.add("A");
        list.add("B");
        list.add("C");
    }

    @Override
    public int getRowCount() {
        return list.size();
    }

    @Override
    public int getColumnCount() {
        return 1;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return rowIndex + "-" + columnIndex;
    }

    @Override
    public String getColumnName(int column) {
        return "Column " + column;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // TODO
    }
}

This is the Stacktrace I got.

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "javax.swing.CellRendererPane.paintComponent(java.awt.Graphics, java.awt.Component, java.awt.Container, int, int, int, int, boolean)" because "this.rendererPane" is null at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2191) at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2092) at java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1888) at com.formdev.flatlaf.ui.FlatTableUI.paint(FlatTableUI.java:397) at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161) at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:797) at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074) at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5255) at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643) at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618) at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556) at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323) at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5203) at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5013) at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:865) at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:848) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848) at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823) at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772) at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "javax.swing.JTable.getColumnModel()" because "this.table" is null at java.desktop/javax.swing.plaf.basic.BasicTableUI.getPreferredSize(BasicTableUI.java:1768) at java.desktop/javax.swing.JComponent.getPreferredSize(JComponent.java:1680) at java.desktop/javax.swing.JTable.setWidthsFromPreferredWidths(JTable.java:3205) at java.desktop/javax.swing.JTable.doLayout(JTable.java:3117) at java.desktop/java.awt.Container.validateTree(Container.java:1722) at java.desktop/java.awt.Container.validateTree(Container.java:1731) at java.desktop/java.awt.Container.validateTree(Container.java:1731) at java.desktop/java.awt.Container.validateTree(Container.java:1731) at java.desktop/java.awt.Container.validate(Container.java:1657) at java.desktop/javax.swing.RepaintManager$3.run(RepaintManager.java:745) at java.desktop/javax.swing.RepaintManager$3.run(RepaintManager.java:743) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/javax.swing.RepaintManager.validateInvalidComponents(RepaintManager.java:742) at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1883) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) Done

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Nur1
  • 418
  • 4
  • 11
  • [Related question](https://stackoverflow.com/q/5111042/522444) – Hovercraft Full Of Eels Jun 13 '22 at 20:42
  • You still miss the point about ALL Swing components being created on the EDT. This would include setting the LAF and creating the table. I also don't know why you have a synchronized list. Swing is single threaded. If you correctly do all updates to the model and components on the EDT there is no need for a synchronized List. Don't know if it will fix any of the problems, the but code should follow Swing conventions. – camickr Jun 13 '22 at 21:03
  • 1
    please format the stacktrace as code to make it readable – kleopatra Jun 14 '22 at 11:45

1 Answers1

0

@camickr is right. Indeed if you initiate your gui like this, with gui components being created on the EDT, your problem disappears. Note the pattern and always use it. I don't think the problem had anything to do with serialization but feel free to tell me otherwise after you've reinstated the serialization code that I removed.

import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.table.AbstractTableModel;
import java.io.ObjectOutputStream;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import com.formdev.flatlaf.FlatDarkLaf;

public class Serialization extends JFrame {

    public void setGui() {
        FlatDarkLaf.setup(); // When using FlatLaf, it causes a NullPointer when serializing.

        MyAbstractModel model = new MyAbstractModel();
        JTable table = new JTable(model);

        getContentPane().add(table);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    Serialization f = new Serialization();
                    f.setGui();
                    f.setSize(200, 200);
                    f.setVisible(true);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

class MyAbstractModel extends AbstractTableModel {

    private List<String> list = new ArrayList<>();

    public MyAbstractModel() {
        list = Collections.synchronizedList(new ArrayList<>());
        list.add("A");
        list.add("B");
        list.add("C");
    }

    @Override
    public int getRowCount() {
        return list.size();
    }

    @Override
    public int getColumnCount() {
        return 1;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return rowIndex + "-" + columnIndex;
    }

    @Override
    public String getColumnName(int column) {
        return "Column " + column;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // TODO
    }
}

Following your refinements, I took the liberty of changing main to the following:

public static void main(String[] args) throws Exception {
    // Create GUI with frame and model
    UI ui = new UI();
    for (int i = 0; i < args.length; i++) {
        SERIALIZATION_THREAD t = SERIALIZATION_THREAD.valueOf(args[i]);
        // Serialize the table model
        System.out.printf("Serializing on thread %s%n", args[i]);
        serialize(ui.model, t);
        sleep(1000);
    }

}

and found that, corresponding with my own investigations, the only argument you can pass without failure is EDT. It would seem that the internal reference to the table in BasicTableUI 'goes null'. Not sure why but it looks like threading is involved.

g00se
  • 3,207
  • 2
  • 5
  • 9
  • Ok, I also played around a bit, it seems like having both the Table model and the serialization code inside the EDT fixes the problem. I created a static serialize method that serializes in a new thread, but this causes the error too. The issue seems to happen when trying to serialize the model from another thread. I first thought that a TableModel is not a Swing component but more like an actual model object which holds data. That's why I didn't put all of the code into the EDT. I wonder if there is a way to serialize in another thread (e.g. when adding a shutdown hook). Thank you. – Nur1 Jun 13 '22 at 22:05
  • OK. Despite this being the 'correct' way to do things, there *is* still a problem which arises when trying to serialize the model. Even when that operation *is* done in a dedicated thread. Could be a bug in the LAF (problem disappears with the default LAF) - not too sure at the moment. – g00se Jun 13 '22 at 22:25
  • 1
    @Nur1 *The issue seems to happen when trying to serialize the model from another thread* - where is your [mre] demonstrating the problem. Saving data from a model should not affect the painting of the component. I don't have access to your LAF so I can't test but maybe if I see some code I can spot an obvious problem like I suggested here. – camickr Jun 14 '22 at 00:26
  • @Nur1. Thanks for the update. Please see mine – g00se Jun 14 '22 at 11:26
  • Thanks for the additional conclusion of yours. Yes, kinda weird behavior, I never thought that threading breaks or nulls any objects. – Nur1 Jun 14 '22 at 20:56
  • Well it could be a bug nonetheless, even though it's *apparently* connected with threading issues. You should probably report it to the LAF authors – g00se Jun 14 '22 at 20:58