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!
Table of Contents
- Introduction
- What is JavaFX and Why Choose It for Desktop Applications?
- Why Integrate JavaFX with Spring Boot: Key Benefits Explained
- Setting Up the Project: JavaFX + Spring Boot from Scratch
- How to Design Modern JavaFX UI with Scene Builder
- Connecting JavaFX Frontend with Spring Boot Backend
- Building Backend with Spring Boot: Event Handling, and Database Connection
- Conclusion and Further Learning
- FAQ
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:
- JavaFX Documentation
- JavaFX CSS Reference Guide
- Spring Boot Reference Guide
- Spring Events Documentation
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.