Introduction

Java Swing is a graphical user interface toolkit included in the Java Development Kit. It allows developers to build cross-platform desktop applications using standard Java.

Swing provides many commonly used desktop components, including:

  • Windows
  • Buttons
  • Text fields
  • Drop-down lists
  • Tables
  • Dialog boxes
  • Menu bars
  • File selectors

Although many modern applications are developed as web applications, Swing is still used in mature enterprise desktop systems, internal business applications, engineering software, development tools, and applications that need to work offline.

In this post, I will first introduce the fundamental concepts and commonly used APIs in Java Swing. After that, I will use these components to build a simplified Residential Energy Rating Tool.

The energy calculation in this project is only for learning purposes. It is not an official NatHERS, FirstRate5, or regulatory energy assessment model.

Why Learn Java Swing?

Java Swing is particularly useful in the following situations:

  1. The application must run directly on a desktop operating system.
  2. The application needs to work without an internet connection.
  3. The project already has an existing Java Swing codebase.
  4. The system requires long-term stability and maintainability.
  5. The application needs access to local files or operating system resources.
  6. The development team wants to use Java for both business logic and the desktop interface.

Basic Structure of a Swing Application

A typical Swing application contains one main window and several child components.

1
2
3
4
5
6
7
8
JFrame
└── JPanel
├── JLabel
├── JTextField
├── JComboBox
├── JCheckBox
├── JButton
└── JTable

The most important Swing classes include:

Class Purpose
JFrame Represents the main application window
JPanel Groups and organizes components
JLabel Displays text or images
JTextField Accepts single-line text input
JTextArea Accepts multi-line text input
JButton Performs an action when clicked
JCheckBox Represents a true or false option
JRadioButton Allows selection from a group of options
JComboBox Displays a drop-down list
JSpinner Accepts values within a defined range
JTable Displays tabular data
JScrollPane Adds scrolling support
JOptionPane Displays messages and confirmation dialogs
JFileChooser Allows users to select files

Creating The First Swing Window

The following example creates a basic desktop window.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.swing.*;

public class HelloSwing {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Hello Swing");
frame.add(new JLabel("Hello, Java Swing!"));

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// set size of the window
// frame.setSize(300, 200);
// set the absolute location to the window
// frame.setLocation(300, 300);
// set the window to the center of the screen
// frame.setLocationRelativeTo(null);
frame.setBounds(100, 100, 450, 300);
frame.setVisible(true);
});
}
}

The application performs the following steps:

  1. Creates a JFrame object, which represents the main window.
  2. Adds a JLabel component to the window, which displays the text “Hello, Java Swing!”.
  3. Sets the default close operation to exit the application when the window is closed.
  4. Sets the size and position of the window using setBounds(). You can also use setSize() and setLocation() to set the size and position separately, or use setLocationRelativeTo(null) to center the window on the screen.
  5. Makes the window visible by calling setVisible(true).

The Event Dispatch Thread (EDT)

Swing uses a special thread called the Event Dispatch Thread, commonly abbreviated as EDT.
The EDT is responsible for:

  • Creating Swing components
  • Updating the interface
  • Handling button clicks
  • Handling keyboard events
  • Processing mouse events
  • Repainting the application window

Swing components are generally not thread-safe. Therefore, Swing interfaces should be created and updated on the EDT.

The recommended application entry point is:

1
2
3
4
5
6
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("My Application");
frame.setVisible(true);
});
}

SwingUtilities.invokeLater() places the interface creation task on the Event Dispatch Thread.

Commonly Used Swing Components

JFrame

JFrame represents the main application window.

1
2
3
4
5
JFrame frame = new JFrame("Residential Energy Rating Tool");
frame.setSize(900, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);

Commonly used methods include:

Method Description
setTitle() Changes the window title
setSize() Sets the window width and height
setMinimumSize() Sets the minimum window size
setDefaultCloseOperation() Defines the close behaviour
setLocationRelativeTo(null) Centers the window
setResizable() Controls whether resizing is allowed
setVisible(true) Displays the window
pack() Calculates the window size from its components

In most applications, pack() is preferred over a hard-coded size because it calculates the required window size automatically.

1
2
3
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);

JPanel

JPanel is a container used to group related components.

1
2
3
JPanel panel = new JPanel();
panel.add(new JLabel("Floor Area:"));
panel.add(new JTextField(10));

A panel can also use a specific layout manager.

1
JPanel panel = new JPanel(new BorderLayout());

Panels are commonly used to separate a page into logical sections, such as:

  • Header area
  • Input form
  • Result panel
  • Button area
  • Table area

JLabel

JLabel displays text, icons, or images.

1
JLabel titleLabel =new JLabel("Residential Energy Rating Tool");

A label can be aligned horizontally.

1
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);

The font can also be customized.

1
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 24));

Labels are commonly used for:

  • Form field names
  • Page titles
  • Result values
  • Instructions
  • Validation messages

JTextField

JTextField accepts a single line of text input.

1
JTextField areaField = new JTextField(15);

The number passed to the constructor represents the preferred number of visible columns.
Reading a value:

1
String areaText = areaField.getText();

Removing spaces:

1
String areaText = areaField.getText().trim();

Converting the value to a number:

1
double floorArea =Double.parseDouble(areaField.getText().trim());

Clearing the field:

1
areaField.setText("");

Setting an initial value:

1
areaField.setText("150");

JTextArea

JTextArea accepts multi-line input.

1
JTextArea notesArea =new JTextArea(6, 30);

Enable line wrapping:

1
2
notesArea.setLineWrap(true);
notesArea.setWrapStyleWord(true);

A text area should normally be placed inside a JScrollPane.

1
JScrollPane scrollPane =new JScrollPane(notesArea);

JButton

JButton creates a clickable button.

1
JButton calculateButton =new JButton("Calculate");

A button click is handled using an ActionListener.

1
2
3
calculateButton.addActionListener(event -> {
System.out.println("Calculate button clicked");
});

For better readability, the action can call a separate method.

1
calculateButton.addActionListener(event -> calculateRating());

This is better than placing a large amount of business logic directly inside the listener.

JCheckBox

JCheckBox represents an option that can be selected or cleared.

1
JCheckBox insulationCheckBox =new JCheckBox("Property has insulation");

Check whether the option is selected:

1
boolean hasInsulation =insulationCheckBox.isSelected();

Select or clear the checkbox programmatically:

1
2
insulationCheckBox.setSelected(true);
insulationCheckBox.setSelected(false);

Checkboxes are suitable for independent boolean options.

Examples include:

  • Has insulation
  • Has solar panels
  • Include archived records
  • Enable notifications

JRadioButton

JRadioButton is normally used when the user must choose one option from a group.

1
2
3
JRadioButton houseButton =new JRadioButton("House");

JRadioButton apartmentButton =new JRadioButton("Apartment");

The buttons must be placed inside a ButtonGroup.

1
2
3
4
ButtonGroup propertyTypeGroup =new ButtonGroup();

propertyTypeGroup.add(houseButton);
propertyTypeGroup.add(apartmentButton);

Set a default selection:

1
houseButton.setSelected(true);

Read the selected option:

1
2
3
if (houseButton.isSelected()) {
System.out.println("House selected");
}

JComboBox

JComboBox displays a drop-down list.

1
2
3
4
5
6
7
8
JComboBox<String> heatingComboBox =
new JComboBox<>(
new String[]{
"Electric Resistance",
"Gas Heating",
"Heat Pump"
}
);

Read the selected value:

1
String heatingSystem =(String) heatingComboBox.getSelectedItem();

Select an item by index:

1
heatingComboBox.setSelectedIndex(0);

Listen for selection changes:

1
2
3
4
heatingComboBox.addActionListener(event -> {
String selectedValue =(String) heatingComboBox.getSelectedItem();
System.out.println(selectedValue);
});

JSpinner

JSpinner is useful when the user must enter a value within a defined range.

1
2
3
4
5
6
7
8
9
JSpinner occupantSpinner =
new JSpinner(
new SpinnerNumberModel(
2,
1,
20,
1
)
);

The values represent:

1
2
3
4
Initial value: 2
Minimum value: 1
Maximum value: 20
Step size: 1

Read the current value:

1
int occupants =(Integer) occupantSpinner.getValue();

Change the value:

1
occupantSpinner.setValue(4);

A spinner is often safer than a text field for bounded numeric input.

JOptionPane

JOptionPane provides simple dialog boxes.
Information dialog

1
JOptionPane.showMessageDialog(frame,"Calculation completed.");

Error dialog

1
JOptionPane.showMessageDialog(frame,"Please enter a valid floor area.","Invalid Input",JOptionPane.ERROR_MESSAGE);

Warning dialog

1
2
3
4
5
6
JOptionPane.showMessageDialog(
frame,
"The selected file already exists.",
"Warning",
JOptionPane.WARNING_MESSAGE
);

Confirmation dialog

1
2
3
4
5
int result = JOptionPane.showConfirmDialog(frame,"Do you want to clear the form?","Confirmation",JOptionPane.YES_NO_OPTION);

if (result == JOptionPane.YES_OPTION) {
clearForm();
}

Input dialog

1
String propertyName =JOptionPane.showInputDialog(frame,"Enter the property name:");

JTable

JTable displays data in rows and columns.

The table itself displays the data, while a table model manages the underlying values.

1
2
3
4
5
6
7
8
9
10
11
String[] columns = {
"Area",
"Occupants",
"Heating",
"Energy Usage",
"Rating"
};

DefaultTableModel tableModel =new DefaultTableModel(columns, 0);

JTable table = new JTable(tableModel);

Add a new row:

1
tableModel.addRow(new Object[]{150,3,"Heat Pump","9,800 kWh/year","5 Stars"});

Remove a row:

1
2
3
4
5
int selectedRow = table.getSelectedRow();

if (selectedRow >= 0) {
tableModel.removeRow(selectedRow);
}

Read a cell value:

1
Object value =tableModel.getValueAt(0, 2);

A table should normally be placed inside a scroll pane.

1
JScrollPane tableScrollPane =new JScrollPane(table);

Disable direct cell editing:

1
2
3
4
5
6
7
8
9
10
DefaultTableModel tableModel = new DefaultTableModel(columns, 0) {

@Override
public boolean isCellEditable(
int row,
int column
) {
return false;
}
};

JScrollPane

JScrollPane adds horizontal or vertical scrolling to another component.

It is commonly used with:

  • JTable
  • JTextArea
  • JList
  • Large panels
  • Images

Example:

1
2
3
JTextArea textArea =new JTextArea(10, 40);

JScrollPane scrollPane =new JScrollPane(textArea);

JMenuBar

JMenuBar creates an application menu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
JMenuBar menuBar = new JMenuBar();

JMenu fileMenu = new JMenu("File");

JMenuItem newItem = new JMenuItem("New");
JMenuItem saveItem = new JMenuItem("Save");
JMenuItem exitItem = new JMenuItem("Exit");

fileMenu.add(newItem);
fileMenu.add(saveItem);
fileMenu.addSeparator();
fileMenu.add(exitItem);

menuBar.add(fileMenu);

frame.setJMenuBar(menuBar);

Handle a menu action:

1
exitItem.addActionListener(event -> System.exit(0));

JFileChooser

JFileChooser allows the user to select a file or directory.

Open a file

1
2
3
4
5
6
7
8
JFileChooser fileChooser =new JFileChooser();

int result =fileChooser.showOpenDialog(frame);

if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile =fileChooser.getSelectedFile();
System.out.println(selectedFile.getAbsolutePath());
}

Save a file

1
2
3
4
5
int result =fileChooser.showSaveDialog(frame);

if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile =fileChooser.getSelectedFile();
}

Swing Layout Managers

Layout managers control the position and size of Swing components.

Using layout managers is recommended because they can automatically adjust the interface when:

  • The window is resized
  • The font size changes
  • The operating system changes
  • Text labels have different lengths
  • Components require different sizes

Common layout managers include:

Layout Manager Typical Use
BorderLayout Main application structure
FlowLayout Buttons and simple horizontal groups
GridLayout Equal-sized rows and columns
GridBagLayout Complex forms
BoxLayout Horizontal or vertical groups
CardLayout Multiple pages or views

BorderLayout

BorderLayout divides a container into five regions.

1
2
3
      NORTH
WEST CENTER EAST
SOUTH

Example:

1
2
3
4
5
JPanel mainPanel =new JPanel(new BorderLayout());

mainPanel.add(headerPanel, BorderLayout.NORTH);
mainPanel.add(contentPanel, BorderLayout.CENTER);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);

Only one component can be placed directly in each region. To place multiple components in one region, put them inside another panel.

FlowLayout

FlowLayout arranges components from left to right.

1
2
3
4
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));

buttonPanel.add(calculateButton);
buttonPanel.add(clearButton);

It is commonly used for:

  • Button groups
  • Toolbars
  • Simple input rows
  • GridLayout

GridLayout places components into equal-sized cells.

1
2
3
4
5
6
7
JPanel panel =new JPanel(new GridLayout(2, 2, 10, 10));

panel.add(new JLabel("Name:"));
panel.add(new JTextField());

panel.add(new JLabel("Email:"));
panel.add(new JTextField());

The parameters represent:

1
2
3
4
Rows: 2
Columns: 2
Horizontal gap: 10
Vertical gap: 10

GridBagLayout

GridBagLayout is more complex but provides better control for business forms.

1
2
3
4
5
6
7
8
9
10
JPanel formPanel =new JPanel(new GridBagLayout());

GridBagConstraints constraints =new GridBagConstraints();

constraints.insets =new Insets(5, 5, 5, 5);

constraints.gridx = 0;
constraints.gridy = 0;

formPanel.add(new JLabel("Floor Area:"),constraints);

Add the input field in the next column:

1
2
3
constraints.gridx = 1;

formPanel.add(new JTextField(15),constraints);

Important GridBagConstraints properties include:

Property Purpose
gridx Column position
gridy Row position
gridwidth Number of columns occupied
gridheight Number of rows occupied
weightx Horizontal resizing weight
weighty Vertical resizing weight
fill Controls component expansion
anchor Controls component alignment
insets Adds external spacing

BoxLayout

BoxLayout arranges components vertically or horizontally.

Vertical layout:

1
2
3
JPanel panel = new JPanel();

panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));

Horizontal layout:

1
panel.setLayout(new BoxLayout(panel,BoxLayout.X_AXIS));

Add spacing:

1
panel.add(Box.createVerticalStrut(10));

CardLayout

CardLayout allows multiple panels to share the same display area.

1
2
3
4
5
6
CardLayout cardLayout =new CardLayout();

JPanel cardPanel =new JPanel(cardLayout);

cardPanel.add(loginPanel, "login");
cardPanel.add(dashboardPanel, "dashboard");

Switch between panels:

1
cardLayout.show(cardPanel,"dashboard");

It is useful for:

  • Login and dashboard pages
  • Multi-step forms
  • Wizard interfaces
  • Settings pages
  • Applications with multiple screens

Event Handling

Swing applications use an event-driven programming model.

An event occurs when the user performs an action, such as:

  • Clicking a button
  • Selecting a menu item
  • Typing in a text field
  • Changing a drop-down selection
  • Selecting a table row
  • Closing a window
  • Pressing a keyboard key

ActionListener

ActionListener handles actions from buttons, menu items, and some other components.

1
button.addActionListener(event -> {System.out.println("Button clicked");});

The traditional anonymous class syntax is:

1
2
3
4
5
6
7
8
9
10
11
12
button.addActionListener(
new ActionListener() {
@Override
public void actionPerformed(
ActionEvent event
) {
System.out.println(
"Button clicked"
);
}
}
);

The lambda version is shorter and easier to read.

ItemListener

ItemListener can detect checkbox and selection state changes.

1
2
3
4
5
6
insulationCheckBox.addItemListener(event -> {
boolean selected =event.getStateChange()== ItemEvent.SELECTED;
System.out.println(
"Selected: " + selected
);
});

MouseListener

MouseListener handles mouse-related actions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
table.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(
MouseEvent event
) {
int selectedRow =
table.getSelectedRow();
System.out.println(
"Selected row: "
+ selectedRow
);
}
}
);

KeyListener

KeyListener handles keyboard events.

1
2
3
4
5
6
7
8
9
10
11
textField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(
KeyEvent event
) {
System.out.println(
textField.getText()
);
}
}
);

For text changes, a DocumentListener is usually more reliable than a KeyListener.

DocumentListener

DocumentListener detects changes in a text component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
textField.getDocument().addDocumentListener(new DocumentListener() {

@Override
public void insertUpdate(
DocumentEvent event
) {
handleTextChange();
}

@Override
public void removeUpdate(
DocumentEvent event
) {
handleTextChange();
}

@Override
public void changedUpdate(
DocumentEvent event
) {
handleTextChange();
}

});

Input Validation

A desktop application should never assume that user input is valid.

For example, when reading a floor area:

1
2
3
4
5
6
7
String areaText =areaField.getText().trim();

if (areaText.isEmpty()) {
throw new IllegalArgumentException(
"Please enter the floor area."
);
}

Validate the numeric format:

1
2
3
4
5
6
7
8
9
double floorArea;

try {
floorArea =Double.parseDouble(areaText);
} catch (NumberFormatException exception) {
throw new IllegalArgumentException(
"Floor area must be a valid number."
);
}

Validate the range:

1
2
3
4
5
if (floorArea <= 0) {
throw new IllegalArgumentException(
"Floor area must be greater than zero."
);
}

Display the validation error:

1
2
3
4
5
6
JOptionPane.showMessageDialog(
frame,
exception.getMessage(),
"Invalid Input",
JOptionPane.ERROR_MESSAGE
);

A reliable application should validate:

  • Required fields
  • Number formats
  • Date formats
  • Minimum and maximum values
  • File existence
  • Conflicting selections
  • Business rules
  • Duplicate records

Long-Running Tasks and SwingWorker

The Event Dispatch Thread should only perform short UI operations.

The following code may freeze the interface:

1
2
3
4
5
6
calculateButton.addActionListener(event -> {
loadLargeFile();
queryDatabase();
callRemoteService();
runComplexCalculation();
});

If these operations take several seconds, the application window may become unresponsive.

Swing provides SwingWorker for background processing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SwingWorker<Double, Void> worker =new SwingWorker<>() {

@Override
protected Double doInBackground() throws Exception {
return runComplexCalculation();
}

@Override
protected void done() {
try {
double result = get();

JOptionPane.showMessageDialog(
frame,
"Result: " + result
);
} catch (Exception exception) {
JOptionPane.showMessageDialog(
frame,
exception.getMessage(),
"Calculation Error",
JOptionPane.ERROR_MESSAGE
);
}
}

};

worker.execute();

The two most important methods are:

  • doInBackground() runs outside the EDT.
  • done() runs on the EDT after the task has completed.

SwingWorker is suitable for:

  • Reading large files
  • Importing data
  • Exporting reports
  • Calling remote APIs
  • Querying databases
  • Running complex calculations
  • Loading large tables

Swing Development Best Practices

Use the Event Dispatch Thread (EDT)

Create and update Swing components on the EDT.

1
2
3
4
5
SwingUtilities.invokeLater(() -> {
ApplicationFrame frame =new ApplicationFrame();

frame.setVisible(true);
});

Avoid Null Layout

The following approach is not recommended:

1
2
3
panel.setLayout(null);

button.setBounds(10,20,120,30);

Absolute positions may fail when:

  • The window is resized
  • The font size changes
  • The application runs on another operating system
  • Text labels become longer
  • Display scaling changes

Use layout managers instead.

1
panel.setLayout(new BorderLayout());

Keep Business Logic Outside Event Listeners

Avoid writing all logic directly inside an ActionListener.

Not recommended:

1
2
3
4
5
6
7
8
button.addActionListener(event -> {
// Read fields
// Validate values
// Perform calculations
// Query the database
// Save files
// Update the table
});

Recommended:

1
button.addActionListener(event -> calculateRating());

Then split the work into smaller methods.

1
2
3
4
5
6
private void handleCalculation() {
Property property = readProperty();
validateProperty(property);
EnergyRatingResult result = ratingService.calculate(property);
displayResult(result);
}

Use Clear Component Names

Recommended names:

  • floorAreaField
  • calculateButton
  • heatingSystemComboBox
  • ratingResultLabel
  • historyTable

Avoid unclear names:

  • text1
  • button2
  • combo
  • label3
  • table1

Use Constants Instead of Magic Numbers

Not recommended:

1
2
3
annualUsage = floorArea * 120;
annualUsage *= 0.75;
annualUsage -= 1800;

Recommended:
1
2
3
4
5
private static final double BASE_USAGE_PER_SQUARE_METRE = 120;

private static final double INSULATION_FACTOR = 0.75;

private static final double SOLAR_REDUCTION = 1800;

Then use the constants:
1
annualUsage = floorArea * BASE_USAGE_PER_SQUARE_METRE;

Practical Project: Residential Energy Rating Tool

Residential Energy Rating Tool