Java Swing is the GUI toolkit that ships with the JDK. A Swing application follows the same pattern every time: you create components such as buttons and text fields, arrange them with a layout manager, and attach listeners that run code when the user clicks, types or moves the mouse. If those three pieces are clear, most desktop screens are straightforward to build.
This guide covers AWT vs Swing, the core components, the BorderLayout, FlowLayout, GridLayout and null layouts, the main listener types, and custom drawing with paintComponent. Examples target Java 17+.
AWT vs Swing: how the two toolkits relate
A GUI application is event-driven. Instead of running top to bottom like a console program, it builds a window and waits for events: a click, a key press, a resize. Java has two built-in toolkits for this.
AWT (java.awt) | Swing (javax.swing) | |
|---|---|---|
| Rendering | Heavyweight: each component wraps a native OS widget | Lightweight: components are drawn in Java |
| Look and feel | Matches the OS, cannot be changed | Pluggable (Metal, Nimbus, system look and feel, third-party themes) |
| Component names | Frame, Button, Label, TextField | JFrame, JButton, JLabel, JTextField |
| Component set | Basic | Richer: tables, trees, tabs, split panes, rich text |
| Still used for | Events, layout managers, Graphics, colors, fonts | The components themselves |
Swing builds on AWT rather than replacing it. Layout managers, event classes and the Graphics API all live in java.awt, so a typical Swing file imports both packages. What you should avoid is mixing AWT components (Button) with Swing components (JButton) in one window.
Your first Java Swing window and the Event Dispatch Thread
Swing is single-threaded. All component creation and updates must happen on one thread, the Event Dispatch Thread (EDT). Listeners already run on the EDT, but main does not, so the first thing a Swing program does is hand the UI setup to the EDT with SwingUtilities.invokeLater:
import javax.swing.*;
public class HelloSwing {
public static void main(String[] args) {
SwingUtilities.invokeLater(HelloSwing::createAndShowGui);
}
private static void createAndShowGui() {
var frame = new JFrame("Hello Swing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JLabel("Running on the EDT", SwingConstants.CENTER));
frame.setSize(400, 200);
frame.setLocationRelativeTo(null); // center on screen
frame.setVisible(true);
}
}Without EXIT_ON_CLOSE, closing the window only hides it and the JVM keeps running. Call setVisible(true) last, after all components are added.
Core Swing components
These cover most forms and tools:
| Component | Purpose |
|---|---|
JFrame | Top-level window with title bar and borders |
JPanel | Generic container for grouping components, each with its own layout |
JLabel | Read-only text or icon |
JButton | Clickable button, fires an ActionEvent |
JTextField / JPasswordField | Single-line input |
JTextArea | Multi-line text; wrap it in a JScrollPane |
JCheckBox, JRadioButton, JComboBox<T> | Choices |
JOptionPane | Ready-made message, confirm and input dialogs |
Every component can also set its mouse cursor with setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)). Other options include WAIT_CURSOR, TEXT_CURSOR and MOVE_CURSOR.
Layout managers in Java Swing
A layout manager decides the size and position of every component in a container, and recalculates when the window is resized or the font changes. That is why you should prefer layout managers over hard-coded pixel positions.
BorderLayout
BorderLayout splits a container into five regions: NORTH, SOUTH, EAST, WEST and CENTER. The edges get their preferred size and center takes the remaining space. It is the default layout of a JFrame's content pane, which is why frame.add(label) above filled the whole window: no region means CENTER. Each region holds one component, so to put several things in a region, group them in a JPanel first.
FlowLayout
FlowLayout places components left to right at their preferred size and wraps to a new row when it runs out of width, like words in a paragraph. It is the default for JPanel and works well for button bars: new FlowLayout(FlowLayout.RIGHT) right-aligns them.
GridLayout
GridLayout(rows, cols, hgap, vgap) divides the container into equal-sized cells and fills them in order, row by row. Every component is stretched to the cell size, so it suits calculator keypads (new GridLayout(4, 3, 4, 4)) and simple label-and-field forms where every cell can be the same size.
Null layout (absolute positioning)
With setLayout(null) there is no layout manager. You position each component yourself with setBounds(x, y, width, height), which combines setLocation and setSize:
var panel = new JPanel(null);
var title = new JLabel("Sign up");
title.setBounds(50, 30, 200, 25);
var email = new JTextField();
email.setBounds(50, 70, 250, 30);
panel.add(title);
panel.add(email);It looks simple, but nothing adapts when the window is resized, text gets clipped when the font or display scaling changes, and every change means recalculating coordinates. Keep it for special cases such as a drag-and-drop canvas. For complex forms, use GridBagLayout, BoxLayout or GroupLayout instead.
Nesting panels to build real screens
Real screens nest panels, each with the layout that suits its part of the screen. Here is a sign-up form built from three layouts:
import java.awt.*;
import javax.swing.*;
public class SignUpForm {
public static void main(String[] args) {
SwingUtilities.invokeLater(SignUpForm::createAndShow);
}
private static void createAndShow() {
var frame = new JFrame("Sign up");
var emailField = new JTextField(20);
var passwordField = new JPasswordField(20);
var fields = new JPanel(new GridLayout(2, 2, 8, 8));
fields.add(new JLabel("Email"));
fields.add(emailField);
fields.add(new JLabel("Password"));
fields.add(passwordField);
var submit = new JButton("Create account");
var buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
buttons.add(submit);
var root = new JPanel(new BorderLayout(0, 12));
root.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
root.add(new JLabel("Create your account"), BorderLayout.NORTH);
root.add(fields, BorderLayout.CENTER);
root.add(buttons, BorderLayout.SOUTH);
submit.addActionListener(e -> {
String email = emailField.getText().trim();
char[] password = passwordField.getPassword();
if (email.isEmpty() || password.length == 0) {
JOptionPane.showMessageDialog(frame, "Email and password are required.");
return;
}
JOptionPane.showMessageDialog(frame, "Welcome, " + email);
java.util.Arrays.fill(password, '\0'); // clear the password from memory
});
frame.getRootPane().setDefaultButton(submit); // Enter submits the form
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(root);
frame.pack(); // size the window to fit its contents
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}Handling events with Swing listeners
Swing's event model has three parts: a source (the button), an event object (ActionEvent) and a listener that you register with an addXxxListener method. One component can have many listeners, and one class can implement several listener interfaces.
ActionListener
ActionListener handles the component's main action: a button click, Enter in a JTextField, a menu item or combo box selection. It has a single method, so write it as a lambda, as the sign-up form shows.
MouseListener and MouseMotionListener
Mouse events are split across two interfaces. MouseListener covers mouseClicked, mousePressed, mouseReleased, mouseEntered and mouseExited. MouseMotionListener covers mouseMoved and mouseDragged. Neither works with a lambda, so extend MouseAdapter, which implements both with empty methods, and override only what you need, as the drawing panel below does.
A common bug is registering the adapter only with addMouseListener and wondering why mouseDragged never fires. Register the same object with addMouseMotionListener as well.
KeyListener and key bindings
KeyListener has keyPressed, keyReleased and keyTyped, with KeyAdapter as its adapter. It only receives events while its component has keyboard focus, so on a panel it often seems to do nothing. For shortcuts, key bindings (InputMap plus ActionMap) are more reliable because they can apply whenever the window is focused. The drawing example below uses one for Escape.
Use KeyListener when a focused component needs raw key events, such as a game panel that tracks which arrow keys are held down. Call setFocusable(true) and requestFocusInWindow() on that panel.
WindowListener
WindowListener reacts to window lifecycle events. The typical use is confirming before closing:
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int choice = JOptionPane.showConfirmDialog(
frame, "Discard unsaved changes?", "Quit", JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION) {
frame.dispose();
}
}
});Custom painting with paintComponent
Every Swing component draws itself through a Graphics object. To draw your own shapes, extend JPanel and override paintComponent. There are three methods to know:
paint()is the top-level AWT painting method. In Swing it also paints borders and children, so don't override it.paintComponent(Graphics g)is where your custom drawing goes. Callsuper.paintComponent(g)first so the background is cleared.repaint()asks Swing to repaint the component soon. Never callpaintComponentdirectly.
This panel draws a dot wherever you click or drag, and clears on Escape:
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
class DrawingPanel extends JPanel {
private final List<Point> points = new ArrayList<>();
DrawingPanel() {
setBackground(Color.WHITE);
var mouse = new MouseAdapter() {
@Override public void mousePressed(MouseEvent e) { addPoint(e.getPoint()); }
@Override public void mouseDragged(MouseEvent e) { addPoint(e.getPoint()); }
};
addMouseListener(mouse);
addMouseMotionListener(mouse);
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ESCAPE"), "clear");
getActionMap().put("clear", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
points.clear();
repaint();
}
});
}
private void addPoint(Point p) {
points.add(p);
repaint(); // schedule a redraw; Swing calls paintComponent for us
}
@Override
public Dimension getPreferredSize() {
return new Dimension(500, 400);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
var g2 = (Graphics2D) g.create();
try {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(new Color(0x2563EB));
for (var p : points) {
g2.fillOval(p.x - 4, p.y - 4, 8, 8);
}
} finally {
g2.dispose();
}
}
}Keep paintComponent fast and free of side effects, because Swing may call it many times. Store state in fields, change it in listeners, then call repaint().
Keeping the UI responsive with SwingWorker
Listeners run on the EDT, so a slow operation inside one freezes the whole window. Move file I/O, network calls and database queries to a SwingWorker, and update components only in done() or process(), which run back on the EDT:
loadButton.addActionListener(e -> {
loadButton.setEnabled(false);
new SwingWorker<String, Void>() {
@Override
protected String doInBackground() throws Exception {
return reportService.fetchReport(); // runs on a background thread
}
@Override
protected void done() { // runs on the EDT
try {
output.setText(get());
} catch (Exception ex) {
output.setText("Failed to load: " + ex.getMessage());
} finally {
loadButton.setEnabled(true);
}
}
}.execute();
});For more on threads, synchronization and executors outside the GUI, see Java threads and multithreading.
FAQ
Is Java Swing still used?
Yes. Swing ships with the JDK in the java.desktop module and is still maintained. It runs many IDEs, internal tools and long-lived enterprise desktop applications.
What is the difference between Swing and JavaFX?
JavaFX is a newer toolkit with CSS styling, FXML layouts and a scene graph, distributed separately from the JDK since Java 11. Swing needs no extra dependencies. They can be embedded in each other through JFXPanel and SwingNode.
Why doesn't my KeyListener work?
It only receives events while its component has keyboard focus, and a panel doesn't take focus on its own. Call setFocusable(true) and requestFocusInWindow(), or switch to key bindings with InputMap and ActionMap.
What are the default layouts of JFrame and JPanel?
A JFrame's content pane uses BorderLayout. A new JPanel uses FlowLayout. You can pass a different layout to the JPanel constructor or call setLayout.
Why do my components not show up?
Usually because they were added after setVisible(true) without calling revalidate() and repaint(), or because two components were added to the same BorderLayout region, in which case only the last one is shown.
Swing checklist
- Build and update all UI on the EDT, starting with
SwingUtilities.invokeLater. - Use the
Jcomponents consistently and don't mix in AWT components. - Nest
JPanels with the right layout for each part of the screen. Avoid null layout for forms. - Use lambdas for
ActionListenerand adapters for mouse, key and window listeners. - Prefer key bindings over
KeyListenerfor shortcuts. - Draw in
paintComponent, change state in listeners and callrepaint(). - Move slow work to
SwingWorker.
Swing code is ordinary object-oriented Java, so classes, objects and constructors and inheritance and the other OOP pillars make components and adapters easier to reason about. If you need a desktop tool or internal application built, Vectorkub builds custom software for teams.
