CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-loader-tools

Tools for generating executable JAR/WAR files with embedded containers for Spring Boot applications

Pending
Overview
Eval results
Files

layer-support.mddocs/

Layer Support for Docker Optimization

Advanced layering system for optimizing Docker image builds by strategically separating application code, dependencies, and Spring Boot loader components into distinct layers. This enables efficient caching and faster container image builds.

Capabilities

Layer Definition

Core class representing a named layer for organizing archive content.

public class Layer {
    /**
     * Create a layer with the specified name.
     * 
     * @param name the layer name
     */
    public Layer(String name);
    
    /**
     * Check if this layer equals another object.
     * 
     * @param obj the object to compare
     * @return true if equal
     */
    @Override
    public boolean equals(Object obj);
    
    /**
     * Get the hash code for this layer.
     * 
     * @return the hash code
     */
    @Override
    public int hashCode();
    
    /**
     * Get the string representation of this layer.
     * 
     * @return the layer name
     */
    @Override
    public String toString();
}

Layer Interface

Primary interface for providing layer information during the packaging process.

public interface Layers extends Iterable<Layer> {
    /**
     * Get an iterator over all layers.
     * 
     * @return iterator for layers
     */
    @Override
    Iterator<Layer> iterator();
    
    /**
     * Get a stream of all layers.
     * 
     * @return stream of layers
     */
    default Stream<Layer> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
    /**
     * Get the layer for an application resource.
     * 
     * @param applicationResource the application resource path
     * @return the layer for this resource
     */
    Layer getLayer(String applicationResource);
    
    /**
     * Get the layer for a library.
     * 
     * @param library the library
     * @return the layer for this library
     */
    Layer getLayer(Library library);
    
    /**
     * Implicit layers implementation that uses default layer assignment.
     */
    Layers IMPLICIT = /* default implementation */;
}

Layer Index

Index file that describes which layer each entry belongs to, used by Docker image builders.

public class LayersIndex {
    /**
     * Create a layers index with the specified layers.
     * 
     * @param layers the layers to include in the index
     */
    public LayersIndex(Layer... layers);
    
    /**
     * Create a layers index from an iterable of layers.
     * 
     * @param layers the layers to include
     */
    public LayersIndex(Iterable<Layer> layers);
    
    /**
     * Add an entry to the specified layer.
     * 
     * @param layer the layer to add the entry to
     * @param name the entry name/path
     */
    public void add(Layer layer, String name);
    
    /**
     * Write the index to an output stream.
     * The index format is compatible with Spring Boot's layered JAR structure.
     * 
     * @param out the output stream to write to
     * @throws IOException if writing fails
     */
    public void writeTo(OutputStream out) throws IOException;
}

Standard Layers

Pre-defined layer implementation following Spring Boot conventions for optimal Docker caching.

public abstract class StandardLayers implements Layers {
    /**
     * Layer for external dependencies.
     * Changes infrequently, provides good caching.
     */
    public static final Layer DEPENDENCIES = new Layer("dependencies");
    
    /**
     * Layer for Spring Boot loader classes.
     * Changes only with Spring Boot version updates.
     */
    public static final Layer SPRING_BOOT_LOADER = new Layer("spring-boot-loader");
    
    /**
     * Layer for snapshot dependencies.
     * Separated from stable dependencies for better cache efficiency.
     */
    public static final Layer SNAPSHOT_DEPENDENCIES = new Layer("snapshot-dependencies");
    
    /**
     * Layer for application code.
     * Changes frequently, placed in top layer.
     */
    public static final Layer APPLICATION = new Layer("application");
}

Implicit Layer Resolver

Default implementation that automatically assigns layers based on content analysis.

public class ImplicitLayerResolver extends StandardLayers {
    // Automatically determines appropriate layer for each component
    // Uses heuristics to optimize Docker image layer efficiency
}

Custom Layers

User-defined layer configuration with custom selection logic for fine-grained control over layer assignment.

public class CustomLayers implements Layers {
    /**
     * Create custom layers with specified selectors.
     * 
     * @param layers the ordered list of layers
     * @param applicationSelectors selectors for application resources
     * @param librarySelectors selectors for library resources
     */
    public CustomLayers(List<Layer> layers, 
                       List<ContentSelector<String>> applicationSelectors,
                       List<ContentSelector<Library>> librarySelectors);
}

Content Selection

Strategy interface for determining which layer content should be assigned to.

public interface ContentSelector<T> {
    /**
     * Get the target layer for selected content.
     * 
     * @return the layer
     */
    Layer getLayer();
    
    /**
     * Check if this selector contains the specified item.
     * 
     * @param item the item to check
     * @return true if the item belongs to this selector's layer
     */
    boolean contains(T item);
}

Include/Exclude Content Selector

Pattern-based content selector using include and exclude rules.

public class IncludeExcludeContentSelector<T> implements ContentSelector<T> {
    // Implementation uses pattern matching for flexible content selection
    // Supports wildcards and regular expressions
}

Content Filters

Filter interfaces for different types of content.

public interface ContentFilter<T> {
    // Base interface for content filtering
}

public class ApplicationContentFilter implements ContentFilter<String> {
    // Filter for application resources (class files, properties, etc.)
}

public class LibraryContentFilter implements ContentFilter<Library> {
    // Filter for library dependencies
}

Usage Examples

Basic Layer Configuration

import org.springframework.boot.loader.tools.*;
import java.io.File;

// Use standard layers for optimal Docker caching
File sourceJar = new File("myapp.jar");
Repackager repackager = new Repackager(sourceJar);

// Enable layered packaging
repackager.setLayers(Layers.IMPLICIT);

// Repackage with layer support
repackager.repackage(Libraries.NONE);

// The resulting JAR will include BOOT-INF/layers.idx
// Docker buildpacks can use this for efficient layer creation

Custom Layer Configuration

import org.springframework.boot.loader.tools.*;
import org.springframework.boot.loader.tools.layer.*;
import java.io.File;
import java.util.List;

// Define custom layers
Layer infrastructureLayer = new Layer("infrastructure");
Layer businessLogicLayer = new Layer("business-logic");
Layer configurationLayer = new Layer("configuration");

// Create content selectors
List<ContentSelector<String>> applicationSelectors = List.of(
    new IncludeExcludeContentSelector<>(configurationLayer, 
        List.of("**/*.properties", "**/*.yml", "**/*.xml"), 
        List.of()),
    new IncludeExcludeContentSelector<>(businessLogicLayer,
        List.of("**/service/**", "**/business/**"),
        List.of()),
    new IncludeExcludeContentSelector<>(infrastructureLayer,
        List.of("**"),  // everything else
        List.of())
);

List<ContentSelector<Library>> librarySelectors = List.of(
    new IncludeExcludeContentSelector<>(infrastructureLayer,
        List.of("org.springframework.*", "org.hibernate.*"),
        List.of()),
    new IncludeExcludeContentSelector<>(businessLogicLayer,
        List.of("com.example.business.*"),
        List.of())
);

// Create custom layers configuration
Layers customLayers = new CustomLayers(
    List.of(infrastructureLayer, businessLogicLayer, configurationLayer),
    applicationSelectors,
    librarySelectors
);

// Apply to repackager
Repackager repackager = new Repackager(new File("myapp.jar"));
repackager.setLayers(customLayers);
repackager.repackage(Libraries.NONE);

Layer Index Generation

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

// Manually create and write a layer index
Layer appLayer = new Layer("application");
Layer libLayer = new Layer("dependencies");
Layer loaderLayer = new Layer("spring-boot-loader");

LayersIndex index = new LayersIndex(appLayer, libLayer, loaderLayer);

// Add entries to layers
index.add(appLayer, "BOOT-INF/classes/com/example/Application.class");
index.add(appLayer, "BOOT-INF/classes/com/example/service/UserService.class");
index.add(libLayer, "BOOT-INF/lib/spring-boot-3.2.0.jar");
index.add(libLayer, "BOOT-INF/lib/logback-classic-1.4.5.jar");
index.add(loaderLayer, "org/springframework/boot/loader/launch/JarLauncher.class");

// Write index to file
File indexFile = new File("layers.idx");
try (FileOutputStream fos = new FileOutputStream(indexFile)) {
    index.writeTo(fos);
}

// The index file can be included in the JAR at BOOT-INF/layers.idx

Integration with Docker Buildpacks

import org.springframework.boot.loader.tools.*;
import java.io.File;

// Configure layers for optimal buildpack performance
public class BuildpackOptimizedLayers extends StandardLayers {
    // Additional custom layers for buildpack optimization
    public static final Layer BUILDPACK_CACHE = new Layer("buildpack-cache");
    public static final Layer CONFIGURATION = new Layer("configuration");
    
    @Override
    public Layer getLayer(String applicationResource) {
        // Buildpack-specific caching frequently accessed resources
        if (applicationResource.endsWith(".properties") || 
            applicationResource.endsWith(".yml") ||
            applicationResource.endsWith(".yaml")) {
            return CONFIGURATION;
        }
        
        // Use standard logic for other resources
        return super.getLayer(applicationResource);
    }
    
    @Override
    public Layer getLayer(Library library) {
        LibraryCoordinates coords = library.getCoordinates();
        if (coords != null) {
            // Separate buildpack-specific libraries
            if (coords.getGroupId().startsWith("io.buildpacks") ||
                coords.getGroupId().startsWith("org.cloudfoundry")) {
                return BUILDPACK_CACHE;
            }
        }
        
        return super.getLayer(library);
    }
}

// Use with repackager
Repackager repackager = new Repackager(new File("myapp.jar"));
repackager.setLayers(new BuildpackOptimizedLayers());
repackager.repackage(Libraries.NONE);

Layer Analysis and Inspection

import org.springframework.boot.loader.tools.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

// Analyze layer distribution for optimization
public class LayerAnalyzer {
    private final Map<Layer, AtomicLong> layerSizes = new ConcurrentHashMap<>();
    private final Map<Layer, AtomicLong> layerCounts = new ConcurrentHashMap<>();
    
    public void analyzeLayers(Layers layers, Libraries libraries) throws IOException {
        // Analyze library distribution
        libraries.doWithLibraries(library -> {
            Layer layer = layers.getLayer(library);
            long size = library.getFile().length();
            
            layerSizes.computeIfAbsent(layer, k -> new AtomicLong(0)).addAndGet(size);
            layerCounts.computeIfAbsent(layer, k -> new AtomicLong(0)).incrementAndGet();
        });
        
        // Print analysis results
        System.out.println("Layer Analysis:");
        System.out.println("==============");
        
        for (Layer layer : layers) {
            long size = layerSizes.getOrDefault(layer, new AtomicLong(0)).get();
            long count = layerCounts.getOrDefault(layer, new AtomicLong(0)).get();
            
            System.out.printf("Layer: %s%n", layer);
            System.out.printf("  Size: %,d bytes (%.2f MB)%n", size, size / 1024.0 / 1024.0);
            System.out.printf("  Count: %,d items%n", count);
            System.out.printf("  Avg Size: %,d bytes%n", count > 0 ? size / count : 0);
            System.out.println();
        }
    }
}

// Usage
LayerAnalyzer analyzer = new LayerAnalyzer();
analyzer.analyzeLayers(Layers.IMPLICIT, libraries);

Dynamic Layer Assignment

import org.springframework.boot.loader.tools.*;
import org.springframework.boot.loader.tools.layer.*;
import java.util.List;
import java.util.function.Predicate;

// Dynamic layer assignment based on runtime criteria
public class DynamicLayers implements Layers {
    private final List<Layer> layers;
    private final Predicate<String> frequentlyChanging;
    private final Predicate<Library> heavyLibraries;
    
    public DynamicLayers(Predicate<String> frequentlyChanging, Predicate<Library> heavyLibraries) {
        this.layers = List.of(
            StandardLayers.SPRING_BOOT_LOADER,
            new Layer("heavy-dependencies"),
            StandardLayers.DEPENDENCIES,
            new Layer("volatile-app"),
            StandardLayers.APPLICATION
        );
        this.frequentlyChanging = frequentlyChanging;
        this.heavyLibraries = heavyLibraries;
    }
    
    @Override
    public Iterator<Layer> iterator() {
        return layers.iterator();
    }
    
    @Override
    public Layer getLayer(String applicationResource) {
        if (frequentlyChanging.test(applicationResource)) {
            return new Layer("volatile-app");
        }
        return StandardLayers.APPLICATION;
    }
    
    @Override
    public Layer getLayer(Library library) {
        if (heavyLibraries.test(library)) {
            return new Layer("heavy-dependencies");
        }
        
        LibraryCoordinates coords = library.getCoordinates();
        if (coords != null && coords.getGroupId().startsWith("org.springframework")) {
            return StandardLayers.SPRING_BOOT_LOADER;
        }
        
        return StandardLayers.DEPENDENCIES;
    }
}

// Usage with intelligent predicates
DynamicLayers dynamicLayers = new DynamicLayers(
    // Frequently changing application resources
    resource -> resource.contains("/controller/") || 
                resource.contains("/service/") ||
                resource.endsWith(".properties"),
    
    // Heavy libraries (>10MB)
    library -> library.getFile().length() > 10 * 1024 * 1024
);

Repackager repackager = new Repackager(new File("myapp.jar"));
repackager.setLayers(dynamicLayers);
repackager.repackage(Libraries.NONE);

Docker Integration

Layer Index Format

The layers index file (BOOT-INF/layers.idx) uses the following format:

- "dependencies":
  - "BOOT-INF/lib/spring-boot-3.2.0.jar"
  - "BOOT-INF/lib/logback-classic-1.4.5.jar"
- "spring-boot-loader":
  - "org/springframework/boot/loader/"
- "application":
  - "BOOT-INF/classes/"
  - "META-INF/"

Dockerfile Integration

# Multi-stage build with layer extraction
FROM eclipse-temurin:17-jre as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

# Final image with optimized layers
FROM eclipse-temurin:17-jre
WORKDIR application

# Copy layers in order of change frequency (least to most)
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./

ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

This layering strategy ensures that:

  1. Dependencies layer - Cached until dependencies change
  2. Spring Boot loader layer - Cached until Spring Boot version changes
  3. Snapshot dependencies layer - Rebuilt when snapshot versions change
  4. Application layer - Rebuilt on every application code change

The result is faster Docker builds and more efficient image distribution.

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-boot--spring-boot-loader-tools

docs

build-integration.md

image-packaging.md

index.md

jar-writing.md

launch-scripts.md

layer-support.md

layout-management.md

library-management.md

main-class-detection.md

repackaging.md

tile.json