Tools for generating executable JAR/WAR files with embedded containers for Spring Boot applications
—
Strategy-based system for determining how classes and libraries are organized within different archive types. The layout system supports JAR, WAR, and expanded directory structures with extensive customization options for different deployment scenarios.
Core interface defining how archive contents are organized and accessed by the Spring Boot loader.
public interface Layout {
/**
* Get the launcher class name for this layout.
* The launcher is responsible for bootstrapping the application.
*
* @return the fully qualified launcher class name
*/
String getLauncherClassName();
/**
* Get the location where a library should be placed within the archive.
*
* @param libraryName the name of the library
* @param scope the scope of the library (COMPILE, RUNTIME, etc.)
* @return the path within the archive where the library should be placed
*/
String getLibraryLocation(String libraryName, LibraryScope scope);
/**
* Get the location where application classes are stored.
*
* @return the path within the archive where classes are located
*/
String getClassesLocation();
/**
* Get the location of the classpath index file.
* Default implementation returns "BOOT-INF/classpath.idx".
*
* @return the path to the classpath index file
*/
default String getClasspathIndexFileLocation() {
return "BOOT-INF/classpath.idx";
}
/**
* Get the location of the layers index file.
* Default implementation returns "BOOT-INF/layers.idx".
*
* @return the path to the layers index file
*/
default String getLayersIndexFileLocation() {
return "BOOT-INF/layers.idx";
}
/**
* Determine if this layout produces executable archives.
*
* @return true if the archive can be executed directly
*/
boolean isExecutable();
}Specialized layout interface for repackaging existing archives with different class locations.
public interface RepackagingLayout extends Layout {
/**
* Get the location where repackaged classes should be stored.
* This may differ from the original classes location.
*
* @return the path for repackaged classes
*/
String getRepackagedClassesLocation();
}Interface for layouts that provide their own loader classes instead of using the standard Spring Boot loader.
public interface CustomLoaderLayout {
/**
* Write custom loader classes to the archive.
*
* @param writer the writer for adding loader classes
* @throws IOException if writing fails
*/
void writeLoadedClasses(LoaderClassesWriter writer) throws IOException;
}Pre-defined layout implementations for common archive types.
public final class Layouts {
/**
* Get the appropriate layout for the given file based on its extension and type.
*
* @param file the file to determine layout for
* @return the appropriate layout
*/
public static Layout forFile(File file);
/**
* Standard JAR layout for executable JAR files.
* Places libraries in BOOT-INF/lib/ and classes in BOOT-INF/classes/.
*/
public static class Jar implements RepackagingLayout {
@Override
public String getLauncherClassName() {
return "org.springframework.boot.loader.launch.JarLauncher";
}
@Override
public String getLibraryLocation(String libraryName, LibraryScope scope) {
return "BOOT-INF/lib/";
}
@Override
public String getClassesLocation() {
return "BOOT-INF/classes/";
}
@Override
public String getRepackagedClassesLocation() {
return "BOOT-INF/classes/";
}
@Override
public boolean isExecutable() {
return true;
}
}
/**
* Expanded JAR layout for directory-based deployment.
* Similar to JAR layout but optimized for exploded directory structure.
*/
public static class Expanded extends Jar {
@Override
public String getLauncherClassName() {
return "org.springframework.boot.loader.launch.PropertiesLauncher";
}
}
/**
* No-operation layout that preserves original structure.
* Used when no repackaging is desired.
*/
public static class None extends Jar {
@Override
public boolean isExecutable() {
return false;
}
}
/**
* WAR layout for web application archives.
* Places libraries in WEB-INF/lib/ and classes in WEB-INF/classes/.
*/
public static class War implements Layout {
@Override
public String getLauncherClassName() {
return "org.springframework.boot.loader.launch.WarLauncher";
}
@Override
public String getLibraryLocation(String libraryName, LibraryScope scope) {
if (scope == LibraryScope.PROVIDED) {
return "WEB-INF/lib-provided/";
}
return "WEB-INF/lib/";
}
@Override
public String getClassesLocation() {
return "WEB-INF/classes/";
}
@Override
public boolean isExecutable() {
return true;
}
}
}Factory interface for creating layout instances based on source files.
public interface LayoutFactory {
/**
* Create an appropriate layout for the given source file.
*
* @param source the source file to create a layout for
* @return the created layout
*/
Layout getLayout(File source);
}
public class DefaultLayoutFactory implements LayoutFactory {
/**
* Create a layout based on file extension and characteristics.
* - .jar files use Layouts.Jar
* - .war files use Layouts.War
* - Directories use Layouts.Expanded
*
* @param source the source file
* @return the appropriate layout
*/
@Override
public Layout getLayout(File source) {
// Implementation determines layout based on file type
}
}import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts;
import java.io.File;
// Automatic layout selection
File jarFile = new File("myapp.jar");
Layout layout = Layouts.forFile(jarFile);
System.out.println("Launcher: " + layout.getLauncherClassName());
System.out.println("Classes location: " + layout.getClassesLocation());
System.out.println("Executable: " + layout.isExecutable());
// Output for JAR file:
// Launcher: org.springframework.boot.loader.launch.JarLauncher
// Classes location: BOOT-INF/classes/
// Executable: trueimport org.springframework.boot.loader.tools.*;
import java.io.File;
// Use specific layout with repackager
File sourceJar = new File("myapp.jar");
Repackager repackager = new Repackager(sourceJar);
// Force WAR layout even for JAR file
Layout warLayout = new Layouts.War();
repackager.setLayout(warLayout);
// Libraries will be placed in WEB-INF/lib/ instead of BOOT-INF/lib/
repackager.repackage(Libraries.NONE);import org.springframework.boot.loader.tools.*;
// Create custom layout for special deployment requirements
public class CustomLayout implements RepackagingLayout {
@Override
public String getLauncherClassName() {
return "com.example.CustomLauncher";
}
@Override
public String getLibraryLocation(String libraryName, LibraryScope scope) {
// Separate compile and runtime dependencies
if (scope == LibraryScope.COMPILE) {
return "CUSTOM-INF/compile-lib/";
}
return "CUSTOM-INF/runtime-lib/";
}
@Override
public String getClassesLocation() {
return "CUSTOM-INF/app-classes/";
}
@Override
public String getRepackagedClassesLocation() {
return "CUSTOM-INF/app-classes/";
}
@Override
public boolean isExecutable() {
return true;
}
}
// Use custom layout
File sourceJar = new File("myapp.jar");
Repackager repackager = new Repackager(sourceJar);
repackager.setLayout(new CustomLayout());
repackager.repackage(Libraries.NONE);import org.springframework.boot.loader.tools.*;
import java.io.File;
// Custom layout factory for special file types
public class CustomLayoutFactory implements LayoutFactory {
@Override
public Layout getLayout(File source) {
if (source.getName().endsWith(".ubar")) {
return new Layouts.War(); // Treat .ubar files as WAR
}
if (source.getName().contains("micro")) {
return new Layouts.Expanded(); // Use expanded layout for micro services
}
return Layouts.forFile(source); // Default behavior
}
}
// Configure repackager with custom factory
File sourceFile = new File("myapp.ubar");
Repackager repackager = new Repackager(sourceFile);
repackager.setLayoutFactory(new CustomLayoutFactory());
repackager.repackage(Libraries.NONE);import org.springframework.boot.loader.tools.*;
import java.io.File;
// Examine how different layouts handle library placement
Layout jarLayout = new Layouts.Jar();
Layout warLayout = new Layouts.War();
String libName = "spring-core-5.3.21.jar";
// JAR layout
String jarLibLocation = jarLayout.getLibraryLocation(libName, LibraryScope.COMPILE);
System.out.println("JAR layout lib location: " + jarLibLocation);
// Output: BOOT-INF/lib/
// WAR layout with different scopes
String warCompileLocation = warLayout.getLibraryLocation(libName, LibraryScope.COMPILE);
String warProvidedLocation = warLayout.getLibraryLocation(libName, LibraryScope.PROVIDED);
System.out.println("WAR compile location: " + warCompileLocation);
System.out.println("WAR provided location: " + warProvidedLocation);
// Output: WEB-INF/lib/
// Output: WEB-INF/lib-provided/import org.springframework.boot.loader.tools.*;
import java.io.IOException;
// Layout with custom loader classes
public class CustomLoaderLayout implements Layout, CustomLoaderLayout {
@Override
public String getLauncherClassName() {
return "com.example.CustomBootstrap";
}
@Override
public String getLibraryLocation(String libraryName, LibraryScope scope) {
return "META-INF/libs/";
}
@Override
public String getClassesLocation() {
return "META-INF/classes/";
}
@Override
public boolean isExecutable() {
return true;
}
@Override
public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException {
// Write custom loader classes
try (var stream = getClass().getResourceAsStream("/custom-loader.jar")) {
writer.writeLoaderClasses("custom-loader.jar");
}
}
}| File Type | Default Layout | Launcher Class | Classes Location | Libraries Location |
|---|---|---|---|---|
| .jar | Layouts.Jar | JarLauncher | BOOT-INF/classes/ | BOOT-INF/lib/ |
| .war | Layouts.War | WarLauncher | WEB-INF/classes/ | WEB-INF/lib/ |
| Directory | Layouts.Expanded | PropertiesLauncher | BOOT-INF/classes/ | BOOT-INF/lib/ |
| Other | Layouts.Jar | JarLauncher | BOOT-INF/classes/ | BOOT-INF/lib/ |
The layout system provides the foundation for Spring Boot's flexible packaging model, enabling different deployment strategies while maintaining consistent runtime behavior.
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-loader-tools