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

application-info.mddocs/

Application Information

Quick Reference

This documentation has been enhanced for AI coding agents with comprehensive examples, complete API signatures, thread safety notes, error handling patterns, and production-ready usage patterns.

Package: org.springframework.boot.info Module: org.springframework.boot:spring-boot Since: 1.4.0

Application information support for accessing build properties, Git commit information, and Java runtime details at runtime. Essential for operations, troubleshooting, and displaying version information in applications.

Overview

Spring Boot's info system provides structured access to:

  • Build Information: Version, name, time, group, artifact from Maven/Gradle builds
  • Git Information: Commit ID, branch, commit time, and detailed Git metadata
  • Java Information: Runtime version, vendor, JVM, and runtime environment details
  • Process Information: Process ID, CPU count, memory usage, virtual threads information
  • OS Information: Operating system name, version, and architecture
  • SSL Information: Certificate details, validity status, and SSL bundle information

Build and Git info objects are automatically configured when relevant property files are present in the classpath. Java, Process, and OS info are available as beans. SSL info requires explicit configuration with SslBundles.

Core Components

BuildProperties

Provides access to application build properties from META-INF/build-info.properties.

package org.springframework.boot.info;

import java.time.Instant;
import java.util.Properties;

/**
 * Provide build-related information such as group, artifact, name, version and time.
 * Information is loaded from META-INF/build-info.properties generated by build plugins.
 *
 * Thread Safety: Immutable and thread-safe after construction.
 *
 * @since 1.4.0
 */
public class BuildProperties extends InfoProperties {

    /**
     * Create an instance with the specified content.
     *
     * @param entries the build properties (typically from build-info.properties)
     */
    public BuildProperties(Properties entries) {
        super(entries);
    }

    /**
     * Return the groupId of the project or null.
     *
     * @return the groupId (e.g., "com.example")
     */
    public @Nullable String getGroup() {
        return get("group");
    }

    /**
     * Return the artifactId of the project or null.
     *
     * @return the artifactId (e.g., "my-application")
     */
    public @Nullable String getArtifact() {
        return get("artifact");
    }

    /**
     * Return the name of the project or null.
     *
     * @return the name (e.g., "My Application")
     */
    public @Nullable String getName() {
        return get("name");
    }

    /**
     * Return the version of the project or null.
     *
     * @return the version (e.g., "1.0.0-SNAPSHOT")
     */
    public @Nullable String getVersion() {
        return get("version");
    }

    /**
     * Return the timestamp of the build or null.
     *
     * @return the build time as Instant
     */
    public @Nullable Instant getTime() {
        return getInstant("time");
    }
}

Generated build-info.properties:

build.group=com.example
build.artifact=my-application
build.name=My Application
build.version=1.0.0-SNAPSHOT
build.time=2025-01-15T10:30:00Z

GitProperties

Provides access to Git commit information from git.properties.

package org.springframework.boot.info;

import java.time.Instant;
import java.util.Properties;

/**
 * Provide git-related information such as commit id and time.
 * Information is loaded from git.properties generated by git-commit-id-plugin.
 *
 * Thread Safety: Immutable and thread-safe after construction.
 *
 * @since 1.4.0
 */
public class GitProperties extends InfoProperties {

    /**
     * Create an instance with the specified content.
     *
     * @param entries the git properties (typically from git.properties)
     */
    public GitProperties(Properties entries) {
        super(entries);
    }

    /**
     * Return the name of the branch or null.
     *
     * @return the branch name (e.g., "main", "develop")
     */
    public @Nullable String getBranch() {
        return get("branch");
    }

    /**
     * Return the full commit id or null.
     *
     * @return the full commit SHA (e.g., "a1b2c3d4e5f6...")
     */
    public @Nullable String getCommitId() {
        return get("commit.id");
    }

    /**
     * Return the abbreviated commit id or null.
     *
     * @return the short commit SHA (e.g., "a1b2c3d")
     */
    public @Nullable String getShortCommitId() {
        String shortId = get("commit.id.abbrev");
        if (shortId != null) {
            return shortId;
        }
        String id = getCommitId();
        if (id == null) {
            return null;
        }
        return (id.length() > 7) ? id.substring(0, 7) : id;
    }

    /**
     * Return the commit time or null.
     *
     * @return the commit timestamp as Instant
     */
    public @Nullable Instant getCommitTime() {
        return getInstant("commit.time");
    }
}

Generated git.properties:

git.branch=main
git.commit.id=a1b2c3d4e5f6789012345678901234567890abcd
git.commit.id.abbrev=a1b2c3d
git.commit.time=2025-01-15T09:45:30Z
git.build.time=2025-01-15T10:30:00Z
git.commit.message.short=Fix bug in user service
git.commit.user.name=John Doe
git.commit.user.email=john.doe@example.com
git.dirty=false
git.tags=v1.0.0

JavaInfo

Provides Java runtime information including version, vendor, runtime environment, and JVM details.

package org.springframework.boot.info;

/**
 * Information about the Java environment the application is running in.
 *
 * Thread Safety: Immutable and thread-safe.
 *
 * @since 2.6.0
 */
public class JavaInfo {

    private final String version;
    private final JavaVendorInfo vendor;
    private final JavaRuntimeEnvironmentInfo runtime;
    private final JavaVirtualMachineInfo jvm;

    /**
     * Create an instance from system properties.
     * Automatically extracts Java version, vendor info, runtime info, and JVM info.
     */
    public JavaInfo() {
        this.version = System.getProperty("java.version");
        this.vendor = new JavaVendorInfo();
        this.runtime = new JavaRuntimeEnvironmentInfo();
        this.jvm = new JavaVirtualMachineInfo();
    }

    /**
     * Return the version of the JVM or null.
     *
     * @return the version (e.g., "17.0.1", "21.0.2")
     */
    public String getVersion() {
        return this.version;
    }

    /**
     * Return vendor information for the Java Runtime.
     *
     * @return the vendor info containing name and version
     */
    public JavaVendorInfo getVendor() {
        return this.vendor;
    }

    /**
     * Return Java Runtime Environment information.
     *
     * @return the runtime environment info
     */
    public JavaRuntimeEnvironmentInfo getRuntime() {
        return this.runtime;
    }

    /**
     * Return Java Virtual Machine information.
     *
     * @return the JVM info
     */
    public JavaVirtualMachineInfo getJvm() {
        return this.jvm;
    }

    /**
     * Information about the Java Vendor of the Java Runtime the application
     * is running in.
     *
     * @since 2.7.0
     */
    public static class JavaVendorInfo {

        private final String name;
        private final String version;

        public JavaVendorInfo() {
            this.name = System.getProperty("java.vendor");
            this.version = System.getProperty("java.vendor.version");
        }

        /**
         * Return the vendor name or null.
         *
         * @return the vendor name (e.g., "Oracle Corporation", "Eclipse Adoptium")
         */
        public String getName() {
            return this.name;
        }

        /**
         * Return the vendor version or null.
         *
         * @return the vendor version
         */
        public String getVersion() {
            return this.version;
        }
    }

    /**
     * Information about the Java Runtime Environment the application is running in.
     */
    public static class JavaRuntimeEnvironmentInfo {

        private final String name;
        private final String version;

        public JavaRuntimeEnvironmentInfo() {
            this.name = System.getProperty("java.runtime.name");
            this.version = System.getProperty("java.runtime.version");
        }

        /**
         * Return the runtime name or null.
         *
         * @return the runtime name (e.g., "OpenJDK Runtime Environment")
         */
        public String getName() {
            return this.name;
        }

        /**
         * Return the runtime version or null.
         *
         * @return the runtime version
         */
        public String getVersion() {
            return this.version;
        }
    }

    /**
     * Information about the Java Virtual Machine the application is running in.
     */
    public static class JavaVirtualMachineInfo {

        private final String name;
        private final String vendor;
        private final String version;

        public JavaVirtualMachineInfo() {
            this.name = System.getProperty("java.vm.name");
            this.vendor = System.getProperty("java.vm.vendor");
            this.version = System.getProperty("java.vm.version");
        }

        /**
         * Return the JVM name or null.
         *
         * @return the JVM name (e.g., "OpenJDK 64-Bit Server VM")
         */
        public String getName() {
            return this.name;
        }

        /**
         * Return the JVM vendor or null.
         *
         * @return the JVM vendor
         */
        public String getVendor() {
            return this.vendor;
        }

        /**
         * Return the JVM version or null.
         *
         * @return the JVM version
         */
        public String getVersion() {
            return this.version;
        }
    }
}

ProcessInfo

Provides process information including CPU count, memory usage, virtual threads, and process identifiers.

package org.springframework.boot.info;

import org.jspecify.annotations.Nullable;

/**
 * Information about the process of the application.
 *
 * Thread Safety: Immutable core properties. Dynamic properties (CPUs, memory)
 * are fetched on each call and reflect current state.
 *
 * @since 3.3.0
 */
public class ProcessInfo {

    private final long pid;
    private final long parentPid;
    private final @Nullable String owner;

    /**
     * Create an instance from current process.
     * Extracts process ID, parent process ID, and process owner.
     */
    public ProcessInfo() {
        ProcessHandle process = ProcessHandle.current();
        this.pid = process.pid();
        this.parentPid = process.parent().map(ProcessHandle::pid).orElse(-1L);
        this.owner = process.info().user().orElse(null);
    }

    /**
     * Number of processors available to the process. This value may change between
     * invocations especially in (containerized) environments where resource usage
     * can be isolated (for example using control groups).
     *
     * @return result of Runtime.availableProcessors()
     */
    public int getCpus() {
        return Runtime.getRuntime().availableProcessors();
    }

    /**
     * Memory information for the process. These values can provide details about
     * the current memory usage and limits selected by the user or JVM ergonomics
     * (init, max, committed, used for heap and non-heap).
     *
     * @return heap and non-heap memory information
     * @since 3.4.0
     */
    public MemoryInfo getMemory() {
        return new MemoryInfo();
    }

    /**
     * Virtual threads information for the process. These values provide details
     * about the current state of virtual threads, including the number of mounted
     * threads, queued threads, the parallelism level, and the thread pool size.
     *
     * @return an instance of VirtualThreadsInfo containing information about
     *         virtual threads, or null if VirtualThreadSchedulerMXBean is not available
     * @since 3.5.0
     */
    public @Nullable VirtualThreadsInfo getVirtualThreads() {
        // Implementation uses reflection to access VirtualThreadSchedulerMXBean
        // Returns null if not available (Java version < 21)
        return null;
    }

    /**
     * Return the process ID.
     *
     * @return the process ID
     */
    public long getPid() {
        return this.pid;
    }

    /**
     * Return the parent process ID.
     *
     * @return the parent process ID, or -1 if no parent
     */
    public long getParentPid() {
        return this.parentPid;
    }

    /**
     * Return the process owner or null.
     *
     * @return the process owner username
     */
    public @Nullable String getOwner() {
        return this.owner;
    }

    /**
     * Virtual threads information.
     *
     * @since 3.5.0
     */
    public static class VirtualThreadsInfo {

        private final int mounted;
        private final long queued;
        private final int parallelism;
        private final int poolSize;

        public VirtualThreadsInfo(int mounted, long queued, int parallelism, int poolSize) {
            this.mounted = mounted;
            this.queued = queued;
            this.parallelism = parallelism;
            this.poolSize = poolSize;
        }

        /**
         * Return the number of mounted virtual threads.
         *
         * @return the mounted count
         */
        public int getMounted() {
            return this.mounted;
        }

        /**
         * Return the number of queued virtual threads.
         *
         * @return the queued count
         */
        public long getQueued() {
            return this.queued;
        }

        /**
         * Return the parallelism level.
         *
         * @return the parallelism
         */
        public int getParallelism() {
            return this.parallelism;
        }

        /**
         * Return the thread pool size.
         *
         * @return the pool size
         */
        public int getPoolSize() {
            return this.poolSize;
        }
    }

    /**
     * Memory information.
     *
     * @since 3.4.0
     */
    public static class MemoryInfo {

        private final MemoryUsageInfo heap;
        private final MemoryUsageInfo nonHeap;
        private final List<GarbageCollectorInfo> garbageCollectors;

        public MemoryInfo() {
            // Implementation uses MemoryMXBean from ManagementFactory
            this.heap = new MemoryUsageInfo(/* heap usage */);
            this.nonHeap = new MemoryUsageInfo(/* non-heap usage */);
            this.garbageCollectors = List.of(/* GC beans */);
        }

        /**
         * Return heap memory usage information.
         *
         * @return the heap memory usage
         */
        public MemoryUsageInfo getHeap() {
            return this.heap;
        }

        /**
         * Return non-heap memory usage information.
         *
         * @return the non-heap memory usage
         */
        public MemoryUsageInfo getNonHeap() {
            return this.nonHeap;
        }

        /**
         * Garbage Collector information for the process. This list provides
         * details about the currently used GC algorithms selected by the user
         * or JVM ergonomics.
         *
         * @return list of GarbageCollectorInfo
         * @since 3.5.0
         */
        public List<GarbageCollectorInfo> getGarbageCollectors() {
            return this.garbageCollectors;
        }

        /**
         * Memory usage information for heap or non-heap.
         */
        public static class MemoryUsageInfo {

            /**
             * Return the initial memory in bytes that the JVM requested
             * from the operating system for memory management, or -1 if undefined.
             *
             * @return the initial memory in bytes
             */
            public long getInit() {
                return 0L; // placeholder
            }

            /**
             * Return the amount of used memory in bytes.
             *
             * @return the used memory in bytes
             */
            public long getUsed() {
                return 0L; // placeholder
            }

            /**
             * Return the amount of memory in bytes that is committed for
             * the JVM to use.
             *
             * @return the committed memory in bytes
             */
            public long getCommitted() {
                return 0L; // placeholder
            }

            /**
             * Return the maximum amount of memory in bytes that can be used
             * for memory management, or -1 if undefined.
             *
             * @return the maximum memory in bytes
             */
            public long getMax() {
                return 0L; // placeholder
            }
        }

        /**
         * Garbage collection information.
         *
         * @since 3.5.0
         */
        public static class GarbageCollectorInfo {

            private final String name;
            private final long collectionCount;

            public GarbageCollectorInfo(String name, long collectionCount) {
                this.name = name;
                this.collectionCount = collectionCount;
            }

            /**
             * Return the name of the garbage collector.
             *
             * @return the GC name (e.g., "G1 Young Generation", "G1 Old Generation")
             */
            public String getName() {
                return this.name;
            }

            /**
             * Return the total number of collections that have occurred.
             *
             * @return the collection count, or -1 if undefined
             */
            public long getCollectionCount() {
                return this.collectionCount;
            }
        }
    }
}

OsInfo

Provides Operating System information.

package org.springframework.boot.info;

/**
 * Information about the Operating System the application is running on.
 *
 * Thread Safety: Immutable and thread-safe.
 *
 * @since 2.7.0
 */
public class OsInfo {

    private final String name;
    private final String version;
    private final String arch;

    /**
     * Create an instance from system properties.
     * Uses "os.name", "os.version", and "os.arch" system properties.
     */
    public OsInfo() {
        this.name = System.getProperty("os.name");
        this.version = System.getProperty("os.version");
        this.arch = System.getProperty("os.arch");
    }

    /**
     * Return the operating system name or null.
     *
     * @return the OS name (e.g., "Linux", "Mac OS X", "Windows 11")
     */
    public String getName() {
        return this.name;
    }

    /**
     * Return the operating system version or null.
     *
     * @return the OS version (e.g., "5.15.0-76-generic", "10.0")
     */
    public String getVersion() {
        return this.version;
    }

    /**
     * Return the operating system architecture or null.
     *
     * @return the OS architecture (e.g., "amd64", "aarch64", "x86_64")
     */
    public String getArch() {
        return this.arch;
    }
}

SslInfo

Provides SSL/TLS certificate information from configured SSL bundles.

package org.springframework.boot.info;

import java.time.Clock;
import java.time.Instant;
import java.util.List;

import org.jspecify.annotations.Nullable;

import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.ssl.NoSuchSslBundleException;

/**
 * Information about the certificates that the application uses.
 *
 * Thread Safety: Thread-safe. Certificate validity checks use the configured Clock.
 *
 * @since 3.4.0
 */
public class SslInfo {

    private final SslBundles sslBundles;
    private final Clock clock;

    /**
     * Creates a new instance.
     *
     * @param sslBundles the SslBundles to extract the info from
     * @since 4.0.0
     */
    public SslInfo(SslBundles sslBundles) {
        this(sslBundles, Clock.systemDefaultZone());
    }

    /**
     * Creates a new instance.
     *
     * @param sslBundles the SslBundles to extract the info from
     * @param clock the Clock to use for validity checks
     * @since 4.0.0
     */
    public SslInfo(SslBundles sslBundles, Clock clock) {
        this.sslBundles = sslBundles;
        this.clock = clock;
    }

    /**
     * Returns information on all SSL bundles.
     *
     * @return information on all SSL bundles
     */
    public List<BundleInfo> getBundles() {
        return this.sslBundles.getBundleNames()
            .stream()
            .map((name) -> new BundleInfo(name, this.sslBundles.getBundle(name)))
            .toList();
    }

    /**
     * Returns an SSL bundle by name.
     *
     * @param name the name of the SSL bundle
     * @return the BundleInfo for the given SSL bundle
     * @throws NoSuchSslBundleException if a bundle with the provided name does not exist
     * @since 3.5.0
     */
    public BundleInfo getBundle(String name) {
        return new BundleInfo(name, this.sslBundles.getBundle(name));
    }

    /**
     * Info about a single SslBundle.
     */
    public final class BundleInfo {

        private final String name;
        private final List<CertificateChainInfo> certificateChains;

        /**
         * Return the bundle name.
         *
         * @return the name
         */
        public String getName() {
            return this.name;
        }

        /**
         * Return certificate chains in this bundle.
         *
         * @return list of certificate chains
         */
        public List<CertificateChainInfo> getCertificateChains() {
            return this.certificateChains;
        }
    }

    /**
     * Info about a single certificate chain.
     */
    public final class CertificateChainInfo {

        private final String alias;
        private final List<CertificateInfo> certificates;

        /**
         * Return the keystore alias for this certificate chain.
         *
         * @return the alias
         */
        public String getAlias() {
            return this.alias;
        }

        /**
         * Return certificates in this chain.
         *
         * @return list of certificates (ordered from end-entity to root)
         */
        public List<CertificateInfo> getCertificates() {
            return this.certificates;
        }
    }

    /**
     * Info about a certificate.
     */
    public final class CertificateInfo {

        /**
         * Return the certificate subject distinguished name or null.
         *
         * @return the subject DN (e.g., "CN=example.com, O=Example Inc, C=US")
         */
        public @Nullable String getSubject() {
            return null; // placeholder
        }

        /**
         * Return the certificate issuer distinguished name or null.
         *
         * @return the issuer DN
         */
        public @Nullable String getIssuer() {
            return null; // placeholder
        }

        /**
         * Return the certificate serial number in hexadecimal or null.
         *
         * @return the serial number in hex format
         */
        public @Nullable String getSerialNumber() {
            return null; // placeholder
        }

        /**
         * Return the certificate version or null.
         *
         * @return the version (e.g., "V3")
         */
        public @Nullable String getVersion() {
            return null; // placeholder
        }

        /**
         * Return the signature algorithm name or null.
         *
         * @return the algorithm name (e.g., "SHA256withRSA")
         */
        public @Nullable String getSignatureAlgorithmName() {
            return null; // placeholder
        }

        /**
         * Return the validity start time or null.
         *
         * @return the not-before timestamp
         */
        public @Nullable Instant getValidityStarts() {
            return null; // placeholder
        }

        /**
         * Return the validity end time or null.
         *
         * @return the not-after timestamp
         */
        public @Nullable Instant getValidityEnds() {
            return null; // placeholder
        }

        /**
         * Return certificate validity information or null.
         *
         * @return the validity info with status and message
         */
        public @Nullable CertificateValidityInfo getValidity() {
            return null; // placeholder
        }
    }

    /**
     * Certificate validity info.
     */
    public static class CertificateValidityInfo {

        private final Status status;
        private final @Nullable String message;

        /**
         * Return the validity status.
         *
         * @return the status (VALID, EXPIRED, or NOT_YET_VALID)
         */
        public Status getStatus() {
            return this.status;
        }

        /**
         * Return the validity message or null.
         *
         * @return descriptive message (e.g., "Not valid after 2025-12-31T23:59:59Z")
         */
        public @Nullable String getMessage() {
            return this.message;
        }

        /**
         * Validity Status.
         */
        public enum Status {

            /**
             * The certificate is valid.
             */
            VALID(true),

            /**
             * The certificate's validity date range is in the future.
             */
            NOT_YET_VALID(false),

            /**
             * The certificate's validity date range is in the past.
             */
            EXPIRED(false);

            private final boolean valid;

            Status(boolean valid) {
                this.valid = valid;
            }

            /**
             * Return whether this status represents a valid certificate.
             *
             * @return true if valid, false otherwise
             */
            public boolean isValid() {
                return this.valid;
            }
        }
    }
}

Usage Examples

Accessing Build Information

package com.example.info;

import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Component;

/**
 * Access build properties at runtime.
 */
@Component
public class BuildInfoService {

    private final BuildProperties buildProperties;

    // BuildProperties is auto-configured if build-info.properties exists
    public BuildInfoService(BuildProperties buildProperties) {
        this.buildProperties = buildProperties;
    }

    public void printBuildInfo() {
        System.out.println("Application Information:");
        System.out.println("  Group: " + buildProperties.getGroup());
        System.out.println("  Artifact: " + buildProperties.getArtifact());
        System.out.println("  Name: " + buildProperties.getName());
        System.out.println("  Version: " + buildProperties.getVersion());
        System.out.println("  Build Time: " + buildProperties.getTime());
    }

    public String getVersionString() {
        return String.format("%s v%s",
            buildProperties.getName(),
            buildProperties.getVersion());
    }

    public boolean isSnapshot() {
        String version = buildProperties.getVersion();
        return version != null && version.contains("SNAPSHOT");
    }
}

Accessing Git Information

package com.example.info;

import org.springframework.boot.info.GitProperties;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;

/**
 * Access Git commit information at runtime.
 */
@Component
public class GitInfoService {

    private final GitProperties gitProperties;

    // GitProperties is auto-configured if git.properties exists
    public GitInfoService(GitProperties gitProperties) {
        this.gitProperties = gitProperties;
    }

    public void printGitInfo() {
        System.out.println("Git Information:");
        System.out.println("  Branch: " + gitProperties.getBranch());
        System.out.println("  Commit ID: " + gitProperties.getShortCommitId());
        System.out.println("  Commit Time: " + gitProperties.getCommitTime());
        System.out.println("  Build Time: " + gitProperties.getBuildTime());
    }

    public String getCommitInfo() {
        return String.format("%s@%s",
            gitProperties.getBranch(),
            gitProperties.getShortCommitId());
    }

    public boolean isMainBranch() {
        String branch = gitProperties.getBranch();
        return "main".equals(branch) || "master".equals(branch);
    }

    public Duration getTimeSinceCommit() {
        Instant commitTime = gitProperties.getCommitTime();
        return (commitTime != null)
            ? Duration.between(commitTime, Instant.now())
            : null;
    }
}

Displaying Version Info REST Endpoint

package com.example.api;

import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.info.JavaInfo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * REST endpoint exposing version information.
 */
@RestController
public class VersionController {

    private final BuildProperties buildProperties;
    private final GitProperties gitProperties;
    private final JavaInfo javaInfo;

    public VersionController(
            BuildProperties buildProperties,
            GitProperties gitProperties,
            JavaInfo javaInfo) {
        this.buildProperties = buildProperties;
        this.gitProperties = gitProperties;
        this.javaInfo = javaInfo;
    }

    @GetMapping("/api/version")
    public Map<String, Object> getVersion() {
        Map<String, Object> version = new HashMap<>();

        // Build info
        Map<String, String> build = new HashMap<>();
        build.put("group", buildProperties.getGroup());
        build.put("artifact", buildProperties.getArtifact());
        build.put("name", buildProperties.getName());
        build.put("version", buildProperties.getVersion());
        build.put("time", buildProperties.getTime().toString());
        version.put("build", build);

        // Git info
        Map<String, String> git = new HashMap<>();
        git.put("branch", gitProperties.getBranch());
        git.put("commit", gitProperties.getShortCommitId());
        git.put("commitTime", gitProperties.getCommitTime().toString());
        version.put("git", git);

        // Java info
        Map<String, Object> java = new HashMap<>();
        java.put("version", javaInfo.getVersion());

        Map<String, String> vendor = new HashMap<>();
        vendor.put("name", javaInfo.getVendor().getName());
        vendor.put("version", javaInfo.getVendor().getVersion());
        java.put("vendor", vendor);

        Map<String, String> runtime = new HashMap<>();
        runtime.put("name", javaInfo.getRuntime().getName());
        runtime.put("version", javaInfo.getRuntime().getVersion());
        java.put("runtime", runtime);

        Map<String, String> jvm = new HashMap<>();
        jvm.put("name", javaInfo.getJvm().getName());
        jvm.put("vendor", javaInfo.getJvm().getVendor());
        jvm.put("version", javaInfo.getJvm().getVersion());
        java.put("jvm", jvm);

        version.put("java", java);

        return version;
    }
}

Response:

{
  "build": {
    "group": "com.example",
    "artifact": "my-application",
    "name": "My Application",
    "version": "1.0.0",
    "time": "2025-01-15T10:30:00Z"
  },
  "git": {
    "branch": "main",
    "commit": "a1b2c3d",
    "commitTime": "2025-01-15T09:45:30Z"
  },
  "java": {
    "version": "17.0.1",
    "vendor": {
      "name": "Oracle Corporation",
      "version": "17.0.1"
    },
    "runtime": {
      "name": "OpenJDK Runtime Environment",
      "version": "17.0.1+12"
    },
    "jvm": {
      "name": "OpenJDK 64-Bit Server VM",
      "vendor": "Oracle Corporation",
      "version": "17.0.1+12"
    }
  }
}

Maven Configuration

Generate build-info.properties with Maven:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>build-info</goal>
                    </goals>
                    <configuration>
                        <additionalProperties>
                            <encoding.source>UTF-8</encoding.source>
                            <encoding.reporting>UTF-8</encoding.reporting>
                            <java.version>${java.version}</java.version>
                        </additionalProperties>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <!-- Git information plugin -->
        <plugin>
            <groupId>pl.project13.maven</groupId>
            <artifactId>git-commit-id-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>revision</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <generateGitPropertiesFile>true</generateGitPropertiesFile>
                <generateGitPropertiesFilename>
                    ${project.build.outputDirectory}/git.properties
                </generateGitPropertiesFilename>
                <includeOnlyProperties>
                    <includeOnlyProperty>^git.branch$</includeOnlyProperty>
                    <includeOnlyProperty>^git.commit.id$</includeOnlyProperty>
                    <includeOnlyProperty>^git.commit.id.abbrev$</includeOnlyProperty>
                    <includeOnlyProperty>^git.commit.time$</includeOnlyProperty>
                    <includeOnlyProperty>^git.build.time$</includeOnlyProperty>
                </includeOnlyProperties>
                <commitIdGenerationMode>full</commitIdGenerationMode>
            </configuration>
        </plugin>
    </plugins>
</build>

Gradle Configuration

// build.gradle
plugins {
    id 'org.springframework.boot' version '3.2.0'
    id 'com.gorylenko.gradle-git-properties' version '2.4.1'
}

springBoot {
    buildInfo()
}

gitProperties {
    keys = ['git.branch', 'git.commit.id', 'git.commit.id.abbrev',
            'git.commit.time', 'git.build.time']
}

Startup Banner with Version Info

package com.example;

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

import java.io.PrintStream;

/**
 * Custom banner displaying version information.
 */
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setBanner(new VersionBanner());
        app.run(args);
    }

    static class VersionBanner implements Banner {
        @Override
        public void printBanner(Environment environment,
                               Class<?> sourceClass,
                               PrintStream out) {
            String version = environment.getProperty("spring.application.version", "unknown");
            String name = environment.getProperty("spring.application.name", "Application");

            out.println("========================================");
            out.println("  " + name + " v" + version);
            out.println("========================================");
        }
    }

    @Bean
    public ApplicationInfoPrinter infoPrinter(
            BuildProperties buildProperties,
            GitProperties gitProperties) {
        return new ApplicationInfoPrinter(buildProperties, gitProperties);
    }
}

class ApplicationInfoPrinter {
    public ApplicationInfoPrinter(BuildProperties build, GitProperties git) {
        System.out.println("\nApplication Details:");
        System.out.printf("  Version: %s%n", build.getVersion());
        System.out.printf("  Built: %s%n", build.getTime());
        System.out.printf("  Git Commit: %s (%s)%n",
            git.getShortCommitId(), git.getBranch());
    }
}

Conditional Bean Based on Environment

package com.example.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.info.BuildProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Configuration based on build properties.
 */
@Configuration
public class EnvironmentConfig {

    /**
     * Enable debug features only for SNAPSHOT versions.
     */
    @Bean
    @ConditionalOnProperty(
        value = "debug.enabled",
        havingValue = "true",
        matchIfMissing = true
    )
    public DebugFeatures debugFeatures(BuildProperties buildProperties) {
        String version = buildProperties.getVersion();
        boolean isSnapshot = version != null && version.contains("SNAPSHOT");

        if (!isSnapshot) {
            System.out.println("Debug features disabled in production build");
            return null;
        }

        System.out.println("Debug features enabled for SNAPSHOT version");
        return new DebugFeatures();
    }
}

Actuator Integration

Spring Boot Actuator automatically exposes build and git info:

# application.properties
# Expose info endpoint
management.endpoints.web.exposure.include=info
management.info.git.mode=full

GET /actuator/info response:

{
  "build": {
    "group": "com.example",
    "artifact": "my-application",
    "name": "My Application",
    "version": "1.0.0",
    "time": "2025-01-15T10:30:00Z"
  },
  "git": {
    "branch": "main",
    "commit": {
      "id": "a1b2c3d",
      "time": "2025-01-15T09:45:30Z"
    }
  }
}

Common Patterns

Pattern 1: Version Display in Web UI

Complete version information display with caching and formatting:

package com.example.version;

import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.info.JavaInfo;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Service providing formatted version information for UI display.
 * Caches formatted strings for performance.
 */
@Service
public class VersionDisplayService {

    private static final DateTimeFormatter DATE_FORMATTER =
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
            .withZone(ZoneId.systemDefault());

    private final BuildProperties buildProperties;
    private final GitProperties gitProperties;
    private final JavaInfo javaInfo;

    private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
    private volatile Map<String, String> cachedVersionInfo;
    private volatile Instant cacheTime;
    private static final Duration CACHE_TTL = Duration.ofMinutes(5);

    public VersionDisplayService(BuildProperties buildProperties,
                                 GitProperties gitProperties,
                                 JavaInfo javaInfo) {
        this.buildProperties = buildProperties;
        this.gitProperties = gitProperties;
        this.javaInfo = javaInfo;
    }

    /**
     * Get comprehensive version information with caching.
     *
     * @return map of version information
     */
    public Map<String, String> getVersionInfo() {
        cacheLock.readLock().lock();
        try {
            if (isCacheValid()) {
                return cachedVersionInfo;
            }
        } finally {
            cacheLock.readLock().unlock();
        }

        cacheLock.writeLock().lock();
        try {
            // Double-check after acquiring write lock
            if (isCacheValid()) {
                return cachedVersionInfo;
            }

            Map<String, String> info = new LinkedHashMap<>();

            // Build information
            info.put("Application Name", buildProperties.getName());
            info.put("Version", buildProperties.getVersion());
            info.put("Group", buildProperties.getGroup());
            info.put("Artifact", buildProperties.getArtifact());
            info.put("Build Time", formatInstant(buildProperties.getTime()));

            // Git information
            info.put("Git Branch", gitProperties.getBranch());
            info.put("Git Commit", gitProperties.getShortCommitId());
            info.put("Commit Time", formatInstant(gitProperties.getCommitTime()));

            // Runtime information
            info.put("Java Version", javaInfo.getVersion());
            info.put("Java Vendor", javaInfo.getVendor());

            // Computed information
            info.put("Version Type", getVersionType());
            info.put("Build Age", getBuildAge());

            cachedVersionInfo = info;
            cacheTime = Instant.now();

            return cachedVersionInfo;
        } finally {
            cacheLock.writeLock().unlock();
        }
    }

    /**
     * Get short version string for display.
     *
     * @return formatted version string
     */
    public String getShortVersion() {
        return String.format("%s v%s (%s@%s)",
            buildProperties.getName(),
            buildProperties.getVersion(),
            gitProperties.getBranch(),
            gitProperties.getShortCommitId());
    }

    /**
     * Check if running snapshot/development version.
     *
     * @return true if snapshot version
     */
    public boolean isSnapshot() {
        String version = buildProperties.getVersion();
        return version != null &&
               (version.contains("SNAPSHOT") ||
                version.contains("SNAPSHOT") ||
                version.contains("-dev"));
    }

    private boolean isCacheValid() {
        return cachedVersionInfo != null &&
               cacheTime != null &&
               Duration.between(cacheTime, Instant.now()).compareTo(CACHE_TTL) < 0;
    }

    private String formatInstant(Instant instant) {
        return instant != null ? DATE_FORMATTER.format(instant) : "N/A";
    }

    private String getVersionType() {
        if (isSnapshot()) {
            return "Development/Snapshot";
        }
        String version = buildProperties.getVersion();
        if (version != null && (version.contains("-RC") || version.contains("-M"))) {
            return "Release Candidate";
        }
        return "Release";
    }

    private String getBuildAge() {
        Instant buildTime = buildProperties.getTime();
        if (buildTime == null) {
            return "Unknown";
        }

        Duration age = Duration.between(buildTime, Instant.now());
        long days = age.toDays();

        if (days == 0) {
            return "Today";
        } else if (days == 1) {
            return "1 day ago";
        } else if (days < 7) {
            return days + " days ago";
        } else if (days < 30) {
            long weeks = days / 7;
            return weeks + " week" + (weeks > 1 ? "s" : "") + " ago";
        } else {
            long months = days / 30;
            return months + " month" + (months > 1 ? "s" : "") + " ago";
        }
    }
}

Pattern 2: Version Comparison and Validation

Version validation and comparison for upgrade checks:

package com.example.version;

import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Service for version comparison and validation.
 * Supports semantic versioning.
 */
@Service
public class VersionComparisonService {

    private static final Pattern VERSION_PATTERN =
        Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(-([\\w.]+))?");

    private final BuildProperties buildProperties;

    public VersionComparisonService(BuildProperties buildProperties) {
        this.buildProperties = buildProperties;
    }

    /**
     * Parse version string into components.
     *
     * @param versionString the version to parse
     * @return parsed version
     * @throws IllegalArgumentException if version format invalid
     */
    public Version parseVersion(String versionString) {
        Matcher matcher = VERSION_PATTERN.matcher(versionString);
        if (!matcher.matches()) {
            throw new IllegalArgumentException("Invalid version format: " + versionString);
        }

        int major = Integer.parseInt(matcher.group(1));
        int minor = Integer.parseInt(matcher.group(2));
        int patch = Integer.parseInt(matcher.group(3));
        String suffix = matcher.group(5); // Optional: SNAPSHOT, RC1, etc.

        return new Version(major, minor, patch, suffix);
    }

    /**
     * Get current application version.
     *
     * @return current version
     */
    public Version getCurrentVersion() {
        return parseVersion(buildProperties.getVersion());
    }

    /**
     * Check if current version is compatible with minimum required version.
     *
     * @param minimumVersion minimum required version
     * @return true if current version is compatible
     */
    public boolean isCompatibleWith(String minimumVersion) {
        Version current = getCurrentVersion();
        Version minimum = parseVersion(minimumVersion);

        return current.compareTo(minimum) >= 0;
    }

    /**
     * Check if upgrade is available.
     *
     * @param latestVersion latest available version
     * @return true if upgrade available
     */
    public boolean isUpgradeAvailable(String latestVersion) {
        Version current = getCurrentVersion();
        Version latest = parseVersion(latestVersion);

        return latest.compareTo(current) > 0;
    }

    /**
     * Determine upgrade type (major, minor, patch).
     *
     * @param targetVersion target version for upgrade
     * @return upgrade type
     */
    public UpgradeType getUpgradeType(String targetVersion) {
        Version current = getCurrentVersion();
        Version target = parseVersion(targetVersion);

        if (target.major > current.major) {
            return UpgradeType.MAJOR;
        } else if (target.minor > current.minor) {
            return UpgradeType.MINOR;
        } else if (target.patch > current.patch) {
            return UpgradeType.PATCH;
        } else {
            return UpgradeType.NONE;
        }
    }

    /**
     * Represents a semantic version.
     */
    public static class Version implements Comparable<Version> {
        public final int major;
        public final int minor;
        public final int patch;
        public final String suffix;

        public Version(int major, int minor, int patch, String suffix) {
            this.major = major;
            this.minor = minor;
            this.patch = patch;
            this.suffix = suffix;
        }

        @Override
        public int compareTo(Version other) {
            int result = Integer.compare(this.major, other.major);
            if (result != 0) return result;

            result = Integer.compare(this.minor, other.minor);
            if (result != 0) return result;

            result = Integer.compare(this.patch, other.patch);
            if (result != 0) return result;

            // Compare suffixes: release > RC > SNAPSHOT
            return compareSuffix(this.suffix, other.suffix);
        }

        private int compareSuffix(String s1, String s2) {
            if (s1 == null && s2 == null) return 0;
            if (s1 == null) return 1;  // Release version is higher
            if (s2 == null) return -1;
            return s1.compareTo(s2);
        }

        @Override
        public String toString() {
            return major + "." + minor + "." + patch +
                   (suffix != null ? "-" + suffix : "");
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Version)) return false;
            Version version = (Version) o;
            return major == version.major &&
                   minor == version.minor &&
                   patch == version.patch &&
                   Objects.equals(suffix, version.suffix);
        }

        @Override
        public int hashCode() {
            return Objects.hash(major, minor, patch, suffix);
        }
    }

    public enum UpgradeType {
        NONE, PATCH, MINOR, MAJOR
    }
}

Pattern 3: Build Information Logging

Comprehensive startup logging with build information:

package com.example.logging;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.info.JavaInfo;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;

/**
 * Logs comprehensive build and runtime information at startup.
 */
@Component
public class BuildInfoLogger implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(BuildInfoLogger.class);

    private final BuildProperties buildProperties;
    private final GitProperties gitProperties;
    private final JavaInfo javaInfo;
    private final Environment environment;

    public BuildInfoLogger(BuildProperties buildProperties,
                          GitProperties gitProperties,
                          JavaInfo javaInfo,
                          Environment environment) {
        this.buildProperties = buildProperties;
        this.gitProperties = gitProperties;
        this.javaInfo = javaInfo;
        this.environment = environment;
    }

    @Override
    public void run(String... args) {
        logBanner();
        logBuildInfo();
        logGitInfo();
        logRuntimeInfo();
        logEnvironmentInfo();
        logStartupComplete();
    }

    private void logBanner() {
        log.info("========================================");
        log.info("  Application Starting");
        log.info("========================================");
    }

    private void logBuildInfo() {
        log.info("Build Information:");
        log.info("  Name:         {}", buildProperties.getName());
        log.info("  Group:        {}", buildProperties.getGroup());
        log.info("  Artifact:     {}", buildProperties.getArtifact());
        log.info("  Version:      {}", buildProperties.getVersion());
        log.info("  Build Time:   {}", buildProperties.getTime());

        // Calculate build age
        Instant buildTime = buildProperties.getTime();
        if (buildTime != null) {
            Duration age = Duration.between(buildTime, Instant.now());
            log.info("  Build Age:    {} days", age.toDays());

            if (age.toDays() > 30) {
                log.warn("  WARNING: Build is more than 30 days old!");
            }
        }
    }

    private void logGitInfo() {
        log.info("Git Information:");
        log.info("  Branch:       {}", gitProperties.getBranch());
        log.info("  Commit ID:    {}", gitProperties.getCommitId());
        log.info("  Short ID:     {}", gitProperties.getShortCommitId());
        log.info("  Commit Time:  {}", gitProperties.getCommitTime());

        // Check if built from non-production branch
        String branch = gitProperties.getBranch();
        if (branch != null && !branch.equals("main") && !branch.equals("master")) {
            log.warn("  WARNING: Built from non-production branch: {}", branch);
        }
    }

    private void logRuntimeInfo() {
        log.info("Runtime Information:");
        log.info("  Java Version:     {}", javaInfo.getVersion());
        log.info("  Java Vendor:      {}", javaInfo.getVendor().getName());
        log.info("  Java VM:          {}", javaInfo.getJvm().getName());
        log.info("  Java VM Version:  {}", javaInfo.getJvm().getVersion());
        log.info("  Runtime Name:     {}", javaInfo.getRuntime().getName());
        log.info("  PID:              {}", ProcessHandle.current().pid());
        log.info("  User:             {}", System.getProperty("user.name"));
        log.info("  Working Dir:      {}", System.getProperty("user.dir"));
    }

    private void logEnvironmentInfo() {
        log.info("Environment:");
        String[] activeProfiles = environment.getActiveProfiles();
        if (activeProfiles.length > 0) {
            log.info("  Active Profiles: {}", String.join(", ", activeProfiles));
        } else {
            log.info("  Active Profiles: (none)");
        }

        String serverPort = environment.getProperty("server.port", "8080");
        log.info("  Server Port:     {}", serverPort);
    }

    private void logStartupComplete() {
        log.info("========================================");
        log.info("  {} v{} Started Successfully",
            buildProperties.getName(),
            buildProperties.getVersion());
        log.info("========================================");
    }
}

Pattern 4: Version Endpoint with Conditional Features

REST endpoint with conditional feature flags based on version:

package com.example.api;

import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

/**
 * Version API endpoint with feature flag support.
 */
@RestController
@RequestMapping("/api")
public class VersionApiController {

    private final BuildProperties buildProperties;
    private final GitProperties gitProperties;

    public VersionApiController(BuildProperties buildProperties,
                               GitProperties gitProperties) {
        this.buildProperties = buildProperties;
        this.gitProperties = gitProperties;
    }

    /**
     * Get version information with feature flags.
     *
     * @return version info and enabled features
     */
    @GetMapping("/version")
    public ResponseEntity<Map<String, Object>> getVersion() {
        Map<String, Object> response = new HashMap<>();

        // Basic version info
        Map<String, String> version = new HashMap<>();
        version.put("name", buildProperties.getName());
        version.put("version", buildProperties.getVersion());
        version.put("commit", gitProperties.getShortCommitId());
        version.put("branch", gitProperties.getBranch());
        response.put("version", version);

        // Feature flags based on version/environment
        Map<String, Boolean> features = new HashMap<>();
        features.put("debugMode", isSnapshot());
        features.put("experimentalFeatures", isSnapshot());
        features.put("detailedErrors", isSnapshot());
        features.put("performanceMetrics", true);
        response.put("features", features);

        // Status information
        Map<String, Object> status = new HashMap<>();
        status.put("type", isSnapshot() ? "development" : "production");
        status.put("buildAge", getBuildAgeDays());
        status.put("needsUpdate", needsUpdate());
        response.put("status", status);

        return ResponseEntity.ok(response);
    }

    /**
     * Health check endpoint with version verification.
     *
     * @return health status
     */
    @GetMapping("/health")
    public ResponseEntity<Map<String, Object>> health() {
        Map<String, Object> health = new HashMap<>();
        health.put("status", "UP");
        health.put("version", buildProperties.getVersion());
        health.put("commit", gitProperties.getShortCommitId());

        // Add warnings if needed
        if (needsUpdate()) {
            health.put("warning", "Build is more than 30 days old");
        }

        return ResponseEntity.ok(health);
    }

    private boolean isSnapshot() {
        String version = buildProperties.getVersion();
        return version != null && version.contains("SNAPSHOT");
    }

    private long getBuildAgeDays() {
        Instant buildTime = buildProperties.getTime();
        return buildTime != null
            ? Duration.between(buildTime, Instant.now()).toDays()
            : -1;
    }

    private boolean needsUpdate() {
        return getBuildAgeDays() > 30;
    }
}

Pattern 5: Build Properties Validation

Validate build configuration and properties at startup:

package com.example.validation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

/**
 * Validates build properties and environment configuration at startup.
 */
@Component
public class BuildPropertiesValidator implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(BuildPropertiesValidator.class);

    private final BuildProperties buildProperties;
    private final GitProperties gitProperties;
    private final Environment environment;

    public BuildPropertiesValidator(BuildProperties buildProperties,
                                   GitProperties gitProperties,
                                   Environment environment) {
        this.buildProperties = buildProperties;
        this.gitProperties = gitProperties;
        this.environment = environment;
    }

    @Override
    public void run(String... args) {
        List<String> errors = new ArrayList<>();
        List<String> warnings = new ArrayList<>();

        validateBuildProperties(errors, warnings);
        validateGitProperties(errors, warnings);
        validateEnvironment(errors, warnings);

        reportResults(errors, warnings);
    }

    private void validateBuildProperties(List<String> errors, List<String> warnings) {
        // Check required properties
        if (buildProperties.getVersion() == null || buildProperties.getVersion().isEmpty()) {
            errors.add("Build version is missing");
        }

        if (buildProperties.getName() == null || buildProperties.getName().isEmpty()) {
            errors.add("Build name is missing");
        }

        // Check version format
        String version = buildProperties.getVersion();
        if (version != null && !version.matches("\\d+\\.\\d+\\.\\d+.*")) {
            warnings.add("Version does not follow semantic versioning: " + version);
        }

        // Check build age
        Instant buildTime = buildProperties.getTime();
        if (buildTime != null) {
            Duration age = Duration.between(buildTime, Instant.now());
            if (age.toDays() > 90) {
                warnings.add("Build is very old: " + age.toDays() + " days");
            }
        } else {
            warnings.add("Build time is not set");
        }
    }

    private void validateGitProperties(List<String> errors, List<String> warnings) {
        // Check Git commit info
        if (gitProperties.getCommitId() == null) {
            warnings.add("Git commit ID is missing");
        }

        // Warn about non-production branches
        String branch = gitProperties.getBranch();
        String[] profiles = environment.getActiveProfiles();
        boolean isProduction = false;
        for (String profile : profiles) {
            if (profile.equals("prod") || profile.equals("production")) {
                isProduction = true;
                break;
            }
        }

        if (isProduction && branch != null &&
            !branch.equals("main") && !branch.equals("master")) {
            warnings.add("Production deployment from non-main branch: " + branch);
        }

        // Check if snapshot version in production
        if (isProduction && buildProperties.getVersion().contains("SNAPSHOT")) {
            errors.add("SNAPSHOT version deployed to production");
        }
    }

    private void validateEnvironment(List<String> errors, List<String> warnings) {
        // Check profile configuration
        String[] profiles = environment.getActiveProfiles();
        if (profiles.length == 0) {
            warnings.add("No active profiles configured");
        }

        // Check for conflicting profiles
        boolean hasDev = false;
        boolean hasProd = false;
        for (String profile : profiles) {
            if (profile.equals("dev") || profile.equals("development")) {
                hasDev = true;
            }
            if (profile.equals("prod") || profile.equals("production")) {
                hasProd = true;
            }
        }

        if (hasDev && hasProd) {
            errors.add("Conflicting profiles: both dev and prod are active");
        }
    }

    private void reportResults(List<String> errors, List<String> warnings) {
        if (!errors.isEmpty()) {
            log.error("Build validation FAILED with {} errors:", errors.size());
            errors.forEach(error -> log.error("  ERROR: {}", error));
            throw new IllegalStateException("Build validation failed");
        }

        if (!warnings.isEmpty()) {
            log.warn("Build validation completed with {} warnings:", warnings.size());
            warnings.forEach(warning -> log.warn("  WARNING: {}", warning));
        } else {
            log.info("Build validation completed successfully");
        }
    }
}

Best Practices

  1. Always Generate Build Info: Include spring-boot-maven-plugin build-info goal in production builds
  2. Include Git Properties: Enable git-commit-id-plugin to track exact source version
  3. Expose Via Actuator: Use /actuator/info endpoint for standardized version access
  4. Version in Logs: Log version information prominently at startup for troubleshooting
  5. Cache Formatted Data: Cache formatted version strings to avoid repeated date formatting
  6. Validate at Startup: Verify build properties are present and valid during application initialization
  7. Use Semantic Versioning: Follow semantic versioning (MAJOR.MINOR.PATCH) for clear version communication
  8. Document Custom Properties: Add custom build properties sparingly and document their purpose
  9. Monitor Build Age: Alert when deployed builds are too old (> 30-90 days)
  10. Feature Flags by Version: Use version/snapshot status to enable debug features conditionally

Common Pitfalls

1. Missing build-info.properties File

Problem: BuildProperties bean not available, causing autowiring failures

Error:

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.example.VersionService required a bean of type 'org.springframework.boot.info.BuildProperties' that could not be found.

Solution:

<!-- Add to pom.xml -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>build-info</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Rationale: BuildProperties bean is only auto-configured when META-INF/build-info.properties exists. The Spring Boot Maven plugin's build-info goal generates this file during build.

2. Null Pointer on Optional Properties

Problem: Accessing properties that may be null without checking

Error:

java.lang.NullPointerException: Cannot invoke "String.contains()" because the return value of "org.springframework.boot.info.BuildProperties.getVersion()" is null

Solution:

// Always check for null
String version = buildProperties.getVersion();
if (version != null && version.contains("SNAPSHOT")) {
    enableDebugFeatures();
}

// Or use Optional pattern
boolean isSnapshot = Optional.ofNullable(buildProperties.getVersion())
    .map(v -> v.contains("SNAPSHOT"))
    .orElse(false);

Rationale: Not all properties are guaranteed to be present in build-info.properties. Group, artifact, and custom properties may be null if not configured in the build.

3. Time Zone Issues with Instant

Problem: Displaying build time in wrong time zone

Error: Build time appears 5-8 hours off from actual build time

Solution:

import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

// Format with explicit time zone
DateTimeFormatter formatter = DateTimeFormatter
    .ofPattern("yyyy-MM-dd HH:mm:ss z")
    .withZone(ZoneId.systemDefault());

String formattedTime = formatter.format(buildProperties.getTime());

// Or convert to ZonedDateTime
ZonedDateTime buildTime = buildProperties.getTime()
    .atZone(ZoneId.systemDefault());

Rationale: Instant represents a point in time in UTC. Direct toString() displays UTC. Format with ZoneId to show local time or specify target time zone explicitly.

4. Git Properties Not Generated

Problem: GitProperties bean not available even though git plugin is configured

Error:

Parameter 1 of constructor in com.example.VersionService required a bean of type 'org.springframework.boot.info.GitProperties' that could not be found.

Solution:

<!-- Ensure plugin execution is configured -->
<plugin>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>revision</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <generateGitPropertiesFile>true</generateGitPropertiesFile>
        <!-- Verify output path -->
        <generateGitPropertiesFilename>
            ${project.build.outputDirectory}/git.properties
        </generateGitPropertiesFilename>
    </configuration>
</plugin>

Rationale: Plugin must be explicitly configured with generateGitPropertiesFile=true and correct output path. Also requires .git directory to be present during build.

5. Optional Dependency Causing Failures

Problem: Application fails when info beans are not required but code expects them

Error: NoSuchBeanDefinitionException at startup

Solution:

// Use @Autowired(required = false) for optional beans
@Service
public class VersionService {

    private final BuildProperties buildProperties;
    private final GitProperties gitProperties;

    public VersionService(
            @Autowired(required = false) BuildProperties buildProperties,
            @Autowired(required = false) GitProperties gitProperties) {
        this.buildProperties = buildProperties;
        this.gitProperties = gitProperties;
    }

    public String getVersion() {
        if (buildProperties != null) {
            return buildProperties.getVersion();
        }
        return "unknown";
    }
}

// Or use Optional
@Service
public class OptionalVersionService {

    private final Optional<BuildProperties> buildProperties;

    public OptionalVersionService(Optional<BuildProperties> buildProperties) {
        this.buildProperties = buildProperties;
    }

    public String getVersion() {
        return buildProperties
            .map(BuildProperties::getVersion)
            .orElse("unknown");
    }
}

Rationale: Info beans are only available if corresponding property files exist. Applications should handle their absence gracefully, especially in test environments.

6. Incorrect Custom Property Access

Problem: Trying to access custom properties that don't exist

Error: Returns null unexpectedly

Solution:

<!-- Add custom properties in plugin configuration -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <additionalProperties>
            <custom.property>${custom.value}</custom.property>
            <java.version>${java.version}</java.version>
        </additionalProperties>
    </configuration>
</plugin>
// Access via get() method, not dedicated getter
String customValue = buildProperties.get("custom.property");

// BuildProperties extends InfoProperties which stores all as key-value pairs

Rationale: Only standard properties (group, artifact, name, version, time) have dedicated getters. Custom properties must be accessed via generic get(String key) method.

7. Build Time Not Set in Tests

Problem: Build time is null in integration tests

Error: NullPointerException when accessing buildProperties.getTime()

Solution:

// Create test configuration with mock properties
@TestConfiguration
public class TestBuildConfig {

    @Bean
    @Primary
    public BuildProperties testBuildProperties() {
        Properties props = new Properties();
        props.setProperty("group", "com.example.test");
        props.setProperty("artifact", "test-app");
        props.setProperty("name", "Test Application");
        props.setProperty("version", "1.0.0-TEST");
        props.setProperty("time", Instant.now().toString());

        return new BuildProperties(props);
    }
}

// Or mock in test
@MockBean
private BuildProperties buildProperties;

@BeforeEach
void setup() {
    when(buildProperties.getVersion()).thenReturn("1.0.0-TEST");
    when(buildProperties.getTime()).thenReturn(Instant.now());
}

Rationale: build-info.properties is generated during Maven build, not available in IDE test execution. Tests need to provide mock or test BuildProperties beans.

8. Comparing Instants Incorrectly

Problem: Direct comparison of Instant objects fails

Error: Logic error in version age calculation

Solution:

// Use Duration for time comparisons
Instant buildTime = buildProperties.getTime();
Instant now = Instant.now();

// Correct: use Duration
Duration age = Duration.between(buildTime, now);
if (age.toDays() > 30) {
    System.out.println("Build is old");
}

// Wrong: comparing Instant objects directly
if (buildTime > now) { // Compilation error
    // ...
}

// Correct comparison
if (buildTime.isAfter(now)) {
    // ...
}

Rationale: Instant objects cannot be compared with > or < operators. Use isAfter(), isBefore(), or Duration.between() for time comparisons.

9. Thread Safety with Caching

Problem: Caching version strings without proper synchronization

Error: Occasional NullPointerException or stale cached data

Solution:

// Use proper synchronization for cache
public class VersionCache {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private volatile String cachedVersion;

    public String getVersion() {
        lock.readLock().lock();
        try {
            if (cachedVersion != null) {
                return cachedVersion;
            }
        } finally {
            lock.readLock().unlock();
        }

        lock.writeLock().lock();
        try {
            if (cachedVersion == null) {
                cachedVersion = buildVersion();
            }
            return cachedVersion;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

// Or use thread-safe lazy initialization
private final Supplier<String> versionSupplier =
    Suppliers.memoize(() -> buildVersion());

public String getVersion() {
    return versionSupplier.get();
}

Rationale: Concurrent access to cached version strings requires proper synchronization to avoid race conditions and ensure visibility across threads.

10. Forgetting to Handle Missing JavaInfo

Problem: Assuming JavaInfo is always available

Error: NoSuchBeanDefinitionException when JavaInfo bean is missing (Spring Boot < 2.6.0)

Solution:

// JavaInfo is only available in Spring Boot 2.6.0+
// Use conditional dependency
@Service
public class RuntimeInfoService {

    private final Optional<JavaInfo> javaInfo;

    public RuntimeInfoService(@Autowired(required = false) JavaInfo javaInfo) {
        this.javaInfo = Optional.ofNullable(javaInfo);
    }

    public String getJavaVersion() {
        return javaInfo
            .map(JavaInfo::getVersion)
            .orElseGet(() -> System.getProperty("java.version"));
    }

    public String getJavaVendorName() {
        return javaInfo
            .map(info -> info.getVendor().getName())
            .orElseGet(() -> System.getProperty("java.vendor"));
    }
}

// Or check Spring Boot version
@ConditionalOnClass(name = "org.springframework.boot.info.JavaInfo")
@Bean
public JavaInfo javaInfo() {
    return new JavaInfo();
}

Rationale: JavaInfo was added in Spring Boot 2.6.0. Applications supporting earlier versions must handle its absence gracefully or use system properties as fallback.

Base Classes

InfoProperties

Base class for components exposing unstructured data with dedicated methods for well-known keys. Provides iteration support and property source conversion.

package org.springframework.boot.info;

import java.time.Instant;
import java.util.Iterator;
import java.util.Properties;
import org.springframework.core.env.PropertySource;

/**
 * Base class for components exposing unstructured data with dedicated methods for well known keys.
 * Provides read-only access to properties loaded from files like build-info.properties or git.properties.
 *
 * Thread Safety: Immutable after construction. Thread-safe.
 *
 * @since 1.4.0
 */
public class InfoProperties implements Iterable<InfoProperties.Entry> {

    /**
     * Create an instance with the specified entries.
     *
     * @param entries the information to expose
     */
    public InfoProperties(Properties entries);

    /**
     * Return the value of the specified property or null.
     *
     * @param key the key of the property
     * @return the property value
     */
    public @Nullable String get(String key);

    /**
     * Return the value of the specified property as an Instant or null if
     * the value is not a valid Long representation of an epoch time.
     *
     * @param key the key of the property
     * @return the property value as Instant
     */
    public @Nullable Instant getInstant(String key);

    /**
     * Returns an iterator over property entries.
     *
     * @return iterator for Entry objects
     */
    @Override
    public Iterator<Entry> iterator();

    /**
     * Return a PropertySource of this instance.
     *
     * @return a PropertySource
     */
    public PropertySource<?> toPropertySource();

    /**
     * Property entry representing a key-value pair.
     */
    public static final class Entry {

        /**
         * Return the key of this entry.
         *
         * @return the key
         */
        public String getKey();

        /**
         * Return the value of this entry.
         *
         * @return the value
         */
        public String getValue();
    }
}

Usage Example:

package com.example.info;

import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.InfoProperties;
import org.springframework.stereotype.Component;

/**
 * Iterate over all build properties.
 */
@Component
public class BuildPropertiesIterator {

    private final BuildProperties buildProperties;

    public BuildPropertiesIterator(BuildProperties buildProperties) {
        this.buildProperties = buildProperties;
    }

    /**
     * Print all properties in build-info.properties.
     */
    public void printAllProperties() {
        System.out.println("All Build Properties:");
        for (InfoProperties.Entry entry : buildProperties) {
            System.out.printf("  %s = %s%n", entry.getKey(), entry.getValue());
        }
    }

    /**
     * Get custom property using the generic get() method.
     */
    public String getCustomProperty(String key) {
        return buildProperties.get(key);
    }

    /**
     * Convert to PropertySource for programmatic access.
     */
    public void addToEnvironment(ConfigurableEnvironment environment) {
        PropertySource<?> propertySource = buildProperties.toPropertySource();
        environment.getPropertySources().addLast(propertySource);
    }
}

Example Output:

All Build Properties:
  build.group = com.example
  build.artifact = my-application
  build.name = My Application
  build.version = 1.0.0
  build.time = 1705320600000
  custom.property = customValue

Thread Safety

All info classes are thread-safe:

  • InfoProperties: Immutable after construction. Internal properties copy ensures thread safety.
  • BuildProperties: Immutable after construction (extends InfoProperties)
  • GitProperties: Immutable after construction (extends InfoProperties)
  • JavaInfo: Immutable after construction. Nested classes (JavaVendorInfo, JavaRuntimeEnvironmentInfo, JavaVirtualMachineInfo) are also immutable
  • ProcessInfo: Immutable core properties (PID, parent PID, owner). Dynamic properties (CPUs, memory) are fetched on each call and reflect current state
  • OsInfo: Immutable after construction
  • SslInfo: Thread-safe. Uses configured Clock for certificate validity checks

Import Statements

// Build and Git info
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.info.InfoProperties;
import org.springframework.boot.info.InfoProperties.Entry;

// Java runtime info
import org.springframework.boot.info.JavaInfo;
import org.springframework.boot.info.JavaInfo.JavaVendorInfo;
import org.springframework.boot.info.JavaInfo.JavaRuntimeEnvironmentInfo;
import org.springframework.boot.info.JavaInfo.JavaVirtualMachineInfo;

// Process info
import org.springframework.boot.info.ProcessInfo;
import org.springframework.boot.info.ProcessInfo.MemoryInfo;
import org.springframework.boot.info.ProcessInfo.MemoryInfo.MemoryUsageInfo;
import org.springframework.boot.info.ProcessInfo.MemoryInfo.GarbageCollectorInfo;
import org.springframework.boot.info.ProcessInfo.VirtualThreadsInfo;

// OS info
import org.springframework.boot.info.OsInfo;

// SSL info
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.info.SslInfo.BundleInfo;
import org.springframework.boot.info.SslInfo.CertificateChainInfo;
import org.springframework.boot.info.SslInfo.CertificateInfo;
import org.springframework.boot.info.SslInfo.CertificateValidityInfo;
import org.springframework.boot.info.SslInfo.CertificateValidityInfo.Status;
import org.springframework.boot.ssl.SslBundles;

// Time handling
import java.time.Clock;
import java.time.Instant;
import java.time.Duration;

// Properties
import java.util.Properties;
import java.util.List;

// Nullable annotations
import org.jspecify.annotations.Nullable;