or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

admin-jmx.mdansi-support.mdaot-native-image.mdapplication-info.mdavailability.mdbootstrap.mdbootstrapping.mdbuilder.mdcloud-platform.mdconfiguration-annotations.mdconfiguration-data.mdconfiguration-properties.mdconversion.mddiagnostics.mdenvironment-property-sources.mdindex.mdjson-support.mdlifecycle-events.mdlogging.mdorigin-tracking.mdresource-loading.mdretry-support.mdssl-tls.mdstartup-metrics.mdsupport.mdsystem-utilities.mdtask-execution.mdthreading.mdutilities.mdvalidation.mdweb-support.md
tile.json

system-utilities.mddocs/

System Utilities

Package: org.springframework.boot.system, org.springframework.boot.util, org.springframework.boot.io, org.springframework.boot.thread

Utilities for accessing system-level information, process management, dependency injection, safe callback invocation, and threading model detection.

Quick Reference

ClassPurposeThread SafetyKey Use Cases
SystemPropertiesSafe system property/env var accessThread-safeCross-platform property lookup with fallbacks
ApplicationHomeApplication home directory detectionThread-safe (immutable)Locating config files, data directories
ApplicationPidProcess ID managementThread-safePID file writing, process monitoring
ApplicationTempApp-specific temp directoriesThread-safeCaching, temp file storage
JavaVersionJava version detection/comparisonThread-safe (enum)Runtime feature detection, version guards
ThreadingThreading model detectionThread-safe (enum)Virtual vs platform thread selection
InstantiatorDependency injection for pluginsNot thread-safePlugin systems, extension points
LambdaSafeSafe generic callback invocationThread-safeGeneric callbacks with type safety
ApplicationResourceLoaderEnhanced resource loadingThread-safeFile/classpath resource loading

System Properties Access

SystemProperties

Thread-safe utility for reading system properties with fallback to environment variables.

package org.springframework.boot.system;

/**
 * Safe access to system properties with environment variable fallback.
 * Thread-safe for concurrent access.
 *
 * @since 2.0.0
 */
public final class SystemProperties {

    /**
     * Return the value of the first found property or environment variable.
     * Checks System.getProperty() first, then System.getenv() for each name.
     *
     * Algorithm:
     * for each property name:
     *   - Try System.getProperty(name)
     *   - If null, try System.getenv(name)
     *   - If found, return immediately
     * return null if none found
     *
     * @param properties property names to check (in order)
     * @return the property value or null
     * @throws IllegalArgumentException if properties array is null or empty
     */
    public static String get(String... properties);
}

Usage Examples

Basic Property Lookup

import org.springframework.boot.system.SystemProperties;

public class PropertyReader {

    public void readProperties() {
        // Single property lookup
        String javaHome = SystemProperties.get("java.home");
        System.out.println("Java home: " + javaHome);

        // With fallback - tries multiple names
        String tmpDir = SystemProperties.get("TMP", "TEMP", "java.io.tmpdir");
        System.out.println("Temp directory: " + tmpDir);

        // Environment variable
        String path = SystemProperties.get("PATH");
        System.out.println("PATH: " + path);
    }
}

Cross-Platform Configuration

import org.springframework.boot.system.SystemProperties;

public class CrossPlatformConfig {

    public String getConfigDirectory() {
        // Works on Windows (APPDATA) and Unix (HOME + .config)
        String configBase = SystemProperties.get(
            "XDG_CONFIG_HOME",  // Linux standard
            "APPDATA",           // Windows
            "user.home"          // Fallback
        );

        if (configBase == null) {
            throw new IllegalStateException("Cannot determine config directory");
        }

        return configBase;
    }

    public String getDatabasePath() {
        // Custom property with standard fallback
        return SystemProperties.get(
            "APP_DB_PATH",           // Custom override
            "DATABASE_PATH",         // Alternative custom
            "user.home"              // Standard fallback
        ) + "/myapp.db";
    }
}

Error Handling

import org.springframework.boot.system.SystemProperties;

public class SafePropertyAccess {

    public void handleMissingProperties() {
        // Returns null for missing properties (no exception)
        String missing = SystemProperties.get("NONEXISTENT_PROPERTY");

        if (missing == null) {
            System.out.println("Property not found, using default");
            missing = "/default/path";
        }

        // Validate result
        String required = SystemProperties.get("REQUIRED_PROPERTY");
        if (required == null) {
            throw new IllegalStateException(
                "REQUIRED_PROPERTY must be set in environment"
            );
        }
    }
}

Thread Safety

  • Read operations: Fully thread-safe, delegates to System.getProperty() and System.getenv()
  • No caching: Reads fresh values on each call
  • Exception handling: Catches and logs SecurityException, never throws

Application Home Directory

ApplicationHome

Detects the application's home directory, suitable for locating configuration files and data relative to the application.

package org.springframework.boot.system;

import java.io.File;

/**
 * Provides access to application home directory.
 * Instances are immutable and thread-safe after construction.
 *
 * @since 2.0.0
 */
public class ApplicationHome {

    /**
     * Create ApplicationHome using manifest to detect source.
     * Uses the class from the manifest's Main-Class attribute.
     */
    public ApplicationHome();

    /**
     * Create ApplicationHome for the specified source class.
     *
     * @param sourceClass the source class (usually your main class)
     *                   or null to use manifest detection
     */
    public ApplicationHome(Class<?> sourceClass);

    /**
     * Return the underlying source used to find home directory.
     * This is usually the JAR file or directory containing the class.
     *
     * @return the source File or null if cannot be determined
     */
    public File getSource();

    /**
     * Return the application home directory.
     * For JARs: The directory containing the JAR
     * For exploded apps: The root directory of the application
     *
     * @return the home directory (never null)
     */
    public File getDir();
}

Usage Examples

Basic Home Detection

import org.springframework.boot.system.ApplicationHome;
import java.io.File;

public class HomeDetection {

    public void detectHome() {
        // Detect home for current class
        ApplicationHome home = new ApplicationHome(HomeDetection.class);

        File source = home.getSource();  // JAR file or class directory
        File homeDir = home.getDir();    // Parent directory

        System.out.println("Running from: " + source);
        System.out.println("Home directory: " + homeDir);

        // source might be: /opt/myapp/myapp.jar
        // homeDir would be: /opt/myapp
    }

    public void detectDefaultHome() {
        // Auto-detect using Main-Class from manifest
        ApplicationHome home = new ApplicationHome();
        System.out.println("Application home: " + home.getDir());
    }
}

Configuration File Loading

import org.springframework.boot.system.ApplicationHome;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class ConfigLoader {

    private final ApplicationHome home;

    public ConfigLoader() {
        this.home = new ApplicationHome(ConfigLoader.class);
    }

    public Properties loadConfig(String configFileName) throws IOException {
        // Look for config file next to application JAR
        File configFile = new File(home.getDir(), configFileName);

        if (!configFile.exists()) {
            throw new IOException("Config file not found: " + configFile);
        }

        Properties props = new Properties();
        try (FileInputStream in = new FileInputStream(configFile)) {
            props.load(in);
        }

        return props;
    }

    public File getDataDirectory() {
        // Create data directory relative to application
        File dataDir = new File(home.getDir(), "data");
        dataDir.mkdirs();
        return dataDir;
    }

    public File getLogsDirectory() {
        // Create logs directory relative to application
        File logsDir = new File(home.getDir(), "logs");
        logsDir.mkdirs();
        return logsDir;
    }
}

Installation Directory Structure

import org.springframework.boot.system.ApplicationHome;
import java.io.File;

public class InstallationManager {

    private final ApplicationHome home;

    public InstallationManager() {
        this.home = new ApplicationHome(InstallationManager.class);
    }

    public void setupInstallation() {
        File appHome = home.getDir();

        // Create standard directory structure
        new File(appHome, "config").mkdirs();
        new File(appHome, "data").mkdirs();
        new File(appHome, "logs").mkdirs();
        new File(appHome, "plugins").mkdirs();
        new File(appHome, "backups").mkdirs();

        System.out.println("Installation setup complete at: " + appHome);
    }

    public void verifyInstallation() {
        File appHome = home.getDir();

        String[] requiredDirs = {"config", "data", "logs"};
        for (String dir : requiredDirs) {
            File directory = new File(appHome, dir);
            if (!directory.exists() || !directory.isDirectory()) {
                throw new IllegalStateException(
                    "Required directory missing: " + directory
                );
            }
        }

        System.out.println("Installation verified");
    }
}

Thread Safety

  • Immutable after construction: Thread-safe for concurrent reads
  • No synchronization needed: All fields set in constructor
  • File operations: Use standard File I/O which is thread-safe

Process ID Management

ApplicationPid

Represents the application's process ID with file writing capability.

package org.springframework.boot.system;

import java.io.File;
import java.io.IOException;

/**
 * Application process ID.
 * Thread-safe for all operations.
 *
 * @since 2.0.0
 */
public class ApplicationPid {

    /**
     * Create ApplicationPid for the current process.
     * Attempts to determine PID from ProcessHandle or ManagementFactory.
     */
    public ApplicationPid();

    /**
     * Return if PID is available.
     * May return false in some environments (e.g., certain containers).
     *
     * @return true if PID is available
     * @since 3.4.0
     */
    public boolean isAvailable();

    /**
     * Return the PID as a Long.
     *
     * @return the PID or null if not available
     * @since 3.4.0
     */
    public Long toLong();

    /**
     * Write PID to the specified file.
     * Creates parent directories if needed.
     * File is overwritten if it exists.
     *
     * @param file the PID file
     * @throws IllegalStateException if PID is not available
     * @throws IOException if file cannot be written
     */
    public void write(File file) throws IOException;

    /**
     * Return PID as string.
     * @return PID string or "" if not available
     */
    @Override
    public String toString();

    /**
     * Compare PIDs for equality.
     * @param obj object to compare
     * @return true if PIDs are equal
     */
    @Override
    public boolean equals(Object obj);

    /**
     * Hash code based on PID value.
     * @return hash code
     */
    @Override
    public int hashCode();
}

Usage Examples

Basic PID Operations

import org.springframework.boot.system.ApplicationPid;
import java.io.File;
import java.io.IOException;

public class PidOperations {

    public void displayPid() {
        ApplicationPid pid = new ApplicationPid();

        if (pid.isAvailable()) {
            System.out.println("Process ID: " + pid.toLong());
            System.out.println("PID as string: " + pid.toString());
        } else {
            System.out.println("PID not available in this environment");
        }
    }

    public void writePidFile() throws IOException {
        ApplicationPid pid = new ApplicationPid();

        if (!pid.isAvailable()) {
            System.out.println("Cannot write PID file - PID not available");
            return;
        }

        File pidFile = new File("/var/run/myapp.pid");

        // Create parent directory if needed
        pidFile.getParentFile().mkdirs();

        // Write PID
        pid.write(pidFile);

        System.out.println("PID " + pid.toLong() + " written to " + pidFile);
    }
}

Production PID File Management

import org.springframework.boot.system.ApplicationPid;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class PidFileManager {

    private final File pidFile;
    private final ApplicationPid pid;

    public PidFileManager(String pidFilePath) {
        this.pidFile = new File(pidFilePath);
        this.pid = new ApplicationPid();
    }

    public void createPidFile() throws IOException {
        if (!pid.isAvailable()) {
            throw new IllegalStateException("PID not available");
        }

        // Check for stale PID file
        if (pidFile.exists()) {
            handleExistingPidFile();
        }

        // Write new PID file
        pidFile.getParentFile().mkdirs();
        pid.write(pidFile);

        // Make sure PID file is deleted on exit
        pidFile.deleteOnExit();

        System.out.println("PID file created: " + pidFile);
    }

    private void handleExistingPidFile() throws IOException {
        // Read existing PID
        String existingPid = Files.readString(pidFile.toPath()).trim();

        // Check if process is still running
        if (isProcessRunning(existingPid)) {
            throw new IllegalStateException(
                "Another instance is running with PID: " + existingPid
            );
        }

        // Delete stale PID file
        System.out.println("Removing stale PID file");
        pidFile.delete();
    }

    private boolean isProcessRunning(String pidString) {
        try {
            long pid = Long.parseLong(pidString);
            ProcessHandle process = ProcessHandle.of(pid).orElse(null);
            return process != null && process.isAlive();
        } catch (NumberFormatException e) {
            return false;
        }
    }

    public void deletePidFile() {
        if (pidFile.exists()) {
            if (pidFile.delete()) {
                System.out.println("PID file deleted");
            } else {
                System.err.println("Failed to delete PID file: " + pidFile);
            }
        }
    }
}

Spring Boot Integration

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.ApplicationPidFileWriter;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class DaemonApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(DaemonApplication.class);

        // Automatically write PID file on startup
        app.addListeners(new ApplicationPidFileWriter("/var/run/myapp.pid"));

        // Alternative: specify via property
        // spring.pid.file=/var/run/myapp.pid

        app.run(args);
    }
}

@Component
class StartupLogger {

    @EventListener(ApplicationReadyEvent.class)
    public void onReady() {
        ApplicationPid pid = new ApplicationPid();
        System.out.println("Application ready, PID: " + pid.toLong());
    }
}

Thread Safety

  • All operations thread-safe: PID value is immutable after detection
  • File writing: Synchronized internally, safe for concurrent calls
  • Lazy initialization: PID detected once on first access

ApplicationPidFileWriter

An ApplicationListener that automatically saves the application's process ID to a file when the application starts. Designed for production deployments where external monitoring systems need to track the application's PID.

Class Definition

package org.springframework.boot.context;

import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import java.io.File;

/**
 * ApplicationListener that saves application PID into a file. Triggered exactly once
 * per JVM. The file name can be overridden via:
 * - System property or environment variable: PIDFILE (or pidfile)
 * - Spring property: spring.pid.file
 *
 * By default, failures to write PID file are logged but don't stop startup. To fail
 * on write errors, set:
 * - System property or environment variable: PID_FAIL_ON_WRITE_ERROR
 * - Spring property: spring.pid.fail-on-write-error=true
 *
 * Note: Access to Spring Environment requires trigger event to be one of:
 * ApplicationEnvironmentPreparedEvent, ApplicationReadyEvent, or ApplicationPreparedEvent.
 *
 * @since 2.0.0
 */
public class ApplicationPidFileWriter
        implements ApplicationListener<SpringApplicationEvent>, Ordered {

    /**
     * Create a new ApplicationPidFileWriter using the default filename 'application.pid'.
     */
    public ApplicationPidFileWriter();

    /**
     * Create a new ApplicationPidFileWriter with a specified filename.
     *
     * @param filename the name of file to write PID to
     */
    public ApplicationPidFileWriter(String filename);

    /**
     * Create a new ApplicationPidFileWriter with a specified file.
     *
     * @param file the file to write PID to
     */
    public ApplicationPidFileWriter(File file);

    /**
     * Sets the type of application event that triggers writing of the PID file.
     * Defaults to ApplicationPreparedEvent.
     *
     * NOTE: If you use ApplicationStartingEvent to trigger the write, you will not
     * be able to specify the PID filename via Spring Environment properties.
     *
     * @param triggerEventType the event type that triggers PID file writing
     */
    public void setTriggerEventType(Class<? extends SpringApplicationEvent> triggerEventType);

    /**
     * Set the order value. Default is HIGHEST_PRECEDENCE + 13.
     *
     * @param order the order value
     */
    public void setOrder(int order);

    /**
     * Get the order value.
     *
     * @return the order value
     */
    @Override
    public int getOrder();

    @Override
    public void onApplicationEvent(SpringApplicationEvent event);
}

Basic Usage

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.ApplicationPidFileWriter;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);

        // Write PID to default location: ./application.pid
        app.addListeners(new ApplicationPidFileWriter());

        // Or specify custom location
        // app.addListeners(new ApplicationPidFileWriter("/var/run/myapp.pid"));

        app.run(args);
    }
}

Configuration via Properties

# application.properties

# Specify PID file location
spring.pid.file=/var/run/myapp.pid

# Fail if PID file cannot be written (default: false)
spring.pid.fail-on-write-error=true
# application.yml
spring:
  pid:
    file: /var/run/myapp.pid
    fail-on-write-error: true

Configuration via Environment Variables

# Override PID file location via environment variable
export PIDFILE=/var/run/myapp.pid

# Or lowercase variant
export pidfile=/var/run/myapp.pid

# Fail on write error
export PID_FAIL_ON_WRITE_ERROR=true

Advanced Configuration

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.ApplicationPidFileWriter;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;

@SpringBootApplication
public class AdvancedPidApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(AdvancedPidApplication.class);

        // Example 1: Write PID after application is fully ready
        ApplicationPidFileWriter pidWriter = new ApplicationPidFileWriter("/var/run/myapp.pid");
        pidWriter.setTriggerEventType(ApplicationReadyEvent.class);
        app.addListeners(pidWriter);

        // Example 2: Write PID early with custom order
        ApplicationPidFileWriter earlyPidWriter = new ApplicationPidFileWriter();
        earlyPidWriter.setTriggerEventType(ApplicationEnvironmentPreparedEvent.class);
        earlyPidWriter.setOrder(100); // Custom ordering
        app.addListeners(earlyPidWriter);

        app.run(args);
    }
}

Production Deployment Example

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.ApplicationPidFileWriter;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@SpringBootApplication
public class ProductionApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ProductionApplication.class);

        // PID file for systemd/init.d management
        String pidFilePath = System.getenv("PID_FILE");
        if (pidFilePath == null) {
            pidFilePath = "/var/run/myapp/application.pid";
        }

        // Ensure PID directory exists
        try {
            File pidFile = new File(pidFilePath);
            if (pidFile.getParentFile() != null) {
                Files.createDirectories(pidFile.getParentFile().toPath());
            }
        } catch (IOException e) {
            System.err.println("Failed to create PID directory: " + e.getMessage());
        }

        // Configure PID writer with failure handling
        ApplicationPidFileWriter pidWriter = new ApplicationPidFileWriter(pidFilePath);
        pidWriter.setTriggerEventType(ApplicationReadyEvent.class);
        app.addListeners(pidWriter);

        app.run(args);
    }
}

@Component
class PidMonitor {

    @EventListener(ApplicationReadyEvent.class)
    public void logPidFileLocation() {
        String pidFilePath = System.getProperty("spring.pid.file",
                                                System.getenv("PIDFILE"));
        if (pidFilePath == null) {
            pidFilePath = "./application.pid";
        }

        File pidFile = new File(pidFilePath);
        if (pidFile.exists()) {
            try {
                String pidContent = Files.readString(pidFile.toPath());
                System.out.println("PID file created at: " + pidFile.getAbsolutePath());
                System.out.println("Process ID: " + pidContent);
            } catch (IOException e) {
                System.err.println("Failed to read PID file: " + e.getMessage());
            }
        } else {
            System.out.println("PID file not found at expected location: " + pidFile.getAbsolutePath());
        }
    }
}

Systemd Integration Example

Create a systemd service file that uses the PID file:

# /etc/systemd/system/myapp.service
[Unit]
Description=My Spring Boot Application
After=network.target

[Service]
Type=forking
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java -jar /opt/myapp/application.jar
PIDFile=/var/run/myapp/application.pid
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Spring Boot application configuration:

# application-prod.yml
spring:
  pid:
    file: /var/run/myapp/application.pid
    fail-on-write-error: true

Docker/Container Integration

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.ApplicationPidFileWriter;

@SpringBootApplication
public class ContainerApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ContainerApplication.class);

        // In containers, PID 1 is typically the application itself
        // Write PID to shared volume for health monitoring
        String pidFile = System.getenv("PID_FILE_PATH");
        if (pidFile == null) {
            pidFile = "/app/data/application.pid";
        }

        app.addListeners(new ApplicationPidFileWriter(pidFile));

        app.run(args);
    }
}

Dockerfile:

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/myapp.jar /app/application.jar

# Create directory for PID file
RUN mkdir -p /app/data

# Set environment variable for PID file location
ENV PID_FILE_PATH=/app/data/application.pid

ENTRYPOINT ["java", "-jar", "/app/application.jar"]

Trigger Event Types

Different trigger events provide different levels of access to Spring components:

Trigger EventSpring Environment AccessApplication Context AccessWhen Fired
ApplicationStartingEvent❌ No❌ NoVery early - before anything
ApplicationEnvironmentPreparedEvent✅ Yes❌ NoAfter Environment prepared
ApplicationPreparedEvent (default)✅ Yes✅ Yes (not refreshed)Before context refresh
ApplicationReadyEvent✅ Yes✅ Yes (fully ready)After application is ready

Recommendation: Use ApplicationReadyEvent for production to ensure PID file is written only after successful startup.

Property Precedence

Property sources are consulted in the following order (first match wins):

  1. System property: spring.pid.file
  2. System property: spring.pidfile
  3. Environment variable: PIDFILE
  4. Environment variable: pidfile
  5. Constructor parameter (if provided)
  6. Default: ./application.pid

Important Notes

  1. One Write Per JVM: The PID file is written exactly once per JVM, even if multiple ApplicationPidFileWriter instances are registered.

  2. Automatic Cleanup: The PID file is marked for deletion on JVM exit using File.deleteOnExit().

  3. Thread Safety: The writer is thread-safe and uses atomic operations to ensure single execution.

  4. Failure Handling: By default, failures to write the PID file are logged but do not stop application startup. Set spring.pid.fail-on-write-error=true to make startup fail on PID write errors.

  5. Container Considerations: In containerized environments, PID 1 is typically the main process. Ensure the PID file directory is writable and consider using a volume mount for persistence.

  6. File Permissions: Ensure the application has write permissions to the PID file directory. On Linux, /var/run typically requires elevated privileges.

Common Use Cases

  1. Process Management: Allow init systems (systemd, supervisord) to track and manage the application process
  2. Health Monitoring: External monitoring tools can verify the process is running
  3. Graceful Shutdown: Send signals (SIGTERM, SIGKILL) to the correct process
  4. Duplicate Prevention: Check if another instance is already running
  5. Log Correlation: Associate log files with specific application instances

Thread Safety

  • Atomic execution: Uses AtomicBoolean to ensure PID file is written exactly once
  • Thread-safe listener: Safe to register from any thread
  • Concurrent safe: Multiple ApplicationPidFileWriter instances are safe but only one will write

Application Temporary Directory

ApplicationTemp

Provides isolated temporary directory per application with subdirectory support.

package org.springframework.boot.system;

import java.io.File;

/**
 * Application-specific temporary directory.
 * Different applications get different locations.
 * Same application gets same location across restarts.
 * Thread-safe for all operations.
 *
 * @since 2.0.0
 */
public class ApplicationTemp {

    /**
     * Create ApplicationTemp using manifest detection.
     */
    public ApplicationTemp();

    /**
     * Create ApplicationTemp for the specified source class.
     *
     * @param sourceClass the source class or null
     */
    public ApplicationTemp(Class<?> sourceClass);

    /**
     * Return the application temp directory.
     * Directory is created if it doesn't exist.
     *
     * @return the application temp directory (never null)
     */
    public File getDir();

    /**
     * Return a subdirectory of the application temp.
     * Subdirectory is created if it doesn't exist.
     *
     * @param subDir the subdirectory name
     * @return a subdirectory
     * @throws IllegalArgumentException if subDir is null
     */
    public File getDir(String subDir);
}

Usage Examples

Basic Temp Directory Usage

import org.springframework.boot.system.ApplicationTemp;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class TempFileOperations {

    private final ApplicationTemp temp;

    public TempFileOperations() {
        this.temp = new ApplicationTemp(TempFileOperations.class);
    }

    public void useTempDirectory() {
        // Get main temp directory
        File tempDir = temp.getDir();
        System.out.println("Temp directory: " + tempDir);

        // Get subdirectories (created automatically)
        File cacheDir = temp.getDir("cache");
        File logsDir = temp.getDir("logs");
        File workDir = temp.getDir("work");

        System.out.println("Cache: " + cacheDir);
        System.out.println("Logs: " + logsDir);
        System.out.println("Work: " + workDir);
    }

    public File writeTempFile(String filename, String content) throws IOException {
        File tempFile = new File(temp.getDir(), filename);

        try (FileWriter writer = new FileWriter(tempFile)) {
            writer.write(content);
        }

        System.out.println("Wrote temp file: " + tempFile);
        return tempFile;
    }
}

Caching Implementation

import org.springframework.boot.system.ApplicationTemp;
import java.io.*;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.Base64;

public class FileCache {

    private final File cacheDir;

    public FileCache() {
        ApplicationTemp temp = new ApplicationTemp(FileCache.class);
        this.cacheDir = temp.getDir("cache");
    }

    public void cache(String key, byte[] data) throws IOException {
        File cacheFile = getCacheFile(key);
        Files.write(cacheFile.toPath(), data);
        System.out.println("Cached: " + key);
    }

    public byte[] get(String key) throws IOException {
        File cacheFile = getCacheFile(key);

        if (!cacheFile.exists()) {
            return null;
        }

        // Check if cache is expired (1 hour)
        long age = System.currentTimeMillis() - cacheFile.lastModified();
        if (age > 3600000) {
            cacheFile.delete();
            return null;
        }

        return Files.readAllBytes(cacheFile.toPath());
    }

    public void clear() {
        File[] files = cacheDir.listFiles();
        if (files != null) {
            for (File file : files) {
                file.delete();
            }
        }
        System.out.println("Cache cleared");
    }

    private File getCacheFile(String key) {
        // Hash key to create safe filename
        String hash = hashKey(key);
        return new File(cacheDir, hash + ".cache");
    }

    private String hashKey(String key) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hash = md.digest(key.getBytes());
            return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
        } catch (Exception e) {
            throw new RuntimeException("Failed to hash key", e);
        }
    }
}

Production Temp Management

import org.springframework.boot.system.ApplicationTemp;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class TempManager {

    private final ApplicationTemp temp;

    public TempManager() {
        this.temp = new ApplicationTemp(TempManager.class);
    }

    public void cleanupOldFiles(int maxAgeDays) throws IOException {
        File tempDir = temp.getDir();
        Instant cutoff = Instant.now().minus(maxAgeDays, ChronoUnit.DAYS);

        cleanDirectory(tempDir, cutoff);
    }

    private void cleanDirectory(File dir, Instant cutoff) throws IOException {
        File[] files = dir.listFiles();
        if (files == null) {
            return;
        }

        for (File file : files) {
            if (file.isDirectory()) {
                cleanDirectory(file, cutoff);
                // Remove directory if empty
                if (file.list().length == 0) {
                    file.delete();
                }
            } else {
                FileTime modified = Files.getLastModifiedTime(file.toPath());
                if (modified.toInstant().isBefore(cutoff)) {
                    Files.delete(file.toPath());
                    System.out.println("Deleted old file: " + file.getName());
                }
            }
        }
    }

    public void reportDiskUsage() {
        File tempDir = temp.getDir();
        long totalSize = calculateDirectorySize(tempDir);

        System.out.println("Temp directory: " + tempDir);
        System.out.println("Total size: " + formatSize(totalSize));
        System.out.println("Free space: " + formatSize(tempDir.getFreeSpace()));
    }

    private long calculateDirectorySize(File dir) {
        long size = 0;
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    size += calculateDirectorySize(file);
                } else {
                    size += file.length();
                }
            }
        }
        return size;
    }

    private String formatSize(long bytes) {
        if (bytes < 1024) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(1024));
        char unit = "KMGTPE".charAt(exp - 1);
        return String.format("%.2f %sB", bytes / Math.pow(1024, exp), unit);
    }
}

Thread Safety

  • Directory creation: Synchronized internally, safe for concurrent access
  • Subdirectory creation: Thread-safe, idempotent
  • File operations: Use standard File I/O which is thread-safe

How Temp Directory is Generated

Location determined by hashing:

  • Application home directory
  • Source JAR location
  • System properties (user.dir, java.home, java.class.path)

This ensures:

  • Different applications get different directories
  • Same application gets same directory across restarts
  • Temp files persist between restarts (useful for caching)

Typical location: ${java.io.tmpdir}/spring-boot-temp-[hash]/

Java Version Detection

JavaVersion

Enum for runtime Java version detection and comparison.

package org.springframework.boot.system;

/**
 * Known Java versions.
 * Thread-safe enum.
 *
 * @since 2.0.0
 */
public enum JavaVersion {

    SEVENTEEN("17", "Console.charset()"),
    EIGHTEEN("18", "Duration.isPositive()"),
    NINETEEN("19", "Future.state()"),
    TWENTY("20", "Class.accessFlags()"),
    TWENTY_ONE("21", "SortedSet.getFirst()"),
    TWENTY_TWO("22", "Console.isTerminal()"),
    TWENTY_THREE("23", "NumberFormat.isStrict()"),
    TWENTY_FOUR("24", "Reader.of(CharSequence)"),
    TWENTY_FIVE("25", "Reader.readAllLines()");

    /**
     * Return the JavaVersion of the current runtime.
     * Detects version by checking for version-specific methods.
     *
     * @return the JavaVersion
     * @throws IllegalStateException if version cannot be determined
     *         or is older than Java 17
     */
    public static JavaVersion getJavaVersion();

    /**
     * Return if this version is equal to or newer than given version.
     *
     * @param version the version to compare
     * @return true if this >= version
     */
    public boolean isEqualOrNewerThan(JavaVersion version);

    /**
     * Return if this version is older than given version.
     *
     * @param version the version to compare
     * @return true if this < version
     */
    public boolean isOlderThan(JavaVersion version);

    /**
     * Return the version string (e.g., "21").
     *
     * @return the version string
     */
    @Override
    public String toString();
}

Usage Examples

Version Detection

import org.springframework.boot.system.JavaVersion;

public class VersionDetection {

    public void detectVersion() {
        JavaVersion version = JavaVersion.getJavaVersion();

        System.out.println("Running on Java " + version);
        System.out.println("Version string: " + version.toString());

        // Simple comparisons
        if (version.isEqualOrNewerThan(JavaVersion.TWENTY_ONE)) {
            System.out.println("Java 21+ features available");
        }

        if (version.isOlderThan(JavaVersion.SEVENTEEN)) {
            throw new IllegalStateException("Java 17 or newer required");
        }
    }
}

Feature Detection

import org.springframework.boot.system.JavaVersion;

public class FeatureDetection {

    private final JavaVersion version;

    public FeatureDetection() {
        this.version = JavaVersion.getJavaVersion();
    }

    public boolean hasVirtualThreads() {
        return version.isEqualOrNewerThan(JavaVersion.TWENTY_ONE);
    }

    public boolean hasSequencedCollections() {
        return version.isEqualOrNewerThan(JavaVersion.TWENTY_ONE);
    }

    public boolean hasRecordPatterns() {
        return version.isEqualOrNewerThan(JavaVersion.TWENTY_ONE);
    }

    public void enableFeatures() {
        if (hasVirtualThreads()) {
            System.out.println("Enabling virtual threads");
            enableVirtualThreads();
        }

        if (hasSequencedCollections()) {
            System.out.println("Using sequenced collections");
            useSequencedCollections();
        }

        if (version.isEqualOrNewerThan(JavaVersion.TWENTY_TWO)) {
            System.out.println("Enabling Java 22+ features");
            enableJava22Features();
        }
    }

    private void enableVirtualThreads() { /* implementation */ }
    private void useSequencedCollections() { /* implementation */ }
    private void enableJava22Features() { /* implementation */ }
}

Version-Dependent Configuration

import org.springframework.boot.system.JavaVersion;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor;

@Configuration
public class ExecutorConfiguration {

    @Bean
    public AsyncTaskExecutor asyncTaskExecutor() {
        JavaVersion version = JavaVersion.getJavaVersion();

        if (version.isEqualOrNewerThan(JavaVersion.TWENTY_ONE)) {
            // Use virtual threads on Java 21+
            System.out.println("Using virtual thread executor");
            SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
            executor.setVirtualThreads(true);
            executor.setConcurrencyLimit(1000);
            return executor;
        } else {
            // Use thread pool on older versions
            System.out.println("Using thread pool executor");
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setQueueCapacity(500);
            executor.setThreadNamePrefix("async-");
            executor.initialize();
            return executor;
        }
    }

    @Bean
    public FeatureFlags featureFlags() {
        JavaVersion version = JavaVersion.getJavaVersion();

        FeatureFlags flags = new FeatureFlags();
        flags.setVirtualThreadsEnabled(
            version.isEqualOrNewerThan(JavaVersion.TWENTY_ONE)
        );
        flags.setRecordPatternsEnabled(
            version.isEqualOrNewerThan(JavaVersion.TWENTY_ONE)
        );

        return flags;
    }
}

class FeatureFlags {
    private boolean virtualThreadsEnabled;
    private boolean recordPatternsEnabled;

    // Getters and setters
    public boolean isVirtualThreadsEnabled() { return virtualThreadsEnabled; }
    public void setVirtualThreadsEnabled(boolean enabled) { this.virtualThreadsEnabled = enabled; }

    public boolean isRecordPatternsEnabled() { return recordPatternsEnabled; }
    public void setRecordPatternsEnabled(boolean enabled) { this.recordPatternsEnabled = enabled; }
}

Version Detection Mechanism

JavaVersion detects the version by checking for version-specific methods:

  • Java 17: Console.charset() method exists
  • Java 18: Duration.isPositive() method exists
  • Java 19: Future.state() method exists
  • Java 20: Class.accessFlags() method exists
  • Java 21: SortedSet.getFirst() method exists
  • Java 22: Console.isTerminal() method exists
  • Java 23: NumberFormat.isStrict() method exists
  • Java 24: Reader.of(CharSequence) method exists
  • Java 25: Reader.readAllLines() method exists

This approach:

  • Reliable: Doesn't depend on parsing version strings
  • No reflection needed: Uses method existence checks
  • Future-proof: New versions can be added

Thread Safety

  • Enum is thread-safe: All instances are singletons
  • Detection is cached: Version detected once on class load
  • No mutable state: All operations are read-only

Threading Model Detection

Threading

Enum for detecting and configuring platform vs virtual threading.

package org.springframework.boot.thread;

import org.springframework.core.env.Environment;

/**
 * Application threading model.
 * Thread-safe enum.
 *
 * @since 3.2.0
 */
public enum Threading {

    /**
     * Platform threads (traditional).
     * Active when virtual threads are not active.
     */
    PLATFORM {
        @Override
        public boolean isActive(Environment environment) {
            return !VIRTUAL.isActive(environment);
        }
    },

    /**
     * Virtual threads (Java 21+).
     * Active when:
     * - spring.threads.virtual.enabled=true
     * - Running on Java 21 or later
     */
    VIRTUAL {
        @Override
        public boolean isActive(Environment environment) {
            if (!JavaVersion.getJavaVersion()
                            .isEqualOrNewerThan(JavaVersion.TWENTY_ONE)) {
                return false;
            }
            return environment.getProperty(
                "spring.threads.virtual.enabled",
                Boolean.class,
                false
            );
        }
    };

    /**
     * Determine if this threading model is active.
     *
     * @param environment the Spring environment
     * @return true if this threading model is active
     */
    public abstract boolean isActive(Environment environment);
}

Usage Examples

Basic Threading Detection

import org.springframework.boot.thread.Threading;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ThreadingDetector {

    @Autowired
    private Environment environment;

    public void detectThreadingMode() {
        boolean virtualEnabled = Threading.VIRTUAL.isActive(environment);
        boolean platformActive = Threading.PLATFORM.isActive(environment);

        System.out.println("Virtual threads: " + virtualEnabled);
        System.out.println("Platform threads: " + platformActive);

        if (virtualEnabled) {
            System.out.println("Application will use virtual threads");
        } else {
            System.out.println("Application will use platform threads");
        }
    }
}

Conditional Executor Configuration

import org.springframework.boot.thread.Threading;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor;

@Configuration
public class ThreadingConfiguration {

    @Bean
    public AsyncTaskExecutor taskExecutor(Environment environment) {
        if (Threading.VIRTUAL.isActive(environment)) {
            return createVirtualThreadExecutor();
        } else {
            return createPlatformThreadExecutor();
        }
    }

    private AsyncTaskExecutor createVirtualThreadExecutor() {
        System.out.println("Creating virtual thread executor");

        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        executor.setVirtualThreads(true);
        executor.setConcurrencyLimit(1000);  // High limit for virtual threads
        executor.setThreadNamePrefix("vt-");

        return executor;
    }

    private AsyncTaskExecutor createPlatformThreadExecutor() {
        System.out.println("Creating platform thread executor");

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("pt-");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();

        return executor;
    }
}

Feature Flags Based on Threading

import org.springframework.boot.thread.Threading;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class ThreadingFeatures {

    private final boolean virtualThreadsActive;

    public ThreadingFeatures(Environment environment) {
        this.virtualThreadsActive = Threading.VIRTUAL.isActive(environment);
    }

    public int getOptimalConcurrencyLimit() {
        // Virtual threads support much higher concurrency
        return virtualThreadsActive ? 10000 : 100;
    }

    public String getThreadingStrategy() {
        return virtualThreadsActive
            ? "high-concurrency-io"
            : "moderate-concurrency-balanced";
    }

    public void logThreadingInfo() {
        System.out.println("Threading mode: " +
            (virtualThreadsActive ? "VIRTUAL" : "PLATFORM"));
        System.out.println("Optimal concurrency: " +
            getOptimalConcurrencyLimit());
        System.out.println("Strategy: " + getThreadingStrategy());
    }
}

Configuration

Enable virtual threads via application.properties:

# Enable virtual threads (requires Java 21+)
spring.threads.virtual.enabled=true

Or application.yml:

spring:
  threads:
    virtual:
      enabled: true

Detection Algorithm

Threading.VIRTUAL.isActive() returns true if:

  1. Java version is 21 or newer (JavaVersion.TWENTY_ONE+)
  2. AND spring.threads.virtual.enabled property is true

Otherwise, Threading.PLATFORM is active (the default).

Thread Safety

  • Enum is thread-safe: All instances are singletons
  • isActive() is thread-safe: Reads from thread-safe Environment
  • No mutable state: All operations are read-only

Common Patterns

Application Directory Structure Setup

import org.springframework.boot.system.ApplicationHome;
import org.springframework.boot.system.ApplicationTemp;
import org.springframework.boot.system.ApplicationPid;
import java.io.File;
import java.io.IOException;

public class ApplicationSetup {

    private final ApplicationHome home;
    private final ApplicationTemp temp;

    public ApplicationSetup() {
        this.home = new ApplicationHome(ApplicationSetup.class);
        this.temp = new ApplicationTemp(ApplicationSetup.class);
    }

    public void initialize() throws IOException {
        setupDirectoryStructure();
        writePidFile();
        logConfiguration();
    }

    private void setupDirectoryStructure() {
        File appHome = home.getDir();

        // Create standard directories
        new File(appHome, "config").mkdirs();
        new File(appHome, "data").mkdirs();
        new File(appHome, "logs").mkdirs();

        // Create temp subdirectories
        temp.getDir("cache");
        temp.getDir("work");

        System.out.println("Directory structure created");
    }

    private void writePidFile() throws IOException {
        ApplicationPid pid = new ApplicationPid();
        if (pid.isAvailable()) {
            File pidFile = new File(home.getDir(), "application.pid");
            pid.write(pidFile);
            System.out.println("PID file written: " + pidFile);
        }
    }

    private void logConfiguration() {
        System.out.println("Application home: " + home.getDir());
        System.out.println("Temp directory: " + temp.getDir());
        System.out.println("Configuration complete");
    }

    public File getConfigFile(String name) {
        return new File(new File(home.getDir(), "config"), name);
    }

    public File getDataFile(String name) {
        return new File(new File(home.getDir(), "data"), name);
    }

    public File getCacheFile(String name) {
        return new File(temp.getDir("cache"), name);
    }
}

Cross-Platform Configuration Loading

import org.springframework.boot.system.SystemProperties;
import org.springframework.boot.system.ApplicationHome;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class CrossPlatformConfig {

    public Properties loadConfiguration() throws IOException {
        // Try multiple config locations
        File configFile = findConfigFile();

        if (configFile == null || !configFile.exists()) {
            throw new IOException("Configuration file not found");
        }

        Properties props = new Properties();
        try (FileInputStream in = new FileInputStream(configFile)) {
            props.load(in);
        }

        return props;
    }

    private File findConfigFile() {
        // 1. Try environment variable
        String configPath = SystemProperties.get("APP_CONFIG_FILE");
        if (configPath != null) {
            return new File(configPath);
        }

        // 2. Try next to application
        ApplicationHome home = new ApplicationHome(CrossPlatformConfig.class);
        File appConfig = new File(home.getDir(), "config/application.properties");
        if (appConfig.exists()) {
            return appConfig;
        }

        // 3. Try user home directory
        String userHome = SystemProperties.get("user.home");
        if (userHome != null) {
            File userConfig = new File(userHome, ".myapp/application.properties");
            if (userConfig.exists()) {
                return userConfig;
            }
        }

        // 4. Try system config directory
        String configDir = SystemProperties.get(
            "XDG_CONFIG_HOME",  // Linux
            "APPDATA"            // Windows
        );
        if (configDir != null) {
            return new File(configDir, "myapp/application.properties");
        }

        return null;
    }
}

Version-Dependent Feature Enablement

import org.springframework.boot.system.JavaVersion;
import org.springframework.boot.thread.Threading;
import org.springframework.core.env.Environment;

public class FeatureManager {

    private final JavaVersion javaVersion;
    private final Environment environment;

    public FeatureManager(Environment environment) {
        this.javaVersion = JavaVersion.getJavaVersion();
        this.environment = environment;
    }

    public void enableFeatures() {
        // Java 21+ features
        if (javaVersion.isEqualOrNewerThan(JavaVersion.TWENTY_ONE)) {
            enableVirtualThreads();
            enableSequencedCollections();
            enableRecordPatterns();
        }

        // Java 22+ features
        if (javaVersion.isEqualOrNewerThan(JavaVersion.TWENTY_TWO)) {
            enableUnnamedVariables();
            enableStatementsBeforeSuper();
        }

        // Threading configuration
        if (Threading.VIRTUAL.isActive(environment)) {
            configureForVirtualThreads();
        } else {
            configureForPlatformThreads();
        }
    }

    private void enableVirtualThreads() {
        System.out.println("Virtual threads enabled");
        // Configure virtual thread pools
    }

    private void enableSequencedCollections() {
        System.out.println("Sequenced collections enabled");
        // Use sequenced collection APIs
    }

    private void enableRecordPatterns() {
        System.out.println("Record patterns enabled");
        // Use record pattern matching
    }

    private void enableUnnamedVariables() {
        System.out.println("Unnamed variables enabled");
    }

    private void enableStatementsBeforeSuper() {
        System.out.println("Statements before super enabled");
    }

    private void configureForVirtualThreads() {
        System.out.println("Configuring for virtual threads");
        // High concurrency limits
    }

    private void configureForPlatformThreads() {
        System.out.println("Configuring for platform threads");
        // Conservative concurrency limits
    }
}

Best Practices

  1. Use ApplicationHome for relative paths - More portable than hard-coded paths
  2. Write PID files for daemon processes - Essential for process management
  3. Use ApplicationTemp for persistent caching - Survives restarts, app-specific
  4. Check JavaVersion for feature detection - More reliable than parsing version strings
  5. Detect Threading mode for optimal configuration - Virtual threads need different limits
  6. Use SystemProperties for cross-platform config - Handles both properties and env vars
  7. Clean up old temp files periodically - Prevent disk space issues
  8. Validate PID file before overwriting - Prevent starting duplicate instances
  9. Log directory locations at startup - Helps with debugging
  10. Handle missing PID gracefully - Some environments don't provide PID

Thread Safety Summary

ClassThread SafetyNotes
SystemPropertiesThread-safeDelegates to System methods
ApplicationHomeThread-safeImmutable after construction
ApplicationPidThread-safeLazy initialization, synchronized writes
ApplicationTempThread-safeSynchronized directory creation
JavaVersionThread-safeEnum singleton
ThreadingThread-safeEnum singleton, reads Environment
InstantiatorNot thread-safeCreate per-thread if needed
LambdaSafeThread-safeStateless operations
ApplicationResourceLoaderThread-safeDelegates to thread-safe ResourceLoader

Common Pitfalls

1. ApplicationPid.write() Not Handling Absence of PID

Problem: Calling write() when PID is not available causes exception.

// WRONG - no availability check
ApplicationPid pid = new ApplicationPid();
pid.write(new File("/var/run/app.pid"));  // May throw IllegalStateException

Error:

IllegalStateException: Unable to determine application PID

Solution: Check availability before writing:

// CORRECT - check before writing
ApplicationPid pid = new ApplicationPid();
if (pid.isAvailable()) {
    pid.write(new File("/var/run/app.pid"));
} else {
    log.warn("PID not available, skipping PID file creation");
}

2. ApplicationTemp Directory Not Cleaned Up

Problem: ApplicationTemp creates directories but never cleans them.

// Creates temp directories but they persist forever
ApplicationTemp temp = new ApplicationTemp();
File cacheDir = temp.getDir("cache");
// Creates /tmp/spring-boot-app-xyz/cache
// Never deleted!

Solution: Add cleanup on shutdown:

@Component
public class TempCleaner {
    private final ApplicationTemp temp = new ApplicationTemp();

    @PreDestroy
    public void cleanup() throws IOException {
        Path tempDir = temp.getDir().toPath();
        if (Files.exists(tempDir)) {
            Files.walk(tempDir)
                .sorted(Comparator.reverseOrder())
                .map(Path::toFile)
                .forEach(File::delete);
        }
    }
}

3. ApplicationHome Returning Unexpected Directory

Problem: ApplicationHome may return unexpected locations based on execution context.

// May not be what you expect
ApplicationHome home = new ApplicationHome(MyClass.class);
File homeDir = home.getDir();
// Could be: JAR directory, class directory, or current directory

Solution: Validate and document assumptions:

ApplicationHome home = new ApplicationHome(MyClass.class);
File homeDir = home.getDir();

// Log actual location for debugging
log.info("Application home: {}", homeDir.getAbsolutePath());

// Validate it's writable if needed
if (!homeDir.canWrite()) {
    throw new IllegalStateException("Home directory not writable: " + homeDir);
}

4. JavaVersion Comparison with Wrong Direction

Problem: Using comparison methods backwards.

// WRONG - logic inverted
if (JavaVersion.getJavaVersion().isOlderThan(JavaVersion.SEVENTEEN)) {
    // Use new features - WRONG! This means < Java 17
    useVirtualThreads();
}

Solution: Use correct comparison direction:

// CORRECT
if (JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_ONE)) {
    // Safe to use Java 21+ features
    useVirtualThreads();
} else {
    // Use platform threads for older versions
    usePlatformThreads();
}

5. Instantiator Not Finding Constructor Parameters

Problem: Instantiator can't inject dependencies because parameters aren't registered.

// Plugin requires Environment
public class MyPlugin {
    public MyPlugin(Environment env, DataSource ds) {
        // Constructor needs both
    }
}

// WRONG - DataSource not registered
Instantiator<MyPlugin> instantiator = new Instantiator<>(
    MyPlugin.class,
    params -> {
        params.add(Environment.class, environment);
        // Missing: DataSource!
    }
);

// Fails to instantiate

Error:

NoSuchMethodException: No suitable constructor found

Solution: Register all required dependencies:

// CORRECT - all dependencies registered
Instantiator<MyPlugin> instantiator = new Instantiator<>(
    MyPlugin.class,
    params -> {
        params.add(Environment.class, environment);
        params.add(DataSource.class, dataSource);  // Add all dependencies
    }
);

6. LambdaSafe Swallowing Exceptions Silently

Problem: Type mismatches in callbacks fail silently without logging.

// Callback expects String
Consumer<String> callback = (s) -> process(s);

// WRONG - passing Integer instead of String
LambdaSafe.callback(Consumer.class, callback, 42)
    .invoke(c -> c.accept(42));  // Silently skipped due to type mismatch!

Solution: Add logger to see why callbacks are skipped:

// CORRECT - with logging
LambdaSafe.callback(Consumer.class, callback, "test")
    .withLogger(MyClass.class)  // Logs type mismatches at DEBUG level
    .invoke(c -> c.accept("test"));

7. SystemProperties Fallback Order Confusion

Problem: Not understanding that it checks each property for both sources before moving to next.

// Checks: System.getProperty("JAVA_HOME"), then System.getenv("JAVA_HOME")
// Then: System.getProperty("java.home"), then System.getenv("java.home")
String javaHome = SystemProperties.get("JAVA_HOME", "java.home");

Misunderstanding: Thinking it checks all system properties first, then all environment variables.

Actual behavior: Alternates between property and env for each name.

Solution: Understand the order:

// Correct understanding:
// 1. System.getProperty("JAVA_HOME")
// 2. System.getenv("JAVA_HOME")
// 3. System.getProperty("java.home")  // <-- Only checked if steps 1-2 return null
// 4. System.getenv("java.home")
String javaHome = SystemProperties.get("JAVA_HOME", "java.home");

8. Threading.VIRTUAL.isActive() Always False

Problem: Virtual threads not enabled even on Java 21+.

// Java 21, but returns false
boolean virtualEnabled = Threading.VIRTUAL.isActive(environment);
// false - why?

Cause: Property not set in environment.

Solution: Enable virtual threads explicitly:

# application.properties
spring.threads.virtual.enabled=true

Or programmatically:

@Configuration
public class ThreadingConfig {
    @Bean
    public TaskExecutor taskExecutor(Environment environment) {
        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();

        // Check if virtual threads should be enabled
        boolean virtualEnabled = Threading.VIRTUAL.isActive(environment);
        executor.setVirtualThreads(virtualEnabled);

        return executor;
    }
}

9. ApplicationResourceLoader Not Following File Resolution Preference

Problem: Expecting classpath but getting file system (or vice versa).

ResourceLoader loader = ApplicationResourceLoader.get();

// WRONG expectation - thinks this loads from classpath
Resource resource = loader.getResource("config.yml");
// Actually loads from file system: ./config.yml

Solution: Be explicit about resource location:

ResourceLoader loader = ApplicationResourceLoader.get();

// Explicit classpath
Resource classpathResource = loader.getResource("classpath:config.yml");

// Explicit file system
Resource fileResource = loader.getResource("file:./config.yml");

// Explicit URL
Resource urlResource = loader.getResource("https://example.com/config.yml");

10. Sharing Instantiator Across Threads

Problem: Instantiator is not thread-safe but used concurrently.

// WRONG - shared across threads
private static final Instantiator<MyPlugin> instantiator = new Instantiator<>(
    MyPlugin.class,
    params -> params.add(Environment.class, environment)
);

public void loadPluginsInParallel(List<String> pluginNames) {
    pluginNames.parallelStream()
        .map(name -> instantiator.instantiate(name))  // UNSAFE!
        .collect(Collectors.toList());
}

Solution: Create new Instantiator per thread or synchronize:

// Option 1: Create per-thread
public void loadPluginsInParallel(List<String> pluginNames) {
    pluginNames.parallelStream()
        .map(name -> {
            Instantiator<MyPlugin> instantiator = new Instantiator<>(
                MyPlugin.class,
                params -> params.add(Environment.class, environment)
            );
            return instantiator.instantiate(name);
        })
        .collect(Collectors.toList());
}

// Option 2: Synchronize access
public synchronized MyPlugin instantiatePlugin(String name) {
    return instantiator.instantiate(name);
}

See Also

  • Application Bootstrapping - SpringApplication configuration
  • Configuration Properties - Type-safe configuration
  • Task Execution - Thread pool configuration