There could such cases when you need to prevent opening multiple java application instances in some scope. Depending on requirements this scope could be: operating system, user session or application config (to prevent run twice using same config). The main reasoning for such behaviour is to avoid data corruption while same resource accessed from different instances or just to deal with stupid users who run new application instead of opening already running one (because they just do not know how to open minimized application).

If you are about to add such behaviour in your Java app you will have to think about your own implementation. Because locking across Java app instances functionality is not available in standard library.

Overall there are 2 basic approaches that allows you to have some kind of application lock:

  • Exclusive lock on some port
  • Exclusive lock on file

My solution is based on the file lock idea because I think that “port lock” approach gives you less capabilities compared to file lock.

I try to create really reusable piece of code :) which can handle locking between application instances. And of course it is cross platform and open source.

Application lock for production usage

If you are searching for production ready solution you should go to Jneat repository.

Advanced approach to this subject reviewed in this article Synchronize code over multiple JVM instances

File locking solution explanation

This explanation is only for educational purpose just in a case if you are very curious.

For some reason many developers suggested to put lock file in the same directory with executable file. This is bad, because you still be able to run another copy of application from another folder where your program was copied. And is is also possible that your application would not have write access to the directory where it was placed.

My approach is based on next principles:

  1. Lock file will be created in temporary system folder.
  2. File name will be generated as md5 hash, based on some application unique keyword.
  3. Another application instances will try to search for the appropriate lock file, and than decide what to do.

This is how it should work in application code.

try {
    // Trying to get LOCK //
    if (!AppLock.setLock("MY_CUSTOM_LOCK_KEY")) {
        // It is up to you to decide what to do here
        // exception is no a good idea
        throw new RuntimeException("Only one application instance may run at the same time!");
    }
    // RUN YOUR CODE
}
finally{
    AppLock.releaseLock(); // Release lock
}

Everything has to be simple there are only two static methods to work with. Here is how whole AppLock class looks like.

import java.io.File;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The Class AppLock.
 *
 * @author Rumato Estorsky
 */
public class AppLock {

        /**
         * Instantiates a new app lock.
         */
        private AppLock() {
        }

        /** The lock_file. */
        File lock_file = null;

        /** The lock. */
        FileLock lock = null;

        /** The lock_channel. */
        FileChannel lock_channel = null;

        /** The lock_stream. */
        FileOutputStream lock_stream = null;

        /**
         * Instantiates a new app lock.
         *
         * @param key Unique application key
         * @throws Exception The exception
         */
        private AppLock(String key) throws Exception {
                String tmp_dir = System.getProperty("java.io.tmpdir");
                if (!tmp_dir.endsWith(System.getProperty("file.separator"))) {
                        tmp_dir += System.getProperty("file.separator");
                }

                // Acquire MD5
                try {
                        java.security.MessageDigest md = java.security.MessageDigest
                                        .getInstance("MD5");
                        md.reset();
                        String hash_text = new java.math.BigInteger(1, md.digest(key
                                        .getBytes())).toString(16);
                        // Hash string has no leading zeros
                        // Adding zeros to the beginnig of has string
                        while (hash_text.length() < 32) {
                                hash_text = "0" + hash_text;
                        }
                        lock_file = new File(tmp_dir + hash_text + ".app_lock");
                } catch (Exception ex) {
                        System.out.println("AppLock.AppLock() file fail");
                }

                // MD5 acquire fail
                if (lock_file == null) {
                        lock_file = new File(tmp_dir + key + ".app_lock");
                }

                lock_stream = new FileOutputStream(lock_file);

                String f_content = "Java AppLock Object\r\nLocked by key: " + key
                                + "\r\n";
                lock_stream.write(f_content.getBytes());

                lock_channel = lock_stream.getChannel();

                lock = lock_channel.tryLock();

                if (lock == null) {
                        throw new Exception("Can't create Lock");
                }
        }

        /**
         * Release Lock.
         * Now another application instance can gain lock.
         *
         * @throws Throwable
         */
        private void release() throws Throwable {
                if (lock.isValid()) {
                        lock.release();
                }
                if (lock_stream != null) {
                        lock_stream.close();
                }
                if (lock_channel.isOpen()) {
                        lock_channel.close();
                }
                if (lock_file.exists()) {
                        lock_file.delete();
                }
        }

        @Override
        protected void finalize() throws Throwable {
                this.release();
                super.finalize();
        }

        /** The instance. */
        private static AppLock instance;

        /**
         * Set application lock.
         * Method can be run only one time per application.
         * All next calls will be ignored.
         *
         * @param key Unique application lock key
         * @return true, if successful
         */
        public static boolean setLock(String key) {
                if (instance != null) {
                        return true;
                }

                try {
                        instance = new AppLock(key);
                } catch (Exception ex) {
                        instance = null;
                        Logger.getLogger(AppLock.class.getName()).log(Level.SEVERE, "Fail to set AppLoc", ex);
                        return false;
                }

                Runtime.getRuntime().addShutdownHook(new Thread() {
                        @Override
                        public void run() {
                                AppLock.releaseLock();
                        }
                });
                return true;
        }

        /**
         * Trying to release Lock.
         * After release you can not user AppLock again in this application.
         */
        public static void releaseLock() {
                try {
                    if (instance == null) {
                            throw new NoSuchFieldException("INSTATCE IS NULL");
                    }
                    instance.release();
                } catch (Throwable ex) {
                        Logger.getLogger(AppLock.class.getName()).log(Level.SEVERE, "Fail to release", ex);
                }
        }
}

Room for improvement

We are creating an empty file in AppLock, but this file can be not empty. You can serialize any data there. When another app instance would start it can read and de-serialize any data from the lock file. You can use this feature to pass the first application instance process id, server port number or anything else to set up communication between app instances.