devxlogo

Converting Your Java App into a Windows Service

Converting Your Java App into a Windows Service

ou probably are reading this article because you want to convert a Java application into a Windows service. I can relate. Recently, I wanted to turn a Java server application that embedded the Jetty web server and ActiveMQ JMS client into a Windows service, enabling users to start it automatically without logging into the system. Further, I wanted it to start and stop in a controlled fashion. After searching SourceForge for a suitable project to help me fulfill these specific requirements and coming up empty, I decided to write a simple customized solution of my own.

I discovered you can use one of two approaches to convert a Java application into a Windows service:

  1. The Windows service launches your Java application in a separate process. (The service has no control over the Java application.)
  2. The Windows service creates a Java virtual machine (JVM) in its own process context and then shuts it down in a controlled manner.

I chose the second approach because it delivered everything I needed. This article describes how to apply this approach when you convert your Java application into a Windows service. It walks you through the development steps while highlighting the core concepts. To follow along, you need to have a good understanding of Windows concepts and the Java Native Interface (JNI) invocation API.

Mimicking Java.exe
Windows Task Manager will list all your Java applications as “Java” processes, so you have no way to identify an individual process. To assign specific names to your Java apps, you can mimic the Java runtime by using the JVM invocation API.

When you run java on the command prompt, the java.exe in the system path loads several dynamically loadable libraries (DLLs). The following DLLs are of interest to this discussion:

  • Javajdk1.6.0_01jreinclientjvm.dll?The Java JVM DLL that exposes the JVM invocation API. Once you create a JVM, jvm.dll in turn loads various support DLLs.
  • Javajdk1.6.0_01jreinhpi.dll
  • Javajdk1.6.0_01jreinverify.dll
  • Javajdk1.6.0_01jreinjava.dll
  • Javajdk1.6.0_01jreinzip.dll

Here is how the java.exe processes the DLL:

  1. Loads the JVM DLL
  2. Creates a JVM
  3. Loads the class you specify
  4. Calls the main method that has the signature: public static void main (String[] args)

With the JNI_CreateJavaVM method defined in jni.h, you can create a JVM instance. You can find jni.h in the include directory.

As an example, take a simple Java program based on the Visual Studio 2005 project HelloKNR (download it here), which says hello to Kernighan and Ritchie (KNR, for short):

package com.doorul;public class HelloKNR {    public static void main(String[] args) {        System.out.println("Hello KNR!");    }}

For simplicity, I have removed from the original HelloKNR project the parser code that deduces JVM options and command line parameters.

The following is the JNI code to mimic java.exe for the example program (download it here). Loading the jvm.dll dynamically instead of statically binding with jvm.lib enables the flexibility to choose the JVM version during deployment (see Listing 1):

int InvokeMain() {	JavaVM *vm;	JavaVMInitArgs vm_args;	JavaVMOption options[1];	jint res;	JNIEnv *env;	jclass cls;	jmethodID mid;	options[0].optionString = CLASS_PATH;	vm_args.version = JNI_VERSION_1_4;	vm_args.options = options;	vm_args.nOptions = 1;	vm_args.ignoreUnrecognized = JNI_FALSE;	//load the JVM DLL	HINSTANCE handle = LoadLibrary(RUNTIME_DLL);	if( handle == 0) {		printf("Failed to load jvm dll %s
",			RUNTIME_DLL);		return -1;	}	//get the function pointer to JNI_CreateJVM	createJVM = (CreateJavaVM)GetProcAddress(handle,			"JNI_CreateJavaVM");	res = createJVM(&vm, (void **)&env, &vm_args);	if (res < 0)  {		printf("Error creating JVM");		return -1;	}	//locate the class	cls = env->FindClass(CLASS_NAME);	if(cls == 0) {		printf("Exception in thread "main"			java.lang.NoClassDefFoundError: %s
",				CLASS_NAME);		return -1;	}	//get the method id of main	mid = env->GetStaticMethodID(cls, "main",		"([Ljava/lang/String;)V");	if(mid == 0) {		printf("Exception in thread "main"			java.lang.NoSuchMethodError: main
");		return -1;	}	//invoke the main method with no parameters	env->CallStaticVoidMethod(cls, mid, 0);	//if there is any exception, print the exception	if(env->ExceptionCheck()) {		env->ExceptionDescribe();		return -1;	}	return 0;}

While loading the class com.doorul.HelloKNR, you have to use the “/” delimiter (i.e., com/doorul/HelloKNR). Also you need to understand the JNI signature format for invoking Java methods. For example, to call the void main(String[] args) method, the signature format is ([Ljava/lang/String;)V: “[” means array; “L;” represents a Java object; and V denotes that the method returns void. You can get more details from the JNI specification web site.

Integrating JNI with the Windows Service API
I needed a continuously running Java app to demonstrate service capability, so I wrote a simple Dummy.java class that waits inside the main method for a stop event. I implemented a shutdown method, which upon invocation sets the stop event, causing the main thread to exit gracefully. The Dummy class also implements a shutdown hook, which is used mainly when it is launched with java.exe.

Typical Windows services (excluding device drivers) are user processes launched and managed by the Service Control Manager (SCM), also known as the services.exe process. A single process can contain many services. In my design, I prefer to contain one JVM instance per process and one Windows service per process.

Installing a service requires getting a handle to the SCM and creating a service, as follows:

SC_HANDLE schSCManager = OpenSCManager(..), SC_MANAGER_CREATE_SERVICE); SC_HANDLE schService = CreateService(..)

Uninstallation requires getting a handle to the service and calling delete on it:

SC_HANDLE schSCManager = OpenSCManager(.., SERVICE_ALL_ACCESS); SC_HANDLE schService = OpenService( schSCManager, ..);DeleteService(schService)

To start up the service, you register the ServiceMain function with StartServiceCtrlDispatcher. The ServiceMain function contains your main functionality. In this case, call the InvokeMain function. Next, call RegisterServiceCtrlHandler(SERVICE_NAME, ServiceHandler);, which registers a Handler for receiving commands from the SCM. To prevent the whole service from crashing when a JVM abort occurs, launch the InvokeMain method in a separate thread.

To play with the Dummy service yourself, build the VS 2005 project DummyService, copy Dummy.java to C:, and compile the code. Run the following commands to install, start, stop, and uninstall the service:

DummyService /i (installs the service)net start DummyService net stop DummyServiceDummyService /u (uninstalls the service)

At least 15-20 configurable parameters can result from integrating JNI with the Windows Service API. Here are the ones to consider when customizing the program to suit your requirements:

  • Windows service parameters:
    • Service name
    • Display name
    • Description
    • Path to executable
    • Startup type (automatic/manual)
    • Parameters can be stored in registry
    • Working directory
    • One service per process or many services in a single process
    • Interacts with desktop option (required if your service has a UI component)
    • Login username and password (run the service with a user account or local system account)
  • Java application-specific parameters:
    • Path to JVM.dll
    • JVM options (-D, -X)
    • Class name
    • Any command line arguments (though they are not recommended)
    • Shutdown timeout
  • Logging parameters:
    • Event logging versus file logging
    • Redirect any standard output/error to some file

During installation, depending on your requirements, you can store parameters under the recommended registry key location: HKLMSystemCurrentControlSet[Service Name]Parameters.

Shutting Down the Java App Gracefully
My first shutdown approach was calling System.exit(0). Although this method called all the registered shutdown hooks concurrently and tried to shutdown the JVM gracefully, it failed to clean the pipe communication that the Windows service established with the SCM. So when I used the System.exit call, I got the following error while stopping the service:

System error 109 has occurred.The pipe has been ended.

I figured out a better way: implement a separate shutdown method and call it. The framework proposed in this article invokes the public static void shutdown() method (if it is implemented) in a separate shutdown thread with a timeout of 20 seconds. This prevents the service from reaching a “not responding” state if the shutdown method never returns. You can configure the timeout period based on your own needs, but make sure it is less than the system timeout. Otherwise, the SCM will terminate your service process.

Beyond the Windows Service
Understanding JNI invocation API provides the flexibility to call Java apps in your C/C++ program. For instance, you may want to provide a web interface to your legacy C application by embedding a Jetty Servlet engine.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist