I develop a JFX app in Clojure. With JFX 2.1 the program compiles/runs fine. But under JFX 2.2 it doesn't, and runs instead into an NPE.
It can be reproduced easily by importing Classes from the javafx.scene.control package.
Here the NPE which I see during building an überjar:
Exception:
Compiling foo.bar
Exception in thread "main" java.lang.ExceptionInInitializerError, compiling:(bar.clj:5)
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3387)
at clojure.lang.Compiler.compile1(Compiler.java:7035)
at clojure.lang.Compiler.compile1(Compiler.java:7025)
at clojure.lang.Compiler.compile(Compiler.java:7097)
at clojure.lang.RT.compile(RT.java:387)
at clojure.lang.RT.load(RT.java:427)
at clojure.lang.RT.load(RT.java:400)
at clojure.core$load$fn__4890.invoke(core.clj:5415)
at clojure.core$load.doInvoke(core.clj:5414)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5227)
at clojure.core$compile$fn__4895.invoke(core.clj:5426)
at clojure.core$compile.invoke(core.clj:5425)
at user$eval31.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6501)
at clojure.lang.Compiler.eval(Compiler.java:6477)
at clojure.core$eval.invoke(core.clj:2797)
at clojure.main$eval_opt.invoke(main.clj:297)
at clojure.main$initialize.invoke(main.clj:316)
at clojure.main$null_opt.invoke(main.clj:349)
at clojure.main$main.doInvoke(main.clj:427)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:419)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
Caused by: java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at foo.bar$loading__4784__auto__.invoke(bar.clj:5)
at clojure.lang.AFn.applyToHelper(AFn.java:159)
at clojure.lang.AFn.applyTo(AFn.java:151)
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3382)
... 26 more
Caused by: java.lang.NullPointerException
at com.sun.glass.ui.Screen.getMainScreen(Screen.java:15)
at com.sun.javafx.tk.quantum.QuantumToolkit.getPrimaryScreen(QuantumToolkit.java:594)
at javafx.stage.Screen.updateConfiguration(Screen.java:102)
at javafx.stage.Screen.checkDirty(Screen.java:97)
at javafx.stage.Screen.getPrimary(Screen.java:176)
at com.sun.javafx.Utils.isQVGAScreen(Utils.java:813)
at javafx.scene.control.UAStylesheetLoader$1.run(UAStylesheetLoader.java:69)
at java.security.AccessController.doPrivileged(Native Method)
at javafx.scene.control.UAStylesheetLoader.loadUAStylesheet(UAStylesheetLoader.java:58)
at javafx.scene.control.UAStylesheetLoader.doLoad(UAStylesheetLoader.java:51)
at javafx.scene.control.Control.(Control.java:92)
... 32 more
Since JFX 2.2 something has changed in some controls (Button, Label, etc). They maybe expect that an Application instance already exists when they get loaded?
In Clojure, development occurs in an organic manner. I start the JVM, without any code. Now in the running JVM I compile Clojure code to bytecode. I discovered this issue after developing a little tool, which I started developing without importing anything from javafx.scene.control.*. Only after the JVM was running I instantiated an Application and updated it frequently. Now I imported Buttons and Labels, etc. and created instances of them and added them to a Scene which became the Scene of my primary Stage. This worked fine. However, after I wrote my little tool I wanted to compile it into an überjar, which caused this NPE.
Please take a look at this Java code which is part of the Clojure compiler — this code imports classes:
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L581
I am not certain what is going on, but this is my suspicion:
When I do (import javafx.scene.control.Label) for example that RT.classForName(c) call from the code snippet above runs, which I think triggers the instantiation of static variables inside the Label class, and this instantiation expects that an Application already exists.
This was not the case in all JFX versions before, and it unfortunately breaks non-Java JFX apps.
It can be reproduced easily by importing Classes from the javafx.scene.control package.
Here the NPE which I see during building an überjar:
Exception:
Compiling foo.bar
Exception in thread "main" java.lang.ExceptionInInitializerError, compiling:(bar.clj:5)
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3387)
at clojure.lang.Compiler.compile1(Compiler.java:7035)
at clojure.lang.Compiler.compile1(Compiler.java:7025)
at clojure.lang.Compiler.compile(Compiler.java:7097)
at clojure.lang.RT.compile(RT.java:387)
at clojure.lang.RT.load(RT.java:427)
at clojure.lang.RT.load(RT.java:400)
at clojure.core$load$fn__4890.invoke(core.clj:5415)
at clojure.core$load.doInvoke(core.clj:5414)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5227)
at clojure.core$compile$fn__4895.invoke(core.clj:5426)
at clojure.core$compile.invoke(core.clj:5425)
at user$eval31.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6501)
at clojure.lang.Compiler.eval(Compiler.java:6477)
at clojure.core$eval.invoke(core.clj:2797)
at clojure.main$eval_opt.invoke(main.clj:297)
at clojure.main$initialize.invoke(main.clj:316)
at clojure.main$null_opt.invoke(main.clj:349)
at clojure.main$main.doInvoke(main.clj:427)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:419)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
Caused by: java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at foo.bar$loading__4784__auto__.invoke(bar.clj:5)
at clojure.lang.AFn.applyToHelper(AFn.java:159)
at clojure.lang.AFn.applyTo(AFn.java:151)
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3382)
... 26 more
Caused by: java.lang.NullPointerException
at com.sun.glass.ui.Screen.getMainScreen(Screen.java:15)
at com.sun.javafx.tk.quantum.QuantumToolkit.getPrimaryScreen(QuantumToolkit.java:594)
at javafx.stage.Screen.updateConfiguration(Screen.java:102)
at javafx.stage.Screen.checkDirty(Screen.java:97)
at javafx.stage.Screen.getPrimary(Screen.java:176)
at com.sun.javafx.Utils.isQVGAScreen(Utils.java:813)
at javafx.scene.control.UAStylesheetLoader$1.run(UAStylesheetLoader.java:69)
at java.security.AccessController.doPrivileged(Native Method)
at javafx.scene.control.UAStylesheetLoader.loadUAStylesheet(UAStylesheetLoader.java:58)
at javafx.scene.control.UAStylesheetLoader.doLoad(UAStylesheetLoader.java:51)
at javafx.scene.control.Control.(Control.java:92)
... 32 more
Since JFX 2.2 something has changed in some controls (Button, Label, etc). They maybe expect that an Application instance already exists when they get loaded?
In Clojure, development occurs in an organic manner. I start the JVM, without any code. Now in the running JVM I compile Clojure code to bytecode. I discovered this issue after developing a little tool, which I started developing without importing anything from javafx.scene.control.*. Only after the JVM was running I instantiated an Application and updated it frequently. Now I imported Buttons and Labels, etc. and created instances of them and added them to a Scene which became the Scene of my primary Stage. This worked fine. However, after I wrote my little tool I wanted to compile it into an überjar, which caused this NPE.
Please take a look at this Java code which is part of the Clojure compiler — this code imports classes:
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L581
I am not certain what is going on, but this is my suspicion:
When I do (import javafx.scene.control.Label) for example that RT.classForName(c) call from the code snippet above runs, which I think triggers the instantiation of static variables inside the Label class, and this instantiation expects that an Application already exists.
This was not the case in all JFX versions before, and it unfortunately breaks non-Java JFX apps.