"Java's JList and JTabbedPane components let you build richer, more organized interfaces — displaying collections of data and grouping related controls into tabs."- Claude 2026
In previous pages we worked with individual objects. In real applications you often need to manage a collection of objects — a roster of characters, an inventory of items, a list of scores. Java gives us two main tools for this:
java.util
In a GUI application, ArrayList is generally more useful because the number of items is often unknown at compile time — users add and remove entries dynamically.
ArrayList
public class Character { private String name; private String charClass; public Character(String name, String charClass) { this.name = name; this.charClass = charClass; } public String getName() { return name; } public String getCharClass() { return charClass; } // toString is used when displaying the object in a JList @Override public String toString() { return name + " (" + charClass + ")"; } public static void main(String[] args) { // Array of Character objects Character[] party = { new Character("Hero", "Warrior"), new Character("Zara", "Mage"), new Character("Fletch", "Archer") }; // Process the array with a for-each loop for (Character c : party) { System.out.println(c); } } }
Hero (Warrior) Zara (Mage) Fletch (Archer)
import java.util.ArrayList; ArrayList<Character> party = new ArrayList<>(); party.add(new Character("Hero", "Warrior")); party.add(new Character("Zara", "Mage")); party.add(new Character("Fletch", "Archer")); // Process with a for-each loop for (Character c : party) { System.out.println(c.getName() + " is a " + c.getCharClass()); } // Remove by index party.remove(0); System.out.println("Party size after removal: " + party.size());
Hero is a Warrior Zara is a Mage Fletch is a Archer Party size after removal: 2
A JList displays a scrollable list of items in the GUI. It does not manage its own data directly — instead it uses a model to hold the data. The standard model for a JList is DefaultListModel, which works like an ArrayList that automatically updates the display whenever items are added or removed.
JList
DefaultListModel
Adds an item to the end of the list and updates the display automatically.
Removes the item at the given index and updates the display.
Returns the index of the currently selected item, or -1 if nothing is selected.
Returns the selected item object, or null if nothing is selected.
Returns the number of items currently in the list.
import javax.swing.*; import java.awt.*; public class PartyRoster extends JFrame { private DefaultListModel<String> listModel; private JList<String> partyList; public PartyRoster() { setTitle("Party Roster"); setSize(300, 250); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new FlowLayout()); // Create the model and pre-populate it listModel = new DefaultListModel<>(); listModel.addElement("Hero (Warrior)"); listModel.addElement("Zara (Mage)"); listModel.addElement("Fletch (Archer)"); // Create the JList backed by the model partyList = new JList<>(listModel); // Wrap in a scroll pane in case the list grows long JScrollPane scrollPane = new JScrollPane(partyList); scrollPane.setPreferredSize(new Dimension(250, 150)); add(scrollPane); setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new PartyRoster()); } }
The power of DefaultListModel is that changes to the model are immediately reflected in the JList on screen. This makes it straightforward to wire up Add and Remove buttons — read input from a text field, update the model, and the display takes care of itself.
import javax.swing.*; import java.awt.*; public class PartyRoster extends JFrame { private DefaultListModel<String> listModel; private JList<String> partyList; private JTextField nameField; private JButton addButton, removeButton; public PartyRoster() { setTitle("Party Roster"); setSize(320, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new FlowLayout()); listModel = new DefaultListModel<>(); listModel.addElement("Hero (Warrior)"); listModel.addElement("Zara (Mage)"); listModel.addElement("Fletch (Archer)"); partyList = new JList<>(listModel); JScrollPane scrollPane = new JScrollPane(partyList); scrollPane.setPreferredSize(new Dimension(280, 150)); nameField = new JTextField(15); addButton = new JButton("Add"); removeButton = new JButton("Remove Selected"); // Add: read the text field and append to the model addButton.addActionListener(e -> { String name = nameField.getText().trim(); if (!name.isEmpty()) { listModel.addElement(name); nameField.setText(""); } }); // Remove: get the selected index and remove it removeButton.addActionListener(e -> { int index = partyList.getSelectedIndex(); if (index != -1) { listModel.removeElementAt(index); } }); add(scrollPane); add(nameField); add(addButton); add(removeButton); setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new PartyRoster()); } }
A JTabbedPane organizes the GUI into multiple tabs — each tab holds a separate JPanel with its own set of components. This is useful when an application has distinct sections that would be cluttered if shown all at once.
JTabbedPane
JPanel
Building a tabbed pane involves three steps:
addTab()
import javax.swing.*; import java.awt.*; public class CharacterApp extends JFrame { public CharacterApp() { setTitle("Character App"); setSize(400, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTabbedPane tabs = new JTabbedPane(); // --- Tab 1: Party Roster --- JPanel rosterPanel = new JPanel(new FlowLayout()); rosterPanel.add(new JLabel("Party members will appear here.")); tabs.addTab("Roster", rosterPanel); // --- Tab 2: Add Character --- JPanel addPanel = new JPanel(new FlowLayout()); addPanel.add(new JLabel("Name:")); addPanel.add(new JTextField(12)); addPanel.add(new JButton("Add to Roster")); tabs.addTab("Add Character", addPanel); // --- Tab 3: Settings --- JPanel settingsPanel = new JPanel(new FlowLayout()); settingsPanel.add(new JCheckBox("Show health bars")); settingsPanel.add(new JCheckBox("Enable sound")); tabs.addTab("Settings", settingsPanel); add(tabs); setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new CharacterApp()); } }
ListSelectionListener
import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.util.ArrayList; public class CharacterApp extends JFrame { // Shared data private DefaultListModel<String> listModel; private ArrayList<String[]> characters; // each entry: [name, class] // Roster tab private JList<String> partyList; // Add/Remove tab private JTextField nameField; private JComboBox<String> classCombo; private JButton addButton, removeButton; // Detail tab private JLabel nameLabel; private JLabel classLabel; private JLabel countLabel; public CharacterApp() { setTitle("Character App"); setSize(420, 320); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Initialize shared data characters = new ArrayList<>(); listModel = new DefaultListModel<>(); // Pre-populate addCharacter("Hero", "Warrior"); addCharacter("Zara", "Mage"); addCharacter("Fletch", "Archer"); JTabbedPane tabs = new JTabbedPane(); // ---- Tab 1: Roster ---- JPanel rosterPanel = new JPanel(new BorderLayout()); partyList = new JList<>(listModel); partyList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); rosterPanel.add(new JScrollPane(partyList), BorderLayout.CENTER); rosterPanel.add(new JLabel("Select a character, then click the Detail tab to view their info."), BorderLayout.SOUTH); tabs.addTab("Roster", rosterPanel); // ---- Tab 2: Add / Remove ---- JPanel addPanel = new JPanel(new FlowLayout()); nameField = new JTextField(10); classCombo = new JComboBox<>(new String[]{"Warrior", "Mage", "Archer"}); addButton = new JButton("Add"); removeButton = new JButton("Remove Selected"); addButton.addActionListener(e -> { String name = nameField.getText().trim(); String charClass = (String) classCombo.getSelectedItem(); if (!name.isEmpty()) { addCharacter(name, charClass); nameField.setText(""); updateCount(); } }); removeButton.addActionListener(e -> { int index = partyList.getSelectedIndex(); if (index != -1) { characters.remove(index); listModel.removeElementAt(index); updateCount(); } }); addPanel.add(new JLabel("Name:")); addPanel.add(nameField); addPanel.add(new JLabel("Class:")); addPanel.add(classCombo); addPanel.add(addButton); addPanel.add(removeButton); tabs.addTab("Add / Remove", addPanel); // ---- Tab 3: Detail ---- // Updates automatically when a character is selected in the Roster tab JPanel detailPanel = new JPanel(new GridLayout(3, 1, 5, 5)); nameLabel = new JLabel("Name: —"); classLabel = new JLabel("Class: —"); countLabel = new JLabel("Party size: " + characters.size()); detailPanel.add(nameLabel); detailPanel.add(classLabel); detailPanel.add(countLabel); tabs.addTab("Detail", detailPanel); // Listen for selection changes in the JList and update the Detail tab partyList.addListSelectionListener(e -> { if (!e.getValueIsAdjusting()) { int index = partyList.getSelectedIndex(); if (index != -1) { String[] c = characters.get(index); nameLabel.setText("Name: " + c[0]); classLabel.setText("Class: " + c[1]); } else { nameLabel.setText("Name: —"); classLabel.setText("Class: —"); } } }); add(tabs); setVisible(true); } private void addCharacter(String name, String charClass) { characters.add(new String[]{name, charClass}); listModel.addElement(name + " (" + charClass + ")"); } private void updateCount() { countLabel.setText("Party size: " + characters.size()); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new CharacterApp()); } }
Save the file as CharacterApp.java, then compile and run from the terminal:
CharacterApp.java
javac CharacterApp.java
java CharacterApp