Did you know? Programmers convert coffee to code.

If you like my articles, sponsor me a coffee.

One JAR file for the project

Until this time I started the Variations project only from Eclipse. Some days ago I downloaded the whole code and compiled it in the command line and wanted to start the packaged jar file — however I couldn’t. The cause was a NoClassDefFoundError.

The error was the following:

Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/context/support/ClassPathXmlApplicationContext
    at biz.hahamo.dev.variations.App.main(App.java:16)
Caused by: java.lang.ClassNotFoundException: org.springframework.context.support.ClassPathXmlApplicationContext
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
	... 1 more

This means, that your classes are not on the class path. In this case, all of the external libraries used in the project (Spring, Liquibase, etc) are not found if you run the single jar file outside of an Eclipse (or any other) IDE.

How can you manage such a problem? There are two main paths: 1) add the external libraries to the classpath; 2) create a single jar file with all your dependencies. I did both of them but the most elegant way is to provide a single JAR file — because with classpath your users can mess badly around.

Classpathing

The first approach is adding your files to the class path (if I write classpath or class path it means the same just my mac corrects the words for me). How can you achieve this? Simply: create a “lib” folder beside your JAR and copy all dependencies into it. If you use maven you have to find out, where your repository is located on your computer and look for all needed JAR files there. Caveat: there are some other libraries which your dependencies depending on. If you want to find all of them: good luck.

However, let’s imagine that we have all the needed JAR dependencies at the “lib” folder beside the variations-1.0-SNAPSHOT.jar. So if you want to start the application let’s execute the following from the command-line:

java -classpath .:lib/* -jar biz.hahamo.dev.variations.App

And the application runs perfectly. If you are using Windows replace the colon (“:”) with a semi-colon (“;”) in the classpath reference. That was almost easy (if I do not count the selection of the right dependencies).

OneJAR

The second thought was creating one JAR containing all dependencies and the project itself. The first tool which you would like to try is One-JAR. It has a maven plugin so you can add it to your package life-cycle and let the magic happen.

A minor drawback was that it did not work with Liquibase: I tried to look at Google to find anything interesting but nothing. I found one possible solution in a Liquibase forum but… It did not help. The mentioned solution was to add some Liquibase libraries to the MANIFEST.MF of the JAR. But it did not help. I got the same exception as without the entry in the manifest file.

And the exception was:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.simontuffs.onejar.Boot.run(Boot.java:313)
    at com.simontuffs.onejar.Boot.main(Boot.java:161)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [biz/hahamo/dev/variations/controller/repository/repository.spring.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [liquibase.integration.spring.SpringLiquibase]: Constructor threw exception; nested exception is liquibase.exception.ServiceNotFoundException: liquibase.exception.ServiceNotFoundException: liquibase.exception.ServiceNotFoundException: Could not find implementation of liquibase.logging.Logger
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1076)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1021)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:703)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at biz.hahamo.dev.variations.App.main(App.java:16)
    ... 6 more
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [liquibase.integration.spring.SpringLiquibase]: Constructor threw exception; nested exception is liquibase.exception.ServiceNotFoundException: liquibase.exception.ServiceNotFoundException: liquibase.exception.ServiceNotFoundException: Could not find implementation of liquibase.logging.Logger
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:164)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:89)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1069)
    ... 19 more
Caused by: liquibase.exception.ServiceNotFoundException: liquibase.exception.ServiceNotFoundException: liquibase.exception.ServiceNotFoundException: Could not find implementation of liquibase.logging.Logger
    at liquibase.logging.LogFactory.getLog(LogFactory.java:46)
    at liquibase.logging.LogFactory.getLogger(LogFactory.java:37)
    at liquibase.integration.spring.SpringLiquibase.<init>(SpringLiquibase.java:134)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:148)
    ... 21 more
Caused by: liquibase.exception.ServiceNotFoundException: liquibase.exception.ServiceNotFoundException: Could not find implementation of liquibase.logging.Logger
    at liquibase.servicelocator.ServiceLocator.newInstance(ServiceLocator.java:188)
    at liquibase.logging.LogFactory.getLog(LogFactory.java:44)
    ... 28 more
Caused by: liquibase.exception.ServiceNotFoundException: Could not find implementation of liquibase.logging.Logger
    at liquibase.servicelocator.ServiceLocator.findClass(ServiceLocator.java:154)
    at liquibase.servicelocator.ServiceLocator.newInstance(ServiceLocator.java:186)
    ... 29 more

I looked at the console output of OneJAR and I saw that it was adding the Liquibase requirements — however could not resolve them. So I looked at another tool, which helped my problem.

Shades of Maven

And this tool was the Maven Shade plugin. It does almost the same as OneJAR: it packs the whole application into a single JAR file — and replaces the generated one. So at the end you have really one jar (OneJAR created a separate file with one-jar in the name so you had to start that instead of the normal variations archive).

The configuration of the plugin is (almost) very simple: just include the following code part into your pom.xml (replacing the right part if you use it for your own project):

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.3</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <mainClass>biz.hahamo.dev.variations.App</mainClass>
                            </transformer>
                        </transformers>
                        <artifactSet>
                            <excludes>
                                <exclude>testng:testng</exclude>
                                <exclude>org.mockito:*</exclude>
                                <exclude>org.apache.maven:lib:tests</exclude>
                            </excludes>
                        </artifactSet>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

The highlighted line needs to be replaced: it is the main class of your application. The other three highlighted lines are the excludes: the plug-in does not include them into the one jar which is created.

Yes, this code is the whole build-tag because you do not need the maven-jar plugin anymore — as the shade assembles a single running jar.

After running a mvn clean package you can start the application with

java -jar target/variations-1.0-SNAPSHOT.jar

but you get the following error message:

INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context]
Offending resource: class path resource [applicationContext.xml]

	at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:70)
	at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
	at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:80)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:316)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1421)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1414)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:187)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:141)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:110)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:508)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:391)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:335)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:303)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:216)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:187)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:251)
	at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127)
	at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93)
	at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129)
	at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:540)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:454)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
	at biz.hahamo.dev.variations.App.main(App.java:16)

OK, it is not a big problem. Simply add the following lines to the transformers-tag

<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
    <resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
    <resource>META-INF/spring.schemas</resource>
</transformer>

and redo the packaging and running the app with

mvn clean package && java -jar target/variations-1.0-SNAPSHOT.jar

Done. Now I have a single Java archive file which I can execute at any time. Or you can execute it. And based on this example you can build your own single JAR file. Well, the result is on my machine around 16 MB — but this isn’t much. It will grow as I add more dependencies and more features to the application. The WAR files we were working on have been around a 120 MBs each so this is only a simple application. Let’s see which size can I achieve at the end of the series. Well, I have the plan to end it up in a WAR file to be deployable to a web server. But this is an other tale for the future.

Next time I’ll talk about the Entities of the application and why I didn’t create them from the database with a reverse modeling tool.

GHajba
 

Senior developer, consultant, author, mentor, apprentice.

I love to share my knowledge and insights what I achieve through my daily work which is not trivial — at least not for me.

Click Here to Leave a Comment Below 2 comments
%d bloggers like this: