JavaFX · CDI · MVC-B · ReactFX

Context & Dependency Injection
for JavaFX

ContextFX is a minimalist dependency injection and context management library for JavaFX applications, designed specifically for multiplatform environments (Web/WASM, Mobile, Desktop) with native ReactFX support.

Zero dependencies ReactFX native GluonFX WASM / Mobile Apache 2.0
Feature ContextFX SupernautFX Gluon Ignite
External dependencies None Micronaut Spring/CDI
ReactFX support ✅ Native ❌ No ❌ No
Binary size Minimal High Medium/High
GluonFX (WASM/Mobile) ✅ Optimized ⚠️ Limited ⚠️ Configuration
Ease of use ✅ Simple Complex Requires framework
Feature Classic Mode Reactive Mode
Requirements GluonFX GluonFX + ReactFX
Main class ContextApplication ReactiveContextApplication
Manager Manager ReactiveManager
Ideal use Traditional applications Reactive applications
GraalVM / GluonFX mobile ✅ via ContextLoader ✅ via ContextLoader
<!-- SampleView.fxml -->
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1"
            prefHeight="400.0"
            prefWidth="600.0"
            xmlns="http://javafx.com/javafx/17"
            fx:controller="com.myapp.views.SampleView">
    <center>
        <VBox alignment="CENTER" spacing="5.0">
            <Label fx:id="greet" />
            <Button fx:id="redirect" />
        </VBox>
    </center>
</BorderPane>
// 1. Initialization
public class MyApp extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        ContextApplication context = new ContextApplication(MyApp.class);
        stage.setTitle("MyApp");
        stage.setHeight(600);
        stage.setWidth(800);
        Manager.getManager(context.getContainer().getComponents()).init(stage, MyApp.class);
    }

}
// 1. ContextLoader
public class MyContextLoader extends ContextLoader {

    public MyContextLoader() {
        super(List.of(
            GreetService.class,
            SampleController.class,
            SampleView.class
        ));
    }

}

// 2. Initialization
public class MyApp extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        ContextApplication context = new ContextApplication(new MyContextLoader());
        stage.setTitle("MyApp");
        stage.setHeight(600);
        stage.setWidth(800);
        Manager.getManager(context.getContainer().getComponents()).init(stage, MyApp.class);
    }

}
// Service — business logic
@Service
public class GreetService {

    public String greet() {
        return "Hello World from JavaFX with ContextFX";
    }

}

// Controller — coordination
@Controller
public class SampleController {

    @Injectable
    private GreetService service;

    public String greet() {
        return service.greet();
    }

    public void redirect() {
        Manager.getManager().switchView("OtherView");
    }

}

// View — UI binding
@View(main = true)
public class SampleView implements Initializable {

    @FXML
    private Label greet;

    @FXML
    private Button redirect;

    private SampleController controller;

    public SampleView() {
        controller = (SampleController) Container.getComponent(SampleController.class);
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        greet.setText(controller.greet());
        redirect.setOnAction(e -> controller.redirect());
    }

}
<!-- CounterView.fxml -->
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1"
            prefHeight="400.0"
            prefWidth="600.0"
            xmlns="http://javafx.com/javafx/17"
            fx:controller="com.myapp.views.CounterView">
    <center>
        <VBox alignment="CENTER" spacing="5.0">
            <Label fx:id="message" />
            <HBox alignment="CENTER" spacing="5.0">
                <Button fx:id="count" />
                <Button fx:id="clean" />
            </HBox>
        </VBox>
    </center>
</BorderPane>
// 1. Initialization
public class MyApp extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        ReactiveContextApplication context = new ReactiveContextApplication(MyApp.class);
        stage.setTitle("MyApp");
        stage.setHeight(600);
        stage.setWidth(800);
        ReactiveManager.getReactiveManager(context.getReactiveContainer().getComponents()).init(stage, MyApp.class);
    }

}
// 1. ContextLoader
public class MyContextLoader extends ContextLoader {

    public MyContextLoader() {
        super(List.of(
            CounterModel.class,
            CounterService.class,
            CounterController.class,
            CounterView.class
        ));
    }

}

// 2. Initialization
public class MyApp extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        ReactiveContextApplication context = new ReactiveContextApplication(new MyContextLoader());
        stage.setTitle("MyApp");
        stage.setHeight(600);
        stage.setWidth(800);
        ReactiveManager.getReactiveManager(context.getReactiveContainer().getComponents()).init(stage, MyApp.class);
    }

}
// Model — reactive state
@ReactiveModel
public class CounterModel {

    private Var<Integer> count;

    public CounterModel() {
        count = Var.newSimpleVar(0);
    }

    public void setCount(Integer count) {
        this.count.setValue(count);
    }

    public Val<Integer> getCount() {
        return count;
    }

}

// Service — business logic
@ReactiveService
public class CounterService {

    @ReactiveInjectable
    private CounterModel model;

    public void count() {
        model.setCount(model.getCount().getValue() + 1);
    }

    public void clean() {
        model.setCount(0);
    }

}

// Controller — coordination
@ReactiveController
public class CounterController {

    @ReactiveInjectable
    private CounterService service;

    public void count() {
        service.count();
    }

    public void clean() {
        service.clean();
    }

}

// View — UI binding
@ReactiveView(main = true)
public class CounterView implements Initializable {

    @FXML
    private Label message;

    @FXML
    private Button count;

    @FXML
    private Button clean;

    private CounterController controller;
    private CounterModel model;

    public CounterView() {
        model = (CounterModel) ReactiveContainer.getComponent(CounterModel.class);
        controller = (CounterController) ReactiveContainer.getComponent(CounterController.class);
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        message.setText("You've clicked 0 times");
        count.setText("Count");
        clean.setText("Clean");

        model.getCount().changes()
                .subscribe(e -> message.setText("You've clicked " + e.getNewValue() + " times"));

        EventStreams.eventsOf(count, ActionEvent.ACTION)
                .subscribe(event -> controller.count());

        EventStreams.eventsOf(clean, ActionEvent.ACTION)
                .subscribe(event -> controller.clean());
    }

}
<dependency>
    <groupId>page.codeberg.rrangelo</groupId>
    <artifactId>contextfx</artifactId>
    <version>1.0.0</version>
</dependency>

If you find ContextFX useful, consider supporting its development.