In this article, we’ll see how to make an old-but-useful legacy Java application into a brand new Java Web Start (JWS for short) application.
JWS is an often overlooked technology that works miracles. With it, you can deploy any Java application, as easily as you would a web one: with the only update server-side and still your application running client-side, with access to all its resources. JWS has two main features: the downloading of the application to the client and the launching of said application. It’s a technology available in the Java Runtime Environment since version 1.4.
JWS is based on a most specific deployment descriptor in JNLP format. This DD takes place along the many JARs of the application on the server. JNLP files are XML although there are no schemas published by Sun, either in DTD or XSD format. You may find some schemas made by people on the web.
In order to publish your application through JWS, you’ll need to perform the following steps.
Step 1: Make your code available as a web resource
I sold you that any Java application can be made available on the web. This means you’ll have to publish both your JAR itself and the associated JNLP.
/jnlp/@codebase
specifies where the root directory lies/jnlp/@href
the name of the JNLP file under this root/jnlp/resources/jar/@href
the path to your JAR
<?xml version="1.0" encoding="utf-8"?>
<jnlp codebase="http://blog.frankel.ch/demo" href="launch.jnlp">
<information>
<title>Legacy Application</title>
<vendor>Nicolas Frankel</vendor>
</information>
<resources>
<jar href="myMainJar.jar" />
</resources>
</jnlp>
Both /jnlp/information/title
and /jnlp/information/vendor
are required and will be shown in the JWS dialog menu.
Step 2: Adress a version of the JRE
Since you will make your application available, ask yourself which version will be used on the client’s computer. In essence, it will amount to 2 things:
/jnlp/@spec
specifies the version of the JNLP XML format/jnlp/resources/j2se/@version
the needed version of the client JRE
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.5+" codebase="http://blog.frankel.ch/demo" href="launch.jnlp">
<information>
<title>Legacy Application</title>
<vendor>Nicolas Frankel</vendor>
</information>
<resources>
<j2se version="1.5+" />
<jar href="myMainJar.jar" />
</resources>
</jnlp>
See how both can use the plus sign in order to indicate a minimum version.
You can even tell to JWS to redirect the browser to a JRE download page or download it automatically. Both are done through a combination of JavaScript and HTML.
Step 3: Reference dependent libraries and the Main Class
Usually, informations about the main class - the one containing the main method - and the JAR dependencies are specified either on the command line or in the JAR’s deployment descriptor.
Informations specified on the command line:
java -cp myMainJar.jar;lib/myFirstDependency.jar;lib/mySecondDependency.jar ch.frankel.blog.jws.MainClass
Informations specified in the MANIFEST.MF
:
java -jar myMainJar.jar
META-INF/MANIFEST.MF
in the JAR:
Manifest-Version: 1.0
Class-Path: lib/myFirstDependency.jar lib/mySecondDependency.jar
Main-Class: ch.frankel.blog.jws.MainClass
The first thing to understand is that JWS ignores what is written is the manifest. So, in either case, you have to tell Java Web Start the main class and your dependencies manually in the JNLP.
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.5+" codebase="http://blog.frankel.ch/demo" href="launch.jnlp">
<information>
<title>Legacy Application</title>
<vendor>Nicolas Frankel</vendor>
</information>
<resources>
<j2se version="1.5+" />
<jar href="myMainJar.jar" />
<jar href="lib/myFirstDependency.jar" />
<jar href="lib/mySecondDependency.jar" />
</resources>
<application-desc main-class="ch.frankel.blog.jws.MainClass" />
</jnlp>
Notice how both the JAR containing the main class and dependents JAR are referenced in the same fashion. The lib folder is for my own purpose only: JWS doesn’t differentiate based on where the JARs are located.
Step 4: Passing system properties to the JVM
Usually, system properties are passed to the JVM on the command line like this:
java -Dch.frankel.firstProp -Dch.frankel.secondProp=aValue -jar myMainJar.jar
This is done likewise in the JNLP:
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.5+" codebase="http://blog.frankel.ch/demo" href="launch.jnlp">
<information>
<title>Legacy Application</title>
<vendor>Nicolas Frankel</vendor>
</information>
<resources>
<j2se version="1.5+" />
<property name="ch.frankel.firstProp" />
<property name="ch.frankel.secondProp" value="aValue" />
<jar href="myMainJar.jar" />
<jar href="lib/myFirstDependency.jar" />
<jar href="lib/mySecondDependency.jar" />
</resources>
<application-desc main-class="ch.frankel.blog.jws.MainClass" />
</jnlp>
Step 5: Passing parameters to your application
Parameters are passed to your main method on the command line like this:
java -jar myMainJar.jar myFirstParam mySecondParam
This is done likewise in the JNLP:
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.5+" codebase="http://blog.frankel.ch/demo" href="launch.jnlp">
<information>
<title>Legacy Application</title>
<vendor>Nicolas Frankel</vendor>
</information>
<resources>
<j2se version="1.5+" />
<property name="ch.frankel.firstProp" />
<property name="ch.frankel.secondProp" value="aValue" />
<jar href="myMainJar.jar" />
<jar href="lib/myFirstDependency.jar" />
<jar href="lib/mySecondDependency.jar" />
</resources>
<application-desc main-class="ch.frankel.blog.jws.MainClass">
<argument>myFirstParam</argument>
<argument>mySecondParam</argument>
</application-desc>
</jnlp>
Step 6: Signing your JARs
Java Web Start applications are meant to run in a sandbox since they are downloaded on the web where, by definition, all resources are unsafe. If your application doesn’t use potentially unsafe operations, well, that’s fine. If it does, you’ll have to explicitly ask for special permissions in the JNLP. Unsafe operations include: file access, clipboard use and so on.
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.5+" codebase="http://blog.frankel.ch/demo" href="launch.jnlp">
<information>
<title>Legacy Application</title>
<vendor>Nicolas Frankel</vendor>
</information>
<resources>
<j2se version="1.5+" />
<property name="ch.frankel.firstProp" />
<property name="ch.frankel.secondProp" value="aValue" />
<security>
<all-permissions/>
</security>
<jar href="myMainJar.jar" />
<jar href="lib/myFirstDependency.jar" />
<jar href="lib/mySecondDependency.jar" />
</resources>
<application-desc main-class="ch.frankel.blog.jws.MainClass">
<argument>myFirstParam</argument>
<argument>mySecondParam</argument>
</application-desc>
</jnlp>
There’s one big but: all all of your JARs must then be signed by the same certificate! It means that if you reference already-signed JARs, you must unsign them first and then sign them using an unique certificate.
If that is too much hassle for you, then you will need to change the litigious parts of your code and replace them with calls to the JNLP API. In that case, kiss good bye to your application’s ability to run outside a Web Start-context. Beware, the API is quite small.
Step 7: Enhance your user experience
Shortcuts, splash screen, ability to run off-line, Java Web Start provides them all. The following JNLP adds these features to the previous one:
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.5+" codebase="http://blog.frankel.ch/demo" href="launch.jnlp">
<information>
<title>Legacy Application</title>
<vendor>Nicolas Frankel</vendor>
<offline-allowed />
<icon kind="splash" href="image/welcome.jpg" />
<shortcut online="false">
<desktop />
<menu submenu="frankel dot ch"/>
</shortcut>
</information>
<resources>
<j2se version="1.5+" />
<property name="ch.frankel.firstProp" />
<property name="ch.frankel.secondProp" value="aValue" />
<security>
<all-permissions/>
</security>
<jar href="myMainJar.jar" />
<jar href="lib/myFirstDependency.jar" />
<jar href="lib/mySecondDependency.jar" />
</resources>
<application-desc main-class="ch.frankel.blog.jws.MainClass">
<argument>myFirstParam</argument>
<argument>mySecondParam</argument>
</application-desc>
</jnlp>
Step 8: Remove bugs from your legacy code
In the context of Java Web Start, some legacy code may not run.
For example, forget ClassLoader.getSystemResource()
:
replace it with the following snippet Thread.currentThread().getContextClassLoader().getResource()
which will run in both JWS and local contexts.
Remember, if you run into a problem, chances are someone did before you:
Google is your friend here.
I hope this article convinced you that your legacy applications can be brought online with only minimal effort. Java Web Start is a standardized, cost-effective and technically sound solution to do this.
To go further:
- Sun'official Java Web Start Demos
- JDiskReport, a very useful application that displays the space each of you directory takes, available with JWS
- Java Web Start Developer Guide
- JNLP API (for secure services inside the sandbox)
- JNLP File Syntax
- JNLP Download Servlet Guide (for dynamic JNLP files)
- Codehaus's Maven Webstart Plug-In (so useful)