JavaFX provides a huge amount of internal support for managing the scene graph. That includes navigating from nodes, as well as events, to the Scene and Stage.
In JavaFX, the Stage
can be navigated to by using a node’s getScene()
method, followed by retrieving the Window
using getWindow()
. As the JavaFX Stage extends the Window class, this can be cast to Stage.
So, getting the Stage
from the current controller is incredibly easy. In fact, I’ll take you through three ways to get to it. But, this comes with a warning.
Warning: there are very limited use cases where using a controller to directly control a window is a good idea. The second half of this article goes into a little more detail about these dangers. I’ll also go through some design ideas for how you can set Stage behaviour in a stable and controllable way.

We’ll go through the simple ways to access the Stage, and then talk through some more advanced design ideas for Stage control.
What we’ll achieve in this tutorial
There are three main ways to get access a Stage
from within a controller:
As promised, I’ll also talk through the dangers of using these strategies and best practice for controlling Stage
and Window
behaviour
Finally, I’ll talk about some basic design principles that should help you set Behaviour in your Stages and Windows controllably.
Accessing the Stage from your Controller
So, in an imaginary program, we have a Project Wizard. It’s pretty simple: all we’re asking the user to do is to add a Project name, and a Project lead. We’re not winning awards for our UI design here, but we still want to satisfy two user expectations:
- When they click the OK button, they commit their input to a database and the window should close
- They should also be able to commit their input and close the window by pressing enter on either text box.

We won’t go into detail about validating the information the user’s provided – this article’s not about that. But our initial plan is that we need to be able to access the Stage
from both the projectNameTextField
and whenever the okButton
is pressed.
We’ll do these each in turn.
1. Access the Stage through a node
All nodes in a scene are nested inside a scene graph that runs from the Scene
to each element – each node – in it. Our scene is organised as a tree, with the Scene
element at the top, and our leaf nodes (like TextField
) at the bottom.
When it displays a Scene
, JavaFX attaches it to a Window
object that is accessible through the scene.getWindow()
method.

To traverse this tree, we need only two commands, regardless of which node we start at:
- We use
node.getScene()
to get the Scene from the node. - Then, we use
scene.getWindow()
to get the Window from the scene.
Window window = projectNameTextField.getScene().getWindow();
As Stage extends the Window class, we can cast getWindow()
to Stage:
Stage thisStage = (Stage) projectNameTextField.getScene().getWindow();
Warning 1: This can be an unstable way to get the Stage
from the controller, especially if you try to do so during the creation or initialization of your controller. The FXMLLoader creates, initializes and loads your controller in a very specific order, all of which happens before we create our Stage
:

So, running any of the code above in either the controller constructor (assuming you’re using some custom controller factory) or initialize()
method, you’ll generate a NullPointerException
because at those points in time, there is no window.
Warning 2: It’s also worth knowing that stage.show()
sets off a bunch of different function calls in the quantum toolkit and the glass rendering system – in the background. So any attempt to do things in the controller straight after stage.show()
dumps you immediately into a race condition.
The only stable way to do this is to inject the Stage into the controller after the load()
method has completed, using a custom method.
We’ll cover that in “Set the Stage
using a custom controller method” below
2. Accessing the Stage through an ActionEvent
Although I’ve said ActionEvent, we can do this for any Event – MouseEvents, GestureEvents and so on. They all represent actions on the scene, and we can use them to access the scene graph.
When we define the onAction
button click in the FXML file, we have to provide a method that is called whenever the ActionEvent
is fired. We’ll need one extra line of code to access the scene graph from the event and cast the source of the event as a Node
.
From there, we traverse the scene graph in the same way as we did before.
public void handleCommitAddProject(ActionEvent event) { Node node = (Node) event.getSource(); Stage thisStage = (Stage) node.getScene().getWindow(); commitToDatabase(); thisStage.hide(); }
Because this code isn’t executed until the button is pressed, we are guaranteed to have a Scene and a Window at this point – so the NullPointerException
issues are more easily avoided.
That being said, it’s still not best practice.
Warning 3: the eagle-eyed among you will have noticed that our controller just got a bunch of new code. Now in addition to controlling the user interactions with the View, it’s got to run database code and hide the Stage
? We’ll get to that later.
3. Set the Stage
using a custom controller method.
As some prep-work for setting the Stage manually, we’ll need to create a method in our controller that will accept the Stage we pass it and set up the listener on the text field. We’ll use that instead of digging around in the scene graph to get it from a node.
//in controller public void setStage(Stage stage) { this.stage = stage; projectNameTextField.setOnAction(event -> { commitToDatabase(); stage.hide(); }); }
Now, once we’ve loaded the view, we can ask the FXMLLoader for the controller, and use that controller to run our custom method.

public class Main extends Application { @Override public void start(Stage stage) throws IOException { //Set stage appearance stuff stage.setTitle("Project Wizard"); stage.getIcons().add(new Image(getClass().getResourceAsStream("EdenCodingIcon.png"))); //Load the view FXMLLoader loader = new FXMLLoader(getClass().getResource("NewProject.fxml")); //Create the scene Parent root = loader.load(); stage.setScene(new Scene(root)); //Set the Stage NewProjectController newProjectController = loader.getController(); newProjectController.setStage(stage); //show the stage stage.show(); } public static void main(String[] args) { launch(args); } }
Warning 4: Again, we have to run database code in the controller? And, of course, whenever we create a new View that does the same thing, we’ll just end up copying and pasting that database code there too…
Controlling Stage behaviour sustainably
If you’re trying to create larger applications, it can be a good idea to design your code to define and encapsulate the responsibilities of every class really clearly.
So, whenever I’m designing a controller, I try to remember what they were originally designed to do!
JavaFX’s basic MVC design pattern
In the JavaFX MVC design pattern, the model, view and controller are designed to act together. The only responsibility of the controller in this MVC design pattern is to maintain the model that the user interacts with through the view. And of course to update the view as necessary.

The Window
– the Viewport through which we interact with the view – is created by a calling class. In our case, Main.java. It does this through the FXMLLoader
.

Now, in the code above, we asked the controller to use the values in the project name and lead TextField to populate a database, and close the Stage. That means adding more models to our MVC.
Adding models to the MVC
When I first started with JavaFX I really didn’t see the problem with adding some extra code to my controller – what’s a little bit of database code between friends? The Project needs to go in the database, so the controller should put it there, right?

I think about the database an an extra model. It has a database location and connection properties. You can store these in a static helper class like DatabaseProperties.java
but it doesn’t fundamentally change the fact that you’re creating a model in the controller.
The sniff test for any model in an MVC pattern is: do we require this model in order to update the View? There are use cases where we want the user to select a database so they can add the project to a particular destination, but in this case, the database is completely separate.
And, sure enough, when we create a different window later, we’ll have to copy and paste the code.
Creating models in the calling class
An alternative design for this is to pass the model information (the intended Project model) back to the calling class. In my opinion, the responsibility for updating the database and determining future actions doesn’t lie with the controller.
I like to think of it this way: The calling class opened a window because it wanted the user to input certain data. So the calling class should determine whether the input it gets is acceptable.
There are several ways to do this, but the easiest is to expose the OK button from the controller and set that code in the calling class (you could do this through a getter, or even an Observer patter)
newProjectController.okButton.setOnAction(event -> { if(newProjectController.inputIsValid()){ Project newProject = newProjectController.getResult(); commitToDatabase(newProject); stage.hide(); } else { notifyUserResultNotAcceptable(); } });
Notice: we still delegated to the controller to work out whether the user’s input was valid by calling newProjectController.inputIsValid()
. From my point of view, the controller maintains the Project model so that’s the controller’s responsibility fair and square.
Now the calling class can ask the controller for the model using newProjectController.getResult()
. It then interacts with the database and – importantly – it decides what to do next.

Conclusions
Accessing the Stage
in JavaFX can be achieved by navigating the scene graph using the getScene()
and getWindow()
methods. As Stage
extends Window
, the resulting Window
can be cast to Stage
, where needed.
When accessing a Stage
, timing is important, as the Stage
is not created until the very end of a View-creation process.
Code designers should also be careful when using the Stage
within a controller. Typically, the controller is responsible only for updating the model and view, and is shouldn’t really be responsible for the Window
lifecycle. This responsibility more comfortably fits with whichever class created the Stage
in the first place.