docs
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.
| Class | Purpose | Thread Safety | Key Use Cases |
|---|---|---|---|
SystemProperties | Safe system property/env var access | Thread-safe | Cross-platform property lookup with fallbacks |
ApplicationHome | Application home directory detection | Thread-safe (immutable) | Locating config files, data directories |
ApplicationPid | Process ID management | Thread-safe | PID file writing, process monitoring |
ApplicationTemp | App-specific temp directories | Thread-safe | Caching, temp file storage |
JavaVersion | Java version detection/comparison | Thread-safe (enum) | Runtime feature detection, version guards |
Threading | Threading model detection | Thread-safe (enum) | Virtual vs platform thread selection |
Instantiator | Dependency injection for plugins | Not thread-safe | Plugin systems, extension points |
LambdaSafe | Safe generic callback invocation | Thread-safe | Generic callbacks with type safety |
ApplicationResourceLoader | Enhanced resource loading | Thread-safe | File/classpath resource loading |
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);
}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);
}
}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";
}
}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"
);
}
}
}System.getProperty() and System.getenv()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();
}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());
}
}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;
}
}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");
}
}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();
}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);
}
}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);
}
}
}
}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());
}
}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.
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);
}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);
}
}# 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# 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=trueimport 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);
}
}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());
}
}
}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.targetSpring Boot application configuration:
# application-prod.yml
spring:
pid:
file: /var/run/myapp/application.pid
fail-on-write-error: trueimport 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"]Different trigger events provide different levels of access to Spring components:
| Trigger Event | Spring Environment Access | Application Context Access | When Fired |
|---|---|---|---|
ApplicationStartingEvent | ❌ No | ❌ No | Very early - before anything |
ApplicationEnvironmentPreparedEvent | ✅ Yes | ❌ No | After 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 sources are consulted in the following order (first match wins):
spring.pid.filespring.pidfilePIDFILEpidfile./application.pidOne Write Per JVM: The PID file is written exactly once per JVM, even if multiple ApplicationPidFileWriter instances are registered.
Automatic Cleanup: The PID file is marked for deletion on JVM exit using File.deleteOnExit().
Thread Safety: The writer is thread-safe and uses atomic operations to ensure single execution.
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.
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.
File Permissions: Ensure the application has write permissions to the PID file directory. On Linux, /var/run typically requires elevated privileges.
AtomicBoolean to ensure PID file is written exactly onceProvides 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);
}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;
}
}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);
}
}
}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);
}
}Location determined by hashing:
This ensures:
Typical location: ${java.io.tmpdir}/spring-boot-temp-[hash]/
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();
}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");
}
}
}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 */ }
}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; }
}JavaVersion detects the version by checking for version-specific methods:
Console.charset() method existsDuration.isPositive() method existsFuture.state() method existsClass.accessFlags() method existsSortedSet.getFirst() method existsConsole.isTerminal() method existsNumberFormat.isStrict() method existsReader.of(CharSequence) method existsReader.readAllLines() method existsThis approach:
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);
}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");
}
}
}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;
}
}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());
}
}Enable virtual threads via application.properties:
# Enable virtual threads (requires Java 21+)
spring.threads.virtual.enabled=trueOr application.yml:
spring:
threads:
virtual:
enabled: trueThreading.VIRTUAL.isActive() returns true if:
JavaVersion.TWENTY_ONE+)spring.threads.virtual.enabled property is trueOtherwise, Threading.PLATFORM is active (the default).
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);
}
}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;
}
}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
}
}| Class | Thread Safety | Notes |
|---|---|---|
SystemProperties | Thread-safe | Delegates to System methods |
ApplicationHome | Thread-safe | Immutable after construction |
ApplicationPid | Thread-safe | Lazy initialization, synchronized writes |
ApplicationTemp | Thread-safe | Synchronized directory creation |
JavaVersion | Thread-safe | Enum singleton |
Threading | Thread-safe | Enum singleton, reads Environment |
Instantiator | Not thread-safe | Create per-thread if needed |
LambdaSafe | Thread-safe | Stateless operations |
ApplicationResourceLoader | Thread-safe | Delegates to thread-safe ResourceLoader |
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 IllegalStateExceptionError:
IllegalStateException: Unable to determine application PIDSolution: 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");
}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);
}
}
}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 directorySolution: 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);
}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();
}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 instantiateError:
NoSuchMethodException: No suitable constructor foundSolution: 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
}
);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"));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");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=trueOr 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;
}
}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.ymlSolution: 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");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);
}