-1

I want to create runnable .jar-file with Maven. The following code shows the main class (App.java), the class where the JFXPanel gets instantiated (ServiceToolEnty.java) and the pom.xml file.

I create a simple JFrame with a button inside (Java Swing site). Once the button has been clicked the JFXPanel should open the JavaFX thread to run javaFX code.

When I run the application in eclipse IDE everything works fine. After I created the .jar file with Maven and I try to run it via terminal (java -jar test-0.0.1-SNAPSHOT.jar), the following error message shows up:

Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: javafx/embed/swing/JFXPanel
    at com.carrier.ccr.test.App$1.actionPerformed(App.java:31)
    at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1972)
    at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2313)
    at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
    at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
    at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:279)
    at java.desktop/java.awt.Component.processMouseEvent(Component.java:6626)
    at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3389)
    at java.desktop/java.awt.Component.processEvent(Component.java:6391)
    at java.desktop/java.awt.Container.processEvent(Container.java:2266)
    at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5001)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
    at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
    at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
    at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
    at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:746)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:744)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:743)
    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)
Caused by: java.lang.ClassNotFoundException: javafx.embed.swing.JFXPanel
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    ... 36 more

App.java:


import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Locale;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
public class App extends JFrame{
 
    public App(){
        setSize(500, 180);
        setVisible(true);
        setLayout(null);
        setLocation(100, 100);

        //Button opens the ServiceTool interface
        JButton masterModeServiceTool = new JButton("Click here");
        masterModeServiceTool.setSize(280, 20);
        masterModeServiceTool.setFont(new Font("Arial",Font.PLAIN,15));
        add(masterModeServiceTool);
        masterModeServiceTool.setVisible(true);
        masterModeServiceTool.setLocation(10, 100);
        masterModeServiceTool.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                try {
                    ServiceToolEntry maServ=new ServiceToolEntry();
                    maServ.main();
                    setVisible(false);
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });

    }


    public static void main(String[] args) {
        App mv =new App();
        mv.addWindowListener(new WindowAdapter()
        {
            public void windowClosing (WindowEvent e) {
                System.exit (0);
            }
        });
    }
}

ServiceToolEnty.java (where the fx app is starting):


import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class ServiceToolEntry {
    private static void initAndShowGUI() {
        // This method is invoked on the EDT thread
        JFrame frame = new JFrame("Click here");
        final JFXPanel fxPanel = new JFXPanel();
        frame.add(fxPanel);
        frame.setSize(1000, 700);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                initFX(fxPanel);
            }
        });
    }


    private static Scene getScene() {
        Group  root  =  new  Group();
        Scene  scene  =  new  Scene(root, Color.ALICEBLUE);
        Text  text  =  new  Text();

        text.setX(40);
        text.setY(100);
        text.setFont(new Font(25));
        text.setText("Finally it's working");
        root.getChildren().add(text);

        return (scene);
    }


    private static void initFX(JFXPanel fxPanel) {
        Scene scene = getScene();
        fxPanel.setScene(scene);
    }


    public static void main() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                initAndShowGUI();
            }
        });
    }
}

pom.xml file:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.carrier.ccr</groupId>
  <artifactId>test</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <name>test</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
            <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>13</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>13</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-swing</artifactId>
            <version>18-ea+5</version>
        </dependency>
        
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
            <plugin> 
    
               <!-- Building an executable jar -->
    
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-jar-plugin</artifactId>
               <version>3.1.0</version>
               <configuration>
                 <archive>
                   <manifest>
    
                   <!-- give full qualified name of your main class-->
                   <addClasspath>true</addClasspath>
                     <mainClass>com.carrier.ccr.test.App</mainClass>
    
                   </manifest>
                 </archive>
               </configuration>
            </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        
        
      </plugins>
    </pluginManagement>
  </build>
</project>
jewelsea
  • 150,031
  • 14
  • 366
  • 406
Felix D
  • 33
  • 7
  • Probably you should add the jars mentioned in dependencies (at least all java-fx jars) to your classpath when you call java -jar. Use '-cp' option for this. – Sergiy Medvynskyy Nov 05 '21 at 15:07
  • Don't depend on mixed version of JavaFX libs. Don't depend on early access software if you can avoid it. Current JavaFX version is 17.0.1, use that for all JavaFX dependencies. – jewelsea Nov 05 '21 at 19:37

1 Answers1

1

You have numerous issues with your code and packaging. I can't really explain them all here now. But I present some alternatives you could try to work with if you wish.

I'll also add the usual caveat: don't mix JavaFX and Swing unless you really have to.

Packaging Alternatives

Ordered from the least to most self-contained options (not most to least desirable):

  1. Thin jar without JavaFX dependencies

    • requires mandating that the application run on a JDK or Java runtime which includes pre-bundled JavaFX modules. For example, from the distributors mentioned below. If used, ensure you select their distributions of the JDK which include JavaFX:

    • You don't need to put JavaFX dependencies in your Maven project as they are already available in the underlying platform.

    • If it is a simple application, without any other dependencies than the standard JDK and JavaFX, then you can package the application as an executable jar with no dependencies.

  2. Shaded "fat" jar (or zip) without JavaFX dependencies

    • If you have non-javafx dependencies, you can use the info in the solutions for "fat" jar with JavaFX dependencies or zip without JRE.
      • Don't place the JavaFX dependencies in your maven project dependency list.
      • Ensure that you and your users are always using a JDK or java runtime which includes JavaFX.
  3. Shaded "fat" jar (jar) with JavaFX dependencies

  4. Zip without JRE (zip, tar, tar.gz, tar.bz2)

  5. Zip with JRE (zip, tar, tar.gz, tar.bz2)

    • use jlink to create a runtime image directory.
    • create a zip of the runtime image with an assembly.
  6. Windows exe (exe)

  7. Native installer (msi, dmg, app, rpm, deb)

In terms of ease of use for users, I'd recommend the later ones which bundle a working Java runtime: either a Native installer, Zip with JRE, or Windows exe.

I think the solutions which require the user to have a valid JRE installed are less desirable for many applications.

  • With no bundled JRE, the difficult work of ensuring the right JRE is present and properly configured is shifted to every user rather than the developer.
  • The Maven shade "fat" jar option which includes JavaFX dependencies isn't one supported by the JavaFX development team.

For Maven Users

  • jlink can be invoked from the OpenJFX JavaFX Maven plugin.
  • jpackage can be invoked from the akman jpackage maven plugin.
    • jpackage can also create images, the same as jlink.
    • If creating native installers, I recommend having jpackage handle both the linking and packaging (jpackage will warn you if you do not do this).
    • On some options for windows apps, I found that jlink did not place the required icons in the right place by default, resulting in weird and hard to track down issues that did not occur when jpackage did the linking.

Zip without JRE Option

The rest of this answer focuses purely on the Zip without JRE option.

See the readme at the end of the answer which describes what this is and how to use it. If it is not what you are looking for, just ignore it and try to solve your issues another way.

The solution is not an executable jar. Instead, it is a set of java modules packaged in jar format, executed via a launcher script and assembled into a compressed archive file.

  • It requires that JRE 17+ be installed on the target machine.

  • The application is only packaged for executing on an Intel Mac (at least when it has been built on an Intel Mac).

    • Packaging for other platforms is possible, but is outside the scope of what I am discussing here.

Swing<->JavaFX integration

The solution is for a Swing+JavaFX application. Normally I wouldn't recommend such a configuration, however it is presented here because that is what the question asked about. The Swing<->JavaFX integration code follows the structure and guidelines provided in Oracle's tutorial: Integrating JavaFX into Swing Applications.

If you don't have Swing in your application, remove the Swing application, the javafx.swing requirement in the module definition and the javafx-swing dependency from Maven, and reconfigure the launcher script to directly call the JavaFX HelloApplication and everything will work fine for a pure JavaFX application distribution without Swing.

File tree

$ tree
├── pom.xml
├── readme.md
└── src
    ├── assembly
    │   └── bin.xml
    └── main
        ├── command
        │   └── swingfx.sh
        ├── java
        │   ├── com
        │   │   └── example
        │   │       └── swingfx
        │   │           ├── HelloApplication.java
        │   │           ├── HelloController.java
        │   │           └── SwingApp.java
        │   └── module-info.java
        └── resources
            └── com
                └── example
                    └── swingfx
                        └── hello-view.fxml

I skipped providing the JavaFX FXML and Controller code because they aren't relevant to the solution, they are just formed from standard code that you can find in any basic FXML tutorial.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>SwingFX</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>SwingFX</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <javafx.version>17.0.1</javafx.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-swing</artifactId>
            <version>${javafx.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <descriptors>
                        <descriptor>src/assembly/bin.xml</descriptor>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

module-info.java

module com.example.swingfx {
    requires javafx.controls;
    requires javafx.fxml;
    requires javafx.swing;

    opens com.example.swingfx to javafx.fxml;
    exports com.example.swingfx;
}

SwingApp.java

package com.example.swingfx;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;

import javax.swing.*;
import java.awt.*;

public class SwingApp {

    private static void initAndShowGUI() {
        JFrame frame = new JFrame("Swing Main App JFrame");
        frame.setSize(500, 180);
        frame.setVisible(true);
        frame.setLayout(null);
        frame.setLocation(100, 100);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Button opens the ServiceTool interface
        JButton toolLauncher = new JButton("Launch JavaFX Tool");
        toolLauncher.setSize(280, 20);
        toolLauncher.setFont(new Font("Arial", Font.PLAIN, 15));
        toolLauncher.setVisible(true);
        toolLauncher.setLocation(10, 100);
        toolLauncher.addActionListener(e -> launchFXTool());
        frame.add(toolLauncher);
    }

    private static void launchFXTool() {
        try {
            // This method is invoked on the EDT thread
            // The method creates a new frame and JavaFX scene whenever it is invoked.
            // If preferred and only one instance is required, then cache the JFrame
            // on first launch, then show and focus the existing JFrame when the tool is relaunched.
            JFrame frame = new JFrame("Swing JFrame with JavaFX scene content");
            final JFXPanel fxPanel = new JFXPanel();
            frame.add(fxPanel);
            frame.setSize(300, 200);
            frame.setVisible(true);

            Platform.runLater(() -> {
                // This logic is invoked on the JavaFX thread
                try {
                    fxPanel.setScene(
                            HelloApplication.createScene()
                    );
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(SwingApp::initAndShowGUI);
    }
}

HelloApplication.java

package com.example.swingfx;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        stage.setTitle("Hello!");
        stage.setScene(createScene());
        stage.show();
    }

    public static Scene createScene() throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        return new Scene(fxmlLoader.load(), 320, 240);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

swingfx.sh

#!/bin/sh
VM_OPTIONS=
DIR=`dirname $0`
MODULE_PATH=$DIR/../lib
MAIN_CLASS=com.example.swingfx/com.example.swingfx.SwingApp 
# if desired you could add a check to ensure a compatible java version is installed and print a friendly error message if not.
# launches a java app with the given vm options and module path, passing the arguments provided to this script to the app.
java $VM_OPTIONS -p $MODULE_PATH -m $MAIN_CLASS "$@"

bin.xml

<assembly>
    <id>bin</id>
    <formats>
        <format>tar.gz</format>
        <format>zip</format>
    </formats>
    <dependencySets>
        <dependencySet>
            <scope>runtime</scope>
            <outputDirectory>lib</outputDirectory>
        </dependencySet>
    </dependencySets>
    <fileSets>
        <fileSet>
            <directory>src/main/command</directory>
            <outputDirectory>bin</outputDirectory>
            <includes>
                <include>*.sh</include>
                <include>*.bat</include>
            </includes>
            <fileMode>0755</fileMode>
        </fileSet>
    </fileSets>
</assembly>

readme.md

Example of a Swing application integrated with JavaFX. The application is built as a Java module application.

Build Requirements

  • Java JDK 17+
  • Maven 3.8+

Execution Requirements

  • Java JRE 17+
  • Mac OS X Intel

Build Instructions

Build and package the application:

mvn package

The build will create the following file:

target/SwingFX-1.0-SNAPSHOT-bin.tar.gz

Execution instructions

  1. Extract the application binary from the archive.

    tar xzvf SwingFX-1.0-SNAPSHOT-bin.tar.gz
    
  2. Run the application:

    SwingFX-1.0-SNAPSHOT/bin/swingfx.sh 
    

Contents of the distribution

$ tar xvzf SwingFX-1.0-SNAPSHOT-bin.tar.gz 
x SwingFX-1.0-SNAPSHOT/bin/swingfx.sh
x SwingFX-1.0-SNAPSHOT/lib/javafx-controls-17.0.1.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-controls-17.0.1-mac.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-graphics-17.0.1.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-graphics-17.0.1-mac.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-base-17.0.1.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-base-17.0.1-mac.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-fxml-17.0.1.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-fxml-17.0.1-mac.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-swing-17.0.1.jar
x SwingFX-1.0-SNAPSHOT/lib/javafx-swing-17.0.1-mac.jar
x SwingFX-1.0-SNAPSHOT/lib/SwingFX-1.0-SNAPSHOT.jar

Troubleshooting

If you see the following error, java is not installed and on the default path:

-bash: java: command not found

If you see an error such as the following:

Caused by: java.lang.module.InvalidModuleDescriptorException: Unsupported major.minor version 61.0

Java is installed but it is not Java 17+, you need to upgrade the Java installation to a later version, you can check the current version using java -version

jewelsea
  • 150,031
  • 14
  • 366
  • 406