In this tutorial we are going to understand executable jar structure of a Spring boot project. We will attempt to create the executable jar using Maven assembly plugin first and then we will create the executable jar with spring-boot maven plugin and compare the jar structure created by the two.
Creating executable jar using maven assembly plugin
Let's create a simple boot project:
Main class
@SpringBootApplication(exclude = {JmxAutoConfiguration.class})
public class Main {
public static void main (String[] args) {
SpringApplication sa = new SpringApplication(Main.class);
sa.setBannerMode(Banner.Mode.OFF);
sa.setLogStartupInfo(false);
ApplicationContext c = sa.run(args);
MyObject bean = c.getBean(MyObject.class);
bean.doSomething();
}
@Component
private static class MyObject {
public void doSomething () {
System.out.println("-------------");
System.out.println("working ...");
System.out.println("-------------");
}
}
}
Note that we have excluded JmxAutoConfiguration.class to reduce the logging outputs.
pom.xml
<project .....;>
<modelVersion>4.0.0</modelVersion>
<groupId>com.logicbig.example</groupId>
<artifactId>boot-jar-structure</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.logicbig.example.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Packaging jar
We are using Window's cmd.exe. We are going run 'mvn package' to create executable jar with dependencies.
D:\examples\boot-jar-structure>mvn -q clean
D:\examples\boot-jar-structure>tree /A /F
Folder PATH listing for volume Data
Volume serial number is 000000BF 68F9:EDFA
D:.
| pom.xml
|
\---src
\---main
\---java
\---com
\---logicbig
\---example
Main.java
D:\examples\boot-jar-structure>mvn -q package
D:\examples\boot-jar-structure>tree /A /F
Folder PATH listing for volume Data
Volume serial number is 0000006D 68F9:EDFA
D:.
| pom.xml
|
+---src
| \---main
| \---java
| \---com
| \---logicbig
| \---example
| Main.java
|
\---target
| boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar
| boot-jar-structure-1.0-SNAPSHOT.jar
|
+---archive-tmp
+---classes
| \---com
| \---logicbig
| \---example
| Main$MyObject.class
| Main.class
|
+---generated-sources
| \---annotations
+---maven-archiver
| pom.properties
|
\---maven-status
\---maven-compiler-plugin
\---compile
\---default-compile
createdFiles.lst
inputFiles.lst
Running executable jar
D:\examples\boot-jar-structure>java -jar target\boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar
2017-01-15 14:43:35.391 INFO 5228 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4d50efb8: startup date [Sun Jan 15 14:43:35 CST 2017]; root of context hierarchy
2017-01-15 14:43:35.450 WARN 5228 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.logicbig.example.Main]; nested exception is java.lang.IllegalArgumentException: No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.
2017-01-15 14:43:35.457 ERROR 5228 --- [ main] o.s.boot.SpringApplication : Application startup failed
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.logicbig.example.Main]; nested exception is java.lang.IllegalArgumentException: No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.
at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:482) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:184) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:324) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:246) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:270) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:93) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:686) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:524) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:761) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:371) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at com.logicbig.example.Main.main(Main.java:17) [boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
Caused by: java.lang.IllegalArgumentException: No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.
at org.springframework.util.Assert.notEmpty(Assert.java:276) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.boot.autoconfigure.EnableAutoConfigurationImportSelector.getCandidateConfigurations(EnableAutoConfigurationImportSelector.java:145) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.boot.autoconfigure.EnableAutoConfigurationImportSelector.selectImports(EnableAutoConfigurationImportSelector.java:84) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:474) ~[boot-jar-structure-1.0-SNAPSHOT-jar-with-dependencies.jar:na]
... 11 common frames omitted
D:\examples\boot-jar-structure>
The above attempt failed, that's because spring boot uses a very different mechanism to load classes which excepts a specific jar structure. We will see that mechanism in the next section.
Let's extract the jar in a temp folder and see the directory structure created by assembly plugin. In following output we are not showing all classes, only folders:
D:\examples\boot-jar-structure\temp>tree /A
Folder PATH listing for volume Data
Volume serial number is 00000073 68F9:EDFA
D:.
+---ch
| \---qos
| \---logback
| +---classic
| | +---boolex
| | +---db
| | | +---names
| | | \---script
| | +---encoder
| | +---filter
| | +---gaffer
| | +---helpers
| | +---html
| | +---jmx
| | +---joran
| | | \---action
| | +---jul
| | +---layout
| | +---log4j
| | +---net
| | | \---server
| | +---pattern
| | | \---color
| | +---selector
| | | \---servlet
| | +---sift
| | +---spi
| | +---turbo
| | \---util
| \---core
| +---boolex
| +---db
| | \---dialect
| +---encoder
| +---filter
| +---helpers
| +---hook
| +---html
| +---joran
| | +---action
| | +---conditional
| | +---event
| | | \---stax
| | +---node
| | +---spi
| | \---util
| | \---beans
| +---layout
| +---net
| | +---server
| | \---ssl
| +---pattern
| | +---color
| | +---parser
| | \---util
| +---property
| +---read
| +---recovery
| +---rolling
| | \---helper
| +---sift
| +---spi
| +---status
| +---subst
| \---util
+---com
| \---logicbig
| \---example
+---META-INF
| +---maven
| | +---ch.qos.logback
| | | +---logback-classic
| | | \---logback-core
| | +---com.logicbig.example
| | | \---boot-jar-structure
| | +---org.slf4j
| | | +---jcl-over-slf4j
| | | +---jul-to-slf4j
| | | +---log4j-over-slf4j
| | | \---slf4j-api
| | +---org.springframework.boot
| | | +---spring-boot
| | | +---spring-boot-autoconfigure
| | | +---spring-boot-starter
| | | \---spring-boot-starter-logging
| | \---org.yaml
| | \---snakeyaml
| +---org
| | \---apache
| | \---logging
| | \---log4j
| | \---core
| | \---config
| | \---plugins
| \---services
\---org
+---aopalliance
| +---aop
| \---intercept
+---apache
| +---commons
| | \---logging
| | \---impl
| \---log4j
| +---helpers
| +---spi
| \---xml
+---slf4j
| +---bridge
| +---event
| +---helpers
| +---impl
| \---spi
+---springframework
| +---aop
| | +---aspectj
| | | +---annotation
| | | \---autoproxy
| | +---config
| | +---framework
| | | +---adapter
| | | \---autoproxy
| | | \---target
| | +---interceptor
| | +---scope
| | +---support
| | | \---annotation
| | \---target
| | \---dynamic
| +---asm
| +---beans
| | +---annotation
| | +---factory
| | | +---access
| | | | \---el
| | | +---annotation
| | | +---config
| | | +---groovy
| | | +---parsing
| | | +---serviceloader
| | | +---support
| | | +---wiring
| | | \---xml
| | +---propertyeditors
| | \---support
| +---boot
| | +---admin
| | +---ansi
| | +---autoconfigure
| | | +---admin
| | | +---amqp
| | | +---aop
| | | +---batch
| | | +---cache
| | | +---cassandra
| | | +---cloud
| | | +---condition
| | | +---context
| | | +---couchbase
| | | +---dao
| | | +---data
| | | | +---cassandra
| | | | +---couchbase
| | | | +---elasticsearch
| | | | +---jpa
| | | | +---mongo
| | | | +---neo4j
| | | | +---redis
| | | | +---rest
| | | | +---solr
| | | | \---web
| | | +---diagnostics
| | | | \---analyzer
| | | +---domain
| | | +---elasticsearch
| | | | \---jest
| | | +---flyway
| | | +---freemarker
| | | +---groovy
| | | | \---template
| | | +---gson
| | | +---h2
| | | +---hateoas
| | | +---hazelcast
| | | +---info
| | | +---integration
| | | +---jackson
| | | +---jdbc
| | | | \---metadata
| | | +---jersey
| | | +---jms
| | | | +---activemq
| | | | +---artemis
| | | | \---hornetq
| | | +---jmx
| | | +---jooq
| | | +---liquibase
| | | +---logging
| | | +---mail
| | | +---mobile
| | | +---mongo
| | | | \---embedded
| | | +---mustache
| | | | \---web
| | | +---orm
| | | | \---jpa
| | | +---reactor
| | | +---security
| | | | \---oauth2
| | | | +---authserver
| | | | +---client
| | | | +---method
| | | | \---resource
| | | +---sendgrid
| | | +---session
| | | +---social
| | | +---solr
| | | +---template
| | | +---test
| | | +---thymeleaf
| | | +---transaction
| | | | \---jta
| | | +---velocity
| | | +---web
| | | +---webservices
| | | \---websocket
| | +---bind
| | +---builder
| | +---cloud
| | +---context
| | | +---config
| | | +---embedded
| | | | +---jetty
| | | | +---tomcat
| | | | \---undertow
| | | +---event
| | | +---properties
| | | \---web
| | +---diagnostics
| | | \---analyzer
| | +---env
| | +---info
| | +---jackson
| | +---jdbc
| | +---json
| | +---jta
| | | +---atomikos
| | | +---bitronix
| | | \---narayana
| | +---lang
| | +---liquibase
| | +---logging
| | | +---java
| | | +---log4j2
| | | \---logback
| | +---orm
| | | \---jpa
| | | \---hibernate
| | +---system
| | +---type
| | | \---classreading
| | +---web
| | | +---client
| | | +---filter
| | | +---servlet
| | | | \---view
| | | | \---velocity
| | | \---support
| | \---yaml
| +---cache
| | +---annotation
| | +---concurrent
| | +---config
| | +---interceptor
| | \---support
| +---cglib
| | +---beans
| | +---core
| | | \---internal
| | +---proxy
| | +---reflect
| | +---transform
| | | \---impl
| | \---util
| +---context
| | +---access
| | +---annotation
| | +---config
| | +---event
| | +---expression
| | +---i18n
| | +---support
| | \---weaving
| +---core
| | +---annotation
| | +---convert
| | | +---converter
| | | \---support
| | +---env
| | +---io
| | | \---support
| | +---serializer
| | | \---support
| | +---style
| | +---task
| | | \---support
| | \---type
| | +---classreading
| | \---filter
| +---ejb
| | +---access
| | +---config
| | \---interceptor
| +---expression
| | +---common
| | \---spel
| | +---ast
| | +---generated
| | +---standard
| | \---support
| +---format
| | +---annotation
| | +---datetime
| | | +---joda
| | | \---standard
| | +---number
| | | \---money
| | \---support
| +---instrument
| | \---classloading
| | +---glassfish
| | +---jboss
| | +---tomcat
| | +---weblogic
| | \---websphere
| +---jmx
| | +---access
| | +---export
| | | +---annotation
| | | +---assembler
| | | +---metadata
| | | +---naming
| | | \---notification
| | \---support
| +---jndi
| | \---support
| +---lang
| +---objenesis
| | +---instantiator
| | | +---android
| | | +---basic
| | | +---gcj
| | | +---perc
| | | \---sun
| | \---strategy
| +---remoting
| | +---rmi
| | +---soap
| | \---support
| +---scheduling
| | +---annotation
| | +---concurrent
| | +---config
| | \---support
| +---scripting
| | +---bsh
| | +---config
| | +---groovy
| | +---jruby
| | \---support
| +---stereotype
| +---ui
| | \---context
| | \---support
| +---util
| | +---backoff
| | +---comparator
| | +---concurrent
| | \---xml
| \---validation
| +---annotation
| +---beanvalidation
| \---support
\---yaml
\---snakeyaml
+---composer
+---constructor
+---emitter
+---error
+---events
+---extensions
| \---compactnotation
+---external
| +---biz
| | \---base64Coder
| \---com
| \---google
| \---gdata
| \---util
| \---common
| \---base
+---introspector
+---nodes
+---parser
+---reader
+---representer
+---resolver
+---scanner
+---serializer
+---tokens
\---util
D:\examples\boot-jar-structure\temp>
Above jar doesn't include dependencies as nested jars but it's a single flat jar with all packages and classes (coming from different jar sources) merged together, this is not what spring boot loading mechanism expects.
Creating executable jar using spring-boot plugin
In above pom, comment out maven-assembly-plugin and add spring-boot plugin
.......
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.4.2.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
....
Packaging
D:\examples\boot-jar-structure>mvn -q clean
D:\examples\boot-jar-structure>tree /A /F
Folder PATH listing for volume Data
Volume serial number is 0000003E 68F9:EDFA
D:.
| pom.xml
|
\---src
\---main
\---java
\---com
\---logicbig
\---example
Main.java
D:\examples\boot-jar-structure>mvn -q package
D:\examples\boot-jar-structure>tree /A /F
Folder PATH listing for volume Data
Volume serial number is 00000056 68F9:EDFA
D:.
| pom.xml
|
+---src
| \---main
| \---java
| \---com
| \---logicbig
| \---example
| Main.java
|
\---target
| boot-jar-structure-1.0-SNAPSHOT.jar
| boot-jar-structure-1.0-SNAPSHOT.jar.original
|
+---classes
| \---com
| \---logicbig
| \---example
| Main$MyObject.class
| Main.class
|
+---generated-sources
| \---annotations
+---maven-archiver
| pom.properties
|
\---maven-status
\---maven-compiler-plugin
\---compile
\---default-compile
createdFiles.lst
inputFiles.lst
Running executable jar
D:\examples\boot-jar-structure>java -jar target\boot-jar-structure-1.0-SNAPSHOT.jar
2017-01-15 15:14:46.863 INFO 11092 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@579bb367: startup date [Sun Jan 15 15:14:46 CST 2017]; root of context hierarchy
-------------
working ...
-------------
2017-01-15 15:14:47.473 INFO 11092 --- [ Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@579bb367: startup date [Sun Jan 15 15:14:46 CST 2017]; root of context hierarchy
D:\examples\boot-jar-structure>
It works this time. Let's extract jar in temp folder and see the directory structure
D:\examples\boot-jar-structure>md temp
D:\examples\boot-jar-structure>cd temp
D:\examples\boot-jar-structure\temp>jar -xf ..\target\boot-jar-structure-1.0-SNAPSHOT.jar
D:\examples\boot-jar-structure\temp>tree /A /F
Folder PATH listing for volume Data
Volume serial number is 000000B2 68F9:EDFA
D:.
+---BOOT-INF
| +---classes
| | \---com
| | \---logicbig
| | \---example
| | Main$MyObject.class
| | Main.class
| |
| \---lib
| jcl-over-slf4j-1.7.21.jar
| jul-to-slf4j-1.7.21.jar
| log4j-over-slf4j-1.7.21.jar
| logback-classic-1.1.7.jar
| logback-core-1.1.7.jar
| slf4j-api-1.7.21.jar
| snakeyaml-1.17.jar
| spring-aop-4.3.4.RELEASE.jar
| spring-beans-4.3.4.RELEASE.jar
| spring-boot-1.4.2.RELEASE.jar
| spring-boot-autoconfigure-1.4.2.RELEASE.jar
| spring-boot-starter-1.4.2.RELEASE.jar
| spring-boot-starter-logging-1.4.2.RELEASE.jar
| spring-context-4.3.4.RELEASE.jar
| spring-core-4.3.4.RELEASE.jar
| spring-expression-4.3.4.RELEASE.jar
|
+---META-INF
| | MANIFEST.MF
| |
| \---maven
| \---com.logicbig.example
| \---boot-jar-structure
| pom.properties
| pom.xml
|
\---org
\---springframework
\---boot
\---loader
| ExecutableArchiveLauncher$1.class
| ExecutableArchiveLauncher.class
| JarLauncher.class
| LaunchedURLClassLoader$1.class
| LaunchedURLClassLoader.class
| Launcher.class
| MainMethodRunner.class
| PropertiesLauncher$1.class
| PropertiesLauncher$ArchiveEntryFilter.class
| PropertiesLauncher$FilteredArchive$1.class
| PropertiesLauncher$FilteredArchive.class
| PropertiesLauncher$PrefixMatchingArchiveFilter.class
| PropertiesLauncher.class
| WarLauncher.class
|
+---archive
| Archive$Entry.class
| Archive$EntryFilter.class
| Archive.class
| ExplodedArchive$1.class
| ExplodedArchive$FileEntry.class
| ExplodedArchive$FileEntryIterator$EntryComparator.class
| ExplodedArchive$FileEntryIterator.class
| ExplodedArchive.class
| JarFileArchive$EntryIterator.class
| JarFileArchive$JarFileEntry.class
| JarFileArchive.class
|
+---data
| ByteArrayRandomAccessData.class
| RandomAccessData$ResourceAccess.class
| RandomAccessData.class
| RandomAccessDataFile$DataInputStream.class
| RandomAccessDataFile$FilePool.class
| RandomAccessDataFile.class
|
+---jar
| AsciiBytes.class
| Bytes.class
| CentralDirectoryEndRecord.class
| CentralDirectoryFileHeader.class
| CentralDirectoryParser.class
| CentralDirectoryVisitor.class
| FileHeader.class
| Handler.class
| JarEntry.class
| JarEntryFilter.class
| JarFile$1.class
| JarFile$2.class
| JarFile$3.class
| JarFile$JarFileType.class
| JarFile.class
| JarFileEntries$1.class
| JarFileEntries$EntryIterator.class
| JarFileEntries.class
| JarURLConnection$1.class
| JarURLConnection$JarEntryName.class
| JarURLConnection.class
| ZipInflaterInputStream.class
|
\---util
SystemPropertyUtils.class
D:\examples\boot-jar-structure\temp>
As seen above, spring-boot creates the jar with nested dependent jars. Application classes are placed under BOOT-INF/classes and dependencies are placed under BOOT-INF/lib. There's a special folder /org/springframework/boot/loader, the contents of this folder come from spring-boot-loader module. There are three launcher classes JarLauncher, WarLauncher and PropertiesLauncher. Their purpose is to load classes and nested JAR files from BOOT-INF. Also let's see what's inside META-INF/Manifest:
Manifest-Version: 1.0
Implementation-Title: boot-jar-structure
Implementation-Version: 1.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: Joe
Implementation-Vendor-Id: com.logicbig.example
Spring-Boot-Version: 1.4.2.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.logicbig.example.Main
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_111
Implementation-URL: http://projects.spring.io/spring-boot/boot-jar-structure/
Main-class is JarLauncher. JarLauncher is a subclass of Launcher class which is a special bootstrap class used as an executable jar's main entry point. It is the actual Main-Class in our jar file and it's used to setup an appropriate URLClassLoader and ultimately call our main() method.
Normally we don't need to know further low level details unless we want to create and load boot executable jar for a different build system (which is not currently supported by spring boot).
In the next tutorial, we will understand spring boot war directory structure.
Example Project
Dependencies and Technologies Used: - Spring Boot 1.4.2.RELEASE
Corresponding Spring Version 4.3.4.RELEASE - spring-boot-starter : Core starter, including auto-configuration support, logging and YAML.
- JDK 1.8
- Maven 3.3.9
|