JavaFX’s user interfaces are fuelled by events. In fact, almost every action a user takes in a user interface will trigger an Event, which in turn will fire some executable code. To make a button do something in JavaFX you need to define the executable code usually by using a convenience method like setOnAction()
.
Then, when the button is clicked, JavaFX does the heavy lifting of fetching that pre-defined code, and executing it on the JavaFX Application thread.

It’s worth bearing in mind, there’s no guarantee your code will be executed immediately, but the delay between a user clicking a button and the action being completed is usually really short (we’re talking milliseconds). For most simple user interfaces, keeping your UI snappy won’t be an issue. If you’re experiencing some lag, you might want to consider outsourcing some thread work to background processes.
In JavaFX, a button event should be defined either by invoking the setOnAction()
convenience method of a Button
object, or by defining the onAction
attribute of the Button
object in an FXML document and linking it to a method that accepts an ActionEvent
object as a parameter.
I’ll walk you through both ways now.
1. Using Java Code to define button events
If you have access to your Button in Java code, you can use the setOnAction()
convenience method to define what happens when the button gets pressed. You can do this by defining an EventHandler<ActionEvent>
object (which might sound terrifying if you’re new to Java or JavaFX). The good news is you can define the button’s effect using a lambda instead.
Here’s an example app using the EventHandler<ActionEvent>
approach. Below, I’ll go through using the lambda approach.
package com.edencoding; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class ButtonEventExampleApp extends Application{ @Override public void start(Stage primaryStage) throws Exception { //create Scene Graph Button button = new Button("Press me!"); StackPane root = new StackPane(button); Scene scene = new Scene(root, 300, 250); //Create Window primaryStage.setTitle("Button Example App"); primaryStage.setScene(scene); primaryStage.show(); //Define Button Action Label label = new Label("Pressed!"); button.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { root.getChildren().setAll(label); } }); } }
Button setOnAction using a lambda
Instead, it’s much more common to define button actions using a lambda.
A button action can be defined using a lambda expression by providing a single argument (the event) as the lambda’s input and the executable code as either an expression or a statement block between curly braces.
In this case, we’ll define it using an expression.
package com.edencoding; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class ButtonEventExampleApp extends Application{ @Override public void start(Stage primaryStage) throws Exception { //create Scene Graph Button button = new Button("Press me!"); StackPane root = new StackPane(button); Scene scene = new Scene(root, 300, 250); //Create Window primaryStage.setTitle("Button Example App"); primaryStage.setScene(scene); primaryStage.show(); //Define Button Action Label label = new Label("Pressed!"); button.setOnAction(event -> root.getChildren().setAll(label)); } }
2. Using an FXML document to define button events
It can be more efficient to use the onAction
attribute of the Button
object. Especially if you’re using the FXML document-loading strategy of JavaFX’s MVC-oriented design.
Using the FXML approach, JavaFX does the heavy lifting of linking the button with the method in the Controller that’s going to define the button’s effect, and you don’t have to worry about EventHandler
objects at all.
To set this up, you’ll need to load the FXML document using the FXMLLoader during app initialisation. The code to do this is in the drop down, but if you’re already using FXML documents, chances are you know how to create a view using an FXML file.
Here’s the code to launch a simple JavaFX application run using an FXML document to create the View.
package com.edencoding; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class ButtonEventExampleApp extends Application{ @Override public void start(Stage primaryStage) throws Exception { //create Scene Graph Parent root = FXMLLoader.load(getClass().getResource("fxml/mainView.fxml")); Scene scene = new Scene(root); //Create Window primaryStage.setTitle("Button Example App"); primaryStage.setScene(scene); primaryStage.show(); } }
Once you have your main App code sorted, you just need to define two elements to create your button action:
- An
onAction
attribute in the Button object in the FXML file - A method in the Controller that takes an
ActionEvent
as an argument and returnsvoid
.
FXML Document
<?import javafx.scene.control.Button?> <?import javafx.scene.layout.StackPane?> <?import javafx.scene.control.Label?> <StackPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="250.0" prefWidth="300.0" fx:controller="com.edencoding.controllers.Controller"> <Button fx:id="button" text="Press Me!" onAction="#handleButtonPress"/> <Label fx:id="label" text="Pressed!" visible="false"/> </StackPane>
Note you also need to define the link to the Controller using the fx:controller attribute in the root node of the View.
In this View, that’s the StackPane.
Java Controller
package com.edencoding.controllers; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; public class Controller { @FXML private Label label; @FXML private Button button; public void handleButtonPress(ActionEvent event) { button.setVisible(false); label.setVisible(true); } }
If you haven’t used FXML documents before, and youre interested in how to do it properly, check out this article on full-MVC structure with JavaFX. It takes you through structure, code and a fully-coded up example of how to get FXML-based MVC running in JavaFX.
Should I use FXML or Java code?
Whether to use FXML or Java code probably depends on what you want to do, and the size of the application you want to create.
Generally speaking, the larger your application becomes, the more beneficial loading from FXML documents can be. I find that the streamlined view generation and MVC architecture enabled by FXML documents usually generates more slimmed-down, efficient code. And of course when it comes to button events, there’s no need to use Event Handlers (you just define a normal method in the Controller and JavaFX handles the rest!)
In smaller applications, there are more documents to handle when using the FXML approach (an extra FXML document and a Controller), which can seem more hassle than it’s worth. Plus, not everything is in Java code.
The next section’s a great example of this. Below, I want to take you through how to fire multiple events using a Button’s single click action. In each case, the example’s provided using Java-only code because it means a single copy-paste class which can run straight from your IDE.
Firing multiple events from a single button click
Sometimes, you just want to get more stuff done, just not all at once. Here are a few situations where you might find yourself needing to complete multiple actions from a single button click:
- Making multiple changes to the Model or View (e.g. loading a confirmation that the user’s action has been completed while updating the Model (underlying data) based on the user’s actions.
- The button click starts an asynchronous process of loading data and you want to fire an additional event when that process is complete.
- Creating some delayed effect that spawns another event at the end of it.
Making multiple changes to the Model or View
If you want to make more changes to your View or Model, some or all of which can be achieved within the Controller, you simply need to nest that code within the lambda expression.
Usually, if you’re intending to make changes to the Model, the Controller should invoke those methods in the Model to enable those changes to take place. This would be where that happens.
Java Code
public class ButtonEventExampleApp extends Application{ Button button; Label label = new Label("Pressed!"); StackPane root; @Override public void start(Stage primaryStage) throws Exception { //create Scene Graph button = new Button("Press me!"); root = new StackPane(button); Scene scene = new Scene(root, 300, 250); //Create Window primaryStage.setTitle("Button Example App"); primaryStage.setScene(scene); primaryStage.show(); //Define Button Action button.setOnAction(event -> { updateButtonToLabel(); updateUserOnActionResults(); }); } public void updateButtonToLabel(){ Label label = new Label("Pressed!"); root.getChildren().setAll(label); } public void updateUserOnActionResults(){ Label statusLabel = new Label("Button clicked and removed!"); root.getChildren().add(statusLabel); StackPane.setAlignment(statusLabel, Pos.BOTTOM_CENTER); } }
Result
Starting an asynchronous process
Often, it’s easier (for structural reasons) to design an application that separates key resources from the UI code. One examples of this is interacting with a REST API or connecting to a database from JavaFX.
In these cases, you can’t immediately update the Model or View, but you do want to keep track of the task’s progress. JavaFX provides support for this with the Service
and Task
classes. Using a Service
and Task
, you can offload work onto a separate thread, and then fire another event once the asynchronous task has completed.

Java code
package com.edencoding; import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class ButtonEventExampleApp extends Application{ Button button; Label label = new Label("Pressed!"); StackPane root; @Override public void start(Stage primaryStage) throws Exception { //create Scene Graph button = new Button("Press me!"); root = new StackPane(button); Scene scene = new Scene(root, 300, 250); //Create Window primaryStage.setTitle("Button Example App"); primaryStage.setScene(scene); primaryStage.show(); //Define Button Action button.setOnAction(event -> { performAsynchronousTask(); }); } private void performAsynchronousTask() { Service<String> service = new Service<>() { @Override protected Task<String> createTask() { return new Task<>() { @Override protected String call() throws Exception { updateProgress(0, 5); for (int i = 1; i <= 5; i++) { Thread.sleep(1000); updateProgress(i, 5); } return "Complete!"; } }; } }; button.textProperty().bind( Bindings.concat("Progress: ", service.progressProperty().multiply(100), " %") ); service.setOnSucceeded(event -> { button.textProperty().unbind(); updateLabelComplete(); }); service.start(); } private void updateLabelComplete(){ button.setVisible(false); label.setText("Complete!"); root.getChildren().setAll(label); } }
Result
Creating a delayed event from a button click
Finally, if you want to create a delayed effect, the PauseTransition
is the object you’ll need. This transition is designed to take no action (but also not hang the thread) for a defined period of time, and allows you to define an event to fire once it’s done.
In this case, we’ll just wait for 5 seconds, although you could accomplish other things. Gmail is famous for having implemented this, allowing you to “un-send” emails for 5 seconds after you’ve clicked “Send.” Google isn’t actually “un-sending” your email. Instead, Gmail creates an Undo button and then just waits for 5 seconds before sending it, just to make sure you’re not having second thoughts.
Java code
package com.edencoding; import javafx.animation.PauseTransition; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import javafx.util.Duration; public class ButtonEventExampleApp extends Application{ Button button; Label label = new Label("Pressed!"); StackPane root; @Override public void start(Stage primaryStage) throws Exception { //create Scene Graph button = new Button("Press me!"); root = new StackPane(button); Scene scene = new Scene(root, 300, 250); //Create Window primaryStage.setTitle("Button Example App"); primaryStage.setScene(scene); primaryStage.show(); //Define Button Action button.setOnAction(event -> { pauseFor5SecondsThenUpdateLabel(); }); } private void pauseFor5SecondsThenUpdateLabel() { //set up pause transition PauseTransition shortPause = new PauseTransition(Duration.seconds(5)); shortPause.setOnFinished(event -> updateLabelComplete()); //update label button.setVisible(false); label.setText("Pausing for 5 seconds..."); root.getChildren().setAll(label); //start pause transition shortPause.playFromStart(); } private void updateLabelComplete(){ label.setText("Pause Complete!"); } }
Result
Conclusions
Creating button events is really simple. By invoking setOnAction
, or onAction
(Java code or FXML documents respectively), you can quickly connect a button click with a chunk of executable code.
JavaFX itself has a lot more support for events than this. In fact, you can fire events based on users interactions with almost any node. You can define events based on interactions from screen gestures to mouse movements, and each one can be set individually.
Given the massive amount you can do with events, they can seem a little complex and overwhelming. If you’re interested in learning more about what they are and how to deploy them, here’s a complete guide.