- 3 Ways to Package Your Java Project into a JAR for Deployment
- 1. Building a single JAR
- 2. Building a JAR and delivering it with other JARs
- A. Acquire the third-party JARs
- B. Build your JAR alone
- C. Write a shell script (aka command-line script)
- 3. Delivering a native application
- A. Put all of your JARs (third-party or not) into a folder with your shell script
- B. Download a copy of the Java Runtime Environment (JRE)
- C. Move the JRE into your bundling folder
- E. Use warp-packer to produce a binary
- Competing tools for bundling Java
- Conclusion
- Packaging the Application
- Creating an Artifact
3 Ways to Package Your Java Project into a JAR for Deployment
In this tutorial, we will look at 3 ways to package your Java project into a JAR file, so you can ship your application to your users. A JAR file is an archive that contains compiled Java source code and other things needed to run your program. With a functioning JAR, your user should only need a Java Runtime to run your code. They shouldn’t need to compile your code by opening up an IDE or using a build tool. This makes it easy to deploy on different platforms. Hence, Java’s slogan «Write once, run anywhere».
1. Building a single JAR
When your project is simple and you don’t have many dependencies, building a JAR from your project is very easy. The jar task on Gradle is enough. Just make sure your build.gradle has your application’s entry point (main class/main method) defined.
jar // this is necessary to run your JAR manifest attributes 'Main-Class': 'yourpackages.morepackages.YourMainClass' > // the rest is to access resource files like images, etc. duplicatesStrategy = DuplicatesStrategy.EXCLUDE from configurations.runtimeClasspath.collect it.isDirectory() ? it : zipTree(it) > > >
Your JAR file is produced inside the directory depending on your project IDE. For projects initialized with gradle only, that is build/libs/ .
2. Building a JAR and delivering it with other JARs
It is possible that a fat JAR cannot be produced for reasons outside of your control. Fat JARs are supposed to contain everything you need to run the program, besides the Java Runtime. But sometimes, a certain dependency creates a problem with the building process. For example, building a Java project with JavaFX creates problems when attaching the JavaFX runtime to your JAR. To remedy this, you can deliver several JARs at once and provide a script that launches the Java Runtime properly by referencing the third-party JARs.
A. Acquire the third-party JARs
For every third-party JARs you cannot integrate into your JAR, get the version of those JARs that you need. For example, download a version of the JavaFX runtime JAR to your computer.
B. Build your JAR alone
C. Write a shell script (aka command-line script)
- Windows:
- Batch (.bat)
- Bash (.sh), if Git Bash is installed
- Z Shell (.zsh)
- Bash (.sh), if the Mac is set up properly
- Bash (.sh)
Begin your shell script with a shebang. This tells the command line what the file is. For example, the following shebang is for a Bash file. It must always be the first line of a shell script.
Create a shell script. Usually, they are named run.sh or execute.sh . Inside it, you call the Java Runtime while referencing the third-party libraries’ JARs and your JAR.
java --module-path path/to/your/third-party-JARs --add-modules module.you.need,second.module.you.need -jar your-project/build/libs/YourOwnJAR.jar
In this command, we call java by giving it a module path. The parameter path/to/your/third-party-JARs you supply here should be the location where you store your third-party JARs. Since we are deploying, we expect the JARs to be located in the same folder as the shell script. Therefore, we can use relative paths, e.g. javafx-sdk-20.0.1/lib . This assumes you moved the JavaFX folder next to your shell script.
your-current-working-directory/ | YourOwnJAR.jar | javafx-sdk-20.0.1/ | | lib/ | | | . lots of JARs | | run.sh
Example run.sh for a JavaFX project
#!/bin/bash java --module-path javafx-sdk-20.0.1/lib --add-modules javafx.controls,javafx.fxml,javafx.graphics,javafx.media -jar YourOwnJar.jar
Don’t forget to grant permission to the script with chmod .
To deliver this to your user, you would zip your-current-working-directory and send the archive. You would notify your user to unzip the archive and run the shell script by double-clicking it or through the command line with ./run.sh . This assumes your user has the appropriate shell language and Java Runtime.
3. Delivering a native application
This method is unlike the other two because it does not require the user to have anything installed, not even a Java Runtime! For situations where you want to deliver maximum application independence, this should be your go-to solution. For example, if you are dealing with people who have no idea what a shell script is or even what coding is, you should be sending everything as a self-contained double-clickable program.
The way this last method works is by shipping the Java Runtime with your JAR and dependencies. For those who don’t know, the Java Runtime is an application designed to run compiled Java code on a specific operating system. As developers, we use the runtime that comes with the Java Development Kit (JDK). Two limitations of this method are the rather large deployment binary and the breaking of the slogan «Write once, run anywhere».
We will make use of the running example. This method builds upon Method 2.
A. Put all of your JARs (third-party or not) into a folder with your shell script
This step is the same as all of Method 2.
B. Download a copy of the Java Runtime Environment (JRE)
Next, you need to download a version of the Java Runtime. Head over to a distributor like the Eclipse Foundation and download a JRE that matches the version of your JDK. Although you can ship the JDK to your user, the file size would be very large (200+ MB) and it would contain many features that your user will never use. That’s why we only need the JRE (40+ MB).
C. Move the JRE into your bundling folder
Assuming you have the same file structure from Method 2, you only need to move the unzipped JRE into your folder. The final file structure should look like this.
Example bundling folder structure
your-bundling-directory/ | jre/ #
D. Edit the shell script
After being bundled (in later steps), the shell script cannot be directly run by the user clicking on it. As such, we need to make the script more independent.
One dependence that the script from Method 2 has is the assumption that the user has the java command installed on their machine. This time, we don't make this assumption and use the java from the Java Runtime copied to your-bundling-directory/jre/ .
Shell scripts like Bash are also dependent on where the user calls them from. After being bundled, the user no longer calls the run.sh script directly. But it is still called by the bundling tool's runtime service when the user clicks on the executable. Therefore, we need to make sure the paths inside the script are all relative to the script's location.
In Bash, there is a special variable called BASH_SOURCE which contains the path to the directory where the Bash script is located. We will use this as a way to have relative paths.
Declare a variable in your run.sh called HERE . Pay attention to the spacing around the assignment operator = . It matters in Bash!
In the above assignment, we make use of another Bash feature, string manipulation. This is done with the curly braces. In our case, we use the percent symbol % to truncate the / symbol and everything after inside the variable. * is the wild card operator from regular expressions. Since BASH_SOURCE contains a path to a directory, it ends with a slash on Linux and we don't want that.
Modify the java command of your run.sh .
"$HERE/jre/bin/java" --module-path "$HERE/javafx-sdk-20.0.1/lib" --add-modules javafx.controls,javafx.fxml,javafx.graphics,javafx.media -jar "$HERE/YourOwnJAR.jar" "$@"
Now, all the paths starts with the $HERE variable, which means it is all relative to the location of the run.sh script. In Bash, we reference variable using the $ symbol followed by the name of the variable. Notice here we put all the paths containing $HERE in double quotes. That is to take care of potential spaces inside path names, such as "Program Files".
You may also notice the last argument is "$@" . This is also one of the default variables in Bash. $@ is a special variable that represents all the parameters the run.sh script has received through the command line. If your Java application takes command line arguments, this is absolutely necessary. Otherwise, it doesn't hurt to have it. This special variable is also enclosed in double quotes, because some arguments may contain white space themselves and that can cause argument splitting issues.
In summary, this is what a run.sh can look like for independent bundling.
#!/bin/bash HERE=$BASH_SOURCE%/*> "$HERE/jre/bin/java" --module-path "$HERE/javafx-sdk-20.0.1/lib" --add-modules javafx.controls,javafx.fxml,javafx.graphics,javafx.media -jar "$HERE/YourOwnJAR.jar" "$@"
Don't forget to test your shell script. Assuming you are outside the bundling directory, you can run the script through the command line.
E. Use warp-packer to produce a binary
Download the warp-packer tool to your system.
If you put the tool next to your bundling directory, then you can run the following commands.
./warp-packer --arch linux-x64 --input_dir your-bundling-directory --exec run.sh --output app.bin chmod +x app.bin
- --arch : the architecture you are deploying to. linux-x64 is the Linux operating system with x64 hardware.
- --input_dir : the bundling directory you just prepared all this time
- --exec : the executable shell script that runs when the binary is clicked
- --output : the name of the produced binary with an extension of your choosing.
The second command grants permission to run the binary.
In the JavaFX example, the binary size is about 108M for one of my students' projects. This is quite expected if you have resources like images in your Java project. Even after warp-packer compresses your files, most of the space is still taken by the Java Runtime Environment.
Competing tools for bundling Java
In a previous version of this article, I recommended jlink and jpackage for bundling Java into a native application. For simple applications, that can still work. However, these default tools in Java are no longer as reliable and as flexible as before. You can still find tutorials on them, but they only teach you how to bundle "Hello World"-level apps with little dependencies. This article dives deeper into software deployment with problematic Java dependencies such as JavaFX. As of the update of this article, I still couldn't get the default JDK tools to work with JavaFX.
Conclusion
In this blog, I explained 3 ways of deploying your Java project. Each method depends on your target user base. If they are okay with having some installed software on their machine. Delivering a JAR or a group of JARs + script is an acceptable solution. However, if you cannot expect your user to have any software installed, you should bundle everything into a standalone executable.
Packaging the Application
We can package the application into a Java ARchive file (JAR).
When the code is ready, we can package our application in a JAR. JAR files are often used to deploy an application to the production server. Once a JAR file has been built, it is called an artifact. Let's take look at how to create artifacts in IntelliJ IDEA.
Creating an Artifact
- Press Cmd+; on macOS, or Shift+Control+Alt+S on Windows to bring up the Project Structure dialog.
- Select Artifacts from the left-hand menu and then press the + icon. Select JAR and then From modules with dependencies.
You don't need to change anything for the Module, however you do need to say which class in your project has the main method. Click the browse button to navigate to your main method. IntelliJ IDEA will show you a list of classes in your project, you only have one so select that.
- Press OK to select your class. All the other defaults are fine for this tutorial, press OK. Now you can see your new JAR file defined in the Project Structure dialog.
- If it matches the above screenshot, press OK. You have now defined how to build the JAR file, but you haven't actually built it yet. You need to build it with your build artifacts.
- Go to Build >Build Artifacts. You will only have one to choose from which is the one that we just defined.
- Press Enter to build the artifact. Your status bar at the bottom-left of the screen will show you when this has completed.
IntelliJ IDEA puts the artifact in a new folder in your out folder called HelloWorld_jar .
To make sure that this JAR file was created correctly you will want to run it. We'll do this in the next step of this tutorial by using a run configuration.