Tuesday, October 30, 2012

Simple but powerful concept: packing your Java application as one (or fat) JAR

Today's post will target an interesting but quite powerful concept: packing your application as single, runnable JAR file, also known as one or fat JAR.

We get used to large WAR archives which contain all dependencies packed together under some common folder structure. With JAR-like packaging the story is a bit different: in order to make your application runnable (via java -jar) all dependencies should be provided over classpath parameter or environment variable. Usually it means there would be some lib folder with all dependencies and some runnable script which will do the job to construct classpath and run JVM. Maven Assembly plugin is well know for making such kind of application distribution.

A slightly different approach would be to package all your application dependencies to the same JAR file and make it runnable without any additional parameters or scripting required. Sounds great but ... it won't work unless you add some magic: meet One-JAR project.

Let's briefly outline the problem: we are writing a stand-alone Spring application which should be runnable just by typing java -jar <our-app.jar>.

As always, let's start with our POM file, which will be pretty simple


    4.0.0

    com.example
    spring-one-jar
    0.0.1-SNAPSHOT
    jar

    spring-one-jar
    http://maven.apache.org

    
        UTF-8
        3.1.1.RELEASE
    

    
        
            cglib
            cglib-nodep
            2.2
        

        
            org.springframework
            spring-core
            ${org.springframework.version}
        

        
            org.springframework
            spring-context
            ${org.springframework.version}
        
    

Our sample application will bootstrap Spring context, get some bean instance and call a method on it. Our bean is called SimpleBean and looks like:

package com.example;

public class SimpleBean {
    public void print() {
        System.out.println( "Called from single JAR!" );
    }
}

Falling in love with Spring Java configuration, let us define our context as annotated AppConfig POJO:

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.example.SimpleBean;

@Configuration
public class AppConfig {
    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }
}

And finally, our application Starter with main():

package com.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.example.config.AppConfig;

public class Starter {
    public static void main( final String[] args ) {
        ApplicationContext context = new AnnotationConfigApplicationContext( AppConfig.class );
        SimpleBean bean = context.getBean( SimpleBean.class );
        bean.print();
    }
}

Adding our main class to META-INF/MANIFEST.MF allows to leverage Java capabilities to run JAR file without explicitly specifying class with main() method. Maven JAR plugin can help us with that.


    
        
            org.apache.maven.plugins
            maven-jar-plugin
            
                
                    
                        com.example.Starter
                    
                
            
        
    

Trying to run java -jar spring-one-jar-0.0.1-SNAPSHOT.jar will print the exception to the console: java.lang.NoClassDefFoundError. The reason is pretty straightforward: even such a simple application as this one already required following libraries to be in classpath.

aopalliance-1.0.jar
cglib-nodep-2.2.jar
commons-logging-1.1.1.jar
spring-aop-3.1.1.RELEASE.jar
spring-asm-3.1.1.RELEASE.jar
spring-beans-3.1.1.RELEASE.jar
spring-context-3.1.1.RELEASE.jar
spring-core-3.1.1.RELEASE.jar
spring-expression-3.1.1.RELEASE.jar

Let's see what One-JAR can do for us here. Thanks to availability of onejar-maven-plugin we can add one to the plugins section of our POM file.


    org.dstovall
    onejar-maven-plugin
    1.4.4
    
        
            
                0.97
                onejar
            
            
                one-jar
            
        
   

Also, pluginRepositories section should contain this repository in order to download the plugin.


    
        onejar-maven-plugin.googlecode.com
        http://onejar-maven-plugin.googlecode.com/svn/mavenrepo
    

As the result, there will be another artifact available in the target folder, postfixed with one-jar: spring-one-jar-0.0.1-SNAPSHOT.one-jar.jar. Running this one with java -jar spring-one-jar-0.0.1-SNAPSHOT.one-jar.jar will print to the console:

Called from single JAR!

Fully runnable Java application as single, redistributable JAR file! The last comment: though our application looks pretty simple, One-JAR works perfectly for complex, large applications as well without any issues. Please, add it to your toolbox, it's really useful tool to have.

Thanks to One-JAR guys!

4 comments:

Jacek Furmankiewicz said...

Now that is a crazy idea. What sort of nut would propose something like this?
:-)

On a more serious note, see this concept taken to its logical conclusion:

http://dropwizard.codahale.com/

I just did a small prototype using this as a REST frontend for a Cassandra backend and it was very straightforward...

Andriy Redko said...

Thanks a lot for this great comment! I have heard about DropWizard and was going to look at it more closely. Thanks!

Sven said...

Have you had a look at the maven-shade-plugin? Similar mechanism, but without the need for a custom classloader.

br, Sven

Andriy Redko said...

Hi Sven! Thank you for pointing out the maven-shade-plugin. I have heard about it (though haven't seen it in the action yet) and I will definitely give it a shot. It would be interesting to compare those two techniques. Thank you very much.