Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Create an Executable Fat JAR With Your Command Line

DZone's Guide to

Create an Executable Fat JAR With Your Command Line

Want to get an executable fat JAR file up and running with just your command line? See the groundwork you need to lay and how to get it done.

· Java Zone
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!

This article is a unification of my blog posts reviewing the possibility of creating fat JARs (Java Archive file) in Java without using any additional plugin, IDE, or any other tool — just pure command line and Java.

In the world of build tools (Ant, Maven, or Gradle) it might not even seem useful to think about the command line. The most famous IDEs (IntelliJ, Eclipse, or NetBeans) offer building tools and implementation immediately. But let's say you only have the command line and no Internet access.

What will you do then ?

Part 1: Compile ExecutableOne.jar (GitHub)

The goal of this first part is to create an executable JAR file. Let's call it ExecutableOne.jar. Opening up your comand line, let's start with creation of a simple project folder: executable-one. The example project structure is following the Maven Standard Directory Layout structure.

./libs
./out
./README.md
./src
./src/main
./src/main/java
./src/main/java/com
./src/main/java/com/exec
./src/main/java/com/exec/one
./src/main/resources

As our intent is to create an executable JAR file, we have to create a main class. Let's do it in the package: com.exec.one. The package can be found in the folder SRC/MAIN/JAVA of our sample project structure.

package com.exec.one;

public class Main {
    public static void main(String[] args){                                                                                                                                            
        System.out.println("Main Class Start");                                                                                                                                        
    }                                                                                                                                                                                  
}

Inside the folder SRC/MAIN/RESOURCES, we create the META-INF folder, and then inside, we place MANIFEST.FM file there. Let's open the newly created MANIFEST.FM file and put in a basic description.

Manifest-Version: 1.0   
Class-Path: .                                                                                                                                                                          
Main-Class: com.exec.one.Main

Note: There will be the only one MANIFEST.FM file per JAR file.

The MANIFEST.FM file contains details on how the JAR file will be used. We're not going into deep details — let's stay focused on the options we have defined.

  1. Manifest-Version : manifest file version.

  2. Class-Path: The application or extension class loader uses the value of this attribute to construct its internal search path. Originally, the class loader downloads and opens each element in its search path. For these purposes, a simple linear search algorithm has been used.

  3. Main-Class: There is the name of the class that the launcher will load at the startup time.

Now, we create the JAR file without any *.jar library. The LIBS folder in our project structure is still empty. To do so, we need to first compile the project by using javac. Meanwhile, we'll store the output in the OUT folder. Let's go back to our command line and, inside the root of the project, type:

$javac -cp ./src/main/java ./src/main/java/com/exec/one/*.java -d ./out/

The project has been compiled into the OUT directory. You can check it by using the ls command.
The second step is to create an executable JAR file from the resources located inside the OUT directory. We go back to the command line and execute the following command:

$jar cvfm ExecutableOne.jar ./src/main/resources/META-INF/MANIFEST.MF -C ./out/ .

Let's briefly review/explain the JAR tool options we have used:

  • c: indicates we want to create a new JAR file.

  • v: generates verbose output to standard output.

  • f: specifies the jarfile to be created.

  • m: indicates the manifest file we use. The manifest file includes name-value pairs.

  • -C: indicates temporary changes to the directory. Classes are added from this directory to the JAR file. The dot indicates all classes (files).

For the final output, open the command line and type:

$java -jar ./ExecutableOne.jar

standard output: 
Main Class Start

Great job! Let's move to Part 2.

Part 2.: Compile ExecutableOne.jar With Additional Packages

The main goal of this section is to show how you how to compile an executable JAR file with additional packages included. For such purposes, we've created MagicService. This service provides us with the getMessage() method and prints the message to the standard output.

Let's open the command line and create a new folder, SERVICE, with file MagicService.java:

$mkdir src/main/java/com/exec/one/service
$vi src/main/java/com/exec/one/service/MagicService.java

The newly created MagicService can be used in the following example:

package com.exec.one.service;                                                                                                                                                          

public class MagicService {                                                                                                                                                            

  private final String message;                                                                                                                                                      
    public MagicService(){                                                                                                                                                             
        this.message = "Magic Message";                                                                                                                                                
    }                                                                                                                                                                                  

    public String getMessage(){                                                                                                                                                        
         return message;                                                                                                                                                                
    }                                                                                                                                                                                  

}

The MagicService is located in a different place in the package structure than the Main class. Now we go back to the Main class and import the newly created MagicService. After the import and service instantiation, the Main class will receive the access to the getMessage() method. The Main class will change in following manner.

package com.exec.one;                                                                                                                                                                  

import com.exec.one.service.MagicService;                                                                                                                                              

public class Main {                                                                                                                                                                    
    public static void main(String[] args){                                                                                                                                            
        System.out.println("Main Class Start");                                                                                                                                        
        MagicService service = new MagicService();                                                                                                                                     
        System.out.println("MESSAGE : " + service.getMessage());                                                                                                                       
     }                                                                                                                                                                                  
} 

Now we have reached the point where the code is ready to compile. Let's go back to the command line and go into the root folder of the Executable-One project. The first step will be to compile/recompile the Executable-One project into the OUT folder. For these purposes, we need to add the location of the newly created class MagicService.java.

javac -cp ./src/main/java ./src/main/java/com/exec/one/*.java ./src/main/java/com/exec/one/**/*.java -d ./out/

The second step is to create an executable JAR file from the compiled classes. We don't need to make any changes to the command because we have not changed the JAR file logic. It means that the MANIFEST.FM file stays as it is, without any changes:

1 Manifest-Version: 1.0                                                                                                                                                                  
2 Class-Path: .                                                                                                                                                                          
3 Main-Class: com.exec.one.Main


Now we can again execute command similar to that of Part 1 in the root directory of the sample project:

jar cvfm ExecutableOne.jar ./src/main/resources/META-INF/MANIFEST.MF -C ./out/ .

By executing the created JAR file, we obtain the message printed into the standard output.

$java -jar ExecutableOne.jar 
output: 
Main Class Start
MESSAGE : Magic Message

Congratulations! Great job, again !

Part 3: Create an Executable Fat JAR (GitHub)

The goal of this part is to create a fat JAR (Java Archive) file that contains all the necessary dependencies of the developed program. As the external library, we need to use the JAR file we created in Part 2. In Part 3, we create a new sample project call it "Executable-Two" (which you can download from the link above).

The executable-two project has following folder structure:

./libs
./libs/ExecutableOne.jar
./out
./README.md
./src
./src/main
./src/main/java
./src/main/java/com
./src/main/java/com/exec
./src/main/java/com/exec/two
./src/main/java/com/exec/two/Main.java
./src/main/resources
./src/main/resources/META-INF
./src/main/resources/META-INF/MANIFEST.MF

The LIBS folder contains the previously created JAR file "ExecutableOne.jar." The "ExecutableOne.jar" contains the MagicService class, which we will use inside "ExecutableTwo." We will instantiate the class MagicService and execute the public method getMessage(). All this will happen inside the Main class of the project "ExecutableTwo".

Let's create following Main class in the project package com.exec.two:

package com.exec.two;                                                                                                                                                                  

import com.exec.one.service.MagicService;                                                                                                                                              

public class Main {                                                                                                                                                                    

    public static void main(String[] args){                                                                                                                                            

        System.out.println("Executable-Two Main");                                                                                                                                     
        MagicService service = new MagicService();                                                                                                                                     
        System.out.println("MagicService from Executable-ONE");                                                                                                                        
        System.out.println("MESSAGE: " + service.getMessage());                                                                                                                        

     }                                                                                                                                                                                  

}

Now we have everything prepared for our fat JAR file creation. We imported MagicService from the JAR library we have previously created and executed its getMessage() method. For the next few steps, we will use javac and the JAR tools provided by the Java JDK. Let's go back the command line and compile the project. In the command, we need to inform the compiler that we should extend its classpath to the used library.

$javac -cp ./src/main/java 
./src/main/java/com/exec/two/*.java -d ./out/ 
-classpath ./libs/ExecutableOne.jar

The "Executable-Two" project has been successfully compiled into the OUT directory.
Now it's time to properly prepare the OUT directory for fat JAR creation. Inside the OUT directory, we have the compiled classes that we have created for "Executable-Two." Meanwhile, the JAR tool only reads the file physically located on the filesystem — it won't read the compressed JAR file. Of course, that means that the JAR tool will not decompress and read any *.jar file located inside the OUT directory.

The result is that even when we copy the ExecutableOne.jar into the OUT directory, the JAR tool will not unpack the ExecutableOne.jar file, and the library will be added (in its compressed form) to the result. Of course, because it's compressed, it will be ignored.

The problem is that the $java -jar tool does not read inner packaged *.jar archive files!

It implies that we need to unpack the previously created Java Archive (JAR) "Executable-One.jar" into the "Executable-Two" project 's OUT directory. Open the command line and type:

$cp libs/ExecutableOne.jar ./out/
$cd ./out
$tar xf ExecutableOne.jar
$rm ExecutableOne.jar


Now, the "Executable-Two" project output directory is ready to be used as the source folder for the new JAR file.

Note: Inside every executable JAR file, there is only one MANIFEST.FM file available.

For packing our "Executable-Two" project into the JAR archive file, we use the newly created manifest file located in the folder ./src/main/resources/META-INF/ :

Manifest-Version: 1.0                                                                                                                                                                  
Class-Path: .                                                                                                                                                                          
Main-Class: com.exec.two.Main

And now we can pack all that together by typing:

$jar cvfm ExecutableTwo.jar ./src/main/resources/META-INF/MANIFEST.FM -C./out/ .

When we do execute the newly created fat JAR file, "ExecutableTwo.jar", we receive the following output.

$java -jar ./ExecutableTwo.jar
output:
Executable-Two Main
MagicService from Executable-ONE
MESSAGE: Magic Message

Congratulations! You've executed your fat JAR file!

Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:
java ,jar ,command line ,executable

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}