Whether you’re importing files, copying links, or pasting content, many users expect to be able to do this using a drag-and-drop gesture on top of the usual Ctrl-O and click-me load buttons.

Luckily, JavaFX has comprehensive support for dragging and dropping both into and out of your applications.
That’s where this article comes in. This article has three apps that demonstrate the drag and drop functionality bundled with JavaFX. Each links to the source code at GitHub, so you can get off the ground quickly.

Save, Share, Grow
Part of my workflow for creating new apps is having a library of reusable code I can (no pun intended) drag and drop into a new project. I’m hoping by creating these articles that you can benefit from those snippets too.
So if it’s useful, save it, come back to it later, and if you like the content, feel free to share. It really helps the website grow 💕.
What’s in this article
- App 1: Drag files into a JavaFX app
- App 2: Drag files out of a JavaFX app
- App 3: Dragging and dropping objects around inside a JavaFX app
What sort of functionality you’ll achieve
By the end of the article, you’ll have reusable code you can deploy immediately. More importantly, you’ll know how the JavaFX drag and drop functionality works, so you can drag files into your apps, export data, and even transfer data between controllers.
How you’ll do it.
Each of the apps is going to be structured in a similar way with a view defined in FXML and the behaviour in Java inside a controller.

Each app is made up of a Pane, with either a draggable shape or a display area, and a title describing how the user should interact with the application.

Then in each case, we’ll go through how to implement the dragging behaviours above to get the desired functionality.
Whenever you drag files over an application window, the system makes some information available to that application about what’s being dragged. That information is called the dragboard.
JavaFX’s drag and drop support comes in the form of a Dragboard
Java object, which is a specialised extension of the Clipboard
object. It is therefore possible to populate the ‘dragboard’ with the same types of data that can exist on the clipboard:
- A file, or list of files
- HTML code, formatted as a string
- Images
- Text
- Rich text (RTF)
- URLs, encoded as a string
On top of that, you can store any data type you want as long as it’s serializable, although that’s not what we’re here to do.
The impressive thing here is that not only can you do this inside your application, you can also take data from the system dragboard, and push data to the system dragboard to interact with the system itself.
This article takes you through how to do all of these things, although you’ll probably not need them all in one application.
App 1: Dragging Files into JavaFX

Alright, this App’s going to be simple, but it’s going to let us drag text files into a JavaFX app and display them in a TextArea
. We could do it with files, or text, or any of the other supported data types. But text will do.
Dragging things in, then…

…displaying the contents

It’s actually pretty easy to do. Objects like files and links can be dragged into a JavaFX application in two steps:
- Activate a Node to allow data to be transferred by setting an action using
node.setOnDragOver()
. - Setting an action using
node.setOnDragDropped()
that detects the files being dragged and displays them inside the app.
Both of these involve using information inside a DragEvent
object, which I’ll show you how to use in each step below.
Before we start, here’s the basic shape of the application – the FXML. The only active part will be the TextArea
, which has an fx:id
attribute so we can refer to it from the controller.
<VBox xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.edencoding.controllers.DragInExample"> <HBox alignment="CENTER" prefHeight="50.0" prefWidth="200.0" style="-fx-background-color: white;"> <Label text="Drag Something from your Desktop" textAlignment="CENTER" wrapText="true"> <font> <Font name="System Bold" size="12.0" /> </font> </Label> </HBox> <StackPane BorderPane.alignment="CENTER"> <TextArea fx:id="textArea" prefHeight="200.0" prefWidth="200.0" /> </StackPane> </VBox>
1. Activating a Node to allow data to be transferred
Allowing data to be dragged into a JavaFX application doesn’t just let you decide how you want it to be transferred. It also lets you give the user feedback about how you want that data to be transferred. Like when you hold the Ctrl
key down and a “+” appears next to the mouse cursor.
It’s the same in JavaFX. The available transfer modes are:
TransferMode.ANY; TransferMode.COPY; TransferMode.MOVE; TransferMode.COPY_OR_MOVE; TransferMode.LINK; TransferMode.NONE; //do not accept data
TransferMode.NONE
is great for turning off drag and drop when you’re not looking for user input.
Here, we just want to use TransferMode.MOVE
. To activate a node and let it accept drag events, we need to invoke setOnDragOver()
, which will be fired when a drag event is detected inside the bounds of the node.
We’ll do this in the initialize() method of the controller.
public void initialize() { node.setOnDragOver(event -> { event.acceptTransferModes(TransferMode.MOVE); }); }
I’m not joking when I say that is literally it. The node will now see and accept move-based drag events. Now we want to be able to do something with the data it sees.
2. Accepting data into the JavaFX application
Now we’ve got a TextArea
that’s listening to drag events, we need to tell it what to do when something gets dropped. To do that, we’ll invoke node.setOnDragDropped()
.
When a user drops something onto the node, we just need to check whether the dropped contents has any files. If it does, we’ll load a file. For the sake of this example, if they load multiple files, we’ll just load the first file in the list.
node.setOnDragDropped(event -> { Dragboard db = event.getDragboard(); if(event.getDragboard().hasFiles()){ //load the file } });
I always prefer to load files asynchronously. If it’s a big file, it could take some time. Ideally we’d also give the user some feedback like a wait icon, but for now I’ll just take some code from when I created a text editor for a Task
that will load in our file.
Here’s some compatible code that executes inside the drag-and-drop app.
If you want to know the full context of how to load files and structure text editors in JavaFX, check out this the full article on how to read a text file into a JavaFX application.
private Task<String> fileLoaderTask(File fileToLoad){ //Create a task to load the file asynchronously Task<String> loadFileTask = new Task<>() { @Override protected String call() throws Exception { BufferedReader reader = new BufferedReader(new FileReader(fileToLoad)); //Use Files.lines() to calculate total lines - used for progress long lineCount; try (Stream<String> stream = Files.lines(fileToLoad.toPath())) { lineCount = stream.count(); } //Load in all lines one by one into a StringBuilder separated by "\n" - compatible with TextArea String line; StringBuilder totalFile = new StringBuilder(); long linesLoaded = 0; while((line = reader.readLine()) != null) { totalFile.append(line); totalFile.append("\n"); updateProgress(++linesLoaded, lineCount); } return totalFile.toString(); } }; //If successful, update the text area, display a success message and store the loaded file reference loadFileTask.setOnSucceeded(workerStateEvent -> { try { textArea.setText(loadFileTask.get()); } catch (InterruptedException | ExecutionException e) { textArea.setText("Could not load file from:\n " + fileToLoad.getAbsolutePath()); } }); //If unsuccessful, set text area with error message and status message to failed loadFileTask.setOnFailed(workerStateEvent -> { textArea.setText("Could not load file from:\n " + fileToLoad.getAbsolutePath()); }); return loadFileTask; }
Now we just need to execute it from our drag listener, which we’ll also put in the controller’s initialize()
method.
node.setOnDragDropped(event -> { Dragboard db = event.getDragboard(); if(event.getDragboard().hasFiles()){ File fileToLoad = db.getFiles().get(0); //get files from dragboard Task loadFiles = fileLoaderTask(); //create asynch task to load files loadFiles.run(); //load file in } });
It’s literally that easy. Just create a Main class that loads your FXML and you’re good to go!
All the code for this app is on my GitHub here. Feel free to steal with pride, and share it if you’ve found it useful.
.
App 2: Dragging files from a JavaFX app

Dragging files out of a JavaFX application is almost as simple, except we do everything in reverse.
Data can be dragged out of a JavaFX application in two steps:
- Activate a node to start drag event using
node.startDragAndDrop()
. This must be initiated inside the drag listenernode.setOnDragDetected()
. - Use the listeners for starting the drag action to push the data into the dragboard, ready for transfer. If it’s a file, save it to a temporary location like you would when dragging out of the window.
Here I’m going to create an app that lets you drag the contents of a text area onto the desktop to save as a file. You could do it with a bunch of other formats, even images, but this works for the example.
1 & 2. Activate a node to start a drag event and push data to the dragboard
The difference between the “dragging files in” app and this one is that once the mouse drags across a node in this app, we actually want to start the dragboard up ourselves. We can do that on any node by invoking node.startDragAndDrop()
. We must do this inside the scope of node.setOnDragDetected()
.
Then, once we’ve started the drag and drop action, we’re going to save a temporary file to disk, and then instruct the dragboard to move that file when the drag gets dropped.
I’m going to drop all of the code in the initialize()
method of the controller:
public void initialize() { textArea.setOnDragDetected(event -> { //1. Start up the drag and drop Dragboard db = textArea.startDragAndDrop(TransferMode.MOVE); //2. Save the file to disk File tempFile = new File("Output.txt"); try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) { writer.write(textArea.getText()); } catch (IOException e) { textArea.setText("Unable to transfer data to dragboard"); } //3. Instruct the dragboard to move the file when the drag's dropped ClipboardContent content = new ClipboardContent(); content.putFiles(Collections.singletonList(tempFile)); db.setContent(content); }); }
And that’s it! The Output.txt
file you created will be moved by the system, so you don’t need to worry about deleting it.
If the drag gets cancelled, you may want to use the node’s setOnDragDone()
method to check whether your temp file is still there and remove it.
All the code for this app is on my GitHub here. Feel free to steal with pride, and share it if you’ve found it useful.
App 3: Dragging objects and data between windows

I spot apps all the time where you need to be able to drag files or data between windows. Every time you drag an attachment from an email into a new mail, that’s exactly what you’re doing.
Any custom data can be transferred between controllers using the drag board in four steps:
- Activate a node in the source window to start drag event using
sourceNode.startDragAndDrop()
. This is must be initiated inside the drag listenersourceNode.setOnDragDetected()
. - Use the listeners for starting the drag action to push the custom data into the dragboard, ready for transfer. If it’s a file, save it to a temporary location like you would when dragging out of the window.
- Activate a node in the target window to allow data to be transferred by setting an action using
targetNode.setOnDragOver()
. - Pull the custom data from the dragboard and display it in the target window
1 & 2. Activate a source node and push data to the drag board
Activating a source node to initiate drag events is just as easy as it was when we were dragging objects out of the window above. Again, we’ll start the drag and drop event, and as we’re dragging the text out of the text area, we can push the text area’s contents into dragboard.
We’ll do this in the initialize()
method of the controller.
public void initialize() { textArea.setOnDragDetected(event -> { //1. Start the drag and drop Dragboard db = textArea.startDragAndDrop(TransferMode.MOVE); //2. Push the text area's content to the dragboard ClipboardContent content = new ClipboardContent(); content.putString(textArea.getText()); db.setContent(content); //3. Clear the text area textArea.setText(""); }); }
Because I’ve used TransferMode.MOVE
I thought it was sensible to clear the text area. But you could equally use TransferMode.COPY
and keep it there too.
3. Activate a target node to allow drag events
Just like above, activating a target node is just as simple as when we were dragging files into the app before.
textArea.setOnDragOver(event -> { event.acceptTransferModes(TransferMode.MOVE); });
Put that in the controller’s initialize()
method, right next to the instructions for textArea.setOnDragDetected()
.
Now, all that’s left is that once something (our text) gets dropped, we want to accept the move and pull the data.
4. Pull the custom data from the dragboard and display it in the target window
Opening a node to drag events means it’s open to all drag events. So before we pull the data in from the dragboard, we need to check the right sort of data is there. We’ll do this using the dragboard’s hasString()
method.
Once we’re confident the dragboard contains data we can use, it’s really easy to pull the data in from the dragboard using it’s getString()
method.
textArea.setOnDragDropped(event -> { Dragboard db = event.getDragboard(); if (db.hasString()) { textArea.setText(db.getString()); } });
Again, this should go in the controller’s initialize()
method.
And that’s it!
A quick note that depending on the type of data you are transferring, you’ll need to slightly modify your module-info.java
file.
When I did this, I got an error that my controllers weren’t being opened to javafx.base. I have a specific folder for my controllers underneath my package name because it helps to organise the code.
Project │ ├───src │ └───main │ ├───java │ │ │ module-info.java │ │ │ │ │ └───com │ │ └───edencoding │ │ │ App.java │ │ │ │ │ └───controllers │ │ DragTextBetweenWindows.java │ │ │ └───resources │ └───com │ └───edencoding │ ├───fxml │ │ dragTextBetweenWindows.fxml │ │ │ └───images │ EdenCodingIcon.png │ └───target //build output
I think the dragboard support runs partly through reflection, so in this case the javafx.base
module needs access to the controller. So I needed to add the following line to your module-info.java
file.
module your.package { requires javafx.controls; requires javafx.fxml; requires java.desktop; opens your.package.controllers to javafx.fxml, javafx.base; exports your.package; }
Extra functionality
JavaFX’s drag and drop functionality is actually so much more comprehensive than just text and files. In fact, you can push any objects you want to the dragboard as long as they’re serializable.
A trick I sometimes use is to push a serializable lookup or index to the dragboard, and use a helper class to move non-serializable objects like nodes between controllers.
You can see an example of that in the GitHub here.
Conclusions
JavaFX has phenomenal functionality for supporting drag and drop applications. In fact, you can transfer both standard and completely custom data objects into, out of, or between windows.
JavaFX’s dragboard support works through its scene graph, so every example involves activating one or more nodes to drag events using the node’s setOnDragStarted() and setOnDragOver() methods.
JavaFX supports transferring files, text, URLs and serializable custom objects, meaning you can transfer significant chunks of data by dragging. When that’s not enough, you can always store non-serializable data objects in a helper class, and pass a serializable reference (like an integer, to look up the object later) into the dragboard.