JavaFX+Maven+Spring – Step by step

In the beginning, I want to mention that the process has been presented on YouTube (link is here) by another author. The problem is only that there it is described on finished application. I want to show how to create a JavaFX application with Maven and Spring support from the beginning.

Let’s start from creating an empty Maven project.

Java 11 don’t contain JavaFX anymore. So for using the library, it is required to add proper dependencies for importing libraries. So in the beginning, I add to project <java.version> declaration:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>11</java.version>
</properties>

In next step I add dependencies for JavaFX libraires:

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>11.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-media</artifactId>
        <version>11.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-graphics</artifactId>
        <version>11.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>11.0.1</version>
    </dependency>
</dependencies>

It’s time to add support for Spring Boot. Firstly I add to pom.xml’s header section <parent></parent> like below:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

In section <dependencies> I add:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-integration</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Finishing adding support for Spring Boot we adding plugins.:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <source>11</source>
                <target>11</target>
            </configuration>
        </plugin>
    </plugins>
</build>

There are also required two more plugins, which are responsible for packing and running an application.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.6.0</version>
    <executions>
        <execution>
            <goals>
                <goal>java</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <mainClass>!!!!!----Path to Main class-----!!!!!</mainClass>
    </configuration>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>!!!!!----Patch to Main class-----!!!!!</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

When configuration pom.xml is finished we can go to creating a class of Main() method. It’s Spring application so the class have to be stored in any package.

public class Main {
    public static void main(String[] args) {

    }
}

JavaFX application has to extend Application class and override start() method:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {

    }

    @Override
    public void start(Stage stage) throws Exception {
        
    }
}

For Spring purpose we adding @SpringBootApplication annotation to the main class and two methods: init() and stop(). The main class contain a variable of type: ConfigurableApplicationContext. It allows us on access to Spring’s context. In init() we are declaring creating an instance for springContext. In stop() we are declaring destruction for spring context.

@SpringBootApplication
public class Main extends Application {
    private ConfigurableApplicationContext springContext;
    public static void main(String[] args) {

    }

    @Override
    public void start(Stage stage) throws Exception {

    }

    public void init() throws  Exception{
        springContext = SpringApplication.run(Main.class);
    }

    public void stop() throws Exception{
        springContext.close();
    }

}

Additionally, it is needed to create Spring Configuration class:

package configuration.spring;

import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfiguration {
}

Next step is to integrating Spring with JavaFX. For this purpose, we are declaring new FXMLoader and setting spring context as controllers factory. Pay an attention that in method .getResource() in FXMLoader path “/” leads to Maven’s static resources folder. It is the folder of name: “resources”.

public void init() throws  Exception{
    springContext = SpringApplication.run(Main.class);
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/MainWindow.fxml"));
    fxmlLoader.setControllerFactory(springContext::getBean);
    rootNode = fxmlLoader.load();
}

Where MainWindow.fxml is located in “resources ” and contain:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="fxml.MainWindow"
            prefHeight="400.0" prefWidth="600.0">

</AnchorPane>

So in the next step, we can add to start() method new scene declaration:

@Override
public void start(Stage stage) throws Exception {
    stage.setScene(new Scene(rootNode, 700, 700));
    stage.setMinWidth(700);
    stage.setMinHeight(700);
    stage.show();
}

Main() metchod contain:

Application.launch(args);

Because of Maven IDE SceneBuilder doesn’t provide the correct path to the controller in MainWindow.fxml file. So let’s edit it to full package path:

fx:controller="pl.drogago.blog.test.spring.fx.controllers.MainWindowController"

Next step is to create a new runtime configuration. In IDE we are looking for Maven management and in plugins, we are finding plugins->exec->exec:java. By clicking right mouse button we choose of context menu Create button. Please, remember about adding to the command’s line prefix: “compile -X”. Full command line content should be:

compile -X  exec:java -f pom.xml

In the end, it requires declaring Beans of JavaFX Controllers in Spring Context. It can be done in two ways:

  • By adding to controllers @Component annotations. Then Spring will automatically create the required beans.
  • By adding Bean’s configuration in Spring’s configuration class
@Component
public class MainWindowController {
}

Or:

@Configuration
public class SpringConfiguration {
    @Bean
    public MainWindowController mainWindowController(){
        return new MainWindowController();
    }
}

In default, Bean can be initialized only once. So error like below means that there is a bean configuration doubled:

Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.6.0:java (default-cli) on project spring.fx: An exception occured while executing the Java class. Exception in Application init method: Invalid bean definition with name 'mainWindowController' defined in class path resource [pl/drogago/blog/test/spring/fx/configuration/spring/SpringConfiguration.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=springConfiguration; factoryMethodName=mainWindowController; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [pl/drogago/blog/test/spring/fx/configuration/spring/SpringConfiguration.class]] for bean 'mainWindowController': There is already [Generic bean: class [pl.drogago.blog.test.spring.fx.controllers.MainWindowController]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file

Finally pom.xml file looks like:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>pl.drogago.blog.examples</groupId>
    <artifactId>spring.fx</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-media</artifactId>
            <version>11.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-graphics</artifactId>
            <version>11.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>11.0.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>pl.drogago.blog.test.spring.fx.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>pl.drogago.blog.test.spring.fx.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Main class:

package pl.drogago.blog.test.spring.fx;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Main extends Application {
    private ConfigurableApplicationContext springContext;
    private Parent rootNode;
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(rootNode, 700, 700));
        stage.setMinWidth(700);
        stage.setMinHeight(700);
        stage.show();
    }

    public void init() throws  Exception{
        springContext = SpringApplication.run(Main.class);
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/MainWindow.fxml"));
        fxmlLoader.setControllerFactory(springContext::getBean);
        rootNode = fxmlLoader.load();
    }

    public void stop() throws Exception{
        springContext.close();
    }

}

MainWindowController.class

import org.springframework.stereotype.Component;
 @Component
 public class MainWindowController {
 }

SpringConfiguration.class

package pl.drogago.blog.test.spring.fx.configuration.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pl.drogago.blog.test.spring.fx.controllers.MainWindowController;


public class SpringConfiguration {
    
}

MainWindow.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.drogago.blog.test.spring.fx.controllers.MainWindowController" />

I hope that the tutorial was helpful.

Leave a comment

Your email address will not be published.