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

jar-writing.mddocs/

JAR Writing and Entry Management

Low-level utilities for writing JAR files with proper directory structure, duplicate handling, manifest generation, and nested library support. The JAR writing system provides the foundation for creating executable Spring Boot archives with embedded dependencies.

Capabilities

High-Level JAR Writing

Primary JAR writer class for creating executable archives with launch script support and automatic resource management.

public class JarWriter implements AutoCloseable {
    /**
     * Create a JAR writer for the specified file.
     * 
     * @param file the target JAR file to create
     * @throws FileNotFoundException if the target file cannot be created
     * @throws IOException if JAR writing initialization fails
     */
    public JarWriter(File file) throws FileNotFoundException, IOException;
    
    /**
     * Create a JAR writer with launch script support.
     * The launch script is prepended to the JAR to make it directly executable on Unix-like systems.
     * 
     * @param file the target JAR file to create
     * @param launchScript the launch script to prepend
     * @throws FileNotFoundException if the target file cannot be created
     * @throws IOException if JAR writing initialization fails
     */
    public JarWriter(File file, LaunchScript launchScript) throws FileNotFoundException, IOException;
    
    /**
     * Create a JAR writer with launch script and custom modification time.
     * 
     * @param file the target JAR file to create
     * @param launchScript the launch script to prepend
     * @param lastModifiedTime the modification time to set on all entries
     * @throws FileNotFoundException if the target file cannot be created
     * @throws IOException if JAR writing initialization fails
     */
    public JarWriter(File file, LaunchScript launchScript, FileTime lastModifiedTime) throws FileNotFoundException, IOException;
    
    /**
     * Close the JAR writer and finalize the archive.
     * This method must be called to ensure the JAR is properly written.
     * 
     * @throws IOException if closing fails
     */
    @Override
    public void close() throws IOException;
}

Abstract JAR Writer

Base class providing comprehensive JAR writing functionality with extensibility points for custom implementations.

public abstract class AbstractJarWriter implements LoaderClassesWriter {
    /**
     * Write the manifest file to the JAR.
     * The manifest defines the main class, classpath, and other metadata.
     * 
     * @param manifest the manifest to write
     * @throws IOException if writing fails
     */
    public void writeManifest(Manifest manifest) throws IOException;
    
    /**
     * Write an entry from an input stream.
     * 
     * @param entryName the name/path of the entry within the JAR
     * @param inputStream the input stream containing entry data
     * @throws IOException if writing fails
     */
    public void writeEntry(String entryName, InputStream inputStream) throws IOException;
    
    /**
     * Write an entry using a custom entry writer.
     * 
     * @param entryName the name/path of the entry within the JAR
     * @param entryWriter the writer for entry content
     * @throws IOException if writing fails
     */
    public void writeEntry(String entryName, EntryWriter entryWriter) throws IOException;
    
    /**
     * Write a nested library JAR to the specified location.
     * Handles compression and ensures proper nested JAR structure.
     * 
     * @param location the location within the JAR where the library should be placed
     * @param library the library to write
     * @throws IOException if writing fails
     */
    public void writeNestedLibrary(String location, Library library) throws IOException;
    
    /**
     * Write an index file containing a list of entries.
     * Used for classpath indexes, layer indexes, and other metadata files.
     * 
     * @param location the location within the JAR for the index file
     * @param lines the lines to write to the index file
     * @throws IOException if writing fails
     */
    public void writeIndexFile(String location, Collection<String> lines) throws IOException;
    
    /**
     * Write default Spring Boot loader classes to the JAR.
     * These classes provide the runtime bootstrap functionality.
     * 
     * @throws IOException if writing fails
     */
    public void writeLoaderClasses() throws IOException;
    
    /**
     * Write specific loader implementation classes.
     * 
     * @param loaderImplementation the loader implementation to use
     * @throws IOException if writing fails
     */
    public void writeLoaderClasses(LoaderImplementation loaderImplementation) throws IOException;
    
    /**
     * Write loader classes from a specific JAR resource.
     * 
     * @param loaderJarResourceName the name of the loader JAR resource
     * @throws IOException if writing fails
     */
    public void writeLoaderClasses(String loaderJarResourceName) throws IOException;
}

Entry Writer Interface

Interface for writing custom entry content with optional size calculation.

public interface EntryWriter {
    /**
     * Write entry content to the output stream.
     * 
     * @param outputStream the output stream to write to
     * @throws IOException if writing fails
     */
    void write(OutputStream outputStream) throws IOException;
    
    /**
     * Get the size of the entry content.
     * Returns -1 if size is unknown or variable.
     * 
     * @return the entry size in bytes, or -1 if unknown
     */
    default int size() {
        return -1;
    }
}

Size Calculating Entry Writer

Entry writer implementation that calculates and tracks the size of written content.

public class SizeCalculatingEntryWriter implements EntryWriter {
    // Implementation calculates size during writing
    // Useful for progress tracking and validation
}

Loader Classes Writer

Interface for writing Spring Boot loader classes that provide runtime bootstrap functionality.

public interface LoaderClassesWriter {
    /**
     * Write default loader classes to the archive.
     * 
     * @throws IOException if writing fails
     */
    void writeLoaderClasses() throws IOException;
    
    /**
     * Write specific loader implementation classes.
     * 
     * @param loaderImplementation the loader implementation to write
     * @throws IOException if writing fails
     */
    void writeLoaderClasses(LoaderImplementation loaderImplementation) throws IOException;
    
    /**
     * Write loader classes from a named JAR resource.
     * 
     * @param loaderJarResourceName the loader JAR resource name
     * @throws IOException if writing fails
     */
    void writeLoaderClasses(String loaderJarResourceName) throws IOException;
    
    /**
     * Write a general entry to the archive.
     * 
     * @param name the entry name
     * @param inputStream the entry content
     * @throws IOException if writing fails
     */
    void writeEntry(String name, InputStream inputStream) throws IOException;
}

Usage Examples

Basic JAR Creation

import org.springframework.boot.loader.tools.JarWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.jar.Manifest;

// Create a simple executable JAR
File targetJar = new File("output/myapp.jar");
try (JarWriter writer = new JarWriter(targetJar)) {
    // Write manifest
    Manifest manifest = new Manifest();
    manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
    manifest.getMainAttributes().putValue("Main-Class", 
        "org.springframework.boot.loader.launch.JarLauncher");
    manifest.getMainAttributes().putValue("Start-Class", "com.example.Application");
    writer.writeManifest(manifest);
    
    // Write application classes
    File classesDir = new File("target/classes");
    writeDirectory(writer, classesDir, "BOOT-INF/classes/");
    
    // Write loader classes
    writer.writeLoaderClasses();
}

// Helper method to write directory contents
private void writeDirectory(JarWriter writer, File dir, String prefix) throws IOException {
    File[] files = dir.listFiles();
    if (files != null) {
        for (File file : files) {
            if (file.isDirectory()) {
                writeDirectory(writer, file, prefix + file.getName() + "/");
            } else {
                try (FileInputStream fis = new FileInputStream(file)) {
                    writer.writeEntry(prefix + file.getName(), fis);
                }
            }
        }
    }
}

JAR with Launch Script

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

// Create executable JAR with Unix launch script
File targetJar = new File("output/myapp.jar");
File scriptTemplate = new File("src/main/scripts/launch.sh");

// Configure launch script properties
Map<String, String> properties = Map.of(
    "initInfoProvides", "myapp",
    "initInfoShortDescription", "My Spring Boot Application",
    "confFolder", "/etc/myapp"
);

LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, properties);

try (JarWriter writer = new JarWriter(targetJar, launchScript)) {
    // Write JAR contents
    // ... (same as above)
}

// The resulting JAR can be executed directly:
// chmod +x myapp.jar
// ./myapp.jar

Advanced Entry Writing

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

File targetJar = new File("output/myapp.jar");
try (JarWriter writer = new JarWriter(targetJar)) {
    // Write custom properties file
    Properties props = new Properties();
    props.setProperty("app.version", "1.0.0");
    props.setProperty("build.time", "2024-01-15T10:30:00Z");
    
    writer.writeEntry("BOOT-INF/classes/application.properties", new EntryWriter() {
        @Override
        public void write(OutputStream outputStream) throws IOException {
            props.store(outputStream, "Application Properties");
        }
        
        @Override
        public int size() {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                props.store(baos, null);
                return baos.size();
            } catch (IOException e) {
                return -1;
            }
        }
    });
    
    // Write text content
    String configContent = "server.port=8080\nlogging.level.root=INFO";
    writer.writeEntry("BOOT-INF/classes/config.properties", outputStream -> {
        outputStream.write(configContent.getBytes(StandardCharsets.UTF_8));
    });
}

Library Integration

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

File targetJar = new File("output/myapp.jar");
try (JarWriter writer = new JarWriter(targetJar)) {
    // Write manifest and classes
    // ... (setup code)
    
    // Write nested libraries
    List<Library> libraries = List.of(
        new Library(new File("lib/spring-boot-3.2.0.jar"), LibraryScope.COMPILE),
        new Library(new File("lib/logback-classic-1.4.5.jar"), LibraryScope.RUNTIME)
    );
    
    Layout layout = new Layouts.Jar();
    for (Library library : libraries) {
        String location = layout.getLibraryLocation(library.getName(), library.getScope());
        writer.writeNestedLibrary(location + library.getName(), library);
    }
    
    // Write classpath index
    List<String> classpathEntries = libraries.stream()
        .map(lib -> layout.getLibraryLocation(lib.getName(), lib.getScope()) + lib.getName())
        .collect(Collectors.toList());
    
    writer.writeIndexFile(layout.getClasspathIndexFileLocation(), classpathEntries);
}

Custom AbstractJarWriter Implementation

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.IOException;
import java.util.jar.JarOutputStream;

// Custom JAR writer with additional functionality
public class CustomJarWriter extends AbstractJarWriter {
    private final JarOutputStream jarOutputStream;
    
    public CustomJarWriter(File file) throws IOException {
        // Initialize JAR output stream
        this.jarOutputStream = new JarOutputStream(new FileOutputStream(file));
    }
    
    @Override
    protected void writeToArchive(ZipEntry entry, EntryWriter entryWriter) throws IOException {
        // Custom implementation for writing entries
        jarOutputStream.putNextEntry(entry);
        if (entryWriter != null) {
            entryWriter.write(jarOutputStream);
        }
        jarOutputStream.closeEntry();
    }
    
    // Additional custom methods
    public void writeCompressedEntry(String name, byte[] data) throws IOException {
        ZipEntry entry = new ZipEntry(name);
        entry.setMethod(ZipEntry.DEFLATED);
        entry.setSize(data.length);
        
        writeToArchive(entry, outputStream -> outputStream.write(data));
    }
    
    public void writeMetadata(String name, Object metadata) throws IOException {
        // Custom metadata serialization
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(metadata);
        oos.close();
        
        writeEntry(name, outputStream -> outputStream.write(baos.toByteArray()));
    }
}

Progress Tracking

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.util.concurrent.atomic.AtomicLong;

// Track JAR writing progress
public class ProgressTrackingJarWriter extends AbstractJarWriter {
    private final AtomicLong totalBytes = new AtomicLong(0);
    private final AtomicLong writtenBytes = new AtomicLong(0);
    private final ProgressCallback callback;
    
    public ProgressTrackingJarWriter(File file, ProgressCallback callback) throws IOException {
        super(/* initialization */);
        this.callback = callback;
    }
    
    @Override
    public void writeEntry(String entryName, EntryWriter entryWriter) throws IOException {
        long entrySize = entryWriter.size();
        if (entrySize > 0) {
            totalBytes.addAndGet(entrySize);
        }
        
        // Wrap entry writer to track progress
        EntryWriter trackingWriter = new EntryWriter() {
            @Override
            public void write(OutputStream outputStream) throws IOException {
                OutputStream trackingStream = new OutputStream() {
                    @Override
                    public void write(int b) throws IOException {
                        outputStream.write(b);
                        writtenBytes.incrementAndGet();
                        updateProgress();
                    }
                    
                    @Override
                    public void write(byte[] b, int off, int len) throws IOException {
                        outputStream.write(b, off, len);
                        writtenBytes.addAndGet(len);
                        updateProgress();
                    }
                };
                entryWriter.write(trackingStream);
            }
            
            @Override
            public int size() {
                return entryWriter.size();
            }
        };
        
        super.writeEntry(entryName, trackingWriter);
    }
    
    private void updateProgress() {
        long total = totalBytes.get();
        long written = writtenBytes.get();
        if (total > 0) {
            double percentage = (double) written / total * 100;
            callback.updateProgress(percentage, written, total);
        }
    }
    
    @FunctionalInterface
    public interface ProgressCallback {
        void updateProgress(double percentage, long writtenBytes, long totalBytes);
    }
}

Integration with Repackager

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

// The repackager uses JAR writers internally
File sourceJar = new File("myapp.jar");
Repackager repackager = new Repackager(sourceJar);

// Configure repackager to use custom settings that affect JAR writing
repackager.setLayout(new Layouts.Jar());
repackager.setLoaderImplementation(LoaderImplementation.DEFAULT);

// The repackager will create its own JarWriter instance internally
repackager.repackage(Libraries.NONE);

// For more control, you can extend Repackager or use JarWriter directly

JAR Structure

The JAR writing system creates archives with the following typical structure:

myapp.jar
├── META-INF/
│   └── MANIFEST.MF                 # JAR manifest with main class
├── BOOT-INF/
│   ├── classes/                    # Application classes
│   │   └── com/example/...
│   ├── lib/                        # Dependency libraries
│   │   ├── spring-boot-3.2.0.jar
│   │   └── logback-classic-1.4.5.jar
│   ├── classpath.idx              # Classpath index (optional)
│   └── layers.idx                 # Layer index (optional)
└── org/springframework/boot/loader/ # Spring Boot loader classes
    └── launch/...

This structure enables the Spring Boot loader to bootstrap the application and load dependencies at runtime.

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