Tools for generating executable JAR/WAR files with embedded containers for Spring Boot applications
—
Utilities for integrating with build tools and generating build metadata for Spring Boot Actuator endpoints. The build integration system provides seamless integration with Maven, Gradle, and other build systems while generating runtime metadata for monitoring and management.
Utility class for generating build-info.properties files consumed by Spring Boot Actuator's info endpoint.
public final class BuildPropertiesWriter {
/**
* Create a build properties writer for the specified output file.
*
* @param outputFile the file where build properties will be written
*/
public BuildPropertiesWriter(File outputFile);
/**
* Write build properties for the given project details.
* Creates a properties file compatible with Spring Boot Actuator's info endpoint.
*
* @param projectDetails the project details to write
* @throws IOException if writing fails
*/
public void writeBuildProperties(ProjectDetails projectDetails) throws IOException;
/**
* Project details containing build metadata.
*/
public static final class ProjectDetails {
// Contains project information like name, version, build time, etc.
// Used to generate build-info.properties for actuator endpoints
}
/**
* Exception thrown when an additional property has a null value.
*/
public static class NullAdditionalPropertyValueException extends IllegalArgumentException {
public NullAdditionalPropertyValueException(String name);
}
}Utility for locating and working with Java executables in the runtime environment.
public class JavaExecutable {
// Provides access to the Java executable used to run the current process
// Useful for spawning Java processes with consistent JVM configuration
}Utility for running external processes, commonly used for build tool integration and system commands.
public class RunProcess {
/**
* Run an external process with the specified arguments.
*
* @param waitForProcess whether to wait for the process to complete
* @param args the command line arguments
* @return the process exit code (if waitForProcess is true)
* @throws IOException if process execution fails
*/
public int run(boolean waitForProcess, String... args) throws IOException;
}import org.springframework.boot.loader.tools.BuildPropertiesWriter;
import org.springframework.boot.loader.tools.BuildPropertiesWriter.ProjectDetails;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.util.Properties;
// Create build properties for actuator endpoint
File outputFile = new File("target/classes/META-INF/build-info.properties");
BuildPropertiesWriter writer = new BuildPropertiesWriter(outputFile);
// Create project details
ProjectDetails details = new ProjectDetails() {{
setGroup("com.example");
setArtifact("myapp");
setName("My Application");
setVersion("1.0.0");
setTime(Instant.now());
// Add custom build information
getAdditional().put("build.user", System.getProperty("user.name"));
getAdditional().put("build.java.version", System.getProperty("java.version"));
getAdditional().put("build.os", System.getProperty("os.name"));
}};
try {
writer.writeBuildProperties(details);
System.out.println("Build properties written to: " + outputFile.getAbsolutePath());
} catch (IOException e) {
System.err.println("Failed to write build properties: " + e.getMessage());
}
// The generated file will be available at /actuator/info endpoint:
// {
// "build": {
// "group": "com.example",
// "artifact": "myapp",
// "name": "My Application",
// "version": "1.0.0",
// "time": "2024-01-15T10:30:00.000Z",
// "build.user": "developer",
// "build.java.version": "17.0.1",
// "build.os": "Linux"
// }
// }import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.util.Properties;
// Maven plugin integration example
public class MavenBuildPropertiesGenerator {
public void generateBuildProperties(MavenProject project, File outputFile) throws IOException {
BuildPropertiesWriter writer = new BuildPropertiesWriter(outputFile);
ProjectDetails details = new ProjectDetails();
details.setGroup(project.getGroupId());
details.setArtifact(project.getArtifactId());
details.setName(project.getName());
details.setVersion(project.getVersion());
details.setTime(Instant.now());
// Add Maven-specific properties
Properties additional = details.getAdditional();
additional.put("build.maven.version", getMavenVersion());
additional.put("build.java.target", project.getProperties().getProperty("maven.compiler.target"));
additional.put("build.java.source", project.getProperties().getProperty("maven.compiler.source"));
// Add SCM information if available
if (project.getScm() != null) {
additional.put("build.scm.url", project.getScm().getUrl());
additional.put("build.scm.connection", project.getScm().getConnection());
}
// Add CI/CD information
String buildNumber = System.getenv("BUILD_NUMBER");
if (buildNumber != null) {
additional.put("build.number", buildNumber);
}
String gitCommit = System.getenv("GIT_COMMIT");
if (gitCommit != null) {
additional.put("build.git.commit", gitCommit);
}
writer.writeBuildProperties(details);
}
private String getMavenVersion() {
// Implementation to get Maven version
return "3.8.1";
}
}import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.util.Properties;
// Gradle plugin integration example
public class GradleBuildPropertiesGenerator {
public void generateBuildProperties(String group, String name, String version,
File outputFile, Properties gradleProperties) throws IOException {
BuildPropertiesWriter writer = new BuildPropertiesWriter(outputFile);
ProjectDetails details = new ProjectDetails();
details.setGroup(group);
details.setArtifact(name);
details.setName(name);
details.setVersion(version);
details.setTime(Instant.now());
// Add Gradle-specific properties
Properties additional = details.getAdditional();
additional.put("build.gradle.version", getGradleVersion());
// Add project properties
gradleProperties.forEach((key, value) -> {
if (key.toString().startsWith("build.")) {
additional.put(key.toString(), value.toString());
}
});
// Add system information
additional.put("build.java.vendor", System.getProperty("java.vendor"));
additional.put("build.java.vm.name", System.getProperty("java.vm.name"));
writer.writeBuildProperties(details);
}
private String getGradleVersion() {
// Implementation to get Gradle version
return "7.6";
}
}import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
// Generate comprehensive build information
public class ComprehensiveBuildInfo {
public void generateBuildInfo(String group, String artifact, String version,
File outputFile) throws IOException {
BuildPropertiesWriter writer = new BuildPropertiesWriter(outputFile);
ProjectDetails details = new ProjectDetails();
details.setGroup(group);
details.setArtifact(artifact);
details.setName(artifact);
details.setVersion(version);
details.setTime(Instant.now());
Properties additional = details.getAdditional();
// Build environment
additional.put("build.user", System.getProperty("user.name"));
additional.put("build.host", getHostname());
additional.put("build.time.formatted",
DateTimeFormatter.ISO_LOCAL_DATE_TIME
.withZone(ZoneId.systemDefault())
.format(Instant.now()));
// Java environment
additional.put("build.java.version", System.getProperty("java.version"));
additional.put("build.java.vendor", System.getProperty("java.vendor"));
additional.put("build.java.home", System.getProperty("java.home"));
// Operating system
additional.put("build.os.name", System.getProperty("os.name"));
additional.put("build.os.version", System.getProperty("os.version"));
additional.put("build.os.arch", System.getProperty("os.arch"));
// Runtime information
Runtime runtime = Runtime.getRuntime();
additional.put("build.runtime.processors", String.valueOf(runtime.availableProcessors()));
additional.put("build.runtime.max.memory", String.valueOf(runtime.maxMemory()));
// Custom application properties
additional.put("build.profile", System.getProperty("spring.profiles.active", "default"));
additional.put("build.type", "executable-jar");
try {
writer.writeBuildProperties(details);
System.out.println("Comprehensive build info written to: " + outputFile);
} catch (BuildPropertiesWriter.NullAdditionalPropertyValueException e) {
System.err.println("Null value detected for property: " + e.getMessage());
throw e;
}
}
private String getHostname() {
try {
return java.net.InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
return "unknown";
}
}
}import org.springframework.boot.loader.tools.RunProcess;
import java.io.IOException;
import java.util.Arrays;
// Execute build commands and capture results
public class BuildToolRunner {
private final RunProcess processRunner = new RunProcess();
public void runMavenBuild(String... goals) throws IOException {
String[] command = new String[goals.length + 1];
command[0] = "mvn";
System.arraycopy(goals, 0, command, 1, goals.length);
System.out.println("Executing: " + Arrays.toString(command));
int exitCode = processRunner.run(true, command);
if (exitCode != 0) {
throw new IOException("Maven build failed with exit code: " + exitCode);
}
System.out.println("Maven build completed successfully");
}
public void runGradleBuild(String... tasks) throws IOException {
String[] command = new String[tasks.length + 1];
command[0] = "./gradlew";
System.arraycopy(tasks, 0, command, 1, tasks.length);
System.out.println("Executing: " + Arrays.toString(command));
int exitCode = processRunner.run(true, command);
if (exitCode != 0) {
throw new IOException("Gradle build failed with exit code: " + exitCode);
}
System.out.println("Gradle build completed successfully");
}
public void runGitCommands() throws IOException {
// Get git information for build metadata
int exitCode;
// Get current commit hash
exitCode = processRunner.run(true, "git", "rev-parse", "HEAD");
if (exitCode != 0) {
System.out.println("Warning: Could not get git commit hash");
}
// Get current branch
exitCode = processRunner.run(true, "git", "rev-parse", "--abbrev-ref", "HEAD");
if (exitCode != 0) {
System.out.println("Warning: Could not get git branch");
}
// Check if working directory is clean
exitCode = processRunner.run(true, "git", "diff-index", "--quiet", "HEAD", "--");
if (exitCode != 0) {
System.out.println("Warning: Working directory has uncommitted changes");
}
}
}
// Usage
BuildToolRunner runner = new BuildToolRunner();
try {
runner.runGitCommands();
runner.runMavenBuild("clean", "compile", "test");
// or
// runner.runGradleBuild("clean", "build");
} catch (IOException e) {
System.err.println("Build process failed: " + e.getMessage());
}import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.util.Properties;
// Generate build properties specifically for Spring Boot Actuator
public class ActuatorBuildInfoGenerator {
public void generateForActuator(ProjectDetails projectDetails, File outputFile) throws IOException {
// Ensure output directory exists
outputFile.getParentFile().mkdirs();
BuildPropertiesWriter writer = new BuildPropertiesWriter(outputFile);
// Add actuator-specific properties
Properties additional = projectDetails.getAdditional();
// Health check information
additional.put("build.health.check.url", "/actuator/health");
additional.put("build.info.url", "/actuator/info");
additional.put("build.metrics.url", "/actuator/metrics");
// Build pipeline information
String pipelineId = System.getenv("CI_PIPELINE_ID");
if (pipelineId != null) {
additional.put("build.pipeline.id", pipelineId);
}
String buildUrl = System.getenv("BUILD_URL");
if (buildUrl != null) {
additional.put("build.url", buildUrl);
}
// Application configuration
additional.put("build.spring.boot.version", getSpringBootVersion());
additional.put("build.spring.version", getSpringVersion());
writer.writeBuildProperties(projectDetails);
System.out.println("Actuator build info generated: " + outputFile.getAbsolutePath());
System.out.println("Available at: /actuator/info");
}
private String getSpringBootVersion() {
// Get Spring Boot version from classpath
Package pkg = org.springframework.boot.SpringBootVersion.class.getPackage();
return pkg != null ? pkg.getImplementationVersion() : "unknown";
}
private String getSpringVersion() {
// Get Spring Framework version
Package pkg = org.springframework.core.SpringVersion.class.getPackage();
return pkg != null ? pkg.getImplementationVersion() : "unknown";
}
}import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
// Validate generated build properties
public class BuildPropertiesValidator {
public void validateBuildProperties(File buildPropertiesFile) throws IOException {
if (!buildPropertiesFile.exists()) {
throw new IOException("Build properties file not found: " + buildPropertiesFile);
}
Properties props = new Properties();
try (FileInputStream fis = new FileInputStream(buildPropertiesFile)) {
props.load(fis);
}
// Validate required properties
validateRequired(props, "build.group", "Build group is required");
validateRequired(props, "build.artifact", "Build artifact is required");
validateRequired(props, "build.version", "Build version is required");
validateRequired(props, "build.time", "Build time is required");
// Validate property formats
validateVersion(props.getProperty("build.version"));
validateTime(props.getProperty("build.time"));
System.out.println("Build properties validation passed");
// Print summary
System.out.println("Build Properties Summary:");
System.out.println("========================");
props.forEach((key, value) -> {
if (key.toString().startsWith("build.")) {
System.out.println(key + " = " + value);
}
});
}
private void validateRequired(Properties props, String key, String message) throws IOException {
if (!props.containsKey(key) || props.getProperty(key).trim().isEmpty()) {
throw new IOException(message + ": " + key);
}
}
private void validateVersion(String version) throws IOException {
if (version == null || !version.matches("\\d+\\.\\d+\\.\\d+.*")) {
throw new IOException("Invalid version format: " + version);
}
}
private void validateTime(String time) throws IOException {
try {
Instant.parse(time);
} catch (Exception e) {
throw new IOException("Invalid build time format: " + time, e);
}
}
}<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<build.user>${user.name}</build.user>
<build.java.target>${maven.compiler.target}</build.java.target>
<build.maven.version>${maven.version}</build.maven.version>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>springBoot {
buildInfo {
properties {
additional = [
'build.user': System.getProperty('user.name'),
'build.java.target': project.targetCompatibility,
'build.gradle.version': gradle.gradleVersion
]
}
}
}The build integration system provides comprehensive support for capturing and exposing build metadata, enabling better application monitoring, debugging, and operational visibility through Spring Boot's Actuator endpoints.
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-loader-tools