Java has two mainstream toolkits for desktop interfaces: Swing, which ships with the JDK and is the quickest way to get a window on screen, and JavaFX, which is a separate dependency with better styling and a cleaner API. This guide covers both, the layout managers that decide where things go, and the single threading rule that causes most GUI bugs.
Which toolkit#
| Swing | JavaFX | |
|---|---|---|
| Included in the JDK | yes | no, add a dependency |
| Styling | look-and-feel classes | CSS |
| Layout in markup | no | FXML |
| Animation and effects | limited | built in |
| Best for | learning, internal tools | new applications |
Start with Swing if you want a window running in the next five minutes. Choose JavaFX for anything you intend to maintain.
A Swing window#
import javax.swing.*;
import java.awt.*;
public class Counter {
public static void main(String[] args) {
SwingUtilities.invokeLater(Counter::createAndShow);
}
private static void createAndShow() {
JFrame frame = new JFrame("Counter");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("0", SwingConstants.CENTER);
label.setFont(label.getFont().deriveFont(48f));
JButton button = new JButton("Add one");
int[] count = {0};
button.addActionListener(e -> {
count[0]++;
label.setText(String.valueOf(count[0]));
});
frame.setLayout(new BorderLayout(10, 10));
frame.add(label, BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.setSize(320, 200);
frame.setLocationRelativeTo(null); // centre on screen
frame.setVisible(true);
}
}
The invokeLater call is not optional — see the threading section below. The array trick for count is a way around lambdas requiring effectively final variables; a field on a class is cleaner in real code.
The same in JavaFX#
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>21</version>
</dependency>
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CounterApp extends Application {
@Override
public void start(Stage stage) {
Label label = new Label("0");
label.setStyle("-fx-font-size: 48px;");
Button button = new Button("Add one");
int[] count = {0};
button.setOnAction(e -> label.setText(String.valueOf(++count[0])));
VBox root = new VBox(15, label, button);
root.setAlignment(Pos.CENTER);
stage.setScene(new Scene(root, 320, 200));
stage.setTitle("Counter");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
JavaFX styles with CSS, which is a large practical advantage if you have any web experience:
.button {
-fx-background-color: #2563eb;
-fx-text-fill: white;
-fx-padding: 8 16;
-fx-background-radius: 6;
}
Layout managers#
A layout manager decides where components go and how they resize. Positioning by pixel coordinates seems simpler and breaks the first time someone resizes the window or uses a different screen scaling.
| Swing | JavaFX | Arranges |
|---|---|---|
BorderLayout |
BorderPane |
five regions: centre plus four edges |
FlowLayout |
FlowPane |
left to right, wrapping |
GridLayout |
GridPane |
rows and columns |
BoxLayout |
VBox, HBox |
a single row or column |
GridBagLayout |
GridPane |
flexible grid, most control |
Nesting is normal — a BorderPane holding a VBox holding an HBox is a perfectly ordinary structure.
The threading rule#
All interface updates must happen on one specific thread — the Event Dispatch Thread in Swing, the JavaFX Application Thread in JavaFX. Touching a component from any other thread produces intermittent, unreproducible bugs.
The corollary is that you must not do slow work on that thread either, because while it is busy the interface cannot repaint. That is what “the application has frozen” means.
// Wrong - freezes the window for five seconds
button.addActionListener(e -> {
String result = slowNetworkCall();
label.setText(result);
});
// Right - Swing
button.addActionListener(e -> {
button.setEnabled(false);
new SwingWorker<String, Void>() {
@Override
protected String doInBackground() {
return slowNetworkCall(); // background thread
}
@Override
protected void done() {
try {
label.setText(get()); // back on the EDT
} catch (Exception ex) {
label.setText("Failed: " + ex.getMessage());
} finally {
button.setEnabled(true);
}
}
}.execute();
});
// Right - JavaFX
Task<String> task = new Task<>() {
@Override
protected String call() {
return slowNetworkCall();
}
};
task.setOnSucceeded(e -> label.setText(task.getValue()));
task.setOnFailed(e -> label.setText("Failed"));
new Thread(task).start();
To update the interface from arbitrary background code, hand the work back to the right thread:
SwingUtilities.invokeLater(() -> label.setText("done")); // Swing
Platform.runLater(() -> label.setText("done")); // JavaFX
Common controls#
// Swing
JTextField field = new JTextField(20);
JCheckBox check = new JCheckBox("Remember me");
JComboBox<String> combo = new JComboBox<>(new String[]{"A", "B"});
JTable table = new JTable(data, columnNames);
JScrollPane scroll = new JScrollPane(table);
int answer = JOptionPane.showConfirmDialog(frame, "Delete this?");
// JavaFX
TextField field = new TextField();
CheckBox check = new CheckBox("Remember me");
ComboBox<String> combo = new ComboBox<>();
TableView<Person> table = new TableView<>();
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Delete this?");
alert.showAndWait();
JavaFX properties and binding#
StringProperty name = new SimpleStringProperty("Ana");
Label label = new Label();
label.textProperty().bind(name);
name.set("Sam"); // the label updates on its own
This is JavaFX’s main advantage over Swing. Bind the interface to your data once and stop writing update code entirely — the same idea that makes web frameworks appealing.
Questions people ask#
Is Swing obsolete?
It is in maintenance rather than active development, but it still works, ships with the JDK, and there is a great deal of existing code using it. Fine for internal tools and for learning.
Why is JavaFX not in the JDK?
It was separated out from Java 11 so it could release on its own schedule. Add it as a dependency, or use a build of the JDK that bundles it.
Should I use FXML or build the interface in code?
FXML separates layout from logic and works with Scene Builder, a visual designer. Code is easier to refactor and keeps everything in one language. Both are common.
What about desktop applications in general?
For a cross-platform desktop application today, JavaFX is a reasonable choice, particularly if your team already knows Java. Electron and Tauri are the main alternatives if web skills are stronger.
Where to go next#
- Java basics — the language underneath.
- Concurrency and multithreading — the threading rule in context.
- Object-oriented programming in Java — components are objects.