FxmlKit = Automatic FXML Loading + Hot Reload + Optional Dependency Injection
// Enable FXML/CSS hot reload during development
FxmlKit.enableDevelopmentMode();
// Zero configuration - automatic FXML loading
new MainView();
// With dependency injection
new MainView(diAdapter);A modern JavaFX FXML framework that eliminates boilerplate code, provides FXML/CSS hot reload, and optional progressive dependency injection support.
- Why FxmlKit
- Core Features
- Acknowledgments
- Quick Start
- Usage
- Hot Reload
- Core Concepts
- Annotations
- FAQ
- Sample Projects
With traditional JavaFX development, every FXML or CSS change requires restarting the application to see the effect. This slows down UI development significantly.
FxmlKit Solution:
public class MyApp extends Application {
@Override
public void start(Stage stage) {
FxmlKit.enableDevelopmentMode(); // ✅ One line to enable hot reload
stage.setScene(new Scene(new MainView()));
stage.show();
}
}Now edit your .fxml or .css files, save, and see changes instantly — no restart required!
Every view requires repetitive code: getting URL, configuring FXMLLoader, handling exceptions, loading stylesheets...
FxmlKit Solution:
// ✅ One line of code, everything handled automatically
public class LoginView extends FxmlView<LoginController> {
}Automatically handles FXML parsing, stylesheet attachment, controller creation, and exception handling.
In traditional approaches, custom components declared in FXML are directly instantiated by FXMLLoader and cannot access the DI container.
FxmlKit Solution:
Controller auto-injection:
public class LoginController {
@Inject private UserService userService;
@PostInject // Automatically invoked
private void afterInject() {
// Dependencies are ready
}
}FXML nodes can also receive auto-injection:
@FxmlObject // One annotation to enable injection
public class StatusCard extends VBox {
@Inject private StatusService statusService;
@PostInject
private void afterInject() {
updateStatus();
}
}FXML usage:
<VBox>
<StatusCard /> <!-- Automatic dependency injection -->
</VBox>Key point: Dependency injection is optional! If you don't need DI, FxmlKit still eliminates FXML loading boilerplate.
- Zero Configuration — Works out of the box, no setup required
- Convention over Configuration — Automatically discovers FXML and stylesheet files
- Hot Reload — FXML and CSS changes reflect instantly during development
- Optional Dependency Injection — Works without DI frameworks, add them when needed
- Automatic Stylesheets — Auto-attaches
.bssand.cssfiles - Nested FXML — Full support for
<fx:include>hierarchies - JPro Ready — Supports multi-user web application data isolation (independent DI container per user session for data security)
- High Performance — Intelligent caching and performance optimization
Comparison with Native JavaFX:
| Feature | Native JavaFX | FxmlKit |
|---|---|---|
| Hot Reload (FXML + CSS) | ❌ Restart required | ✅ Instant refresh |
| User Agent Stylesheet Hot Reload | ❌ None | ✅ All levels (App/Scene/SubScene/Custom Control) |
| Automatic FXML Loading | ❌ Manual loading code | ✅ Zero-config auto-loading |
| Automatic Stylesheet Attachment | ❌ Manual code required | ✅ Auto-attach (including nested FXML) |
| Controller Dependency Injection | ✅ Automatic injection | |
| FXML Node Injection | ❌ Nearly impossible | ✅ @FxmlObject - one line |
| Multi-layer fx:include Support | ✅ Full support (with injection & styles) | |
| @PostInject Lifecycle | ❌ None | ✅ Supported |
| JPro Multi-user Isolation | ❌ Manual implementation | ✅ Native support |
- afterburner.fx — Inspired our convention-over-configuration approach (auto-resolving FXML/CSS by class name). We extended this with FXML node injection, multi-layer nesting, and JPro multi-user isolation.
- CSSFX — Inspired our CSS hot reload approach (file:// URI replacement). Our implementation features shared WatchService, debounced refresh, and WeakReference-based cleanup.
Maven:
<dependency>
<groupId>com.dlsc.fxmlkit</groupId>
<artifactId>fxmlkit</artifactId>
<version>1.0.0</version>
</dependency>Gradle:
implementation 'com.dlsc.fxmlkit:fxmlkit:1.0.0'If you need to use Guice for dependency injection: Directly depend on fxmlkit-guice (includes core module)
Maven:
<dependency>
<groupId>com.dlsc.fxmlkit</groupId>
<artifactId>fxmlkit-guice</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>7.0.0</version>
</dependency>Gradle:
implementation 'com.dlsc.fxmlkit:fxmlkit-guice:1.0.0'
implementation 'com.google.inject:guice:7.0.0'If you need to use other DI frameworks: Continue using the core fxmlkit module and implement the DiAdapter interface or extend the BaseDiAdapter class. Similarly, even when using Guice, you can choose not to depend on the fxmlkit-guice module and implement a GuiceDiAdapter yourself (refer to fxmlkit-guice source code - the implementation is very simple).
1. Create FXML File
src/main/resources/com/example/HelloView.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<VBox xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.HelloController"
spacing="10" alignment="CENTER">
<Label fx:id="messageLabel" text="Hello, FxmlKit!"/>
<Button text="Click Me" onAction="#handleClick"/>
</VBox>2. Create Controller
src/main/java/com/example/HelloController.java:
package com.example;
import javafx.fxml.FXML;
import javafx.scene.control.*;
public class HelloController {
@FXML private Label messageLabel;
@FXML
private void handleClick() {
messageLabel.setText("Hello from FxmlKit!");
}
}3. Create View
src/main/java/com/example/HelloView.java:
package com.example;
import com.dlsc.fxmlkit.fxml.FxmlView;
public class HelloView extends FxmlView<HelloController> {
// That's it!
}4. Use View
public class HelloApp extends Application {
@Override
public void start(Stage stage) {
stage.setScene(new Scene(new HelloView()));
stage.setTitle("FxmlKit Demo");
stage.show();
}
}Optional: Add Stylesheet
Create src/main/resources/com/example/HelloView.css, and FxmlKit will automatically attach it!
FxmlKit supports three usage patterns - choose based on your needs:
Use Case: Learning JavaFX, quick prototyping, simple applications
public class MainView extends FxmlView<MainController> {
}
// Usage
stage.setScene(new Scene(new MainView()));Features:
- ✅ Automatic FXML loading
- ✅ Automatic stylesheet attachment
- ✅ Automatic controller creation
- ❌ No dependency injection
Use Case: Desktop applications that need dependency injection
// Application startup - set global DI adapter
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(UserService.class).toInstance(new UserService());
bind(ConfigService.class).toInstance(new ConfigService());
}
});
FxmlKit.setDiAdapter(new GuiceDiAdapter(injector));
// Create view - controller and nodes automatically receive injection
public class LoginView extends FxmlView<LoginController> {
}
// Usage - same as zero configuration mode
LoginView view = new LoginView();Features:
- ✅ Global DI configuration - set once, use everywhere
- ✅ Automatic controller injection
- ✅ Automatic FXML node injection (with
@FxmlObject) - ✅ Supports multiple DI frameworks (Guice, Spring, CDI, etc.)
Use Case: JPro web applications where each user needs isolated data
// Create independent DI container for each user session
Injector userInjector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(UserSession.class).toInstance(new UserSession(userId));
bind(UserService.class).toInstance(new UserService());
}
});
// Pass DI adapter when creating view
LoginView view = new LoginView(new GuiceDiAdapter(userInjector));Use Cases:
- JPro web applications (one DI container per user session)
- Desktop applications (one DI container per Tab/Window)
- Scenarios requiring strict data isolation
FxmlKit provides built-in hot reload for rapid UI development. Edit your FXML or CSS files and see changes instantly without restarting.
public class MyApp extends Application {
@Override
public void start(Stage stage) {
// Enable hot reload (call BEFORE creating views)
FxmlKit.enableDevelopmentMode();
stage.setScene(new Scene(new MainView()));
stage.show();
}
}Important: Call enableDevelopmentMode() before creating any views. Views created before enabling won't be monitored.
| File Type | Behavior | Runtime State |
|---|---|---|
.fxml |
Full view reload | Lost (user input, scroll position) |
.css / .bss |
Stylesheet refresh only | Preserved |
Monitored Stylesheets:
- Normal stylesheets (
scene.getStylesheets(),parent.getStylesheets()) - User Agent Stylesheets (Application, Scene, SubScene levels)
- Custom control stylesheets (
Region.getUserAgentStylesheet()overrides)
For Scene and SubScene level User Agent Stylesheets, hot reload works automatically with native JavaFX API:
scene.setUserAgentStylesheet("/styles/theme.css"); // Auto-monitoredFor Application level, use FxmlKit's bridged property to enable hot reload:
// ✅ Supports hot reload + property binding
FxmlKit.setApplicationUserAgentStylesheet("/styles/dark-theme.css");
// Or bind to a theme selector
FxmlKit.applicationUserAgentStylesheetProperty()
.bind(themeComboBox.valueProperty());Note: Using Application.setUserAgentStylesheet() directly still works, but won't trigger hot reload.
FxmlKit automatically supports hot reload for custom controls that override getUserAgentStylesheet():
public class VersionLabel extends Label {
@Override
public String getUserAgentStylesheet() {
return VersionLabel.class.getResource("version-label.css").toExternalForm();
}
}How it works: During development mode, FxmlKit automatically detects custom controls with overridden getUserAgentStylesheet() and promotes the stylesheet to getStylesheets().add(0, ...). This enables hot reload monitoring while preserving the intended low-priority behavior (index 0 = lowest priority among author stylesheets).
Priority note: This promotion slightly changes CSS specificity semantics — the stylesheet becomes an "author stylesheet" instead of a true "user agent stylesheet". In practice, this rarely causes issues since:
- The stylesheet is added at index 0 (lowest priority)
- This only affects development mode
- Production builds use the native UA stylesheet mechanism
If you encounter styling conflicts during development, you have two options:
- Increase selector specificity in your custom control's CSS
- Temporarily remove the
getUserAgentStylesheet()override and add the stylesheet togetStylesheets()instead. Once development is complete, reverse this change to restore the proper UA stylesheet behavior.
// Enable only FXML hot reload
FxmlKit.setFxmlHotReloadEnabled(true);
// Enable only CSS hot reload
FxmlKit.setCssHotReloadEnabled(true);
// Enable both (equivalent to enableDevelopmentMode())
FxmlKit.setFxmlHotReloadEnabled(true);
FxmlKit.setCssHotReloadEnabled(true);
// Disable all
FxmlKit.disableDevelopmentMode();If you prefer CSSFX for CSS hot reload, disable FxmlKit's built-in CSS monitoring:
// Use FxmlKit for FXML hot reload only
FxmlKit.setFxmlHotReloadEnabled(true);
FxmlKit.setCssHotReloadEnabled(false);
// Use CSSFX for CSS
CSSFX.start();Hot reload is for development only. Do not enable in production environments.
Option 1: Simply comment out the line before release
public class MyApp extends Application {
@Override
public void start(Stage stage) {
// FxmlKit.enableDevelopmentMode(); // Comment out for production
stage.setScene(new Scene(new MainView()));
stage.show();
}
}Option 2: Use JVM argument for automatic switching
public class MyApp extends Application {
// Set via JVM argument: -Ddev.mode=true
private static final boolean DEV_MODE = Boolean.getBoolean("dev.mode");
@Override
public void start(Stage stage) {
if (DEV_MODE) {
FxmlKit.enableDevelopmentMode();
}
stage.setScene(new Scene(new MainView()));
stage.show();
}
}Run in development: java -Ddev.mode=true -jar myapp.jar
Run in production: java -jar myapp.jar
FxmlKit uses convention over configuration to automatically find FXML and stylesheet files:
src/main/resources/com/example/
├── UserView.fxml ← UserView.java auto-matches
├── UserView.css ← Auto-attached
└── UserView.bss ← Binary stylesheet (priority)
Convention: FXML file has the same name as Java class and is in the same resource directory.
| Feature | FxmlView | FxmlViewProvider |
|---|---|---|
| Type | IS-A Node (extends StackPane) | HAS-A Node (holds Parent) |
| Loading | Eager (loads immediately) | Lazy (loads on first getView()) |
| Usage | Direct use as Node | Call getView() to get Node |
| Use Case | Used directly as a node | Lazy loading to save resources |
FxmlView Example:
public class LoginView extends FxmlView<LoginController> {
}
// Usage - direct use as a Node
LoginView view = new LoginView(); // FXML loaded immediately
scene.setRoot(view); // view itself is a StackPaneFxmlViewProvider Example:
public class MainViewProvider extends FxmlViewProvider<MainController> {
}
// Usage - need to call getView()
MainViewProvider provider = new MainViewProvider(); // FXML not loaded yet
Parent view = provider.getView(); // FXML loaded here
scene.setRoot(view);Mark custom components with @FxmlObject to enable dependency injection:
@FxmlObject
public class StatusCard extends VBox {
@Inject private StatusService statusService;
@PostInject
private void afterInject() {
// statusService is ready
updateStatus();
}
}Use in FXML:
<VBox>
<StatusCard /> <!-- Automatic injection -->
</VBox>FxmlKit defaults to EXPLICIT_ONLY strategy (only injects objects marked with @FxmlObject).
Purpose: Specify FXML file location, overriding default auto-resolution rules.
Use Case: Typically not needed, as FxmlKit auto-resolves. Only use when FXML file is not in default location.
@FxmlPath("/shared/Common.fxml")
public class LoginView extends FxmlView<LoginController> {}Purpose: Mark a class to enable dependency injection when created in FXML.
Supported Object Types:
- Custom JavaFX controls (Button, TextField subclasses, etc.)
- Layout containers (Pane, HBox, VBox subclasses, etc.)
- Non-visual objects (MenuItem, ContextMenu, etc.)
- Any custom class declared in FXML
Example - Custom Control:
@FxmlObject
public class UserAvatar extends Circle {
@Inject private UserService userService;
@PostInject
private void afterInject() {
loadUserImage();
}
}Example - Non-Visual Object:
@FxmlObject
public class CustomMenuItem extends MenuItem {
@Inject private ActionService actionService;
@PostInject
private void afterInject() {
setOnAction(e -> actionService.execute());
}
}FXML usage:
<MenuBar>
<Menu text="Actions">
<CustomMenuItem text="Execute"/> <!-- Also receives injection -->
</Menu>
</MenuBar>Note:
- Custom objects without
@FxmlObjectwon't receive dependency injection (unless injection strategy is set toAUTO) - If using
AUTOstrategy but want to exclude certain types, useFxmlKit.excludeNodeType()or add@SkipInjectionannotation on the class
Purpose: Mark a method to be automatically invoked after all @Inject field injections are complete.
Use Case: Initialize components, load data, or set up listeners after dependencies are ready.
Execution Timing: Immediately after all @Inject field injections are complete.
Example:
public class UserProfileController {
@Inject private UserService userService;
@Inject private ConfigService configService;
@PostInject
private void afterInject() {
// All @Inject fields are now ready
userService.loadUserData();
configService.applySettings();
}
}Method Requirements:
- Must be no-arg
- Can have any access modifier (private, protected, public)
- Supports inheritance (parent class
@PostInjectmethods execute first)
A: No! FxmlKit works perfectly without DI frameworks. You can use just FxmlView for automatic FXML loading and stylesheet attachment. Add DI only when you need it.
A: LiteDiAdapter is a simple DI container for small projects and learning scenarios.
Dependency Required:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>Usage:
import javax.inject.Inject;
LiteDiAdapter di = new LiteDiAdapter();
di.bindInstance(UserService.class, new UserService());
FxmlKit.setDiAdapter(di);When to use: Small projects, learning, prototyping
When to upgrade: For larger projects or advanced features, use Guice or other mature DI frameworks
Reason: Zero-configuration mode (Method 1) doesn't support dependency injection.
Solution: Configure a DiAdapter:
FxmlKit.setDiAdapter(diAdapter);A: After dependency injection completes. The timing differs between controllers and nodes:
Execution order: Constructor → @Inject → @FXML → initialize() → @PostInject
Usually not needed - use initialize() instead:
public class LoginController implements Initializable {
@Inject private UserService userService; // ① Injected
@FXML private Button loginButton; // ② Injected by JavaFX
@Override
public void initialize(URL location, ResourceBundle resources) {
// ③ Both @Inject and @FXML available here
loginButton.setOnAction(e -> userService.login());
}
@PostInject
private void afterInject() {
// ④ Called after initialize() - usually not needed for controllers
userService.loadSettings(); // Example: if you need it
}
}Execution order: Constructor → @Inject → @PostInject
Required if you need injected dependencies:
@FxmlObject
public class StatusLabel extends Label {
@Inject private StatusService statusService;
public StatusLabel() {
// ① Constructor - statusService is null here ❌
}
@PostInject
private void afterInject() {
// ② Injected - statusService available now ✅
setText(statusService.getStatus());
}
}Rule: Use @PostInject for initialization requiring @Inject dependencies in @FxmlObject nodes.
A: Check these:
- File naming: Must match class name -
LoginView.java→LoginView.css - File location: Must be in same package resource directory
- Auto-attach enabled:
FxmlKit.setAutoAttachStyles(true)(default is true) - CSS priority:
.bssfiles have higher priority than.css
A: Check these:
- Call order:
enableDevelopmentMode()must be called before creating views - File location: Source files must be in
src/main/resources(Maven/Gradle standard) - IDE auto-build: Enable automatic build in your IDE for seamless hot reload
- Debug logging: Set
FxmlKit.setLogLevel(Level.FINE)to see hot reload messages
A: FxmlKit is JPro-ready. Create an independent DI container for each user session:
public class JProApp extends Application {
@Override
public void start(Stage stage) {
// Create independent DI container per user
Injector userInjector = createUserInjector();
// Pass DI adapter when creating view
MainView view = new MainView(new GuiceDiAdapter(userInjector));
Scene scene = new Scene(view);
stage.setScene(scene);
}
}See fxmlkit-samples module's tier3.multiuser package for a complete implementation simulating JPro multi-user scenarios (using TabPane to simulate multi-user sessions).
Complete sample code is in the fxmlkit-samples module, organized into three tiers by complexity:
tier1/
├── hello/ # Simplest Hello World
├── i18n/ # Internationalization example
├── provider/ # FxmlViewProvider usage examples
└── viewpath/ # Custom FXML path
└── theme/ # User Agent Stylesheet hot reload (Application level + Custom Control)
tier2/
├── fxmlobject/ # @FxmlObject node injection examples
├── guice/ # Guice integration examples
└── login/ # Complete login application example
tier3.multiuser/ # Simulates JPro multi-user scenario (using TabPane)