NullPointerException in invokeLater while running through Java Webstart

After upgraded from JRE 1.7.0_21 to 1.7.0_25-b15 my application started to throw NullPointerException in SwingUtilities.invokeLater(...) when it is run from Java WebStart. Surprisingly when it is executed as a standalone application (outside JWS), it works great.

Here is the top of the stack:

Exception in thread "AWT-EventQueue-2" java.lang.NullPointerException
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
at AppletView$8.setBaseUnits(AppletView.java:536)
    (...)

To get you full picture: the method setBaseUnits(..) is called as a callback from RMI by remote server. The full stack trace is quite long.

Is there something in security model that changed in RMI or JWS that could break things ? If so I would expect some security exception, but it could be something that is not correctly detected in JRE and leads to NPE.

Any suggestions are appreciated.


---- Update1:

There are similar issues with JRE 1.7.0_25 update probably regarding some security changes and AppContext objects: https://forums.oracle.com/message/11080621 https://forums.oracle.com/thread/2552799 . I tried suggested fix: https://forums.oracle.com/message/11082162#11082162 but without any success.

I can see 3 AWT-EventQueue threads in my application with numbers from 0 to 2. It looks like JRE creates additional event queues for different application contexts if program is started by JWS. There are 3 AppContext and 3 EVTs in JWS and there is only one context and EVT if program is executed from IDE.


---- Update2:

There is a workaround as suggested by guruman below (thanks a lot). Unfortunately all the calls to the SwingUtilities.invokeLater(..) from RMI threads must be replaced, and the program starts to depend on Sun JRE internal API.

I am still looking for more general approach not specific to Sun JRE. I think it is a JRE bug. Maybe it could be patched somehow: AppContext should not be null in RMI thread.


---- Update3:

I've made a simple test case to show the problem. It consists 4 files. To run this test case one need to sign the destination jar (TestCase.jar). First of all specify correct codebase in launch.jnlp, then run the server by Java Web Start (eg. using javaws launch.jnlp). A following frame should appear on the screen:

The server application frame after start

Then the RMI client could be executed. After successful execution the frame should consist:

The server application frame after successful RMI call

but if You try to execute the server using JWS You will get the following exception in the client program (the exception is propagated from RMI server to RMI client):

Exception in thread "main" java.lang.NullPointerException
    at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
    at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
    at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
    at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
    at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
    at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
    at testcase.RmiServiceImpl.callBack(RmiServiceImpl.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
    at sun.rmi.transport.Transport$1.run(Transport.java:177)
    at sun.rmi.transport.Transport$1.run(Transport.java:174)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
    at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:273)
    at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:251)
    at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:160)
    at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
    at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
    at com.sun.proxy.$Proxy0.callBack(Unknown Source)
    at testcase.RmiClient.main(RmiClient.java:22)

So here they are the test case files:

1) JNLP file definition launch.jnlp:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jnlp codebase="file:/home/user/NetBeansProjects/TestCase/dist/" href="launch.jnlp" spec="1.0+">
    <information>
        <title>TestCase</title>
        <vendor>digital_infinity</vendor>
        <homepage href=""/>
        <description>TestCase</description>
        <description kind="short">TestCase</description>
    </information>
<security>
  <all-permissions/>
</security>
    <update check="always"/>
    <resources>
        <j2se version="1.7+"/>
        <jar href="TestCase.jar" main="true"/>
    </resources>
    <application-desc main-class="testcase.RmiServiceImpl">
    </application-desc>
</jnlp>

2) RMI interface definition (RmiService.java):

package testcase;    
public interface RmiService extends java.rmi.Remote  {
    void callBack() throws java.rmi.RemoteException;
}

3) RMI service code and the service main class:

package testcase;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 */
public class RmiServiceImpl extends java.rmi.server.UnicastRemoteObject 
implements RmiService {

    final static int PORT = 1099;

    static JFrame frame;
    static JTextField textField;

    public RmiServiceImpl() throws RemoteException {
        super(PORT);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        Registry reg;
        RmiServiceImpl service = new RmiServiceImpl();
        try {
            reg = LocateRegistry.getRegistry(PORT);
            reg.rebind("test", service);
        } catch (RemoteException ex) {
            reg = LocateRegistry.createRegistry(PORT);
            reg.rebind("test", service);
        }
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                frame = new JFrame("Test App");
                textField = new JTextField("Before call to callBack");
                frame.getContentPane().add(textField);
                frame.pack();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }

    /** RMI callback */
    public void callBack() {
        Runnable rn = new Runnable() {
            public void run() {
                textField.setText("CallBack succesfully called.");
                frame.pack();
            }
        };
        SwingUtilities.invokeLater(rn);
    }
}

4) Simple client code:

package testcase;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiClient {
    public static void main(String[] args) throws Exception {
        //now we trying to communicate with object through RMI
        Registry reg = LocateRegistry.getRegistry(RmiServiceImpl.PORT);
        //after got the registry, lookup the object and finally do call
        RmiService serv = (RmiService) reg.lookup("test");
        serv.callBack();
    }
}

---- Update4:

JRE Bug I submitted: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019272

Other related bugs:

  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019274
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8028290
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8017770
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021370

I found what I believe to be a better solution to this bug.

I just added the following code before calling SwingUtilities or any Swing related component method. It create a new AppContext for the RMI Thread (RMI thread must be the current Thread when running the code below).

if(AppContext.getAppContext() == null){
    SunToolkit.createNewAppContext();
}

Due to the needs of my application I was able to add it on a single method that was using SwingUtilities, but you may need to add it to every method on your RMI Callable Object.

The code needs to run only once, so check the behavior of your application.


The problem occurs in the Webstart environment. Before Webstart version of Java 7u25 the AppContext was set on the system thread group. Yet it is set on the main thread group.

If you have a thread based on a thread group where its parent or grandparent is not the main thread group it has no sun.awt.AppContext.

You should create your thread based on the thread group of the security manager if one exists.

Runnable task = ....
ThreadGroup threadGroup = System.getSecurityManager() != null
                                    ? System.getSecurityManager().getThreadGroup()
                                    : Thread.currentThread().getThreadGroup();
Thread t = new Thread(threadGroup, task, "my thread", 0);

Here is a workaround for JDK-8019274, packaged in a utility class. For us, invokeAndWait() was still an issue. This example has the existing fix for invokeLater() and a new fix for invokeAndWait().

Notes:

  • You'll need to include the jnlp.jar in your project.
  • Call init() early in your main() method, before calling invokeLater()
  • Replace all your calls to SwingUtilities invokeLater() and invokeAndWait() with these calls

(Disclaimer: This is from our product. Some aspects of this solution may not apply to you.)

public class JreFix {
    private static String badVersionInfo = null;
    private static AppContext awtEventDispatchContext = null;
    private static AppContext mainThreadContext = null;
    private static Boolean isWebStart = null;
    private static BasicService basicService = null;
    private static IntegrationService integrationService = null;

    /**
     * Call this early in main().  
     */
    public static void init() {
        if (isWebstart() && isApplicableJvmType()) {
            String javaVersion = System.getProperty("java.version");

            if ("1.7.0_25".equals(javaVersion)) {
                badVersionInfo = "7u25";
            }
            else if ("1.7.0_40".equals(javaVersion)) {
                badVersionInfo = "7u40";
            }
            else if (javaVersion != null && "1.6.0_51".equals(javaVersion.substring(0,8))) {
                badVersionInfo = "6u51";
            }
            else if ("javaws-10.25.2.16".equals(System.getProperty("javawebstart.version"))) {
                badVersionInfo = "Web Start 10.25.2.16";
            }
        }

        if (badVersionInfo != null) {
            mainThreadContext = AppContext.getAppContext();
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        awtEventDispatchContext = AppContext.getAppContext();
                    }
                });
            }
            catch (Exception e) {
                displayErrorAndExit(null);
            }

            if (mainThreadContext == null || awtEventDispatchContext == null) {
                 displayErrorAndExit(null);
            }
        }
    }

    public static void invokeNowOrLater(Runnable runnable) {
        if (hasAppContextBug()) {
            invokeLaterOnAwtEventDispatchThreadContext(runnable);
        }
        else {
            SwingUtilities.invokeLater(runnable);
        }
    }

    public static void invokeNowOrWait(Runnable runnable) {
        if (hasAppContextBug()) {
            fixThreadAppContext(null);
        }

        try {
            SwingUtilities.invokeAndWait(runnable);
        } 
        catch (Exception e) {
            // handle it
        }
    }

    public static boolean hasAppContextBug() {
        return isJreWithAppContextBug() && AppContext.getAppContext() == null;
    }

    public static void invokeLaterOnAwtEventDispatchThreadContext(Runnable runnable) {
        sun.awt.SunToolkit.invokeLaterOnAppContext(awtEventDispatchContext, runnable);
    }

    public static void fixThreadAppContext(Component parent) {
        try {
            final Field field = AppContext.class.getDeclaredField("threadGroup2appContext");
            field.setAccessible(true);
            Map<ThreadGroup, AppContext> threadGroup2appContext = (Map<ThreadGroup, AppContext>)field.get(null);
            final ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
            threadGroup2appContext.put(currentThreadGroup, mainThreadContext);
        } 
        catch (Exception e) {
            displayErrorAndExit(parent);
        }

        if (AppContext.getAppContext() == null) {
             displayErrorAndExit(parent);
        }
    }

    private static boolean isJreWithAppContextBug() {
        return badVersionInfo != null;
    }

    private static void displayErrorAndExit(Component parent) {
        JLabel msgLabel = new JLabel("<html>" + 
                "Our application cannot run using <b>Web Start</b> with this version of Java.<p><p>" +
                "Java " + badVersionInfo + " contains a bug acknowledged by Oracle (JDK-8019274).");
        JOptionPane.showMessageDialog(parent, msgLabel, "Java Version Error", JOptionPane.ERROR_MESSAGE);
        System.exit(1);
    }

    private static boolean isApplicableJvmType() {
        String vendor = System.getProperty("java.vendor");
        String vmName = System.getProperty("java.vm.name");
        if (vendor != null && vmName != null) {
            return vmName.contains("Java HotSpot") &&
                    (vendor.equals("Oracle Corporation") || 
                     vendor.equals("Sun Microsystems Inc."));
        }

        return false;
    }

    private static boolean isWebstart() {
        if (isWebStart == null) {
            try { 
                basicService = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService");             
                isWebStart = true;
            } 
            catch (UnavailableServiceException e) { 
                isWebStart = false;
            }           

            try {
                integrationService = (IntegrationService) ServiceManager.lookup("javax.jnlp.IntegrationService");
            } 
            catch (UnavailableServiceException e) {
            }
        }
        return isWebStart;
    }
}

Java 7u65 which came out yesterday (2014-07-15) claims to have fixed this or a very similar issue, in JDK-8019724. I'm testing right now to find out - a driver from one of our vendors doesn't function under Java Web Start, and it has been keeping us on Java 6.

ETA: Yes, it looks like this resolves our problems!