I'm trying to generate a executable jar file from a JavaFX project I'm working on which is running ok when executing through the Eclipse IDE.
But when executing from the generated jar file, a runtime error occurs when the FXML
tries to load a custom component (class which extends
the JFX component BorderPane
).
What's wrong?
Here's the FXML
file, where a custom class, CardPane
, which extends BorderPane
is used.
<?xml version="1.0" encoding="UTF-8"?>
<?import com.tsi.ui.CardPane?>
<?import java.lang.*?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<?import com.tsi.ui.CardPane?>
<BorderPane fx:id="mainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="400.0" style="-fx-background-color: #454449;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<center>
<GridPane fx:id="gridPane" opacity="0.99" prefHeight="386.0" prefWidth="380.0" BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<!--
The below command (referenced in the stack trace as line 40,
which here is not precise, since I've deleted some useless
stuff for the post propose) is where the error occurs
-->
<CardPane fx:id="pane00" prefHeight="200.0" prefWidth="200.0" />
<CardPane fx:id="pane01" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" />
<CardPane fx:id="pane02" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" />
<CardPane fx:id="pane10" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="1" />
<CardPane fx:id="pane11" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<CardPane fx:id="pane12" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<CardPane fx:id="pane20" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="2" />
<CardPane fx:id="pane21" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<CardPane fx:id="pane22" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="2" />
</children>
</GridPane>
</center>
<!-- I will show only the interesting point -->
...
</BorderPane>
The CardPane is being used just as a class that extends BorderPane with the layout positions already populated with some other nodes. In this code, we've some of them populating the content of the cells of a GridPane.
Here's a few lines to show how it's defined:
public class CardPane extends BorderPane {
private Card card;
private Label cardName;
private Label cardValue;
private ImageView valueIcon;
private HBox hBox;
private Node armaSprite;
public CardPane() {
hBox = new HBox();
cardName = new Label();
cardValue = new Label();
valueIcon = new ImageView(new Image(Sprite.CAMINHO + File.separator + "CoracaoIcon.png", 27, 22, false, false));
}
I honestly don't know if this approach (extending the BorderPane
class, making my own component this way) is a good practice. If not, I will be glad if someone could let me know what should be avoided.
But above all, the main point is, everything works fine inside Eclipse, but not when the project is exported. Why?
Here's the exception printed on Windows console. Again, it occurs only when the jar is executed with java.exe -jar '.\Dungeon Cards.jar'
. Never on the IDE.
javafx.fxml.LoadException:
file:/C:/Users/ramon/Desktop/Dungeon%20Cards.jar!/com/tsi/ui/fxml/game.fxml:40
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at com.tsi.app.Jogo.<init>(Jogo.java:40)
at com.tsi.app.DungeonCards.iniciarJogo(DungeonCards.java:138)
at com.tsi.ui.Instrucoes.esconder(Instrucoes.java:72)
at com.tsi.ui.Instrucoes$2.handle(Instrucoes.java:52)
at com.tsi.ui.Instrucoes$2.handle(Instrucoes.java:1)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$352(GlassViewEventHandler.java:248)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
at com.sun.glass.ui.View.notifyKey(View.java:966)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found
at javafx.scene.image.Image.validateUrl(Image.java:1118)
at javafx.scene.image.Image.<init>(Image.java:659)
at com.tsi.ui.CardPane.<init>(CardPane.java:42)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1009)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
... 39 more
Caused by: java.lang.IllegalArgumentException: Invalid URL or resource not found
at javafx.scene.image.Image.validateUrl(Image.java:1110)
... 51 more
@Zephyr pointed how I've missed the "Caused By:" statement on the StackTrace when writing the post. It clearly indicates that the ImageView
could not be instantiated due a malformed URL.
The trick is that I had the following constant pointing to the root path of where my image resources are:
public static final String CAMINHO = "/com/tsi/sprites";
The URL was being defined as CAMINHO + File.separator + [image_file].png
Resulting in: "/com/tsi/sprites\[image_file].png"
The File.separator
used on the constructor concatenation is holding the Windows pattern of file separating (backslash), while the constant value CAMINHO
uses normal slashes (Linux pattern).
Somehow, Eclipse can abstract that misplaced combination, but while running outside it, Windows doesn't.
To solve it, I just changed File.separator
to "/"
.
As it says in this article, while working with resources, File.separator
should be avoided:
"[...] use system-specific file separator only when working with files and when displaying a path to the user. For all other cases use forward slash."