Java Single Application Instance

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...
          }
       });
    }
}

18 Comments

Post a comment here or discuss this and other topics in the forums

poblem under windows...

hello.
it works perfecty for me under linux but under windows 7 and XP even it prevents multiple opening. ut even if i close the application after i cannot open new instance. the port stays locked till the reboot or till i force the end of the java application killing it.
I added:

public static void closeInstance() {
if (socket != null) {
try {
socket.close();
instanceListenerThread.stop();
} catch (IOException e) {
log.error("Error while closing the socket");
}
}
}

which i call from a shutDownHook but still under windows the porsta stays lock after the first execution.
any idea??

Cheers

bug on windows

Just a note:

InetAddress.getLocalHost() doesn't returns 127.0.0.1 on windows, if the machine is on a local network, it will return the IP of the network instead, this causes a big problem, and this won't work correctly.

A workaroud is to use InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); instead.

Thanks

We are in the process of converting an Eclipse RCP application from having only one document per instance to allowing several documents in one instance and a part of that process is to enforce a "one instance only"-policy. Using lock files in various manners are not a good option for us as we still need to communicate with the "primary" instance to pass on some information (application arguments) when a secondary instance is initiated so we went with the socket approach. With some tweaks to make it fit in with our application and the RCP-style of things (for example extension point support for listeners, adding configurability) it will work great.

Always nice to find some nuggets on the web so you dont need to "reinvent the wheel" so often. :)

/Erik

Issue with that code using the port

I used that code to create a single instance of java application...its working perfectly fine when i implemented it in my computer but, when i try to execute it on another computer its not working, i am executing it on fedora 13, but i wheb i try to execute it on windows machine of others, its working.....
can anyone tell me what will be the reason for it.....plz reply, its urgent.......

Careful 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.

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?

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, ...

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!

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.

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 ?

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.

Thanks!

I must have accidentally left that out when I originally wrote the article. I'll add your lines in for completion.

No Problem!

I'm just happy people are finding it and saving programming time.

Works great!

Thanks a lot for your work.

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.

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