Posted in

How to add menu items to a JPopupMenu in Swing?

Hey everyone, thanks for stopping by. I’m Sam, and over the past 12 years, I’ve been working on everything Swing-related as part of a vendor that builds enterprise-grade UI components for desktop applications. I’ve lost count of how many times developers have pulled me aside at conferences or slid into my DMs frustrated about JPopupMenu. Most of the time, their issue isn’t even building the menu itself—it’s adding the right items that behave consistently, follow Java’s Swing conventions, and don’t break the app when they scale from a small internal tool to a full-blown enterprise platform. Today, I’m walking you through exactly how to add menu items to a JPopupMenu the right way, with real-world lessons I’ve picked up from clients across fintech, healthcare, and logistics. Swing

First, let’s get one thing straight: JPopupMenu isn’t a random collection of buttons stuck together in a corner. It’s a lightweight, context-sensitive component that’s part of the Swing core API, designed to respond to user actions—right-clicks, context-sensitive keys, even touch events on desktop. When our team builds custom components for clients, we start with the fundamentals because cutting corners here leads to messy, hard-to-maintain code down the line. I’ve seen clients scrap entire JPopupMenu implementations because a single misconfigured menu item threw a NullPointerException when a null value was passed, or because shortcuts didn’t work across different operating systems.

Let’s start with the absolute basics. To use JPopupMenu, you don’t need any special dependencies—everything is in javax.swing, which makes it accessible, but that also means there’s room for error if you’re not following the official guidelines. The first step is always to create the JPopupMenu instance itself. I usually do this when I’m attaching it to a component, because context menus shouldn’t exist if their parent component isn’t there. For example, if you’re building a table where users right-click to edit or delete rows, you’d create the JPopupMenu when you initialize the table, not later.

Wait, let’s write that first working example, so you can see what I’m talking about. Let’s say we’re building a simple right-click menu for a JTable, something most of our healthcare clients use to open patient records or update appointment statuses. Here’s the core code you’d start with:

JPopupMenu patientContextMenu = new JPopupMenu();
JMenuItem viewRecordItem = new JMenuItem("View Patient Record");
JMenuItem editDetailsItem = new JMenuItem("Edit Patient Details");
JMenuItem deleteEntryItem = new JMenuItem("Delete Appointment");

// Now add those items to the popup menu
patientContextMenu.add(viewRecordItem);
patientContextMenu.add(editDetailsItem);
patientContextMenu.add(deleteEntryItem);

// Then attach the menu to the table so it pops up on right-click
table.setComponentPopupMenu(patientContextMenu);

That’s the bare minimum. But if you stop here, you’re missing the parts that make the menu actually useful. The items don’t do anything when clicked, and there’s no separation between actions—users hate when a menu is just a long list of links without any visual cues. That’s where the next level comes in, and this is where most new developers fumble.

First, adding action listeners. For each JMenuItem, you need to tie it to an action that makes sense for your application. When our team builds client tools, we always use Action objects instead of anonymous inner classes, even for simple actions. Why? Because if you need to reuse that action elsewhere—say, a toolbar button that also opens a patient record—you don’t have to rewrite the logic. Let’s adjust the example to show that:

Action viewRecordAction = new AbstractAction("View Patient Record") {
@Override
public void actionPerformed(ActionEvent e) {
// Get the selected row from the table first, that’s the key to the data
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
String patientId = (String) table.getValueAt(selectedRow, 0);
openPatientRecord(patientId); // This is your custom method, whatever that is
}
}
};

JMenuItem viewRecordItem = new JMenuItem(viewRecordAction);

See the difference? By wrapping the action in an AbstractAction, we can use that same action for the JMenuItem, and later add it to a toolbar with the exact same behavior. It’s cleaner, more maintainable, and it prevents duplicate code that will come back to bite you when you need to update the action’s logic. We’ve had a lot of clients come to us with code where they had 15 different right-click menus all with almost identical edit actions, and changing the logic meant updating 15 separate places. Avoid that from the start.

Next, adding separators. Grouping related actions makes the menu easier to scan, which is critical for users who might be working quickly—like a logistics worker updating 20 orders an hour, or a nurse flipping through patient records. Separators are super easy to add, right between the calls to add your menu items. Let’s update the example:

patientContextMenu.add(viewRecordItem);
patientContextMenu.add(editDetailsItem);
patientContextMenu.addSeparator(); // This is the magic line that adds a visual divider
patientContextMenu.add(deleteEntryItem);

That’s it. The separator is automatically sized to match the JPopupMenu, and it works across all Swing Look and Feels—Windows, macOS, Linux, even custom ones we build for enterprise clients. I’ve seen developers try to draw their own dividers, which never align correctly with text size or menu padding, so save yourself the headache and use the built-in addSeparator() method.

Now, what about more advanced items? Not every action is a plain text item. Sometimes you need a checkbox for toggling a setting, like marking a patient’s chart as urgent, or a radio button group for filtering table views. Swing has JCheckBoxMenuItem and JRadioButtonMenuItem built right in, and they work seamlessly with JPopupMenu. Let’s say we want to add an option to toggle urgent status for a patient:

JCheckBoxMenuItem markUrgentItem = new JCheckBoxMenuItem("Mark as Urgent");
// Add an action listener to update the patient data
markUrgentItem.addActionListener(e -> {
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
boolean isUrgent = markUrgentItem.isSelected();
updatePatientUrgentStatus(selectedRow, isUrgent);
}
});

// Add that above the delete action, so it’s grouped with view/edit
patientContextMenu.add(markUrgentItem);
patientContextMenu.addSeparator();
patientContextMenu.add(deleteEntryItem);

For radio buttons, it’s almost the same, but you need a ButtonGroup to ensure only one is selected at a time. That’s a common gotcha—forgetting the ButtonGroup means all radio items can be selected, which breaks their intended behavior. Let’s add a filter group for the table, another common feature our clients use:

ButtonGroup filterGroup = new ButtonGroup();
JRadioButtonMenuItem showAllItem = new JRadioButtonMenuItem("Show All Appointments");
JRadioButtonMenuItem showUrgentItem = new JRadioButtonMenuItem("Show Only Urgent");

filterGroup.add(showAllItem);
filterGroup.add(showUrgentItem);

// Set showAll as the default selected option
showAllItem.setSelected(true);

// Add actions to filter the table when selection changes
ActionListener filterAction = e -> {
if (showUrgentItem.isSelected()) {
filterTableByUrgentStatus(true);
} else {
filterTableByUrgentStatus(false);
}
};

showAllItem.addActionListener(filterAction);
showUrgentItem.addActionListener(filterAction);

// Add these to the popup, before the main patient actions
patientContextMenu.addSeparator();
patientContextMenu.add(new JLabel("Filter Appointments")); // You can add labels for section headers
patientContextMenu.add(showAllItem);
patientContextMenu.add(showUrgentItem);

This works perfectly, and it’s all part of the standard Swing API. The key here is that you don’t need any third-party libraries to do this—Swing’s built-in components are powerful enough for 99% of use cases, and we find that overcomplicating with custom components usually leads to bugs and poor performance.

Now, let’s talk about the things that go wrong all the time, and how we fix them when our clients come to us for support. First, null pointer exceptions when the parent component’s state changes. For example, if a user selects a row, then deletes it before opening the context menu, the selected row is null, and calling getValueAt will throw an NPE. That’s why in all our action code, we always check for selectedRow != -1 first. It’s a small line of code that prevents a lot of crashes, especially in enterprise apps that run 24/7.

Second, inconsistent behavior across operating systems. Swing’s Look and Feels (LaFs) do a lot of the heavy lifting, but there are some common pitfalls. For example, keyboard shortcuts: if you want a shortcut for a menu item, like Ctrl+E for Edit, you set it on the Action object, not the JMenuItem itself. That way, the shortcut works whether you click the menu item or not. Let’s adjust our edit action to include a shortcut:

Action editDetailsAction = new AbstractAction("Edit Patient Details") {
@Override
public void actionPerformed(ActionEvent e) {
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
String patientId = (String) table.getValueAt(selectedRow, 0);
openEditPatientWindow(patientId);
}
}
};

// Set the accelerator for Ctrl+E, this works across Windows and macOS (switches to Cmd+E automatically on Mac)
editDetailsAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control E"));

JMenuItem editDetailsItem = new JMenuItem(editDetailsAction);

That’s another mistake we see all the time—developers set accelerators directly on the JMenuItem, which doesn’t account for OS-specific key mappings. The Action class handles that automatically, so you just use "control X" and Swing adjusts it for macOS without you having to write extra code.

Third, performance issues with large menus. If you have a JPopupMenu with 20+ items, it can start to lag on older machines, especially with high-DPI displays. Our advice here is to only add items that are relevant to the current context. For example, if a patient is already discharged, you don’t need to show the "Delete Appointment" item, or you can disable it. Let’s add a check to enable/disable menu items dynamically when the popup is about to show:

patientContextMenu.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
// Get the selected row when the popup is about to show, not when it was created
int selectedRow = table.getSelectedRow();
boolean isDischarged = false;
if (selectedRow != -1) {
String status = (String) table.getValueAt(selectedRow, 2);
isDischarged = status.equalsIgnoreCase("Discharged");
}
// Disable delete if the patient is discharged
deleteEntryItem.setEnabled(!isDischarged);
// Disable edit for discharged patients too
editDetailsItem.setEnabled(!isDischarged);
}

@Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
@Override public void popupMenuCanceled(PopupMenuEvent e) {}

});

This is way better than hiding or showing items dynamically, because it only runs when the popup is about to open, not every time the table updates. We use this trick for all our enterprise components, and it cuts down on unnecessary UI updates and improves responsiveness.

Now, let’s talk about when you might need to go a step further. For most use cases, the standard JPopupMenu and its components work perfectly, but if you’re building a custom application with specific design needs (like matching your brand’s color scheme, or custom animations), our team can help. We’ve built custom JPopupMenu implementations for clients in banking where the menu has to comply with strict accessibility standards, and for logistics companies where the menu needs to work on touch screens used by warehouse workers wearing gloves.

The mistake I see most often here is developers thinking they need to rebuild JPopupMenu from scratch to get custom styling. That’s rarely necessary—we can adjust the LaF, customize borders, fonts, and icons without rewriting core functionality. For example, to add a custom icon to a menu item, you just add it to the Action:

editDetailsAction.putValue(Action.SMALL_ICON, new ImageIcon("edit-icon.png"));

Swing will automatically apply that icon to the menu item, and it scales correctly for high-DPI displays if you use proper sized icons.

At the end of the day, adding items to JPopupMenu is straightforward if you follow Swing’s conventions, avoid cutting corners on maintainability, and test across the environments your users actually work in. Our team has built hundreds of Swing-based applications, and the clients that run into the least trouble with JPopupMenu are the ones that start with the core components, use Action objects for reusable logic, and test their menus in real-world scenarios—like a nurse with a mouse and a warehouse worker using a touch screen.

If you’re working on a Swing project and you’re struggling with JPopupMenu, building enterprise-grade UI components, or just want to make your desktop app faster and more reliable, our team is here to help. We work with businesses of all sizes, from startups building their first internal tool to Fortune 500 companies deploying Swing applications across thousands of desktops. We provide custom development, support, and component optimization to make your Swing projects work exactly as you need them to.

Swing Accessories References

  1. Oracle. Java Swing API Specification: JPopupMenu. https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/javax/swing/JPopupMenu.html
  2. Oracle. Java Swing API Specification: JMenuItem. https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/javax/swing/JMenuItem.html
  3. Oracle. Java Swing Action API Documentation. https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/java/awt/event/Action.html
  4. Oracle. Java Look and Feel Guidelines for Swing. https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/javax/swing/plaf/basic/BasicLookAndFeel.html
  5. Swing Tutorials. Oracle Corporation. How to Use Menus. https://docs.oracle.com/javase/tutorial/uiswing/components/menu.html

Pujiang Shenli Chain Co., Ltd.
We’re well-known as one of the most experienced swing suppliers in China, featured by quality products and low price. Please feel free to buy discount swing made in China here from our factory. Contact us for more details.
Address: No. 18, Zaifeng Road, Pujiang County, Zhejiang Province
E-mail: Chen@shenlichain.com
WebSite: https://www.chainshenli.com/