Posts

Creating Modern Desktop Apps with JavaFX and Spring Boot

Apr 24, 2025
Catherine Edelveis
39.0

Introduction 

Desktop applications are far from obsolete in 2025. For industries such as Healthcare or Internet of Things, offline capabilities and responsive UIs still matter. So, if you want to create a desktop version of your application or have an idea for a rich client app with a responsive UI, JavaFX will be your faithful companion on this journey! It offers multiple graphics and media packages that enable writing cross-platform desktop interfaces Java style.

But JavaFX not only interacts with any Java API, it can be integrated with Java frameworks such as Spring Boot, unlocking robust backend architecture, dependency injection, clear bean lifecycle management, centralized event handling, and many more cool Spring features. This will enable you to build complex, maintainable, scalable, responsive, and beautiful applications!

In this article, we will explore why JavaFX and Spring Boot combo is a perfect match for modern desktop development. Or, if you are eager to start coding, you can jump right to the practice section where I show how to integrate these two technologies!

What is JavaFX and Why Choose It for Desktop Applications?

JavaFX is a set of graphics and media packages for creating rich client applications in pure Java. JavaFX-based applications consistently run in desktop, mobile, and embedded environments. They can also interact with server applications. 

JavaFX was first introduced in 2008 as part of the Java Development Kit. Starting with Java 11, it was separated from the JDK into an open-source OpenJFX project. So, in order to use it, you need to add necessary dependencies or install a JDK that bundles with JavaFX such as Liberica JDK.

But why should you use JavaFX for desktop development?

  • Pure Java. JavaFX is fully Java-based meaning that you can write familiar code and use solutions from the Java ecosystem.
  • Cross-platform. JavaFX applications run on Windows, Linux, and macOS. They can be embedded into a web page or other Java applications. Besides, they can be deployed to embedded systems, Raspberry PIs with a touchskreen, and ported to mobile devices. 
  • CSS and FXML. You can separate the frontend and backend thanks to CSS and FXML support and even use a separate tool called Scene Builder for creating UIs without coding.
  • Out-of-the-box controls. JavaFX provides many common controls to build a fully-featured desktop app.
  • Multitouch and multithreading. You can build highly responsive interactive applications as JavaFX supports multitouch operations.
  • Rich multimedia capabilities. JavaFX supports 2D shapes, images, gradients, animations, 3D rendering, and custom graphics with Canvas API. Besides, it supports playback of common audio formats and video playback with support for various codecs and container formats.

Thanks to these capabilities, solutions that you can build with JavaFX include:

  • Business dashboards;
  • HMI panels for industrial automation;
  • Hospital device frontends;
  • Interactive educational applications;
  • Smart home controllers;
  • Raspberry Pi interfaces.

Why Integrate JavaFX with Spring Boot: Key Benefits Explained

For all its might, JavaFX lacks modern dependency injection and bean lifecycle management. The business logic is built manually, which can make complex applications tangled and overcomplicated.

In comes Spring Boot! 

Spring Boot is tailored to creating production-ready applications in a fast and efficient way. It provides a robust Inversion of Control mechanism, a set of starter dependencies to reduce manual configuration, automatic configuration of third-party dependencies, and directly embeddable servers.

As a result, coupling JavaFX with Spring Boot will give you:

  • Dependency injection to keep controllers, services, and DAOs clean and testable;
  • Bean lifecycle management to centralize configuration and app logic;
  • Centralized event management system;
  • Spring-based data access model to implement familiar data management logic and reduce boilerplate code required for database connection.

Therefore, you can have the best of two worlds:

  • Beautiful and responsive UI with graphics and multimedia support from JavaFX;
  • Centralized configuration management, dependency injection, and support for Spring ecosystem components from Spring Boot.

Setting Up the Project: JavaFX + Spring Boot from Scratch 

The code for this tutorial is a simplified version of my pet project on GitHub. It is a work-in-progress educational application for learning the piano basics. The application is aimed at helping users learn and practice solfeggio concepts like chords, intervals, and scales through a virtual piano keyboard.

Don’t worry, the code snippets I’m going to show here are not specific to the music learning program and can be used as a foundation for any app.

Disclaimer: as JavaFX and Spring Boot are complex and flexible solutions, there’s no single approach to befriending them. What I’ll describe is one of the options tailored to scalability and sophisticated event management. 

Project Initialization

First thing first, let’s create a new project.

Go to Spring Initializr and create a bare-bone project based on the preferred Java version and build system. I’m using JDK 21 and Maven. Don’t add any dependencies just yet.

With JavaFX, there are two options: you can add the dependencies for required OpenJFX packages or download a JDK that bundles JavaFX, such as Liberica JDK Full. In the latter case, you don’t have to add FX dependencies manually, which can be plentiful. They will be available out-of-the-box!

You can also get Liberica JDK with JavaFX through SDKMAN:

21.0.7.fx-librca

Set Liberica JDK Full as the Project SDK.

Since we use Spring Boot, we have web support on the classpath. We don’t need it for JavaFX, so, disable the web server in the application.properties file:

spring.main.web-application-type=none

Now, the application is loaded with JavaFX and Spring Boot powers and is ready to use them for good!

Create Spring Boot and JavaFX Application Classes

You might be surprised, but we need two main classes in our application! The reason is that Spring Boot and JavaFX have their own startup cycle. Spring Boot has a main class annotated with @SpringBootApplication that starts the Spring ApplicationContext using SpringApplication.run(...) in the public static void main() method:

@SpringBootApplication
public class Main {

	public static void main(String[] args) {
		SpringApplication.run(Main.class, args);
	}
}

The ApplicationContext is responsible for spinning up all the backend logic: initializing beans, setting up services, repositories, and configurations. Therefore, Spring Boot expects a clean, standard main() method to bootstrap the entire context.

On the contrary, the JavaFX main class extends javafx.application.Application and is required to launch the JavaFX UI thread, which is also known as the JavaFX Application Thread. It’s where the GUI is initialized — in the start(Stage primaryStage) method. 

JavaFX expects Application.launch() to be called from a class that extends Application, and it must run on its own thread.

Consequently, we have to separate the main classes because

  • JavaFX needs to control the UI thread and lifecycle.
  • Spring Boot needs to initialize the backend context independently.
  • They can’t both “own” the main() method unless you coordinate them.

So, how do we make Spring Boot and JavaFX work together?

Let’s create a class called CoolFxApplication — because, why not? — and make it extend javafx.application.Application:

import javafx.application.Application;

public class CoolFxApplication extends Application { }

Next, we need to override several essential methods for starting and exiting the JavaFX application:

@Override
public void init() {
}

@Override
public void stop() {
}

@Override
public void start(Stage primaryStage) {
}

Next, add ConfigurableApplicationContext to the class fields:

private ConfigurableApplicationContext applicationContext;

We can now make the JavaFX Application class delegate to the Spring Boot context in the init() method, like this:

@Override
public void init() {
    applicationContext = new SpringApplicationBuilder(Main.class).run();
}

@Override
public void stop() {
    applicationContext.close();
}

Finally, call Application.launch(...) from the public static void main() method of our @SpringBootApplication-annotated Main class:

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        Application.launch(CoolFxApplication.class, args);
    }
}

This approach keeps Spring Boot and JavaFX responsibilities clean and working in sync. Our job in setting up the Spring Boot Main class is done, but we’re not finished with the main JavaFX class! The whole point of making a desktop app is in providing the user with a beautiful and responsive interface, and we don’t have one yet.

So, leave the CoolFxApplication class be for a while, we will continue configuring it after creating a user interface.

How to Design Modern JavaFX UI with Scene Builder

You can design and style a UI for a JavaFX application programmatically, with FXML, or CSS, and of course, you can combine all three.

Key JavaFX Components

JavaFX applications use the Scene Graph pattern for arranging a UI layout. In a Scene Graph, all components be it containers, figures, controls, or media objects are treated as nodes.

Nodes can have a parent and children. A node that doesn’t have a parent is a root node. All the other nodes can have one parent. But all nodes can have one, several, or no children.

The key components of JavaFX architecture:

  • The javafx.stage.Stage represents a window of a JavaFX application. When the application starts, it creates a root Stage object. It is passed to the start(Stage primaryStage) method of the main JavaFX class. The primaryStage is the primary window of the application.
  • The javafx.scene.Scene is placed onto the Stage to display the content;
  • Containers are the layout panes. There are several out-of-the-box containers for setting up common layouts such as rows, stacks, etc;
  • Controls such as buttons, text fields, check boxes, etc. support user interactions.

You can also use built-in Charts, Shapes, or draw a custom node with Canvas.

Rapid UI Development with Scene Builder

FXML is an XML-based language for designing a UI layout. It helps to separate the layout code from the application code and keep to the MVC (Model — View — Controller) pattern.

Good news is that you don’t need to write an FXML file yourself.You can download a Scene Builder application or install it as a plugin in IntelliJ IDEA and use its drag-and-drop interface to rapidly design a layout layer. If you have ever worked with Figma, the concept is familiar to you.

Scene Builder has a laconic and user-friendly interface. The tabs on the left contain UI components and display the hierarchy of nodes. The tabs on the right let you style the nodes and bind them to the code. 

Scene Builder UI

To learn more about working with Scene Builder, refer to the previous article.

After you save the changes, the FXML file will be generated automatically.

Of course, you are not limited to FXML and Scene Builder, especially if you need custom nodes. For instance, I usually use Scene Builder to draft a basic interface and then I style it in the CSS file and polish the functionality programmatically. If I need a component not provided out-of-the-box, I code it. This way, I reduce the time spent on designing a layout foundation and can focus on customizations.

Alright, let’s create a couple of simple UIs.

The first one is going to be a Login page where the user can sign in to their profile or sign in. We’ll keep it simple and use only username for login, but you can integrate Spring authentication and authorization capabilities and create a full fledged login form with a password.

Drag a Pane to the center of the screen. Then, drag two Labels to the top of the Pane and change their text in the properties tab to “Your Personal Assistant Greets You!” and “Sign in with username or sign up:”

Then, drag a TextField and drop it under the Labels. Finally, drag and drop two Buttons under the TextField and change their text properties to “Sign In” and “Sign Up.”

The most important part is to bind these components to the code. Click on the TextField, go to the Code tab and enter userName under fx:id.

For the Sign in button, enter signInButton under fx:id and loadUserAndOpenHomePage under onAction. This is going to be the name of the method in our code that handles the action performed with the button.

For the Sign up button, enter signUpButton under fx:id and saveUserAndOpenHomePage under onAction.

This is the resulting window:

Login Page in Scene Builder

Go to the Controller tab and enter org.java.coolfxdemo.controller.LoginController under controller class. We need to provide the reference path from Source Root. This is going to be the class that handles this particular window.

Save the file as login.fxml in the resources directory of your project.

Create a new file, drag and drop a Pane, then add a Label to it. Enter helloLabel under fx:id and org.java.coolfxdemo.controller.HomeController under controller class. Save the file as home.fxml to resources. 

Integrating FXML File with the Application Code

Let’s create two classes that we specified in the FXML files. Create a new directory controller and two classes in this directory, LoginController and HomeController. These classes can extend the Initializable interface that initializes a controller after its root element has been processed. Keep in mind that it is not a Spring Boot controller, so no @Controller annotation! Instead, we need a @Component annotation so that Spring Boot recognizes these classes as beans.

Add Button signInButton and Button signUpButton annotated with @FXML to the LoginController. In addition, create two methods, loadUserAndOpenHomePage() and saveUserAndOpenHomePage():

@Component
public class LoginController implements Initializable {

    @FXML
    private Button signInButton;

    @FXML
    private Button signUpButton;

    public void loadUserAndOpenHomePage() {    }


    public void saveUserAndOpenHomePage() {    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {    }

}

Perform similar actions with HomeController:

@Component
public class HomeController implements Initializable {

    @FXML
    private Label helloLabel;

    @Override
    public void initialize(URL location, ResourceBundle resources) {   }

}

We’ll add some meat to these classes later.

Cool, we have linked the FXML files with the application code. What we need now is to load the files, extract the root node, place it on the stage, and make the app do something useful Spring Boot style. In and out, 20 minutes adventure!

Let’s look briefly at UI styling before moving on.

UI Styling with CSS

The process of styling JavaFX nodes with CSS is similar to CSS styling applied to HTML DOMs. The CSS syntax is the same, but the properties have a bit different names prefixed with "-fx-". For instance, instead of the font-family JavaFX has -fx-font-family. Another example: the ":active" and ":focus" dynamic pseudo-classes are not supported. Instead, JavaFX supports ":pressed" and ":focused" pseudo-classes that have similar functionality. To learn more about JavaFX CSS styling, refer to JavaFX CSS Reference Guide.

The stylesheet is applied to the Scene or its root node with the getStyleSheets() method.

Styles can be applied to the root section to define global styles or to specific nodes. In the latter case, you can use the “.” selector to apply styles to the whole node type or the “#” selector to apply styles to a unique node with an id.

For instance, to set the universal font for the application, the text color for all Labels, and the background color for the signUpButton, we can do the following:

.root {
    -fx-font-family: Verdana;
}

.label {
    -fx-text-fill: #000000;
    -fx-wrap-text: true;
}

#signUpButton {
    -fx-background-color:  #DBFFCB;
}

Our ultra-minimalistic UI is ready, let’s move on to the most exciting part of intertwining Spring Boot and JavaFX!

Connecting JavaFX Frontend with Spring Boot Backend

Load the FXML File

Create a config directory. It will hold all our configuration classes.

Firstly, create an enum FxmlView, whose single purpose will be to return the link to the required FXML file. We currently have two files, so the code will look like this:

public enum FxmlView {

    LOGIN {

        @Override
        public String getFxmlPath() {
            return "/fxml/login.fxml";
        }
    },

    HOME {

        @Override
        public String getFxmlPath() {
            return "/fxml/home.fxml";
        }
    };

    public abstract String getFxmlPath();
}

Later on, you can conveniently add more pages as your application grows. 

Secondly, create the FxmlLoader class and annotate it with @Component. This class will be responsible for loading FXML resources:

@Component
public class FxmlLoader {

    private final ApplicationContext context;

    public FxmlLoader(ApplicationContext context) {
        this.context = context;
    }

    public Parent load(String fxmlPath) throws IOException {
        FXMLLoader loader = new FXMLLoader();
        loader.setControllerFactory(context::getBean);
        loader.setLocation(getClass().getResource(fxmlPath));
        return loader.load();
    }
}

As you can see, we make use of Spring Boot’s Dependency Injection to inject ApplicationContext. The class has a single method that receives a String with the path to the FXML file and loads the object hierarchy from it using a special FXMLLoader class of JavaFX. The loader also sets a controller factory for creating instances of Controller classes specified in the FXML. In our case, these controllers are Spring beans as we annotated them with @Component, so we ask the ApplicationContext to get a required bean.

Create the ApplicationConfig Class

Create the ApplicationConfig class annotated with @Configuration. Create the fxmlLoader and applicationTitle fields and inject these dependencies in the ApplicationConfig constructor. You can set the title for your application in the .properties file and use the @Value annotation for Spring to configure this property from the file:

@Configuration
public class ApplicationConfig {

    private final FxmlLoader fxmlLoader;
    private final String applicationTitle;

    public ApplicationConfig(FxmlLoader fxmlLoader,
                             @Value("${application.title}") String applicationTitle) {
        this.fxmlLoader = fxmlLoader;
        this.applicationTitle = applicationTitle;
    }
}

Now, watch closely: we need to create a very special bean that will load the parent nodes from the FXML files, set them on the Scene, show the Stage, and switch Scenes when necessary. Let’s call it StageManager.

This bean has to be instantiated in the ApplicationConfig class, but we must annotate it with @Lazy because the StageManager that receives a Stage in the constructor must be loaded after the Stage is created in the main application class:

@Bean
@Lazy
public StageManager stageManager(Stage stage) throws IOException {
    return new StageManager(fxmlLoader, stage, applicationTitle);
}

What are we waiting for? Let’s create this super important bean!

Create the StageManager Class to Manage Stage and All Scenes

Create the StageManager class and annotate it with @Component. Add the primaryStage, fxmlLoader, and applicationTitle fields and inject them in the constructor:

@Component
public class StageManager {

    private final Stage primaryStage;
    private final FxmlLoader fxmlLoader;
    private final String applicationTitle;

    public StageManager(FxmlLoader fxmlLoader,
                        Stage primaryStage,
                        String applicationTitle) {
        this.primaryStage = primaryStage;
        this.fxmlLoader = fxmlLoader;
        this.applicationTitle = applicationTitle;
    }
}

Now, let’s create a switchScene() method, which

  • Receives the FxmlView constant,
  • Adds the application title to the stage with primaryStage.setTitle(applicationTitle),
  • Loads the root node with Parent rootNode = loadRootNode(view.getFxmlPath()),
  • Creates a new Scene object and sets the parent node on the Scene with Scene scene = new Scene(rootNode),
  • Bootstraps the stylesheet,
  • Sets the scene on the Stage with primaryStage.setScene(scene), and
  • Shows the Stage with primaryStage.show().
public void switchScene(final FxmlView view) {

    primaryStage.setTitle(applicationTitle);

    Parent rootNode = loadRootNode(view.getFxmlPath());

    Scene scene = new Scene(rootNode);
    String stylesheet = Objects.requireNonNull(getClass()
                    .getResource("/styles/styles.css"))
            .toExternalForm();

    scene.getStylesheets().add(stylesheet);

    primaryStage.setScene(scene);
    primaryStage.show();
}

Let’s create a separate method for node loading:

private Parent loadRootNode(String fxmlPath) {
    Parent rootNode;
    try {
        rootNode = fxmlLoader.load(fxmlPath);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    return rootNode;
}

The switchScene() method will be called in the main JavaFX Application class when we first start the application. Afterwards, when we need to switch between Scenes, we will call another method that uses the Stage with all its settings from above and sets a new Scene onto it. We reuse the Stage object to avoid glitches when switching Scenes: 

public void switchToNextScene(final FxmlView view) {

    Parent rootNode = loadRootNode(view.getFxmlPath());
    primaryStage.getScene().setRoot(rootNode);

    primaryStage.show();
}

The complete code looks like this:

@Component
public class StageManager {

    private final Stage primaryStage;
    private final FxmlLoader fxmlLoader;
    private final String applicationTitle;

    public StageManager(FxmlLoader fxmlLoader,
                        Stage primaryStage,
                        String applicationTitle) {
        this.primaryStage = primaryStage;
        this.fxmlLoader = fxmlLoader;
        this.applicationTitle = applicationTitle;
    }

    public void switchScene(final FxmlView view) {

        primaryStage.setTitle(applicationTitle);

        Parent rootNode = loadRootNode(view.getFxmlPath());

        Scene scene = new Scene(rootNode);
        String stylesheet = Objects.requireNonNull(getClass()
                        .getResource("/styles/styles.css"))
                .toExternalForm();

        scene.getStylesheets().add(stylesheet);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public void switchToNextScene(final FxmlView view) {

        Parent rootNode = loadRootNode(view.getFxmlPath());
        primaryStage.getScene().setRoot(rootNode);

        primaryStage.show();
    }


    private Parent loadRootNode(String fxmlPath) {
        Parent rootNode;
        try {
            rootNode = fxmlLoader.load(fxmlPath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return rootNode;
    }
}

Use the StageManager Bean to Switch Between Scenes

The StageManager bean is ready, let’s add it as a field along with the Stage to the CoolFxApplication class. In the start() method, initialize the Stage object by linking it to the primaryStage created by JavaFX. In addition, initialize the StageManager bean by retrieving it from the ApplicationContext

public class CoolFxApplication extends Application {


    private static Stage stage;

    private ConfigurableApplicationContext applicationContext;
    private StageManager stageManager;

    @Override
    public void init() {
        applicationContext = new SpringApplicationBuilder(Main.class).run();
    }

    @Override
    public void stop() {
        applicationContext.close();
        stage.close();
    }

    @Override
    public void start(Stage primaryStage) {
        stage = primaryStage;
        stageManager = applicationContext.getBean(StageManager.class, primaryStage);
    }
}

The final touch: Create a method called showLoginScene() as the Login Page is the entry point to our app, and call the switchScene() method of the StageManager passing the required FxmlView constant, in this case, LOGIN:

@Override
public void start(Stage primaryStage) {
    stage = primaryStage;
    stageManager = applicationContext.getBean(StageManager.class, primaryStage);
    showLoginScene();
}

private void showLoginScene() {
    stageManager.switchScene(FxmlView.LOGIN);
}

From now on, every time we start the application, the primary Stage will be created, after that, the StageManager bean will be instantiated via ApplicationConfig, and then retrieved in the CoolFxApplication class through the ApplicationContext to handle the initial Scene setup. 

You can now inject the dependency on StageManager to Controllers to switch between Scenes when a specific event is triggered. For example, in the LoginController class, we have two buttons that handle the user login and open the home page. So, we can inject the StageManager in the constructor and call its switchToNextScene() method in the loadUserAndOpenHomePage() and saveUserAndOpenHomePage() methods. 

Note that we must annotate the LoginController constructor with @Lazy to load it after the relevant Scene object is created:

@Component
public class LoginController implements Initializable {

    @FXML
    private Button signInButton;

    @FXML
    private Button signUpButton;

    @FXML
    private TextField userName;

    private final StageManager stageManager;

    @Lazy
    public LoginController(StageManager stageManager) {
        this.stageManager = stageManager;
    }

    public void loadUserAndOpenHomePage() {
        stageManager.switchToNextScene(FxmlView.HOME);
    }

    public void saveUserAndOpenHomePage() {
        stageManager.switchToNextScene(FxmlView.HOME);
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {}

You can now run the application with the ./mvnw spring-boot:run command and make sure that it starts successfully and switches to the next scene when you click the buttons. 

Building Backend with Spring Boot: Event Handling, and Database Connection

We successfully launched the application, but right now, it doesn’t do anything useful except for switching between scenes. Let’s fix that and add the database connection and event handling capabilities.

Integrate Spring Data JPA

You can set up a database connection with JavaFX even if you don’t use any framework, but in this case, you will have to do it pure Java style: establish the connection, write and execute the query, and so on.

However, as we use Spring Boot, we can delegate all these tasks to Spring Data!

I went with Spring Data JPA as it is the most optimal for my app’s architecture and purposes.

You know the drill from now on.

Add the JPA dependency to pom.xml:

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

Create a User class annotated with @Entity:

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "user_name")
    private String userName;

//constructors, getters, setters, equals(), and hashCode()

}

Create a UserRepository class that extends JpaRepository:

public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByUserName(String userName);
}

Create a UserService class annotated with @Service that works with data:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public Optional<User> findByUsername(String username) {
        return userRepository.findByUserName(username);
    }

    public void saveUser(String username) {
        User newUser = new User();
        newUser.setUserName(username);
        userRepository.save(newUser);
    }

}

Nothing fancy, only Spring as we love it!

Set Up SQLite 

Embedded databases are a good match for desktop applications as you want to store user’s data locally. I have chosen SQLite because it is a perfect match for desktop apps and embedded devices as it provides fast, efficient, reliable data management without the need for administration.

Bootstrapping SQLite is fairly easy. You need to add two dependencies to the pom.xml. The first one is SQLite itself:

<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.49.1.0</version>
</dependency>

And the second one is Hibernate. As of Hibernate 6, SQLite dialect is supported, which is great because we don’t have to write the dialect ourselves:

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-community-dialects</artifactId>
</dependency>

In the application.properties file, configure the database connection properties:

spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
driverClassName=org.sqlite.JDBC
url=jdbc:sqlite:data/assistant.db
username=sa
password=sa

spring.sql.init.mode=always
spring.jpa.hibernate.ddl-auto=update
spring.jpa.defer-datasource-initialization=false

Finally, as Spring Boot doesn’t support SQLite configuration out-of-the-box, we have to do it manually. Create the DataSourceConfig class annotated with @Configuration that loads a DataSource bean with all required configs: 

@Configuration
public class DataSourceConfig {

    @Autowired
    Environment env;

    @Bean
    public DataSource getDataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(Objects.requireNonNull(env.getProperty("driverClassName")));
        dataSource.setUrl(env.getProperty("url"));
        dataSource.setUsername(env.getProperty("username"));
        dataSource.setPassword(env.getProperty("password"));
        return dataSource;
    }
}

That’s it! We integrated SQLite into our application, and now we can perform any CRUD operation. Let’s add the logic for saving and loading a user to our LoginController (don’t forget to add the dependency on UserService).

Note that we are throwing a RuntimeException for simplicity sake. For real-world application, you will require more sensible error handling. 

@Component
public class LoginController implements Initializable {

    @FXML
    private Button signInButton;

    @FXML
    private Button signUpButton;

    @FXML
    private TextField userName;

    private final StageManager stageManager;

    private final UserService userService;


    @Lazy
    public LoginController(StageManager stageManager, UserService userService) {
        this.stageManager = stageManager;
        this.userService = userService;
    }


    public void loadUserAndOpenHomePage() {
        Optional<User> user = userService.findByUsername(userName.getText());
        if (user.isEmpty()) {
            throw new RuntimeException();
        }
        stageManager.switchToNextScene(FxmlView.HOME);

    }


    public void saveUserAndOpenHomePage() {

        String name = userName.getText();

        Optional<User> user = userService.findByUsername(name);
        if (user.isPresent() || name.length() > 20 || name.isBlank()) {
            throw new RuntimeException();
        }
        userService.saveUser(name);
        stageManager.switchToNextScene(FxmlView.HOME);

    }
}

Run the application. It must save a new user and load the existing one. You can add custom exceptions to notify the user in case of incorrect input. Explore the project for examples.

Use ApplicationEvent to Manage Events

You can set up event handling by means of JavaFX or Spring features. In this tutorial, I’ll show you how to handle events Spring-style, but you can, of course, go with JavaFX capabilities. 

Our application is supposed to load custom user data after the user has signed in. User login qualifies as an event that needs to be handled.

Let’s settle with a simple example for brevity's sake and instead of loading user settings, we will make the app greet the user on the home page by their username. It means that HomeController must receive the username from LoginController. Events can serve as data carriers between application classes.

To add event handling support, we must modify our ApplicationConfig and StageManager.

Add the dependency on org.springframework.context.ApplicationEventPublisher to ApplicationConfig and add it to the StageManager constructor. This ApplicationEventPublisher will be our centralized event handler. It will publish events, and other classes can listen to required events.

@Configuration
public class ApplicationConfig {

    private final FxmlLoader fxmlLoader;
    private final String applicationTitle;
    private final ApplicationEventPublisher eventPublisher;

    public ApplicationConfig(FxmlLoader fxmlLoader,
                             @Value("${application.title}") String applicationTitle, ApplicationEventPublisher eventPublisher) {
        this.fxmlLoader = fxmlLoader;
        this.applicationTitle = applicationTitle;
        this.eventPublisher = eventPublisher;
    }

    @Bean
    @Lazy
    public StageManager stageManager(Stage stage) throws IOException {
        return new StageManager(fxmlLoader, stage, applicationTitle, eventPublisher);
    }
}

@Component
public class StageManager {

    private final Stage primaryStage;
    private final FxmlLoader fxmlLoader;
    private final String applicationTitle;
    private final ApplicationEventPublisher eventPublisher;

    public StageManager(FxmlLoader fxmlLoader,
                        Stage primaryStage,
                        String applicationTitle,
                        ApplicationEventPublisher eventPublisher) {
        this.primaryStage = primaryStage;
        this.fxmlLoader = fxmlLoader;
        this.applicationTitle = applicationTitle;
        this.eventPublisher = eventPublisher;
    }

Next, create a LoginEvent class that extends ApplicationEvent. It must receive the username in its constructor, so that when the ApplicationEventPublisher creates this type of event, it will immediately become the carrier of the data we are interested in.

public class LoginEvent extends ApplicationEvent {

    private String userName;

    public LoginEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }

}

The next step is to add the dependency on ApplicationEventPublisher to LoginController and attach the ChangeListener provided by JavaFX to the username value in the initialize() method. The ChangeListener is called whenever the value of the ObservedValue changes.

We can use a Lambda function to add event handling logic to the changed() method of the ChangeListener. In this case, we are going to call the ApplicationEventPublisher to publish the LoginEvent.

@Component
public class LoginController implements Initializable {

    @FXML
    private Button signInButton;

    @FXML
    private Button signUpButton;

    @FXML
    private TextField userName;

    private final StageManager stageManager;

    private final UserService userService;

    private final ApplicationEventPublisher eventPublisher;

    @Lazy
    public LoginController(StageManager stageManager, UserService userService, ApplicationEventPublisher eventPublisher) {
        this.stageManager = stageManager;
        this.userService = userService;
        this.eventPublisher = eventPublisher;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        userName.textProperty().addListener(new ChangeListener<>() {
            @Override
            public void changed(ObservableValue<? extends String> observable,
                                String oldText,
                                String newText) {
                eventPublisher.publishEvent(new LoginEvent(this, newText));
            }
        });

    }
}

The last thing we have to do is to add the method annotated with @EventListener to HomeController. It will listen to LoginEvent and dynamically set the value of Label. 

To change Label text dynamically, we must bind a StringProperty to it in the initialize() method. From now on, we can change the value of the StringProperty, which will be reflected in the Label value.

The code looks like this:

@Component
public class HomeController implements Initializable {

    @FXML
    private Label helloLabel;

    private final StageManager stageManager;

    StringProperty nameProperty = new SimpleStringProperty();

    @Lazy
    public HomeController(StageManager stageManager) {
        this.stageManager = stageManager;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        helloLabel.textProperty().bind(nameProperty);

    }

    @EventListener
    public void handleLoginEvent(LoginEvent event) {
        nameProperty.setValue("Hello, " + event.getUserName() + "!");
    }

}

Run the application. It should greet you by the name after you have logged in.

Conclusion and Further Learning

We explored how to combine the power of JavaFX and Spring Boot to build modern desktop applications. You can explore the full code on GitHub. We walked through integrating both technologies in a single project, using FXML and Scene Builder to create clean and modular UIs, styling with CSS, embedding SQLite for lightweight local storage, and leveraging Spring’s event system to organize application logic.

This stack allows you to reuse your enterprise Java skills in desktop development and create interactive apps with a modern UI.

Further learning sources:

As for me, I will continue working on my Solfeggio Trainer and publish more articles on JavaFX and Spring Boot development. The next article will be about approaches to packaging and deploying JavaFX and Spring Boot applications. Subscribe to our newsletter so as not to miss these goodies!

FAQ

Frequently Asked Questions

What are the alternatives to JavaFX?

Some alternatives to JavaFX are Swing (older Java UI), SWT used in Eclipse, JavaScript-based Electron, and Qt with C++ or Python bindings.

Is JavaFX compatible with all Java libraries?

Yes, JavaFX can work with any standard Java library.

How difficult is it for a beginner to learn JavaFX and Spring Boot?

JavaFX is beginner-friendly for UI development, and Spring Boot simplifies backend logic. Therefore, learning both is not too challenging with the right tutorials.

Which IDEs are best suited for desktop application development?

You can use any IDE for desktop development. IntelliJ IDEA is a great choice because it has excellent JavaFX support, Scene Builder plugin, and Spring Boot integration out of the box.

When to choose Electron or JavaFX?

Choose Electron for building cross-platform apps with web tech. Pick JavaFX for Java-native apps that need strong performance, lower memory use, or deep Java backend integration.

Subcribe to our newsletter

figure

Read the industry news, receive solutions to your problems, and find the ways to save money.

Further reading