Any application more complicated than a calculator is going to need multiple windows. From login screens to dashboard editors and warnings, windows often control the flow of an application’s behaviour or state.
A new window can be created in JavaFX by creating and opening a Stage. Before opening, each Stage will need a Scene, which can be constructed from FXML or using Java code. Window “modality” can be used to control the state of an application in which multiple windows are open and can be controlled by invoking initModality()
.
Alerts and Dialogs can be used to create pre-formatted windows, and can be customised significantly. They’re usually used to facilitate quick interactions with the user, like displaying a warning, or asking a question, rather than prolonged user interaction.
What you’ll get from this article
Trying to create a window can be achieved in two main ways (click the links to jump to that section):
There are some specific use cases where creating views directly from Java code will lead to a better user experience, like where you’re running in a really resource-constrained environment. Check out the second section for more details on that one.
Next, because they’re so important in controlling the flow of information in your application, I’ll also cover how to:
- Block input to some or all windows when opening a new one
- Close the current Window when opening a new one
- Open a new Scene in the current window
Finally, I want to touch on which other options are available to JavaFX developers when they’re opening new windows. It doesn’t always have to be a completely custom Stage object.
In fact, I think a really important part of using JavaFX effectively is being aware of the tools that are already available when you want to do something like create a Window.
Create a new window with FXML
One of the main benefits of creating a Stage
rather than an Alert
or Dialog
is the complete control it gives you on the layout and styling of the view. This can facilitate incredibly complex user interactions, which is why it’s ideal for the main windows in your application.

I think the most effective way to create and style a view is by splitting the responsibility between Java code, FXML and CSS. It’s how JavaFX was designed, and you can generate some really beautiful looking windows with very little code.
Elemetn | Responsibility |
---|---|
Java | Create the Stage , and Scene objects.Show the window. |
FXML | Define the layout of the view |
CSS | Style the view |
OK, here’s a quick example of how you’d go about doing that.
//Create Stage Stage newWindow = new Stage(); newWindow.setTitle("New Scene"); //Create view from FXML FXMLLoader loader = new FXMLLoader(getClass().getResource("mainWindow.fxml")); //Set view in window newWindow.setScene(new Scene(loader.load())); //Launch newWindow.show();
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <VBox alignment="CENTER" spacing="20.0" styleClass="container" stylesheets="@styles.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1"> <Label styleClass="app-title" text="Fancy App Title"/> <TextField maxWidth="200.0" promptText="Enter your name"/> <Button text="OK"/> </VBox>
@font-face{ font-family: 'GloriousFree'; font-style: normal; font-weight: 400; src: url('https://edencoding.com/wp-content/uploads/2021/02/GloriousFree-dBR6.ttf') format('truetype'); } .root{ -fx-background-color: linear-gradient(to bottom right, #2c3e50, #3498db); } .label{ -fx-text-fill: white; } .app-title{ -fx-font: 36px 'Glorious Free'; } .text-field { -fx-border-color: grey; -fx-border-width: 0 0 1 0; -fx-background-color: transparent; } .text-field:focused { -fx-border-color: -fx-focus-color; }
Result:

This is far and away my preferred way of creating windows and views. You absolutely don’t have to include CSS when you’re defining views this way, but the user experience you get by including some styling is just immeasurably deeper.
That being said, if it’s a really simple view, you might want to create it from Java code. That has some performance benefits, because the view won’t be created be reflectively populating your controller and creating the scene graph.
Create a new window with only Java code
Loading views from FXML won’t cause an issue in most situations. However, if you’re running in a resource-constrained environment, loading your views from Java code may improve lead times considerably. If you’ve tried running JavaFX on a Raspberry Pi, that’s the sort of thing I’m talking about.
Loading a view from FXML involves reading the FXML file from disk, parsing the XML structure, and creating the view using Java reflection. That’s always going to be slower than reading pre-compiled Java byte code.
If you’re planning on running the application on something like a Pi, you may want to test load times for the FXML approach against the Java one.
//Create Stage Stage newWindow = new Stage(); newWindow.setTitle("New Scene"); //Create view in Java Label title = new Label("This is a pretty simple view!"); TextField textField = new TextField("Enter your name here"); Button button = new Button("OK"); button.setOnAction(event -> { //handle button press }); VBox container = new VBox(title, textField, button); //Style container container.setSpacing(15); container.setPadding(new Insets(25)); container.setAlignment(Pos.CENTER); //Set view in window newWindow.setScene(new Scene(container)); //Launch newWindow.show();
Result:

Here, the view may not be quite as ‘sexy’, but with a little tweaking, you can still create a very functional window.
Blocking input to the current window
Depending on how you want to use your window, it may be important to block user input to other windows in the application.
This behaviour is controlled using window ‘modality‘, which is set individually on each window by invoking initModality(Modality modality)
.
Behaviour can be controlled in one of two ways: “window modal” and “application modal”. Let’s take a look at each one:
Modality.WINDOW_MODAL
:
This behaviour blocks user input to the window that launched this new window (and any windows ‘above’ those). BUT, allow input to other windows that are open

When setting the modality to WINDOW_MODAL
, you will also need to invoke initOwner(Stage stage)
on the new Stage. By passing in the current stage, we lock input to just that Stage
newStage.initOwner(currentStage);
If no owner is specified, JavaFX will treat the window as it it has no owner, and no blocking behaviour will be applied.
Modality.APPLICATION_MODAL
- This behaviour will block input to all other windows in the application, regardless of how they were produced.

These behaviours can be really useful in controlling the user’s attention and interaction with the software. Ensuring the user provides information before returning to a window can be useful.
Closing the current Window when opening a new one
Another way to control the user’s ability to interact with our application is to close the current window as the new window opens. This ensures that the underlying state of the application changes at the same time that we change the view presented to the user.

To close any Stage in JavaFX, simply invoke hide()
or close()
on the Stage object to be closed. These method invocations are equivalent and both internally set the showing
property of the window to false. Both of these methods must be executed from the JavaFX Application thread.
//close the stage stage.close(); //equivalent stage.hide();
Because of JavaFX’s automated garbage collection, once any retained references to the closed stage are lost, it will become eligible for collection.
Once the last window in an application has exited, JavaFX will automatically attempt to close the application. In most cases, this is the desired behaviour. It can be overridden by setting Platform.setImplicitExit(false)
.
Lastly, there are a few occasions where long-running background tasks might keep an application running even after the last window has closed down. The safest way to ensure this doesn’t happen is to use the concurrency options that are included with JavaFX, rather than using Java-only multi-threading approaches.
Open a new Scene in the current window
Opening a new Scene
in the current window can be done in two simple steps:
- Identify the current
Stage
(window) in code - Create a new Scene and invoke
setScene()
on the Stage.
1. Identifying the current Stage
The current window can be navigated to from anywhere in the scene graph. To get the current stage, simply identify an object that is currently displayed, and invoke getScene().getWindow()
. This returns a Window
object, which we can cast to Stage
.

2. Creating and setting a new Scene
Creating and setting a new Scene
can be accomplished using either the FXML or Java-only methods above. For the sake of this example, we can do it really easily using the FXML file we defined above.
//Get the current Stage Stage currentStage = (Stage) button.getScene().getWindow() //Create view from FXML FXMLLoader loader = new FXMLLoader(getClass().getResource("secondWindow.fxml")); //Set new Scene in current window currentStage.setScene(new Scene(loader.load()));
Default Window types available in JavaFX
Often, you won’t need a fully-fledged, customised Stage
to interact with your user. Especially if your new window will mediate a temporary interaction, like displaying information.
In those cases, it might be more convenient to use an Alert
of Dialog
, which are pre-formatted windows for just that purpose.
Deciding which window you need is mostly based on the type of interaction you need to have with your user. The number of options can seem overwhelming, but I usually break them down into three general types of interaction:
- Information only
- A quick question you need to ask your user
- A completely custom approach
Here they all are, viewed in those three ways:

How to choose which window to use
With that in mind, you probably already have a pretty good idea of how to use most of these, but I find choosing which window to use often comes down to the flow I want to achieve in your application.
In fact, I’ll often draw out the flow I want for an application when I’m designing it.

> Alerts
For simple windows, where I’m displaying information to the user, an Alert
is usually enough. They’re simple to create, and are relatively easy to customise, and simple user interaction can be registered using customised buttons.
Code:
//Create an Alert with predefined warning image Alert alert = new Alert(AlertType.WARNING); //Set text in conveinently pre-defined layout alert.setTitle("Warning"); alert.setHeaderText("Are you sure?"); alert.setContentText("Do you want to close the application?"); //Set custom buttons ButtonType okButton= new ButtonType("Yes, exit", ButtonType.OK); ButtonType cancelButton= new ButtonType("No, do not exit", ButtonData.CANCEL_CLOSE); alert.getButtonTypes().setAll(okButton, cancelButton); //Prevent all interaction with application until resolved. alert.initModality(Modality.APPLICATION_MODAL); //Launch alert.showAndWait();
Result:

> Dialogs
Dialogs allow you to have a slightly richer user interaction. Their feedback isn’t limited to button clicks, and results can still be returned using the convenient Optional
API.
Dialogs are amazingly extensible, meaning you can rig up a LoginDialog
object in the background and launch it straight from the place you need to in code. You can even load custom content into the layout using FXML and CSS (as can be Alert
, to be fair).
Code:
// Create the login dialog. LoginDialog dialog = new LoginDialog(); //Get result with lambda syntax dialog.showAndWait() .ifPresent(result -> login(result));
package com.edencoding; import javafx.beans.binding.Bindings; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.util.Pair; import java.io.IOException; public class LoginDialog extends Dialog<Pair<String, String>> { @FXML private TextField usernameField; @FXML private PasswordField passwordField; public static ButtonType loginButtonType = new ButtonType("Login", ButtonBar.ButtonData.OK_DONE); public LoginDialog() { setDefaultInformation(); setDefaultButtons(); setContent(); setBehaviour(); setStyleSheets(); } private void setDefaultInformation() { //Default login prompt information setTitle("Login"); setHeaderText("Login"); // Default login image setGraphic( new ImageView( new Image(getClass().getResourceAsStream("/images/padlock.png")) ) ); } private void setDefaultButtons() { getDialogPane().getButtonTypes().setAll(loginButtonType, ButtonType.CANCEL); } private void setContent() { //Create the content using FXML FXMLLoader loader = new FXMLLoader(getClass().getResource("loginView.fxml")); loader.setController(this); try { getDialogPane().setContent(loader.load()); } catch (IOException e) { e.printStackTrace(); } } public void setBehaviour() { // If username and password have both been set, return values, else return null setResultConverter(dialogButton -> !usernameField.getText().isBlank() && !passwordField.getText().isBlank() ? new Pair<>(usernameField.getText(), passwordField.getText()) : null ); //Only enable login button when username and password have both been entered Node loginButton = getDialogPane().lookupButton(LoginDialog.loginButtonType); loginButton.disableProperty() .bind(Bindings.or( usernameField.textProperty().isEmpty(), passwordField.textProperty().isEmpty() )); } private void setStyleSheets() { getDialogPane().getStylesheets().add( getClass().getResource("styles.css").toExternalForm() ); } }
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <GridPane hgap="15.0" vgap="15.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1"> <padding> <Insets bottom="15.0" left="15.0" right="15.0" top="15.0" /> </padding> <Label text="Username" /> <Label text="Password" GridPane.rowIndex="1" /> <TextField fx:id="usernameField" GridPane.columnIndex="1" /> <PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="1" /> </GridPane>
.button{ -fx-background-color: #375fcb; } .button:hover{ -fx-background-color: #fcc200; } .button:pressed{ -fx-background-color: #FEEBAA; } .text-field { -fx-border-color: grey; -fx-border-width: 0 0 1 0; -fx-background-color: transparent; } .text-field:focused { -fx-border-color: -fx-focus-color; }
Result:

> Stages
For custom, or complex interactions, a Stage
is usually appropriate. Good signs you’ll need a Stage
are:
- Custom behaviour requiring a Controller
- Custom layouts not achievable with the DialogPane system
- It’s a main window in your application
Conclusions
Launching a new window in JavaFX requires a Stage
and Scene
, populated with at least one root Node
. The normal way to achieve this is by loading a View through JavaFX’s FXMLLoader utility class. However, it’s just as possible in Java code.
To control the application’s state while opening another window, developers can halt input, close the current window, or simply open the new content directly into the current Stage. Blocking input, for instance, might be important for displaying an important warning to the user.
Finally, JavaFX comes with default pre-formatted windows called Alerts and Dialogs, which can be useful in passing interactions with users. I’ve definitely used them in prototyping, even if I come back and create a custom styled window later.