Creating a Distribution Package of Your Java Application

Say, you have written a Java application and now it is time to create windows installer package to upload it to your site so that anybody could download it, run and enjoy your proggy. Of course, one can distribute a plain JAR file, but not all users will be able to start it successfully.

It is better to give them a normal plain installer package that they know how to run through the wizard. You may also want to bundle a JRE with your application so your program will run on every machine with no JRE installed. I would also advise obfuscate your code, thus, no evil hacker could get your code too easily. It also makes sense to wrap your Java application in a regular windows executable (EXE) file.

You will need the following tools and libraries that are distributed free of charge:

1. Ant tool.
2. NSIS: a scriptable win32 installer/uninstaller system.
3. ProGuard: java shrinker, optimizer, and obfuscator.
4. Launch4j: Cross-platform Java executable wrapper for creating lightweight Windows native EXEs.
5. NSIS Ant task: to compile NSIS scripts.

The goal is to create a single build.xml file that will compile (JAVAC), obfuscate, shrink, optimize (ProGuard) our code, create an EXE file (Launch4j) and pack it all including other files (like license text file) to an installer package (NSIS).

The ant build file consists of a number of targets that can depend upon other targets in the same build file. That means before the target does something useful it will run the targets it depends upon:

Initialization Target

First, let’s define a number of properties:

XML:

  1. <property name="src" location="d:\myproggy\src" />
  2.     <property name="lib" location="lib" />
  3.     <property name="images" location="images" />
  4.     <property name="nsis" location="nsis" />
  5.     <property name="dist" location="dist" />
  6.     <property name="html" location="html" />
  7.     <property name="build" location="build" />
  8.     <property name="launch4j.dir" location="../.." />
  9.     <property name="original.dir" location="d:\myproggy" />

You may want to justify the paths according to your system.

The target init will depend upon target ‘clean’ – we want to clean everything before the build starts:

XML:

  1. <target name="clean" description="clean up">
  2.         <delete dir="${build}"/>
  3.         <delete dir="${html}"/>
  4.         <delete dir="${images}"/>
  5.         <delete dir="${lib}"/>
  6.         <delete dir="${dist}"/>
  7.     </target>

After it is over, the target ‘init’ will run.

XML:

  1. <target name="init" depends="clean">
  2.         <tstamp />
  3.         <mkdir dir="${build}" />
  4.         <mkdir dir="${html}" />
  5.         <mkdir dir="${images}" />
  6.         <mkdir dir="${lib}" />
  7.         <mkdir dir="${dist}" />
  8.        
  9.         <copy todir="${html}">
  10.             <fileset dir="${original.dir}/html/"/>
  11.         </copy>
  12.        
  13.         <copy todir="${images}">
  14.             <fileset dir="${original.dir}/images/"/>
  15.         </copy>  
  16.  
  17.         <copy todir="${lib}">
  18.             <fileset dir="${original.dir}/lib"/>
  19.         </copy>  
  20.        
  21.     </target>

We create a number of folders, copy source code, libraries, html, and image files.

Compile Target

XML:

  1. <target name="compile" depends="init" description="compile the source">
  2.         <javac srcdir="${src}" destdir="${build}" classpathref="dist.classpath" source="1.5" debug="on" />
  3.        
  4.           <copy todir="${build}">
  5.             <fileset dir="${src}">
  6.               <include name="*.vm"/>
  7.               <include name="*.properties"/>
  8.             </fileset>
  9.           </copy>      
  10.           
  11.     </target>

Compile the classes and copy the required files, such as Velocity templates and property files to the folder with the freshly compiled classes.

JAR Target

Next step is to create a JAR file and feed it to ProGuard that will “obfuscate, shrink, optimize” our code. ProGuard task has a number of limitations and requirements, so while my example is pretty clear you may want to read the ProGuard documentation. In a few words you name the libraries that are used by your application, specify you jar file name, and the name of the jar that will be the result of ProGuard’s work. ProGuard modifies the names of classes, so you need to keep the name of your Main-Class untouched. Anyway, consult the docs or you may run into some issues here.

XML:

  1. <target name="jar" depends="compile" description="create the jar">
  2.    
  3.         <fileset dir="${lib}" id="lib.dist.fileset">
  4.             <include name="**/*.jar" />
  5.         </fileset>
  6.        
  7.         <pathconvert pathsep=" " property="dist.classpath" refid="lib.dist.fileset">
  8.             <map from="${lib}" to=".\lib" />
  9.         </pathconvert>
  10.        
  11.         <!-- Put everything in ${build} into a jar file -->
  12.         <jar jarfile="${ant.project.name}.jar">
  13.             <fileset dir="${build}" includes="**/*" />
  14.             <manifest>
  15.                 <!-- SET YOUR MAIN CLASS HERE -->
  16.                 <attribute name="Main-Class" value="com.javazing.MyProggyLauncher" />
  17.                 <attribute name="Class-Path" value=". ${dist.classpath}" />
  18.             </manifest>
  19.         </jar>
  20.        
  21.         <!-- obfuscate and optimize by ProGuard -->
  22.         <taskdef resource="proguard/ant/task.properties" classpath="${launch4j.dir}/lib/proguard.jar" />
  23.         <proguard>
  24.        
  25.             -libraryjars jre/lib/rt.jar
  26.             -libraryjars lib/commons-collections-3.1.jar
  27.             -libraryjars lib/commons-io-1.2.jar
  28.             -libraryjars lib/commons-lang-2.1.jar
  29.             -libraryjars lib/commons-logging.jar
  30.             -libraryjars lib/hsqldb.jar
  31.             -libraryjars lib/jdom-1.0.jar
  32.             -libraryjars lib/log4j.jar
  33.             -libraryjars lib/lucene-core-1.9.1.jar
  34.             -libraryjars lib/org.eclipse.core.commands_3.2.0.I20060511-0800a.jar
  35.             -libraryjars lib/org.eclipse.equinox.common_3.2.0.v20060512.jar
  36.             -libraryjars lib/org.eclipse.jface.text_3.2.0.v20060518-0800.jar
  37.             -libraryjars lib/org.eclipse.jface_3.2.0.I20060511-0800a.jar
  38.             -libraryjars lib/org.eclipse.osgi_3.2.0.v20060510.jar
  39.             -libraryjars lib/org.eclipse.swt.win32.win32.x86_3.2.0.v3224.jar
  40.             -libraryjars lib/org.eclipse.text_3.2.0.v20060518-0800.jar
  41.             -libraryjars lib/rome-0.8.jar
  42.             -libraryjars lib/velocity-1.4.jar         
  43.            
  44.             -injars      ${ant.project.name}.jar
  45.             -outjars     obfuscated_${ant.project.name}.jar
  46.  
  47.             -keepclasseswithmembers public class * {
  48.             public static void main(java.lang.String[]);
  49.             }
  50.  
  51.             -dontskipnonpubliclibraryclasses
  52.  
  53.             -keep public class com.javazing.MyProggyLauncher
  54.            
  55.             -keep class * implements java.sql.Driver
  56.            
  57.            
  58.         </proguard>
  59.        
  60.     </target>

EXE Target

After the JAR target, we have a jar file that needs to be wrapped to become a native Windows executable file. We use Launch4j tool here. Launch4j needs an xml file with the settings:

proggy.xml file:

XML:

  1. <launch4jConfig>
  2.   <dontWrapJar>false</dontWrapJar>
  3.   <headerType>0</headerType>
  4.   <jar>..\obfuscated_my_cool_proggy.jar</jar>
  5.   <outfile>..\my_cool_proggy.exe</outfile>
  6.   <errTitle>MyProggy</errTitle>
  7.   <jarArgs></jarArgs>
  8.   <chdir>.</chdir>
  9.   <customProcName>false</customProcName>
  10.   <stayAlive>false</stayAlive>
  11.   <icon>proggy.ico</icon>
  12.   <jre>
  13.     <path>jre</path>
  14.     <minVersion>1.5.0</minVersion>
  15.     <maxVersion></maxVersion>
  16.     <initialHeapSize>0</initialHeapSize>
  17.     <maxHeapSize>0</maxHeapSize>
  18.     <args></args>
  19.   </jre>
  20.   <splash>
  21.     <file>proggy.bmp</file>
  22.     <waitForWindow>true</waitForWindow>
  23.     <timeout>60</timeout>
  24.     <timeoutErr>true</timeoutErr>
  25.   </splash>
  26. </launch4jConfig>

You may want to get an ICO & BMP images for your application. The icon file is the image to be rendered at the left top corner of every Windows application. The BMP file will be displayed as a splash file when the proggy starts. Java 1.6 has built-in splash functionality, so you may want to use it along with some nice PNG image with shadows and other alpha tricks, and not the Launch4J’s ugly bmp file.

XML:

  1. <target name="exe" depends="jar">
  2.         <taskdef name="launch4j" classname="net.sf.launch4j.ant.Launch4jTask" classpath="${launch4j.dir}/launch4j.jar
  3.             :${launch4j.dir}/lib/xstream.jar" />
  4.         <launch4j configFile="./l4j/proggy.xml" />
  5.        
  6.     </target>

Distibution Target

NSIS comes with a number of templates, samples, some nice graphics and GUI application. Therefore, you will definitely want to see its samples. my_proggy.nsi is the name of the script that will guide you installation and deinstallation processes: create folders, copy files, create menu group, create desktop icons etc.

Sample NSIS script:

NSIS:

  1. !include "MUI.nsh"
  2.  
  3.   !define MUI_ICON "proggy.ico"
  4.   !define MUI_UNICON "proggy.ico"
  5.  
  6. Function .onInit
  7.     # the plugins dir is automatically deleted when the installer exits
  8.     InitPluginsDir
  9.     File /oname=$PLUGINSDIR\splash.bmp "D:\myproggy\splash.bmp"
  10.     #optional
  11.     #File /oname=$PLUGINSDIR\splash.wav "C:\myprog\sound.wav"
  12.  
  13.     splash::show 1000 $PLUGINSDIR\splash
  14.  
  15.     Pop $0 ; $0 has '1' if the user closed the splash screen early,
  16.             ; '0' if everything closed normally, and '-1' if some error occurred.
  17. FunctionEnd
  18.  
  19.  
  20. ;--------------------------------
  21. ;General
  22.  
  23.   ;Name and file
  24.   Name "${PRODUCT_NAME}"
  25.   OutFile "${EXE_NAME}"
  26.  
  27.   ;Default installation folder
  28.   InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
  29.  
  30.   ;Get installation folder from registry if available
  31.   InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" ""
  32.  
  33. ;--------------------------------
  34. ;Interface Configuration
  35.  
  36.   !define MUI_HEADERIMAGE
  37.   !define MUI_HEADERIMAGE_BITMAP "proggy_header.bmp" ; optional
  38.  
  39. ;--------------------------------
  40. ;Variables
  41.  
  42.   Var MUI_TEMP
  43.   Var STARTMENU_FOLDER
  44.  
  45. ;--------------------------------
  46. ;Interface Settings
  47.  
  48.   !define MUI_ABORTWARNING
  49.  
  50. ;--------------------------------
  51. ;Pages
  52.  
  53.   !insertmacro MUI_PAGE_LICENSE "license.rtf"
  54.   !insertmacro MUI_PAGE_DIRECTORY
  55.  
  56.   ;Start Menu Folder Page Configuration
  57.   !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
  58.   !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\${PRODUCT_NAME}"
  59.   !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
  60.   !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
  61.   !insertmacro MUI_PAGE_INSTFILES
  62.   !insertmacro MUI_UNPAGE_CONFIRM
  63.   !insertmacro MUI_UNPAGE_INSTFILES
  64.  
  65. ;--------------------------------
  66. ;Languages
  67.  
  68.   !insertmacro MUI_LANGUAGE "English"
  69.  
  70. ;--------------------------------
  71. ;Installer Sections     
  72. Section "install"
  73.  
  74. ;Add files
  75.   SetOutPath "$INSTDIR"
  76.  
  77.   File "${FOLDER}\${EXE_NAME}"
  78.   File "${FOLDER}\settings.ini"
  79.  
  80.   SetOutPath "$INSTDIR"
  81.   file /r ${FOLDER}\images
  82.   file /r ${FOLDER}\html
  83.   file /r ${FOLDER}\jre
  84.   file /r ${FOLDER}\lib
  85.   file ${FOLDER}\evaluation_license.txt
  86.   file ${FOLDER}\license.rtf
  87.  
  88. ;create desktop shortcut
  89.   CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE_NAME}" ""
  90.  
  91. ;create start-menu items
  92.   CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
  93.   CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
  94.   CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE_NAME}" "" "$INSTDIR\${EXE_NAME}" 0
  95.  
  96. ;write uninstall information to the registry
  97.   WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${SHORT_NAME}" "DisplayName" "${SHORT_NAME} (remove only)"
  98.   WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${SHORT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
  99.  
  100.   WriteUninstaller "$INSTDIR\Uninstall.exe"
  101.  
  102. SectionEnd
  103. ;--------------------------------
  104. ;Uninstaller Section
  105.  
  106. Section "Uninstall"
  107.  
  108.   ;ADD YOUR OWN FILES HERE...
  109. ;Delete Files
  110.   RMDir /r "$INSTDIR\*.*"   
  111.  
  112. ;Remove the installation directory
  113.   RMDir "$INSTDIR"
  114.  
  115. ;Delete Start Menu Shortcuts
  116.   Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
  117.   Delete "$SMPROGRAMS\${PRODUCT_NAME}\*.*"
  118.   RmDir  "$SMPROGRAMS\${PRODUCT_NAME}"
  119.  
  120. ;Delete Uninstaller And Unistall Registry Entries
  121.   DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\${SHORT_NAME}"
  122.   DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${SHORT_NAME}"
  123.  
  124.   Delete "$INSTDIR\Uninstall.exe"
  125.  
  126.   RMDir "$INSTDIR"
  127.  
  128.   !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
  129.    
  130.   Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
  131.  
  132.   ;Delete empty start menu parent diretories
  133.   StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
  134.  
  135.   startMenuDeleteLoop:
  136.     ClearErrors
  137.     RMDir $MUI_TEMP
  138.     GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
  139.    
  140.     IfErrors startMenuDeleteLoopDone
  141.  
  142.     StrCmp $MUI_TEMP $SMPROGRAMS startMenuDeleteLoopDone startMenuDeleteLoop
  143.   startMenuDeleteLoopDone:
  144.  
  145.   DeleteRegKey /ifempty HKCU "Software\${PRODUCT_NAME}"
  146.  
  147. SectionEnd
  148.  
  149. ;Function that calls a messagebox when installation finished correctly
  150. Function .onInstSuccess
  151.   MessageBox MB_OK "You have successfully installed ${PRODUCT_NAME}. Use the desktop icon to start the program."
  152. FunctionEnd
  153.  
  154. Function un.onUninstSuccess
  155.   MessageBox MB_OK "You have successfully uninstalled ${PRODUCT_NAME}."
  156. FunctionEnd

And the appropriate target:

XML:

  1. <target name="dist"  depends="exe" description="make installation package for distribution">
  2.    
  3.         <taskdef name="nsis" classname="net.sf.nsisant.Task">
  4.             <classpath location="nsisant-1.2.jar"/>
  5.         </taskdef>
  6.        
  7.         <nsis script="nsis/my_proggy.nsi" verbosity="4" out="nsis_build.log" noconfig="yes">
  8.             <define name="VERSION" value="1.0"/>
  9.             <define name="PRODUCT_NAME" value="My Proggy"/>
  10.             <define name="EXE_NAME" value="${ant.project.name}.exe"/>
  11.             <define name="SHORT_NAME" value="MyCoolProggy"/>
  12.             <define name="FOLDER" value="D:\launch4j\MyProggy"/>
  13.         </nsis>
  14.        
  15.         <copy file="${nsis}/${ant.project.name}.exe" tofile="${dist}/${ant.project.name}.exe"/>
  16.        
  17.         <delete file="${nsis}/${ant.project.name}.exe"/>
  18.        
  19.         <delete dir="${build}"/>
  20.         <delete dir="${html}"/>
  21.         <delete dir="${images}"/>
  22.         <delete dir="${lib}"/>
  23.        
  24.         <delete file="${ant.project.name}.jar"/>
  25.         <delete file="${ant.project.name}.exe"/>
  26.         <delete file="obfuscated_${ant.project.name}.jar" />
  27.        
  28.        
  29.     </target>

It is really convenient. After you make some changes in the code, you run a single file and have an installer package ready.

By the way, it also makes sense to check for the required system paths before the build process. Create a .BAT file:

DOS:

  1. @echo off
  2. if "%ANT_HOME%"=="" goto noAntHome
  3. if "%JAVA_HOME%"=="" goto noJavaHome
  4.  
  5. call "%ANT_HOME%\bin\ant.bat" dist
  6. goto end
  7.  
  8. :noAntHome
  9. echo ANT_HOME environment variable is not set
  10. goto end
  11.  
  12. :noJavaHome
  13. echo JAVA_HOME environment variable is not set
  14.  
  15. :end

Good luck!

Download build.xml file.

13 Responses to “Creating a Distribution Package of Your Java Application”

  1. Demetrios Kyriakis Says:

    Even if nothing new, this is a very nice "compilation", thank you.

    The only problem is that there's no such solution for java web applications. A webapplication would be distributed as a WAR file, but the problem is:
    #1 there's no generic lightweigh web based installer.
    #2 there's no obfuscator that can work in a webapplication and be aware of JSPs (java server pages) or other wide used web tehnologies.
    #3 and finally there's no tutorial like your's that summaries everything in one page, so that "it just works" :).

    Thank you,

    Demetrios.

  2. Distributing a Java application, in one easy Ant build at Blog of Bug Says:

    [...] http://www.javazing.com/2007/04/05/creating-a-distribution-package-of-your-java-application/ [...]

  3. Creating a Distribution Package of Your Java Application « FlashColony Blog Says:

    [...] Stuff Javazing blog has an article on how to do that. Ultimately before you’ll be able to follow the tutorial you going to have to download the [...]

  4. John Says:

    Hello

    I have an application i created in java, But i find it hard to create an icon that will run the application !!!!!!!.

  5. Paco De la Puerta Says:

    Sergey,
    Thank you for pointing me in this direction. I have been looking for it for long. I have made some simple programs to be distributed along with a book I am writing. Purchasing commercial utility programs is at this point for me expensive. This can solve my problem. Hope is not too complicated because of the use of the 4 tools and libraries mentioned above. I am also sure that many other programmers are in the same boat as me. I will try, and will be in contact with you in the future. At any rate, I do thank you very much.
    Paco De la Puerta

  6. Champak Says:

    Hi..
    Thank you so much. I had some difficulty connecting all these 4 tools in producing an installer for my project. But I managed to get it right after 3,4 days of effort.

  7. Fredric Says:

    This is a great setup, thanks for writing about it. I've been tasked to come up with the deployment/install strategy for a new product here at work and came up with the same idea... sort of... I'm glad to see that this idea has worked for someone else.

    Btw, I saw that NSIS has a few examples on how to create a native launcher via NSIS so you could eliminate the launch4J step.

  8. Ravi Teja Says:

    Hi...
    Thank you. your blog has helped me to an extent, but still I am facing some difficulties. I am glad if u can help me in this issue. I have to create a installation file which copies files to a specified folder and then inserts an icon into existing software application and clicking that icon should call the main class of that .jar file.

  9. S.Balasundaram Says:

    Thank u sir.. it is more use full...It teach nice thing & who expert in java & wish to improve the performance of java application using native code..

  10. Grigory Says:

    Sergey, you do great job, many thanks. A bit pity, what have not explanations how to integrate this solutions in netbeans. But I see that you use eclipse IDE.

  11. Venky Says:

    Hi Sergey

    Its a clear and neat expected explanation for distribution of Java application ,its much appreciated.

    For me,it may take another two or more readings to understand and imply it completely,none the less,your step by step explanation is awesome.

    As of now,my Java application development is simple so I haven't used any IDE environment for developing Java applications.I may use IDE environment soon,most likely "Netbeans".

    If possible,throwing lights on integrating these in "Netbeans" environment will be much appreciated.

    Well, I also thank Grigory for mentioning about IDE platform in his comments in this post.

    By
    Venky

  12. Venky Says:

    Hi Sergey,

    Its clear and neat expected explanation for distribution of Java applications,its much appreciated.

    For me it may take two or more readings to use it effectively.

    If possible,please throw lights on integrating in "Netbeans" environment,it will be much appreciated.

    With Thanks
    Venky

  13. Nonso Says:

    How can i create a java program that installs the startup image of a phone. thanks

Leave a Reply