Java Programs as Windows Services

I recently needed to run a Java program as a Windows service and opted for Commons-daemon procrun. This wrapper is used by both Tomcat and JBoss Wildfly to wrap their servers — but it took a bit of figuring out how to get my application running.

This post sets out an example of using procrun to wrap a Java process.

Download

I downloaded procrun from here. The download contains three different version of the procrun.exe:

You need to use the right version for your JVM and chipset

Code

The code is based on the EchoServer and EchoClient examples from Oracle.

EchoServer

import java.net.*; 
import java.io.*;   
public class EchoServer {     
  public static void main(String[] args) throws IOException {
    if (args.length != 1) {
      System.err.println("Usage: java EchoServer <port number>");
      System.exit(1);         
    }           

    int portNumber = Integer.parseInt(args[0]); 
    try (ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[0]));
         Socket clientSocket = serverSocket.accept(); 
         PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
         BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        ) {
      String inputLine;             
      while ((inputLine = in.readLine()) != null) {
        out.println(inputLine);             
      }         
    } catch (IOException e) {
      System.out.println("Exception caught when trying to listen on port " + portNumber + " or listening for a connection");   
      System.out.println(e.getMessage());         
    }
  }
}

EchoClient

The client is changed to take a shutdown parameter:

import java.io.*; 
import java.net.*;   
public class EchoClient {     
  public static void main(String[] args) throws IOException {
    if (args.length != 3) {             
      System.err.println("Usage: java EchoClient <host name> <port number>");  
      System.exit(1);
    }

    String hostName = args[0];         
    int portNumber = Integer.parseInt(args[1]);         
    String shutdown = args[2];           

    try (Socket echoSocket = new Socket(hostName, portNumber);             
         PrintWriter out = new PrintWriter(echoSocket.getOutputStream(), true); 
         BufferedReader in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));             
         BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {
      String userInput;             
      if (shutdown != null && !"".equals(shutdown)) { 
        userInput = shutdown;             
      }             

      while ((userInput = stdIn.readLine()) != null) {
        out.println(userInput);                 
        System.out.println("echo: " + in.readLine());  
      }         
    } catch (UnknownHostException e) {
      System.err.println("Don't know about host " + hostName); 
      System.exit(1);         
    } catch (IOException e) {
      System.err.println("Couldn't get I/O for the connection to " +  hostName);
      System.exit(1);         
    }
  }
}

Prunssrv

I've also created a simple class to stop and start the server:

import java.net.*; 
import java.io.*; 
public class Prunssrv {
  public static void prunsrvStartServer(String[] args) throws Exception {
    String[] newArgs = new String[1];         
    newArgs[0] = System.getProperty("prunsrv.port"); // -Dprunsrv.port=8080         
    EchoServer.main(newArgs);     
  }          

  public static void prunsrvStopServer(String[] args) throws Exception { 
    String[] newArgs = new String[2];         
    newArgs[0] = System.getProperty("prunsrv.server"); // -Dprunsrv.server=localhost         
    newArgs[1] = System.getProperty("prunsrv.port"); // -Dprunsrv.port=8080         
    newArgs[1] = "shutdown";                  
    EchoClient.main(newArgs);     
  } 
}

Putting it all together:

  1. Add the above classes and procrun.exe to a directory – C:\procrun
  2. Compile – javac *.java
  3. Create Archive – jar cvf simpleechoserver.jar *.class *.jar

Service.bat

You don't need to create a service.bat file, but it's cleaner and simpler. Store this in your code directory.

@echo off 
setlocal 

set SERVICE_NAME=SimpleEchoServer 
set PR_INSTALL=%~dp0%prunsrv.exe 
set PR_DESCRIPTION="Simple Echo Server Service" 

REM Service log configuration 
set PR_LOGPREFIX=%SERVICE_NAME% 
set PR_LOGPATH=%~dp0%\ 
set PR_STDOUTPUT=%~dp0%\stdout.txt 
set PR_STDERROR=%~dp0%\stderr.txt 
set PR_LOGLEVEL=Debug   

REM Path to java installation 
set PR_JVM=%JAVA_HOME%\jre\bin\server\jvm.dll 
set PR_CLASSPATH=simpleechoserver.jar   

REM Startup configuration 
set PR_STARTUP=auto 
set PR_STARTMODE=jvm 
set PR_STARTCLASS=Prunssrv 
set PR_STARTMETHOD=prunsrvStartServer   

REM Shutdown configuration 
set PR_STOPMODE=jvm 
set PR_STOPCLASS=Prunssrv 
set PR_STOPMETHOD=prunsrvStopServer 
set PR_STOPTIMEOUT=120   

REM JVM configuration 
set PR_JVMMS=256 
set PR_JVMMX=1024 
set PR_JVMSS=4000 

REM JVM options 
set prunsrv_port=8080 
set prunsrv_server=localhost 
set PR_JVMOPTIONS=-Dprunsrv.port=%prunsrv_port%;-Dprunsrv.server=%prunsrv_server% 

REM current file 
set "SELF=%~dp0%service.bat" 

REM current directory 
set "CURRENT_DIR=%cd%"   

REM start - This takes the input from installService and places it between x's 
REM       - if there are none then you get xx as a null check 
if "x%1x" == "xx" goto displayUsage 
set SERVICE_CMD=%1 
REM ahift moves to next field 
shift 
if "x%1x" == "xx" goto checkServiceCmd 
:checkServiceCmd 
if /i %SERVICE_CMD% == install goto doInstall 
if /i %SERVICE_CMD% == remove goto doRemove 
if /i %SERVICE_CMD% == uninstall goto doRemove 
echo Unknown parameter "%SERVICE_CMD%" 
:displayUsage 
echo. 
echo Usage: service.bat install/remove 
goto end 
:doRemove 
echo Removing the service '%PR_INSTALL%' '%SERVICE_NAME%' ... 
%PR_INSTALL% //DS//%SERVICE_NAME% 
if not errorlevel 1 goto removed 
echo Failed removing '%SERVICE_NAME%' service 
goto end 
:removed 
echo The service '%SERVICE_NAME%' has been removed 
goto end 
:doInstall 
echo Installing the service '%PR_INSTALL%' '%SERVICE_NAME%' ... 
%PR_INSTALL% //IS//%SERVICE_NAME% 
goto end 
:end 
echo Exiting service.bat ... 
cd "%CURRENT_DIR%"

Key Points

Running service.bat

You may need to run this as administrator:

C:\procrun>service.bat Usage: service.bat install/remove Exiting service.bat ...

To install:

service.bat install

And uninstall:

service.bat remove

You can then test:

java EchoClient localhost 8080 hello echo: hello ...

If you go to your Windows Services, you will now see SimpleEchoServer with stop/start/restart options

Prunmgr.exe

The final trick is to use prunmgr. This the procrun manager and allows you to see the procrun operating parameters. To get started, go to your copy of prunmgr.exe and rename or copy it to the SERVICE_NAME in your batch file:

set SERVICE_NAME=SimpleEchoServer

So:

C:\procrun>copy prunmgr.exe SimpleEchoServer.exe

You then run the SimpleEchoServer.exe as administrator.

 

 

 

 

Top