Skip to content

Routing

Spring JavaFX Boot features a Spring MVC-inspired routing system that cleanly separates route configuration from FXML view controllers. If you've worked with Spring MVC or Angular, the patterns will feel familiar.

Architecture Overview

The routing system mirrors Spring MVC's request handling model, adapted for a desktop UI:

Spring MVC Spring JavaFX Boot Role
DispatcherServlet FxRouter Central dispatcher — orchestrates navigation
RequestMappingHandlerMapping FxRouteRegistry Scans and registers routes at startup
@RestController @FxRoutes Route configuration class (singleton)
@GetMapping @FxMapping Maps a method to a route path
Model FxModel Carries data from handler to view
ViewResolver ViewResolver Resolves view name to FXML template

Defining Routes

Routes are defined in classes annotated with @FxRoutes. Each method annotated with @FxMapping maps a path to a view:

@FxRoutes
public class AppRoutes {

    @FxMapping("/")
    public String layout(FxModel model) {
        return "layout";  // resolves to /templates/layout.fxml
    }

    @FxMapping(value = "/main", parent = "/", title = "page.title.main")
    public String main(FxModel model) {
        model.put("greeting", "Hello!");
        return "main";  // resolves to /templates/main.fxml
    }

    @FxMapping(value = "/second", parent = "/", title = "page.title.second")
    public String second(FxModel model) {
        return "second";
    }
}

Return type

@FxMapping methods must return a String (the view name). They can accept FxModel or Map<String, Object> as parameters.

Inject FxRouter into any Spring-managed bean and call navigateTo():

@Controller
@Scope("prototype")
@RequiredArgsConstructor
public class MainController {

    private final FxRouter router;

    @FXML
    private void onGoToSecond() {
        router.navigateTo("/second");
    }
}

Passing Parameters

Pass data to the target view via a parameter map:

router.navigateTo("/details", Map.of("itemId", 42, "mode", "edit"));

Parameters are added to the FxModel and injected into the target controller's @ModelAttribute fields:

@Controller
@Scope("prototype")
public class DetailsController {

    @ModelAttribute
    private Integer itemId;

    @ModelAttribute
    private String mode;

    @FXML
    private void initialize() {
        // itemId = 42, mode = "edit"
    }
}

View Resolution

ViewResolver maps logical view names to FXML templates using a configurable prefix and suffix:

"main"     → /templates/main.fxml
"settings" → /templates/settings.fxml

Configure in application.properties:

spring.javafx.view.prefix=/templates/   # default
spring.javafx.view.suffix=.fxml         # default

Route Logging

Routes are logged at startup so you can verify the route table:

Mapped "/"            → AppRoutes.layout()
Mapped "/main"        → AppRoutes.main()        [parent: /]
Mapped "/second"      → AppRoutes.second()       [parent: /]
Mapped "/demo/window" → AppRoutes.demoWindow()
Mapped "/demo/modal"  → AppRoutes.demoModal()
Registered 5 FxMapping route(s)

When router.navigateTo("/main") is called:

  1. FxRouteRegistry resolves "/main" and finds parent = "/"
  2. The router builds the ancestor chain: ["/", "/main"]
  3. For each level in the chain, starting from the root:
    • If the parent "/" is already active — reuse its cached layout
    • If not — invoke the @FxMapping handler, load the FXML, cache as ActiveRoute
  4. The handler is invoked — it populates FxModel and returns a view name
  5. ViewResolver resolves the view name to an FXML template path
  6. The FXML is loaded with Spring's ApplicationContext as the controller factory
  7. @ModelAttribute fields are injected into the controller from the model
  8. The optional onModelReady(FxModel) hook is called (if defined on the controller)
  9. @FXML initialize() runs — all model data is available
  10. The child view is placed into the parent's @RouterOutlet

Reloading

Call router.reload() to force a full reload of the current route and all its parents. This is useful after a locale change:

Locale.setDefault(newLocale);
messages.setLocale(newLocale);
router.reload();  // rebuilds all layouts with new translations