Launching a Spring Boot Application Programmatically
Starting a Spring Boot application in the command line is as simple as it gets. But what if we want to run our application from another Java program?
In this article, Toptal Java Developer Igor Delac demonstrates how you can start a Spring Boot app programmatically using just a few lines of code.
Starting a Spring Boot application in the command line is as simple as it gets. But what if we want to run our application from another Java program?
In this article, Toptal Java Developer Igor Delac demonstrates how you can start a Spring Boot app programmatically using just a few lines of code.
Igor is an enthusiastic and skilled professional with extensive experience in Java development, system administration, and more.
Expertise
Previously At
This article will demonstrate how to start a Spring Boot application from another Java program. A Spring Boot application is typically built into a single executable JAR archive. It contains all dependencies inside, packaged as nested JARs.
Likewise, a Spring Boot project is usually built as an executable JAR file by a provided maven plugin that does all the dirty work. The result is a convenient, single JAR file that is easy to share with others, deploy on a server, and so on.
Starting a Spring Boot application is as easy as typing java -jar mySpringProg.jar, and the application will print on console some nicely formatted info messages.
But what if a Spring Boot developer wants to run an application from another Java program, without human intervention?
How Nested JARs Work
To pack a Java program with all dependencies into a single runnable JAR file, dependencies that are also JAR files have to be provided and somehow stored inside the final runnable JAR file.
“Shading” is one option. Shading dependencies is the process of including and renaming dependencies, relocating the classes, and rewriting affected bytecode and resources in order to create a copy that is bundled alongside with an application’s (project) own code.
Shading allows users to unpack all classes and resources from dependencies and pack them back into a runnable JAR file. This might work for simple scenarios, however, if two dependencies contain the same resource file or class with the exact same name and path, they will overlap and the program might not work.
Spring Boot takes a different approach and packs dependency JARs inside runnable JAR, as nested JARs.
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
+-dependency1.jar
+-dependency2.jar
A JAR archive is organized as a standard Java-runnable JAR file. Spring Boot loader classes are located at org/springframework/boot/loader
path, while user classes and dependencies are at BOOT-INF/classes
and BOOT-INF/lib
.
Note: If you’re new to Spring, you may also want to take a look at our Top 10 Most Common Spring Framework Mistakes article.
A typical Spring Boot JAR file contains three types of entries:
- Project classes
- Nested JAR libraries
- Spring Boot loader classes
Spring Boot Classloader will first set JAR libraries in the classpath and then project classes, which makes a slight difference between running a Spring Boot application from IDE (Eclipse, IntelliJ) and from console.
For additional information on class overrides and the classloader, you can consult this article.
Launching Spring Boot Applications
Launching a Spring Boot application manually from the command line or shell is easy as typing the following:
java -jar example.jar
However, starting a Spring Boot application programmatically from another Java program requires more effort. It’s necessary to load the org/springframework/boot/loader/*.class
code, use a bit of Java reflection to instantiate JarFileArchive
, JarLauncher
, and invoke the launch(String[])
method.
We will take a more detailed look at how this is accomplished in the following sections.
Loading Spring Boot Loader Classes
As we already pointed out, a Spring Boot JAR file is just like any JAR archive. It is possible to load org/springframework/boot/loader/*.class
entries, create Class objects, and use them to launch Spring Boot applications later on.
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
. . .
public static void loadJar(final String pathToJar) throws IOException . . . {
// Class name to Class object mapping.
final Map<String, Class<?>> classMap = new HashMap<>();
final JarFile jarFile = new JarFile(pathToJar);
final Enumeration<JarEntry> jarEntryEnum = jarFile.entries();
final URL[] urls = { new URL("jar:file:" + pathToJar + "!/") };
final URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);
Here we can see classMap
will hold Class objects mapped to their respective package names, e.g., String value org.springframework.boot.loader.JarLauncher
will be mapped to the JarLauncher.class
object.
while (jarEntryEnum.hasMoreElements()) {
final JarEntry jarEntry = jarEntryEnum.nextElement();
if (jarEntry.getName().startsWith("org/springframework/boot")
&& jarEntry.getName().endsWith(".class") == true) {
int endIndex = jarEntryName.lastIndexOf(".class");
className = jarEntryName.substring(0, endIndex).replace('/', '.');
try {
final Class<?> loadedClass = urlClassLoader.loadClass(className);
result.put(loadedClass.getName(), loadedClass);
}
catch (final ClassNotFoundException ex) {
}
}
}
jarFile.close();
The end result of the while loop is a map populated with Spring Boot loader class objects.
Automating the Actual Launch
With loading out of the way, we can proceed to finalize the automatic launch and use it to actually start our app.
Java reflection allows the creation of objects from loaded classes, which is quite useful in the context of our tutorial.
The first step is to create a JarFileArchive
object.
// Create JarFileArchive(File) object, needed for JarLauncher.
final Class<?> jarFileArchiveClass = result.get("org.springframework.boot.loader.archive.JarFileArchive");
final Constructor<?> jarFileArchiveConstructor =
jarFileArchiveClass.getConstructor(File.class);
final Object jarFileArchive =
jarFileArchiveConstructor.newInstance(new File(pathToJar));
The constructor of the JarFileArchive
object takes a File(String)
object as an argument, so it must be provided.
The next step is to create a JarLauncher
object, which requires Archive
in its constructor.
final Class<?> archiveClass = result.get("org.springframework.boot.loader.archive.Archive");
// Create JarLauncher object using JarLauncher(Archive) constructor.
final Constructor<?> jarLauncherConstructor = mainClass.getDeclaredConstructor(archiveClass);
jarLauncherConstructor.setAccessible(true);
final Object jarLauncher = jarLauncherConstructor.newInstance(jarFileArchive);
To avoid confusion, please note that Archive
is actually an interface, while JarFileArchive
is one of the implementations.
The last step in the process is to call the launch(String[])
method on our newly created jarLauncher
object. This is relatively straightforward and requires just a few lines of code.
// Invoke JarLauncher#launch(String[]) method.
final Class<?> launcherClass = result.get("org.springframework.boot.loader.Launcher");
final Method launchMethod =
launcherClass.getDeclaredMethod("launch", String[].class);
launchMethod.setAccessible(true);
launchMethod.invoke(jarLauncher, new Object[]{new String[0]});
The invoke(jarLauncer, new Object[]{new String[0]})
method will finally start the Spring Boot application. Note that the main thread will stop and wait here for the Spring Boot application to terminate.
A Word About the Spring Boot Classloader
Examining our Spring Boot JAR file will reveal the following structure:
+--- mySpringApp1-0.0.1-SNAPSHOT.jar
+--- META-INF
+--- BOOT-INF
| +--- classes # 1 - project classes
| | |
| | +--- com.example.mySpringApp1
| | \--- SpringBootLoaderApplication.class
| |
| +--- lib # 2 - nested jar libraries
| +--- javax.annotation-api-1.3.1
| +--- spring-boot-2.0.0.M7.jar
| \--- (...)
|
+--- org.springframework.boot.loader # 3 - Spring Boot loader classes
+--- JarLauncher.class
+--- LaunchedURLClassLoader.class
\--- (...)
Note the three types of entries:
- Project classes
- Nested JAR libraries
- Spring Boot loader classes
Both project classes (BOOT-INF/classes
) and nested JARs (BOOT-INF/lib
) are handled by the same class loader LaunchedURLClassLoader
. This loader resides in the root of the Spring Boot JAR application.
The LaunchedURLClassLoader
will load the class content (BOOT-INF/classes
) after the library content (BOOT-INF/lib
), which is different from the IDE. For example, Eclipse will first place class content in the classpath and then libraries (dependencies).
LaunchedURLClassLoader
extends java.net.URLClassLoader
, which is created with a set of URLs that will be used for class loading. The URL might point to a location like a JAR archive or classes folder. When performing class loading, all of the resources specified by URLs will be traversed in the order the URLs were provided, and the first resource containing the searched class will be used.
Wrapping Up
A classic Java application requires all dependencies to be enumerated in the classpath argument, making the startup procedure somewhat cumbersome and complicated.
In contrast, Spring Boot applications are handy and easy to start from the command line. They manage all dependencies, and the end user does not need to worry about the details.
However, starting a Spring Boot application from another Java program makes the procedure more complicated, as it requires loading Spring Boot’s loader classes, creating specialized objects such as JarFileArchive
and JarLauncher
, and then using Java reflection to invoke the launch
method.
Bottom line: Spring Boot can take care of a lot of menial tasks under the hood, allowing developers to free up time and focus on more useful work such as creating new features, testing, and so on.
Understanding the basics
What is the difference between Spring and Spring Boot?
Spring Boot makes it easy to create standalone, production-grade, Spring-based applications that are easy to run or deploy, while the Spring framework is a comprehensive set of Java libraries used to develop rich web, desktop, or mobile applications.
What is Spring Boot and how does it work?
Spring Boot provides maven templates, built-in Tomcat web server, and some predefined configurations to simplify the use of Spring. Most Spring Boot applications need very little Spring configuration. Spring Boot is used to create Java applications that can be started by using java -jar or more traditional war deployments.
What is Spring Boot architecture?
Spring Boot architecture provides starters, auto-configuration, and component scan in order to get started with Spring without the need for complex XML configuration files.
Igor Delac
Split, Croatia
Member since November 3, 2014
About the author
Igor is an enthusiastic and skilled professional with extensive experience in Java development, system administration, and more.
Expertise
PREVIOUSLY AT