Technology5 minute read

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.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

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.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Igor Delac
Verified Expert in Engineering

Igor is an enthusiastic and skilled professional with extensive experience in Java development, system administration, and more.

Expertise

PREVIOUSLY AT

Ericsson
Share

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.

Hire a Toptal expert on this topic.
Hire Now
Igor Delac

Igor Delac

Verified Expert in Engineering

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.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Expertise

PREVIOUSLY AT

Ericsson

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Join the Toptal® community.