Java beginners often get confused about what JAR, WAR, and EAR files are, what they can do, and what they can't do. This article will clear up some of that confusion by showing six different ways of building and packaging a Java application using gradle which seems to be the up and coming Java build tool of choice for many projects.
Executable JAR File
At its heart, a JAR file is simply a zip file containing Java class files, possibly data files, and some metadata that tells the JVM what's in the file. A JAR file can act as an executable if it contains a manifest file identifying what class to run when the jar file itself is executed with a command like java -jar myjarfile.jar. Gradle supports generation of the manifest file automatically with configuration in the build.gradle file. Let's suppose our main class is called DemoApp and lives in a package called demo. The following build.gradle file does the job:
apply plugin: 'java'
jar {
manifest {
attributes('Main-Class': 'demo.DemoApp')
}
}
repositories {
jcenter()
}
dependencies {
testCompile 'junit:junit:4.12'
}
As is normally the case with gradle (unless you override it), your Java code should in src/main/java and your JUnit tests in src/test/java. Any data files that you want copied to the JAR go in src/main/resources and data files used by your unit tests (if you have any) go in src/test/resources.
So if you add in other dependencies does it add those to the JAR file? No. The key thing to remember is that while a JAR file is just a zip file and could contain any file including another JAR file, it usually does no good to put other JAR files inside a normal JAR file because the Java class loading system will not load classes from such nested JAR files. What you need is the other JAR files to be on the classpath. You could implement something by hand to set the classpath such as a set of portable start up scripts, but there's an easier way.
Distribution Zip or Tar File
So what if our code depended on a library like, say, Apache Commons Lang 3.4. The gradle application plugin can create a zip file and/or a compressed tar file that when exploded contains your application's executable JAR file, a directory with all the JAR files your application depends on, and Linux and Windows start up scripts that launch your executable JAR file with all the runtime dependencies on the classpath. It's just this easy:
apply plugin: 'application'
mainClassName = 'demo.DemoApp'
repositories {
mavenCentral()
}
dependencies {
compile 'org.apache.commons:commons-lang3:3.4'
testCompile 'junit:junit:4.12'
}
Using the application plugin gives you distZip and distTar tasks to create the zip or tar file. You also have the installDist task to give you a directory that is essentially the contents of the exploded zip or tar file which is handy for testing except you don't have to bother to extract anything yourself.
UberJar File
An alternative solution to the nested JAR file problem is to take the contents of all the dependent JAR files and flatten them into a single JAR file along with your application class files. Such a JAR file has been called an UberJar file. If this approach is preferred over the application plugin in any situation, the shadow plugin can do the job:
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '1.2.1'
}
repositories {
jcenter()
}
dependencies {
compile 'org.apache.commons:commons-lang3:3.4'
testCompile 'junit:junit:4.12'
}
WAR File
Usually a Java web application is packaged as a WAR file (the exception being when a Java web server such as Tomcat or Jetty is embedded into an executable JAR file, but more on that later). A WAR file is special Java EE type of JAR file that, interestingly enough, does support embedded JAR files within it that will properly be loaded by the container.
apply plugin: 'war'
repositories {
jcenter()
}
dependencies {
compile 'org.apache.commons:commons-lang3:3.4'
providedCompile 'javax:javaee-api:7.0'
testCompile 'junit:junit:4.12'
}
All of your compile and runtime dependenices will be placed in the proper location in the WAR file and the providedCompile dependencies are for things provided by the container that you don't want in the WAR file and will be left out such as the Java EE API and implementation.
With a WAR file build such as this one, your HTML, CSS, Javascript, and JSP files go into src/main/webapp, and your Java code and unit tests go in the usual places (src/main/java, and src/test/java).
EAR File
Now things get a little interesting. An EAR file is another Java EE variant of a JAR file that can contain one or more WAR files or EJB-JAR files that are to be deployed together as a single enterprise application. An EJB-JAR file is simply a library JAR file than can contain EJBs and EJB deployment descriptors and is subject to the normal constraint that nested JAR files will not be on the classpath.
To build an EAR file with gradle, we'll use a multiproject gradle build. We'll build an EJB-JAR and package it into an EAR file with its dependencies, so we'll have one subproject called ejbjar and another called ear. The following is the top level settings.gradle file:
include 'ejbjar'
include 'ear'
The following is the build.gradle file for the ejbjar subproject and is essentially a normal library JAR file except that you can put any Java EE deployment descriptors that you might have in src/main/resources/META-INF.
apply plugin: 'java'
repositories {
jcenter()
}
dependencies {
compile 'org.apache.commons:commons-lang3:3.4'
compile 'javax:javaee-api:7.0'
testCompile 'junit:junit:4.12'
}
And the following is the build.gradle file for the ear subproject:
apply plugin: 'ear'
repositories {
jcenter()
}
dependencies {
deploy project(':ejbjar')
earlib 'org.apache.commons:commons-lang3:3.4'
}
The key thing about the gradle ear plugin is that is supports two dependency types. The deploy type is used to specify WAR or EJB-JAR files that should be deployed when the EAR file is deployed. The earlib dependency type is used to specify library JAR files used by the WAR and EJB-JAR files that you're deploying and they will be packaged up in the EAR file and be available on the classpath for your application.
Spring Boot
If you are building an application based on Spring Framework, using Spring Boot may be a excellent way to package your application. You have the choice of building a WAR file or an executable UberJar file (which can contain an embedded Tomcat instance if your application is a web application) and it is easy to switch back and forth between the two if you change your mind. Spring Boot offers many conveniences such as embedded databases and just happens to be another way of packaging your application among many other features. The following build.gradle shows an example of a non-web application built as an executable Uberjar.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.5.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'spring-boot'
repositories { jcenter() }
jar {
manifest {
attributes('Main-Class': 'demo.DemoApp')
}
}
dependencies {
compile 'org.apache.commons:commons-lang3:3.4'
compile 'org.springframework.boot:spring-boot-starter:1.2.5.RELEASE'
testCompile 'junit:junit:4.12'
}
Spring boot is major topic in its own right, but this should give you a taste of how to get started with Spring Boot and gradle.
Summary
So there you have it: six examples of packaging a Java application using gradle. To learn more about gradle in depth, I recommend Gradle in Action by Benjamin Muschko.Labels: Gradle, Java