{"id":3482,"date":"2026-09-23T11:17:06","date_gmt":"2026-09-23T03:17:06","guid":{"rendered":"http:\/\/www.lumbinilive.com\/blog\/?p=3482"},"modified":"2026-09-23T11:17:06","modified_gmt":"2026-09-23T03:17:06","slug":"how-to-add-menu-items-to-a-jpopupmenu-in-swing-44d1-2b6b81","status":"publish","type":"post","link":"http:\/\/www.lumbinilive.com\/blog\/2026\/09\/23\/how-to-add-menu-items-to-a-jpopupmenu-in-swing-44d1-2b6b81\/","title":{"rendered":"How to add menu items to a JPopupMenu in Swing?"},"content":{"rendered":"<p>Hey everyone, thanks for stopping by. I\u2019m Sam, and over the past 12 years, I\u2019ve been working on everything Swing-related as part of a vendor that builds enterprise-grade UI components for desktop applications. I\u2019ve 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\u2019t even building the menu itself\u2014it\u2019s adding the right items that behave consistently, follow Java\u2019s Swing conventions, and don\u2019t break the app when they scale from a small internal tool to a full-blown enterprise platform. Today, I\u2019m walking you through exactly how to add menu items to a JPopupMenu the right way, with real-world lessons I\u2019ve picked up from clients across fintech, healthcare, and logistics. <a href=\"https:\/\/www.chainshenli.com\/swing\/\">Swing<\/a><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.chainshenli.com\/uploads\/45306\/small\/rubber-coated-anchor-chainf3a88.jpg\"><\/p>\n<p>First, let\u2019s get one thing straight: JPopupMenu isn\u2019t a random collection of buttons stuck together in a corner. It\u2019s a lightweight, context-sensitive component that\u2019s part of the Swing core API, designed to respond to user actions\u2014right-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\u2019ve 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\u2019t work across different operating systems.<\/p>\n<p>Let\u2019s start with the absolute basics. To use JPopupMenu, you don\u2019t need any special dependencies\u2014everything is in javax.swing, which makes it accessible, but that also means there\u2019s room for error if you\u2019re not following the official guidelines. The first step is always to create the JPopupMenu instance itself. I usually do this when I\u2019m attaching it to a component, because context menus shouldn\u2019t exist if their parent component isn\u2019t there. For example, if you\u2019re building a table where users right-click to edit or delete rows, you\u2019d create the JPopupMenu when you initialize the table, not later.<\/p>\n<p>Wait, let\u2019s write that first working example, so you can see what I\u2019m talking about. Let\u2019s say we\u2019re 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\u2019s the core code you\u2019d start with:<\/p>\n<p>JPopupMenu patientContextMenu = new JPopupMenu();<br \/>\nJMenuItem viewRecordItem = new JMenuItem(&quot;View Patient Record&quot;);<br \/>\nJMenuItem editDetailsItem = new JMenuItem(&quot;Edit Patient Details&quot;);<br \/>\nJMenuItem deleteEntryItem = new JMenuItem(&quot;Delete Appointment&quot;);<\/p>\n<p>\/\/ Now add those items to the popup menu<br \/>\npatientContextMenu.add(viewRecordItem);<br \/>\npatientContextMenu.add(editDetailsItem);<br \/>\npatientContextMenu.add(deleteEntryItem);<\/p>\n<p>\/\/ Then attach the menu to the table so it pops up on right-click<br \/>\ntable.setComponentPopupMenu(patientContextMenu);<\/p>\n<p>That\u2019s the bare minimum. But if you stop here, you\u2019re missing the parts that make the menu actually useful. The items don\u2019t do anything when clicked, and there\u2019s no separation between actions\u2014users hate when a menu is just a long list of links without any visual cues. That\u2019s where the next level comes in, and this is where most new developers fumble.<\/p>\n<p>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\u2014say, a toolbar button that also opens a patient record\u2014you don\u2019t have to rewrite the logic. Let\u2019s adjust the example to show that:<\/p>\n<p>Action viewRecordAction = new AbstractAction(&quot;View Patient Record&quot;) {<br \/>\n@Override<br \/>\npublic void actionPerformed(ActionEvent e) {<br \/>\n\/\/ Get the selected row from the table first, that&#8217;s the key to the data<br \/>\nint selectedRow = table.getSelectedRow();<br \/>\nif (selectedRow != -1) {<br \/>\nString patientId = (String) table.getValueAt(selectedRow, 0);<br \/>\nopenPatientRecord(patientId); \/\/ This is your custom method, whatever that is<br \/>\n}<br \/>\n}<br \/>\n};<\/p>\n<p>JMenuItem viewRecordItem = new JMenuItem(viewRecordAction);<\/p>\n<p>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\u2019s cleaner, more maintainable, and it prevents duplicate code that will come back to bite you when you need to update the action\u2019s logic. We\u2019ve 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.<\/p>\n<p>Next, adding separators. Grouping related actions makes the menu easier to scan, which is critical for users who might be working quickly\u2014like 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\u2019s update the example:<\/p>\n<p>patientContextMenu.add(viewRecordItem);<br \/>\npatientContextMenu.add(editDetailsItem);<br \/>\npatientContextMenu.addSeparator(); \/\/ This is the magic line that adds a visual divider<br \/>\npatientContextMenu.add(deleteEntryItem);<\/p>\n<p>That\u2019s it. The separator is automatically sized to match the JPopupMenu, and it works across all Swing Look and Feels\u2014Windows, macOS, Linux, even custom ones we build for enterprise clients. I\u2019ve 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.<\/p>\n<p>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\u2019s 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\u2019s say we want to add an option to toggle urgent status for a patient:<\/p>\n<p>JCheckBoxMenuItem markUrgentItem = new JCheckBoxMenuItem(&quot;Mark as Urgent&quot;);<br \/>\n\/\/ Add an action listener to update the patient data<br \/>\nmarkUrgentItem.addActionListener(e -&gt; {<br \/>\nint selectedRow = table.getSelectedRow();<br \/>\nif (selectedRow != -1) {<br \/>\nboolean isUrgent = markUrgentItem.isSelected();<br \/>\nupdatePatientUrgentStatus(selectedRow, isUrgent);<br \/>\n}<br \/>\n});<\/p>\n<p>\/\/ Add that above the delete action, so it&#8217;s grouped with view\/edit<br \/>\npatientContextMenu.add(markUrgentItem);<br \/>\npatientContextMenu.addSeparator();<br \/>\npatientContextMenu.add(deleteEntryItem);<\/p>\n<p>For radio buttons, it\u2019s almost the same, but you need a ButtonGroup to ensure only one is selected at a time. That\u2019s a common gotcha\u2014forgetting the ButtonGroup means all radio items can be selected, which breaks their intended behavior. Let\u2019s add a filter group for the table, another common feature our clients use:<\/p>\n<p>ButtonGroup filterGroup = new ButtonGroup();<br \/>\nJRadioButtonMenuItem showAllItem = new JRadioButtonMenuItem(&quot;Show All Appointments&quot;);<br \/>\nJRadioButtonMenuItem showUrgentItem = new JRadioButtonMenuItem(&quot;Show Only Urgent&quot;);<\/p>\n<p>filterGroup.add(showAllItem);<br \/>\nfilterGroup.add(showUrgentItem);<\/p>\n<p>\/\/ Set showAll as the default selected option<br \/>\nshowAllItem.setSelected(true);<\/p>\n<p>\/\/ Add actions to filter the table when selection changes<br \/>\nActionListener filterAction = e -&gt; {<br \/>\nif (showUrgentItem.isSelected()) {<br \/>\nfilterTableByUrgentStatus(true);<br \/>\n} else {<br \/>\nfilterTableByUrgentStatus(false);<br \/>\n}<br \/>\n};<\/p>\n<p>showAllItem.addActionListener(filterAction);<br \/>\nshowUrgentItem.addActionListener(filterAction);<\/p>\n<p>\/\/ Add these to the popup, before the main patient actions<br \/>\npatientContextMenu.addSeparator();<br \/>\npatientContextMenu.add(new JLabel(&quot;Filter Appointments&quot;)); \/\/ You can add labels for section headers<br \/>\npatientContextMenu.add(showAllItem);<br \/>\npatientContextMenu.add(showUrgentItem);<\/p>\n<p>This works perfectly, and it\u2019s all part of the standard Swing API. The key here is that you don\u2019t need any third-party libraries to do this\u2014Swing\u2019s 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.<\/p>\n<p>Now, let\u2019s 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\u2019s 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\u2019s why in all our action code, we always check for selectedRow != -1 first. It\u2019s a small line of code that prevents a lot of crashes, especially in enterprise apps that run 24\/7.<\/p>\n<p>Second, inconsistent behavior across operating systems. Swing\u2019s 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\u2019s adjust our edit action to include a shortcut:<\/p>\n<p>Action editDetailsAction = new AbstractAction(&quot;Edit Patient Details&quot;) {<br \/>\n@Override<br \/>\npublic void actionPerformed(ActionEvent e) {<br \/>\nint selectedRow = table.getSelectedRow();<br \/>\nif (selectedRow != -1) {<br \/>\nString patientId = (String) table.getValueAt(selectedRow, 0);<br \/>\nopenEditPatientWindow(patientId);<br \/>\n}<br \/>\n}<br \/>\n};<\/p>\n<p>\/\/ Set the accelerator for Ctrl+E, this works across Windows and macOS (switches to Cmd+E automatically on Mac)<br \/>\neditDetailsAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(&quot;control E&quot;));<\/p>\n<p>JMenuItem editDetailsItem = new JMenuItem(editDetailsAction);<\/p>\n<p>That\u2019s another mistake we see all the time\u2014developers set accelerators directly on the JMenuItem, which doesn\u2019t account for OS-specific key mappings. The Action class handles that automatically, so you just use &quot;control X&quot; and Swing adjusts it for macOS without you having to write extra code.<\/p>\n<p>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\u2019t need to show the &quot;Delete Appointment&quot; item, or you can disable it. Let\u2019s add a check to enable\/disable menu items dynamically when the popup is about to show:<\/p>\n<p>patientContextMenu.addPopupMenuListener(new PopupMenuListener() {<br \/>\n@Override<br \/>\npublic void popupMenuWillBecomeVisible(PopupMenuEvent e) {<br \/>\n\/\/ Get the selected row when the popup is about to show, not when it was created<br \/>\nint selectedRow = table.getSelectedRow();<br \/>\nboolean isDischarged = false;<br \/>\nif (selectedRow != -1) {<br \/>\nString status = (String) table.getValueAt(selectedRow, 2);<br \/>\nisDischarged = status.equalsIgnoreCase(&quot;Discharged&quot;);<br \/>\n}<br \/>\n\/\/ Disable delete if the patient is discharged<br \/>\ndeleteEntryItem.setEnabled(!isDischarged);<br \/>\n\/\/ Disable edit for discharged patients too<br \/>\neditDetailsItem.setEnabled(!isDischarged);<br \/>\n}<\/p>\n<pre><code>@Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}\n@Override public void popupMenuCanceled(PopupMenuEvent e) {}\n<\/code><\/pre>\n<p>});<\/p>\n<p>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.<\/p>\n<p>Now, let\u2019s 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\u2019re building a custom application with specific design needs (like matching your brand\u2019s color scheme, or custom animations), our team can help. We\u2019ve 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.<\/p>\n<p>The mistake I see most often here is developers thinking they need to rebuild JPopupMenu from scratch to get custom styling. That\u2019s rarely necessary\u2014we 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:<\/p>\n<p>editDetailsAction.putValue(Action.SMALL_ICON, new ImageIcon(&quot;edit-icon.png&quot;));<\/p>\n<p>Swing will automatically apply that icon to the menu item, and it scales correctly for high-DPI displays if you use proper sized icons.<\/p>\n<p>At the end of the day, adding items to JPopupMenu is straightforward if you follow Swing\u2019s 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\u2014like a nurse with a mouse and a warehouse worker using a touch screen.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.chainshenli.com\/uploads\/45306\/small\/beam-for-swing-set6f841.jpg\"><\/p>\n<p>If you\u2019re working on a Swing project and you\u2019re 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.<\/p>\n<p><a href=\"https:\/\/www.chainshenli.com\/swing\/swing-accessories\/\">Swing Accessories<\/a> References<\/p>\n<ol>\n<li>Oracle. Java Swing API Specification: JPopupMenu. https:\/\/docs.oracle.com\/javase%2F7%2Fdocs%2Fapi%2F%2F\/javax\/swing\/JPopupMenu.html<\/li>\n<li>Oracle. Java Swing API Specification: JMenuItem. https:\/\/docs.oracle.com\/javase%2F7%2Fdocs%2Fapi%2F%2F\/javax\/swing\/JMenuItem.html<\/li>\n<li>Oracle. Java Swing Action API Documentation. https:\/\/docs.oracle.com\/javase%2F7%2Fdocs%2Fapi%2F%2F\/java\/awt\/event\/Action.html<\/li>\n<li>Oracle. Java Look and Feel Guidelines for Swing. https:\/\/docs.oracle.com\/javase%2F7%2Fdocs%2Fapi%2F%2F\/javax\/swing\/plaf\/basic\/BasicLookAndFeel.html<\/li>\n<li>Swing Tutorials. Oracle Corporation. How to Use Menus. https:\/\/docs.oracle.com\/javase\/tutorial\/uiswing\/components\/menu.html<\/li>\n<\/ol>\n<hr>\n<p><a href=\"https:\/\/www.chainshenli.com\/\">Pujiang Shenli Chain Co., Ltd.<\/a><br \/>We&#8217;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.<br \/>Address: No. 18, Zaifeng Road, Pujiang County, Zhejiang Province<br \/>E-mail: Chen@shenlichain.com<br \/>WebSite: <a href=\"https:\/\/www.chainshenli.com\/\">https:\/\/www.chainshenli.com\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey everyone, thanks for stopping by. I\u2019m Sam, and over the past 12 years, I\u2019ve been &hellip; <a title=\"How to add menu items to a JPopupMenu in Swing?\" class=\"hm-read-more\" href=\"http:\/\/www.lumbinilive.com\/blog\/2026\/09\/23\/how-to-add-menu-items-to-a-jpopupmenu-in-swing-44d1-2b6b81\/\"><span class=\"screen-reader-text\">How to add menu items to a JPopupMenu in Swing?<\/span>Read more<\/a><\/p>\n","protected":false},"author":264,"featured_media":3482,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[3445],"class_list":["post-3482","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-industry","tag-swing-4b9d-2bbf9c"],"_links":{"self":[{"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/posts\/3482","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/users\/264"}],"replies":[{"embeddable":true,"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/comments?post=3482"}],"version-history":[{"count":0,"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/posts\/3482\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/posts\/3482"}],"wp:attachment":[{"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/media?parent=3482"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/categories?post=3482"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.lumbinilive.com\/blog\/wp-json\/wp\/v2\/tags?post=3482"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}