A single instance application is one that only allows for 1 of the application to run no matter how many times the user tries to launch. Windows and linux native applications have an API to detect the instance of an application but when in Java, it is not quite as easy or reliable. To complicate matters, often times the requirement will be for the original application instance to react in some way to the launch, as in bringing the window to the front, opening a file or displaying a message.
Fortunately it's not all that difficult to code this if you know the trick. In this article I'll give you all the code you need to copy and paste into your application to make it single instance.
I've seen suggestions to use files as a cross-instance lock, but that is unreliable as application crashes and other IO errors may not allow for the file to be cleaned up. The better solution in my opinion is to use a local server socket.
This code example works by having the first instance attempt to open a listening socket on the localhost interface. If it's able to open the socket, it is assumed that this is the first instance of the application to be launched. If not, the assumption is that an instance of this application is already running. The new instance must notify the existing instance that a launch was attempted, then exit. The existing instance takes over after receiving the notification and fires an event to the listener that handles the action.
For security, I added a shared key that is used between the sender the receiver. In my implementation, the only constraint is that it must end with a newline character. That was just for ease of coding. The imports are all in java.io or java.net.
public class ApplicationInstanceManager {
private static ApplicationInstanceListener subListener;
/** Randomly chosen, but static, high socket number */
public static final int SINGLE_INSTANCE_NETWORK_SOCKET = 44331;
/** Must end with newline */
public static final String SINGLE_INSTANCE_SHARED_KEY = "$$NewInstance$$\n";
/**
* Registers this instance of the application.
*
* @return true if first instance, false if not.
*/
public static boolean registerInstance() {
// returnValueOnError should be true if lenient (allows app to run on network error) or false if strict.
boolean returnValueOnError = true;
// try to open network socket
// if success, listen to socket for new instance message, return true
// if unable to open, connect to existing and send new instance message, return false
try {
final ServerSocket socket = new ServerSocket(SINGLE_INSTANCE_NETWORK_SOCKET, 10, InetAddress
.getLocalHost());
log.debug("Listening for application instances on socket " + SINGLE_INSTANCE_NETWORK_SOCKET);
Thread instanceListenerThread = new Thread(new Runnable() {
public void run() {
boolean socketClosed = false;
while (!socketClosed) {
if (socket.isClosed()) {
socketClosed = true;
} else {
try {
Socket client = socket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String message = in.readLine();
if (SINGLE_INSTANCE_SHARED_KEY.trim().equals(message.trim())) {
log.debug("Shared key matched - new application instance found");
fireNewInstance();
}
in.close();
client.close();
} catch (IOException e) {
socketClosed = true;
}
}
}
}
});
instanceListenerThread.start();
// listen
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
return returnValueOnError;
} catch (IOException e) {
log.debug("Port is already taken. Notifying first instance.");
try {
Socket clientSocket = new Socket(InetAddress.getLocalHost(), SINGLE_INSTANCE_NETWORK_SOCKET);
OutputStream out = clientSocket.getOutputStream();
out.write(SINGLE_INSTANCE_SHARED_KEY.getBytes());
out.close();
clientSocket.close();
log.debug("Successfully notified first instance.");
return false;
} catch (UnknownHostException e1) {
log.error(e.getMessage(), e);
return returnValueOnError;
} catch (IOException e1) {
log.error("Error connecting to local port for single instance notification");
log.error(e1.getMessage(), e1);
return returnValueOnError;
}
}
return true;
}
public static void setApplicationInstanceListener(ApplicationInstanceListener listener) {
subListener = listener;
}
private static void fireNewInstance() {
if (subListener != null) {
subListener.newInstanceCreated();
}
}
}
public interface ApplicationInstanceListener {
public void newInstanceCreated();
}
public class MyApplication {
public static void main(String[] args) {
if (!ApplicationInstanceManager.registerInstance()) {
// instance already running.
System.out.println("Another instance of this application is already running. Exiting.");
System.exit(0);
}
ApplicationInstanceManager.setApplicationInstanceListener(new ApplicationInstanceListener() {
public void newInstanceCreated() {
System.out.println("New instance detected...");
// this is where your handler code goes...
}
});
}
}
Bookmark or Share this article:
Related Articles on Robert Green's DIY:
14 Comments
Post a comment here or discuss this and other topics in the forumsCareful with port choice
Nice, simple approach to this problem. Just want to make a note that for this to work reliably you would have to guarantee that your application is the only one that will bind to that port.
For those who want a simple lock...
This method uses a simple lock on a file and cleans up behind itself reliably and works across all platforms...
private static boolean lockInstance(final String lockFile) {
try {
final File file = new File(lockFile);
final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
final FileLock fileLock = randomAccessFile.getChannel().tryLock();
if (fileLock != null) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
fileLock.release();
randomAccessFile.close();
file.delete();
} catch (Exception e) {
log.error("Unable to remove lock file: " + lockFile, e);
}
}
});
return true;
}
} catch (Exception e) {
log.error("Unable to create and/or lock file: " + lockFile, e);
}
return false;
}
This is the simplest and most reliable approach that I've found so I thought I'd share it.
There is one major problem
There is one major problem with this approach: It doesn't work on multi-user systems running multiple GUI sessions. The first user to open the application would receive the start events of all other users. This might add a whole lot of security issues...
As for how common this is: Just think about the user switching in recent windows versions, terminal servers, Unix machines, ...
You are totally correct.
You are totally correct. This doesn't work for virtualized space using real network ports. One could keep a list of sessions and ports somewhere accessible to all users. I don't know. Suggestions, anyone?
Good work
Yeah! This approach of creating Server socket nice thing to do.
Any one can follow this way with out any fear.
I used this approach for two or three of my projects long time ago.
I did n't get any bad feed back on this usage.
Thank you!
Many thanks I've spent half my day trying to something like this. Your's works perfectly!
aww...
I spent an entire day figuring out how to do the same thing... oh well at least I know other people are using the same technique. good work, it's far more elegant then file locking.
Works great!
Thanks a lot for your work.
No Problem!
I'm just happy people are finding it and saving programming time.
thanks!
It works, excellent! A few remarks:
- I had to comment out the calls to the log function
- fireNewInstance is missing in the above code. I implemented it as:
private static void fireNewInstance() {
if (subListener != null) {
subListener.newInstanceCreated();
}
}
Many thanks, your code saved me hours of work.
Thanks!
I must have accidentally left that out when I originally wrote the article. I'll add your lines in for completion.
re: Java Single Application Instance
An alternative offers the (internal) sun.jvmstat package from tools.jar.
package com.tutego.jvmstat;
import java.util.Set;
import javax.swing.JOptionPane;
import sun.jvmstat.monitor.*;
public class JvmStat{
@SuppressWarnings("unchecked")
public static void main( String[] args ) throws Exception {
MonitoredHost monitoredhost = MonitoredHost.getMonitoredHost( "//localhost" );
for ( int id : (Set) monitoredhost.activeVms() ) {
VmIdentifier vmidentifier = new VmIdentifier( "" + id );
MonitoredVm monitoredvm = monitoredhost.getMonitoredVm( vmidentifier, 0 );
System.out.printf( "%d %s %s %s%n", id,MonitoredVmUtil.mainClass( monitoredvm, true ),MonitoredVmUtil.jvmArgs( monitoredvm ),MonitoredVmUtil.mainArgs( monitoredvm ) );
}
}
}
Are there two occurrences of the result of MonitoredVmUtil.mainClass(), the program is started twice.
Christian
This is a good method. How
This is a good method. How reliable it is to use in a production code. Why sun is exposing these useful APIs for public use, any idea ?
I'm not sure how private
I'm not sure how private those are, but if you want code that can be stable and trusted, stick with well-documented, public API.
Post new comment