Table of Contents#
- Why Does the Application Keep Running? The Root Cause
- Common Scenarios Where This Happens
- Proper Fixes to Stop the Application
- Best Practices to Avoid the Issue
- Conclusion
- References
Why Does the Application Keep Running? The Root Cause#
To understand why your Java application persists after closing the main window, we need to dive into two key concepts: Java threads and the Event Dispatch Thread (EDT).
1.1 Java Threads and JVM Termination#
The Java Virtual Machine (JVM) exits only when all non-daemon threads have finished executing. Daemon threads are background threads that do not prevent the JVM from exiting—they are terminated automatically when the last non-daemon thread finishes.
By default, most threads in Java (including the main thread and Swing’s EDT) are non-daemon threads. If any non-daemon thread remains active, the JVM will not exit, even if the main window is closed.
1.2 The Event Dispatch Thread (EDT)#
Swing applications rely on a special thread called the Event Dispatch Thread (EDT) to handle all GUI operations (e.g., rendering, user input events like clicks or key presses). The EDT is a non-daemon thread, which means it alone can keep the JVM running.
When you close a JFrame window, the default behavior is not to terminate the EDT. Instead, the window is simply hidden or disposed of, leaving the EDT (and thus the JVM) active.
Common Scenarios Where This Happens#
Let’s explore specific situations where your application might fail to exit after closing the main window.
2.1 Incorrect Default Close Operation#
The JFrame class has a property called defaultCloseOperation, which determines what happens when the user clicks the window’s close button (the "X" in the title bar). By default, this is set to HIDE_ON_CLOSE, which only hides the window but does not terminate the application.
Other possible values for defaultCloseOperation include:
DO_NOTHING_ON_CLOSE: The close button does nothing (you must handle closing manually).DISPOSE_ON_CLOSE: The window is disposed of (releases resources), but the JVM continues running.EXIT_ON_CLOSE: The entire application exits (kills all threads, including the EDT).
If you’re using HIDE_ON_CLOSE (default) or DISPOSE_ON_CLOSE, the EDT remains active, so the JVM won’t exit.
2.2 Multiple Open Windows/Frames#
If your application has multiple JFrame windows (e.g., a main window and a dialog), closing one window may not close the others. Even if you set DISPOSE_ON_CLOSE for the main window, other open windows will keep the EDT alive.
2.3 Active Background Threads#
If your application spawns custom background threads (e.g., for file I/O, network requests, or periodic tasks) and those threads are non-daemon, they will prevent the JVM from exiting—even if all windows are closed.
Proper Fixes to Stop the Application#
Now that we understand the root causes, let’s explore actionable fixes to ensure your application exits cleanly.
3.1 Set EXIT_ON_CLOSE for the Main JFrame#
The simplest fix for most single-window applications is to set the main JFrame’s defaultCloseOperation to EXIT_ON_CLOSE. This tells the JVM to terminate all threads (including the EDT) when the window is closed.
Example Code:
import javax.swing.JFrame;
public class MainApp {
public static void main(String[] args) {
JFrame frame = new JFrame("My Application");
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Critical line
frame.setVisible(true);
}
}Why this works: EXIT_ON_CLOSE calls System.exit(0) under the hood, which terminates the JVM and all running threads.
3.2 Handle Multiple Windows Gracefully#
If your application uses multiple JFrame windows, closing one should not exit the app prematurely. Instead, exit only when the last window is closed.
To do this:
- Set
defaultCloseOperationtoDISPOSE_ON_CLOSEfor all windows. - Use a
WindowListenerto track open windows and exit when the last one is disposed.
Example Code:
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class MultiWindowApp {
private static int openWindows = 0;
public static void main(String[] args) {
createWindow("Main Window");
createWindow("Secondary Window");
}
private static void createWindow(String title) {
JFrame frame = new JFrame(title);
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Track window opening/closing
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
openWindows++;
}
@Override
public void windowClosed(WindowEvent e) {
openWindows--;
if (openWindows == 0) {
System.exit(0); // Exit when last window closes
}
}
});
frame.setVisible(true);
}
}3.3 Manage Background Threads#
If your app uses custom background threads, ensure they are either:
- Daemon threads: Mark them as daemon so they don’t block JVM exit.
- Stopped explicitly: Terminate them when the application closes.
Example 1: Daemon Thread
Thread backgroundThread = new Thread(() -> {
while (true) {
// Perform background work (e.g., polling)
try { Thread.sleep(1000); } catch (InterruptedException e) { break; }
}
});
backgroundThread.setDaemon(true); // Daemon thread—JVM exits when non-daemon threads finish
backgroundThread.start();Example 2: Explicitly Stopping a Thread
Use a flag to signal the thread to stop, and handle cleanup in a WindowListener:
public class AppWithThread {
private static volatile boolean running = true;
public static void main(String[] args) {
JFrame frame = new JFrame("Thread Example");
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // Handle closing manually
// Background thread
Thread worker = new Thread(() -> {
while (running) {
System.out.println("Working...");
try { Thread.sleep(1000); } catch (InterruptedException e) { running = false; }
}
System.out.println("Worker thread stopped.");
});
worker.start();
// Stop thread and exit on window close
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
running = false; // Signal thread to stop
worker.interrupt(); // Wake thread if sleeping
frame.dispose();
System.exit(0);
}
});
frame.setVisible(true);
}
}Best Practices to Avoid the Issue#
To prevent your Java application from lingering after closing windows, follow these best practices:
- Use
EXIT_ON_CLOSEfor single-window apps: For simple apps with one main window, this is the most reliable fix. - Prefer
DISPOSE_ON_CLOSEfor multi-window apps: Combine it with aWindowListenerto exit when the last window closes. - Mark background threads as daemon: Unless the thread must complete work (e.g., saving data), use daemon threads to avoid blocking JVM exit.
- Avoid orphaned threads: Always ensure threads terminate when the app closes (use flags or
interrupt()). - Test close behavior: Verify that closing all windows or the main window terminates the app in your IDE and deployed environments.
Conclusion#
Your Java application keeps running after closing the main window because non-daemon threads (like the EDT) remain active. The most common culprit is the default HIDE_ON_CLOSE behavior of JFrame, which hides the window but leaves the EDT running.
To fix this:
- Use
JFrame.EXIT_ON_CLOSEfor single-window apps. - Track window closures with
WindowListenerfor multi-window apps. - Ensure background threads are either daemon threads or stopped explicitly.
By following these steps, you’ll ensure your application exits cleanly and avoids frustrating users with lingering processes.