JavaFX’s user interfaces are almost completely event-driven. Most people are familiar with action events, key events and mouse events, but almost every complex update to a user interface is accomplished using events of one sort or another. And yet, Events are one of the most under-played parts of JavaFX.

As a general rule, if you need to accomplish a simple value-based change, this can be done with Property binding. For everything else, I use events.
What is an Event?
If you’ve ever sorted a column in a TableView, changed a TreeView, or edited a cell, you’ve already used events to update the interface in the background. Events are a versatile, stable way to drive changes on the JavaFX Application thread by setting executable code ahead of time.
An Event is any change of state in an input device (such as mouse or touch screen), a user action (ActionEvent), or a background task. Events can also be fired as a result of scroll or edit events on complex nodes such as TableView
and ListView
.
At a very basic level, the Event
object is a class with a surprisingly small number of parameters. It doesn’t hold any executable code, nor does it run any code. It’s a flag to the system that something has changed.
JavaFX then provides significant support to run executable code, defined separately through the EventHandler
class, on any changes of state. There are 90 separate types of event supported by JavaFX, with the capability to extend the Event class and define additional custom functionality.
How to use this resource
Events are such a fundamental part of JavaFX, I thought they deserved an attempt to summarise everything in one place. I’ll attempt to do that justice by going through:
I’ve tried to make this article as readable as possible, whilst also including as much of the useful information that usually gets left out. To that end, low-level detail and code examples are inside drop-downs to make scrolling a little less painful.
1. How Events Work
I wasn’t lying when I said an Event
is surprisingly simple. What is complicated is the background processes that implement the event handling system. In this section, we’ll go through what defines an Event, and how to set up your code to execute whenever a certain event is detected.
The Event Class
In JavaFX, an Event
has four properties:
The boolean parameter consumed
is usually missed, but it’s really important to application behaviour. Let’s go through each one in turn.
a. Source
JavaFX assigns a node as the source of an event at creation, which is defined according to the cause of the event (mouse, key, action etc).
Java natively supports events with the EventObject
class. Even before JavaFX layers on its own attributes of Target and Type, the fundamental part of an Event is the source. Target, type, and Consumed are constructs JavaFX introduces, but every event has to be caused by something. That’s the source.
An event source is the Object
that cause the event. A wide variety of state changes can cause an event:
Even though it may be an input device that has changed state, JavaFX will assign its own source, which is often a node.
To allow this flexibility, the event source is stored as an Object
and can be accessed through the getSource()
method.
Unlike the event target, in some specific cases, the source of an event can change throughout the execution of the event. In Event Types, I’ll talk about how this can work.
b. Target
JavaFX assigns a node as the target of an event at creation. This is the node that has been acted upon by the event. All events throughout the lifecycle of the Event are delivered to the same Target node.
JavaFX extends the EventObject
included in java.util
to add additional parameters. The first of these is the target, which defines the node on which the event acted. For example, as I type in a TextField
, every keystroke is registered as a KeyEvent
and the target for each of these events will be the TextField
.

EventObject
class to add custom event types and the EventTarget
parameterExtending the EventObject
gives JavaFX the flexibility to store and specify the event target in a JavaFX-specific way. In this case, the target of any event in JavaFX must implement the EventType
interface. This interface includes a single method that takes an EventDispatchChain
as a parameter, and returns an EventDispatchChain
.
EventDispatchChain buildEventDispatchChain(EventDispatchChain tail);
That’s at the centre of how JavaFX handles firing events. A huge number of JavaFX objects extend the EventTarget
interface:
This allows JavaFX to fire events on any element of the user interface, but it also allows us as developers to fire these events too.
Because of how events are managed by JavaFX, it also ensures that the executable code we provide is dispatched on the Application thread. This is an incredibly stable way to update complex elements like charts, and visually-complicated controls.
c. Type
JavaFX assigns the event type based on the origin of the event (mouse, key, action etc.) and the nature of the state change (key released, mouse clicked, task started and so on).
Event types are a JavaFX-specific parameter defined by the EventType
class. As far as I know, JavaFX defines event types using the EventType
class for two reasons:
Type safety
When defining an EventType
, you must specify the Event
for which you are defining the type, and a name for the event. JavaFX has 90 pre-defined event types which can be accessed as static member variables of the Event
and sub-Event
classes.
Differentiation from AWT
Java’s other windowing solution is the abstract windowing toolkit (AWT). Type safety for events in this toolkit was achieved by specifying events as integers.
That’s not perfect type-safety, but they were defined as static member variables like MOUSE_CLICKED = 500
so you could at least refer to them easily.
Had JavaFX also done that, MouseEvent.MOUSE_CLICKED
(AWT import) and MouseEvent.MOUSE_CLICKED
(JavaFX import) would be pretty hard to spot. If the numbers behind those static variables were different, you might not notice until your code crashed and you had to debug it by hand.
d. consumed
At any point in this process, including before an event has reached its target, the event can be consumed and the process immediately stops.
I can’t understate how useful this is in controlling events throughout a scene and how often you’ll use it in event-driven development.
A fundamental part of how JavaFX handles events is that events percolate through the scene graph. An event on a leaf node will fire events of that type from the Window
at the top, down to the node and back up again.
If an event is consumed part-way through the execution of the dispatch chain, it immediately stops, and no additional events are fired.
It is possible to stop an event before it reaches the target node. This is a common use for event filters.
Events can be consumed using the consume()
method. You can determine whether an event has been consumed by invoking isConsumed()
.
Event Firing – Phases
Whenever an event fires in JavaFX, it travels from the scene to the node and then from the node back up to the scene. This requires three things:
- Knowing which node the event should travel to
- Identifying which Window, in multi-window applications, and
- Knowing the route to take between the scene and the node
These are the first steps in firing an event. Step 1 is called Target Selection. Steps 2 and 3 are taken together in a process called Route Construction.
We’ll go through each of these and then finish by going through how events are fired.
a. Target Selection
JavaFX selects the target of an event based on the type of event that has occurred and the input device that has created the event.
Because target selection is type-dependent, I’ve taken a more detailed look at target selection for each event type in the drop downs below.
b. Route Construction
In the case of foreground events – events where the target is a node within the scene – the target node creates a route between itself and the Window
.
The logic goes something like this:
Get all the nodes between here and the Scene:
- Add target node to the route as the end (tail).
- If I target has a parent, append the parent node to the route
- Continue adding parents until I find a node that doesn’t have a parent
- This is the root node.
- Use the root node to get the
Scene
.
Then, get the extra dispatch chain for the scene and window:
- For the
Scene
just identified, add itself to the event dispatch chain (route) - Use the Scene to get the Window
- Add the wind;es
EventDispatcher
as the very last link in the event dispatch chain.
After this, we return the route created from the initial call of buildEventDispatchChain()
on the Target node.
For background events, the dispatch chain building process is much shorter. The EventHelper
class contains a dispatch chain intended for Tasks and Services. The event is just added to the Helper event dispatcher and returned.
After route construction, events are ready to be dispatched from every link in the dispatch chain.
c. Event Capturing
Now we have a dispatch chain, the event starts at the first element of the chain and is forwarded, one-by-one, through every node’s EventDispatcher
until the target node is reached.
This is where the difference between event filters and event handlers is important. If you haven’t heard of event filters, they can be added to any EventTarget
(nodes, scene, charts, tasks and so on) using the addEventFilter()
method.
Event filters are great for controlling the behaviour of child nodes, because they are executed by parents before children as the event traverses the scene graph.
Note: A node needs to have an event filter of the correct type for that filter to fire.

Event filters can be added to any EventTarget
(nodes, tasks and services) by invoking addEventFilter()
with the right EventType
and EventHandler
.
(check out How to define executable code in an EventHandler below)
Once the EventDispatcher
on the target node has fired any relevant filters, it fires any relevant handlers and passes the event back up the chain in a process known as event bubbling.
d. Event Bubbling
Events registered with targets are executed in ascending order from the event target, to the Window.
Note: A node needs to have an event handler of the correct type for that handler to fire.

Event handlers can be added in the same way as event filters, but by using invoking addEventHandler()
method on the target.
Let’s see how to define event filters and event handlers.
2. How to define executable code in an EventHandler
Whenever you want to define a response to an event, we need to use an EventHandler
object. In spite of its name, we use the same object for both event filters and event handlers.
Creating an EventHandler:
We can do this by defining the EventHandler
as an anonymous inner class, or as a lambda expression.
Defining an EventHandler
The EventHandler
object is an object with a single method, which we need to override when we instantiate it. As the EventHandler interface is parameterized, we need to specify what event type we’re handling. In this case we’ll use a MouseEvent.
EventHandler<MouseEvent> eventHandler = new EventHandler<>() { @Override public void handle(MouseEvent event) { //executable code } };
Replacing the definition with a lambda
An EventHandler
is a functional interface (an interface with a single method). For convenience, the handle()
method of the EventHandler
interface can be specified with a lambda instead.
EventHandler<MouseEvent> eventHandler = event -> { //executable code };
Adding and removing event filters and handlers
When we define and add an event handler to a node (or task), we define what type of that event we want our code to trigger from. As an example, if we want our code to trigger from any mouse event on the root node of our scene, we can use the event type MouseEvent.ANY
.
If you want to remove an event handler at a later point in code, you will need to keep a reference to the EventHandler
object you create.
//define the handler EventHandler<MouseEvent> eventHandler = event -> { //executable code }; //add the handler rootNode.addEventHandler( MouseEvent.ANY, //specify the type of event to listen for eventHandler); //later remove the handler rootNode.removeEventHandler( MouseEvent.ANY, //this should be the same as defined above eventHandler );
It’s equally easy to define and remove event filters using the addEventFilter()
and removeEventFilter()
methods.
A few simple things to remember though:
Using convenience methods
Sometimes it’s expedient to use convenience methods like setOnMousePressed()
to define event handlers. The syntax for event handlers is:
setOn EventType ()
Here are a few examples:
Convenience methods are useful because you can remove the handler without having to remember the reference by invoking the same method and passing a null reference:
setOnMousePressed(null)
They are also guaranteed to execute after any other event handlers of the same type on that node.
3. Event Types
In general, events can be categories into input events, and events specific to JavaFX. ActionEvents require input, but also span input types, and are created internally by JavaFX, so I’ve included these in JavaFX-specific events, rather than input ones.
Operating System-Generated (Input) Events
Input events, such as mouse, keyboard and gesture events, happen above the level of the application, and are passed to our app by the operating system. The JavaFX background processes use this information to identify the right node to enact the event on.

This integration with the operating system is actually really handy. If the user tries to click, and wobbles the mouse a little bit – but inside a system-defined safe-area called the “hysteresis position” – the event doesn’t count as a drag.
For each event, I’ve set out the operating-system parameters your don’t need to worry about (or can’t control) as well the types of event in each case, how to use them, and any quirks of behaviour it’s useful to be aware of.
a. Key Events
Key events are generated by the operating system whenever a key changes state – that is, it’s pressed or released. If a key remains pressed for a long time, the OS will initiate additional key events.
Key events store extra parameters to allow you to access the keys that are currently down, as well as the key that has caused the event.
Convenience methods are also present to determine whether cross-platform keys like Ctrl on windows and Command on Mac are down. This is accessed by invoking isShortcutDown()
.
Operating System Settings
The following operating system settings are already provided, and do not need to be set inside JavaFX:
Target Selection
The node selected as the target of any key event is the currently focused Control
, or the Scene itself, if no Conrol
is present.
A scene can only have one focused node. Focus is defined by the Scene’s FocusModel
and a control can request focus using the method requestFocus()
.
Only a Control
can hold a scene’s focus.
Key Event Types
There are only three types of key event:
How to use KeyEvent
KeyEvents are easy to attach to any class that extends EventTarget (remembering that’s every control, chart, cell and shape).
Key events can be added using the convenience methods setOnKeyPressed()
, setOnKeyReleased()
or setOnKeyTyped()
, for example:
textField.setOnKeyPressed(event -> { //executable code inside lambda expression });
This will overwrite any previous behaviour set for that event type. Alternatively, they can be added by using the addEventFilter()
and addEventHandler()
methods:
textField.addEventHandler(KeyEvent.KEY_PRESSED, event -> { //executable code inside lambda expression });
Event filters are an excellent way to set key commands that you want to operate at the Window or Scene-level, regardless of which control has focus.
Behaviour
JavaFX handles key events fairly intelligently. THis means it attempts to minimise the key events that fire handlers throughout the scene. One example of this is typing inside an input control (TextField
, TextArea
and so on). Using non-special keys such as numbers and symbols, the key events are consumed after the target’s event handler.
This leaves space for parent nodes to control the behaviour of their children without setting off unnecessary event handlers for key events unlikely to have an effect on the wider scene.
This is different if any special keys are depressed. Here I’m talking about Ctrl, Alt, Shift, Esc, Enter, or Tab. It doesn’t matter whether they caused the event, but if they’re pressed at the time of the event, the subsequent key event will travel the entire depth of the scene graph.
a. Mouse Events
The MouseEvent
class defines any change in the mouse cursor position or button states. As with key events, they are provided by the operating systemto the application, alongside the default mouse behaviour expectations that have been set by the user.

Operating System Settings
The following operating system settings are already provided, and do not need to be set inside JavaFX:
Unlike with key events, which are automatically assigned to the focused node, the formal ‘target selection’ process for mouse events involves determining which nodes are underneath the mouse cursor.
Target Selection
In the case of mouse events, the uppermost node underneath the pixel the mouse is currently over is selected as the target regardless of where the scene is focused.
Mouse Event Types
There are 10 supported mouse events in JavaFX, alongside the standard ANY type.
Behaviour
Every node between the node and the scene will execute the event as if the mouse has clicked every pane beneath the node. However, this isn’t specific to mouse events, it’s just the standard way JavaFX handles events.
If a node is displaying outside the layout bounds of its parent, the mouse events will still traverse the scene graph through the parent rather than the obscured node underneath the mouse cursor.
Mouse events can be generated during touch events. To identify whether a mouse event was generated from a touch event, invoke isSynthesized()
on any MouseEvent
. For more info, see touch events.
a. Touch Events
JavaFX provides support for touch-sensitive screens on laptops, tablets and mobile devices, and can also generate mouse events in JavaFX.
Touch events like pressing, releasing and moving are supported. Currently the long-press mechanic doesn’t seem to be supported, so you’ll have to work out this mechanic yourself if you want to use it.
Operating System Settings
The following operating system settings are already provided, and do not need to be set inside JavaFX:
Target Selection
As with mouse events, the uppermost node underneath the pixel the mouse is currently over is selected as the target regardless of where the scene is focused.
Touch Event Types
There are four supported touch events in JavaFX, alongside the standard ANY type.
A little caution is advised with respect to stationary pulses (see Behaviour below).
How to use Touch Events
Touch points can be added in the same way that mouse events can, using four convenience methods (named according to convention such as setOnTouchMoved()
, or by adding event handlers and filters (see mouse events)
To add a long-press mechanic to a window, you can manually track touch length using only a few variables.
private boolean countingLongPress = false; private boolean preventLongPressMechanic = false; private long touchStarted; private long touchLength; private long LONG_PRESS_WAIT_TIME = 500000000; // 1 second = 1e9 nanoseconds public void addTouchListeners(Scene scene) { scene.addEventFilter(TouchEvent.TOUCH_PRESSED, event -> { if(event.getTouchCount() != 1) preventLongPressMechanic = true; if(!preventLongPressMechanic){ countingLongPress = true; touchStarted = System.nanoTime(); } }); scene.addEventFilter(TouchEvent.TOUCH_MOVED, event -> { touchLength = 0; countingLongPress = false; preventLongPressMechanic = true; //prevent additional actions until new event }); scene.addEventFilter(TouchEvent.TOUCH_STATIONARY, event -> { if(countingLongPress){ long currentTime = System.nanoTime(); touchLength = System.nanoTime() - touchStarted; } if(touchLength > LONG_PRESS_WAIT_TIME){ //execute long-press code by adding here touchLength = 0; countingLongPress = false; preventLongPressMechanic = true; //prevent additional actions until new event } }); scene.addEventFilter(TouchEvent.TOUCH_RELEASED, event -> { if(event.getTouchCount() == 1){ //last point being released countingLongPress = false; preventLongPressMechanic = false; } }); }
I’ve added this code to a Controller, and set the Scene using a convenience method I’ll call from whatever class launched my Window. This might be more sustainable on multiple scenes using dependency injection.
Behaviour
JavaFX tries to be helpful and translate touch events into mouse events. This maintains behaviour for apps that weren’t designed to include touch support and synthesises a tap into a click.
A MOUSE_PRESSED event is generated directly after a touch event ends, so if your used holds their finger on the screen, the mouse event on’t be created until they remove it.
TOUCH_STATIONARY will fire sporadically during gestures, so it’s not a 1:1 ratio between touch events and gesture events that are created. Touch events shouldn’t be used to handle gesture events.
a. Gesture Events
Gestures are part of JavaFX’s in-built support for touch-sensitive devices. There are four types of gesture: rotate, swipe, scroll and zoom.
Gesture events are specifically designed to support multi-touch devices. On a system where the only input devices are a mouse and a keyboard, Gesture events cannot be create (or at least aren’t created by default if unless you have some fancy-pants gesture synthesizer)
Operating System Settings
The following operating system settings are already provided, and do not need to be set inside JavaFX:
Target Selection
As with mouse events and touch events, JavaFX attempts to identify which node was picked by the gesture.
For multi-point touch gestures, the centre-point of all touch points is usually used as the pixel location.
For gestures that are completed on a trackpad, the mouse location at the time of the gesture is usually used.
Gesture Event Types
There are 13 types of specific gesture event in addition to the ANY event types. As gestures are so complicated, the ANY types exist in both the parent GestureEvent
class (GestureEvent.ANY
), or within specific gesture types (e.g. RotateEvent.ANY
).
Of course, GestureEvent.ANY
will select all gestures, while RotateEvent.ANY
will select all rotations (but not other gestures).
How to use Gesture Events
You can include them in your code by using convenience methods (e.g. setOnRotate()) or by adding event handlers like so:
scene.addEventFilter(RotateEvent.ROTATE, event -> { System.out.println("Rotated"); });
The nuances of gesture behaviour are a little too complex to comfortably explain in full here. If you’re interested, the documentation for gesture events is excellent, or Oracle’s tutorial is here.
Behaviour
Swipe and scroll behaviour are not mutually exclusive. A gesture intended by the user to create a scrolling action can also create a swipe event.
If the user is scrolling through a text element, using the system-defined values for text and page height is the method that JavaFX prefers:
switch(event.getTextDeltaYUnits()) { case LINES: // scroll about event.getTextDeltaY() lines break; case PAGES: // scroll about event.getTextDeltaY() pages break; case NONE: // scroll about event.getDeltaY() pixels break; }
JavaFX-originated Events
JavaFX also has the ability to create and dispatch its own events. There are three types of these events:
Each of these gives JavaFX the ability to layer functionality over the user interface.
a. Action Events
Action events are designed to span multiple input types, defining a single piece of executable code to use int he case of a specific user action. Controls such as the TextField and buttons have action events.
This layers functionality over the user interface in a way that can span multiple user inputs. An action, from the perspective of the JavaFX system, is some sort of affirmative user action. Examples of this are:
In all these cases, JavaFX provides support for taking actions based on consistent user expectations, such as submitting a form by pressing the Enter key on the last text field in a form, rather than having to click the button. In this way, we can use the same executable code – the same EventHandler – to manage both the text field and the Submit button in our app.
Target Selection
Action events are designed to span multiple input methods. Therefore, the target of the action event is the node that was clicked on, touched, or in focus (in the case of a key event).
Action Event Types
The ActionEvent
class only has one EventType
– ACTION
– which makes it sound like a Michael Bay film, but in reality it’s an accurate summary of everything an ActionEvent
can do. A user clicking on a button doesn’t have multiple states. It does one thing – commit to an action.
How to use Action Events
It’s most common to add action event handlers using the convenience method setOnAction()
. Multiple functionality is rarely required, so overriding other event handlers is less of a concern (see Input Events).
textField.setOnAction(event -> { //eevnt action defined with lambda });
As with any event, they can be set using the addEventHandler()
and addEventFilter()
methods.
Finally, they can be defined in FXML using the following syntax:
<TextField onAction="#handleTextCommit" fx:id="textField"/>
The “#” symbol is a flag to the FXMLLoader
to inject the method handleTextCommit(ActionEvent event)
into the onAction Property of the TextField
. The method handleTextCommit()
must be present in the controller (with the right ActionEvent
argument) or a load exception will be thrown.
Behaviour
Be aware that an action event and the input event that triggers them are not mutually exclusive. Therefore, as an action event fires, the associated key press, mouse click or touch is also fired.
Its not common to filter events using addEventFilter()
on higher nodes in the scene, but it is possible, as with any other type of event.
b. Worker State Events
Long-running tasks can easily be outsourced to workers such as Tasks or Services. It’s can be useful to fire events as these progress.
Conveniently, because event handlers are managed by JavaFX, these events are run on the Application thread, meaning direct updates to the interface are possible.
Tasks and Services both provide support for WorkerStateEvent
listeners to be added and fired at multiple stages of the task’s or service’s lifecycle.
Target Selection
Unlike input events, which are targetted in the foreground of the UI, Worker State Events do not target a node in the interface.
Regardless of which class defines the task or sets listeners, the Target of a Task is the class that invokes the run()
method on the task.
WorkerStateEvent Types
The WorkerStateEvent has 6 types in addition to the obligatory ANY.
Each of these corresponds to a separate stage of the lifecycle of the task.
How to use Worker State Events
Creating and using tasks is as simple as defining them and invoking the run() command. To add event handlers to a task, you can use the convenience methods available, or define them manually with addEventFilter()
or addEventHandler()
using EventHandlers or lambda expressions.
task.setOnRunning(event -> { //executable code });
Either way, event handlers should be defined before starting a task.
c. Edit, Modification and Sorting Events
These events span a number of uses, but they’re all responsible for updating complex UI elements that can’t be achieved through property binding.
Any modification to a UI component such as a TreeView
, TableView
or ListView
is done through Events.
If you are defining your own custom controls, Event management, in combination with strong property binding, can ensure that your UI is kept up to date without having to create custom Tasks, or flooding the UI thread.
Target Selection
The target of these events is defined by which control was in focus when the change occurred. If you’ve edited a tree, it might be the CheckBoxTreeItem
. If you’ve sorted a TableView
, it’s the TableView
.
Event Types
With the exception of the SortEvent
and ScrollToEvent
classes, each Event
is defined as an inner class within the control to which they’re useful.
How to use These Events
These can be filtered and handled in the usual way, using event handlers, or convenience methods, although a few general rules are helpful in applying them in the right place:
Sort events and scroll events should be tracked from the control, not the column (e.g. a TableView
not a TableColumn
).
Editing is best tracked by the column, not the parent control, although filters can be added to parents as usual.
Conclusions
Events are one of the most important parts of JavaFX. Almost all of executable code you need to update a user interface can be accomplished with events and property binding.
Events mediate the vast majority of interactions with the user. For anything more complex than binding the text content of a TextField
to a FilteredList
, events should be used to drive UI updates based on user interaction.
Even when a user’s typing, I use events preferentially over change listeners, because events are more stable, and you’re less likely to get multiple change events triggering for a single key stroke.
Finally, events are also good for mediating between complex background tasks and the user interface. The best way to get around a hanging UI is to use tasks, and to attach an event to the completion of that task.