Home | Send Feedback

Exposing build information of Spring Boot and Angular applications

Published: 12. June 2019  •  java, spring, javascript

In most applications that run on your computer, you can find information about the application's version in a menu, often in an "About" dialog.

About dialog from Notepad++

notepad++ about

This information is useful for users who want to know if they are running the newest version. It is also helpful for developers when users send the version number along with error reports.

Exposing build information can also be useful for web applications. As a developer, you can verify, using a version number, if the correct package is installed on a production server. This is especially useful if you are not in charge of installing applications in the production environment.

Another use case for exposing build information in web applications is to verify if a Service Worker is working correctly. By checking the version number, you can verify that the Service Worker loaded and cached the new version after an application update.

In the following tutorial, we will explore how to expose build information from a Spring Boot and Angular application.

Spring Boot

Spring Boot has built-in support for exposing build information; we only have to enable it. With Actuator on the classpath, almost everything is autoconfigured. However, let's first look at an example without Actuator.

Without Actuator

Spring Boot exposes build information, such as the version number and build timestamp, through the BuildProperties bean.

However, before you can access this information, you need to configure the build step and add the build-info goal to the spring-boot-maven-plugin if you are using Maven.

      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>build-info</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

pom.xml

For Gradle, check out the documentation:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-build-info

When you compile your application, you will find a new file in the target folder: target/classes/META-INF/build-info.properties.

This file contains information from the pom.xml, such as the version, group, and artifact ID. Spring Boot automatically reads this file during startup and instantiates the BuildProperties singleton, which you can inject into any Spring-managed class.

  @Autowired
  private BuildProperties buildProperties;

  @GetMapping("/build-info")
  public BuildProperties buildInfo() {
    return this.buildProperties;
  }

Application.java

You can expose this information with a simple GET endpoint. Only expose the information you need. If you only need the version number, create a more streamlined GET endpoint.

  @GetMapping("/version")
  public String version() {
     return this.buildProperties.getVersion();
  }

Similarly to build information, Spring Boot can also expose Git information, such as the branch name and commit ID, if your project is under Git control. Similar to build information, you need to configure your build system first.

In a Maven project, you need to add this plugin.

          <plugin>
              <groupId>io.github.git-commit-id</groupId>
              <artifactId>git-commit-id-maven-plugin</artifactId>
          </plugin>

pom.xml

See the official documentation for setting up a Gradle project.

This plugin creates the file target/classes/git.properties during build time, and Spring Boot reads it and creates the singleton GitProperties.

  @Autowired
  private GitProperties gitProperties;

  @GetMapping("/git-info")
  public GitProperties gitInfo() {
    return this.gitProperties;
  }

Application.java


Another useful piece of information in a Spring application is the active profile. For this, we don't need support from the build system; we can inject the Environment singleton and access the active profile with getActiveProfiles().

  @Autowired
  private Environment environment;

  @GetMapping("/profile-info")
  public Map<String, Object> profileInfo() {
    return Map.of("activeProfiles",
        String.join(",", this.environment.getActiveProfiles()), "defaultProfiles",
        String.join(",", this.environment.getDefaultProfiles()));
  }

Application.java

I would not recommend exposing all the information just because it is available. Only expose the information that you need. Also, make sure that you secure these endpoints in public-facing web applications. It makes very little sense to expose Git information to all users of your service. Perhaps only expose the version number to all users and secure all other endpoints, so only the developers and operators of the service have access.


With Actuator

With Actuator on the classpath, we have even less to configure, because this library autoconfigures specific endpoints for us.

Make sure that you configure your build system according to the description in the previous section. Actuator depends on the same files and beans.

With the proper build setup, we only have to expose specific Actuator endpoints with a setting in application.properties.

management.endpoints.web.exposure.include=health,info,env

You can now access these endpoints with http://..../actuator/info and http://..../actuator/env. Be careful with the /actuator/env endpoint, which not only contains information about the active profile but also other information about the running Spring environment. If you are only interested in the active profile, you are better off writing your own GET endpoint that exposes just this information.

I recommend exposing these endpoints only if you need the information during runtime, and you should secure them because they are not useful for all users of your service.

If you want to expose a version number to all users, it is better to create a GET endpoint that returns just the version number instead of all available build information and Git information like the /actuator/info endpoint does.

Angular

In an Angular client application, we don't have built-in support for exposing build information, but it's not too complicated to expose this information.

An Angular app, by default (when you create the app with the CLI), has environment files in src/environments. This folder usually contains a file for development, environment.ts, and one file for production, environment.prod.ts.

These files contain an environment object similar to this example:

export const environment = {
  production: true,
  serverURL: 'http://localhost:8080'
};

In your Angular application, you can import this file anywhere you want:

import {environment} from '../environments/environment';

app.component.ts

and access the information:

const version = environment.version;

Make sure that you always import the environment file without the suffix .prod. During a production build (ng build --configuration production), the Angular CLI replaces environment.ts with environment.prod.ts, so it's essential that you only reference the environment file in your source code.

              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],

angular.json

We can now leverage this environment system for exposing build-time information.


We create a new JavaScript file in the root of the project that reads the version number from package.json and writes this information into environment.prod.ts.

I'm using the replace-in-file package here, which you need to install first:

npm install replace-in-file -D
const replaceInFile = require('replace-in-file');
const packageJson = require('./package.json');
const packageVersion = packageJson.version;
const simpleGit = require('simple-git')();

replaceInFile.sync({
    files: './src/environments/environment.prod.ts',
    from: [/version: '.+'/, /buildTimestamp: \d+/],
  to: [`version: '${packageVersion}'`, `buildTimestamp: ${Math.floor(Date.now() / 1000)}`]
});

build-info-env.js

This script only works when the fields version and buildTimestamp exist in the environment object. Add some dummy values before you run the script.

Then, run the script from the shell with Node. I called my script build-info-env.js:

node ./build-info-env.js

Open environment.prod.ts and check if the values are applied correctly.

export const environment = {
  production: true,
  serverURL: 'http://localhost:8080',
  version: '0.0.1',
  buildTimestamp: 1560334669
};

I also recommend adding these fields to the development environment file, environment.ts. When you display the values in your application, you will always know if you are working on the development version or the production version.

export const environment = {
  production: false,
  serverURL: 'http://localhost:8080',
  version: 'DEVELOPMENT',
  buildTimestamp: null
};

Git information

Next, we will go one step further and also expose Git information. For this, I install the simpleGit package. It is a very convenient library if you need to run Git commands from Node.js applications.

npm install simpleGit -D

Then, add the following code to the build-info-env.js script. This code extracts the ID and time of the last commit and writes it into environment.prod.ts.

simpleGit.log(['--pretty=format:%h,%H,%at', '-n', '1'], (err, log) => handleGitLog(log, err));

function handleGitLog(log, err) {
  if (!err) {
    const response = log.latest.hash;
    const splitted = response.split(',');
    const gitInfo = {
      shortCommitId: splitted[0],
      commitId: splitted[1],
      commitTime: parseInt(splitted[2])
    };

    replaceInFile.sync({
      files: './src/environments/environment.prod.ts',
      from: [/shortCommitId: '.+'/, /commitId: '.+'/, /commitTime: \d+/],
      to: [`shortCommitId: '${splitted[0]}'`, `commitId: '${splitted[1]}'`, `commitTime: ${splitted[2]}`]
    });

  } else {
    console.log(err);
  }
}

build-info-env.js

Make sure that the three fields shortCommitId, commitId, and commitTime exist in environment.prod.ts with some dummy values before you start the script.

After you run the script with node ./build-info-env.js, the environment object looks like this.

import {Env} from './env';

export const environment: Env = {
  production: true,
  serverURL: 'http://localhost:8080',
  version: '0.0.1',
  buildTimestamp: 1735922531,
  shortCommitId: '942fd64',
  commitId: '942fd646a9950bfdc871ac60c3cfcca89ab755ea',
  commitTime: 1735922248
};

environment.prod.ts

You can then read these values in the TypeScript code:

  constructor(private readonly httpClient: HttpClient) {
    this.clientInfo = {
      version: environment.version,
      buildTimestamp: environment.buildTimestamp ? environment.buildTimestamp * 1000 : null,
      shortCommitId: environment.shortCommitId,
      commitId: environment.commitId,
      commitTime: environment.commitTime ? environment.commitTime * 1000 : null
    };
  }

app.component.ts

and display them to the user:

<div>
  <h1>Build-Info</h1>
</div>

<h2>Server</h2>
@if (info$ | async; as info) {
  <h3>Build Info</h3>
  <p>Name: <strong>{{info.build.name}}</strong></p>
  <p>Group: <strong>{{info.build.group}}</strong></p>
  <p>Artifact: <strong>{{info.build.artifact}}</strong></p>
  <p>Version: <strong>{{info.build.version}}</strong></p>
  <p>Time: <strong>{{info.build.time}}</strong></p>
  <h3>Git Info</h3>
  <p>Branch: <strong>{{info.git.branch}}</strong></p>
  <p>Commit Id: <strong>{{info.git.commitId}}</strong></p>
  <p>Short Commit Id: <strong>{{info.git.shortCommitId}}</strong></p>
  <p>Commit Time: <strong>{{info.git.commitTime}}</strong></p>
  <h3>Profile Info</h3>
  <p>Active: <strong>{{info.profile.activeProfiles}}</strong></p>
  <p>Default: <strong>{{info.profile.defaultProfiles}}</strong></p>
}

<hr>

  <h2>Client</h2>

  <h3>Build Info</h3>
  <p>Version: <strong>{{clientInfo.version}}</strong></p>
  <p>Build Time: <strong>{{clientInfo.buildTimestamp | date:'medium'}}</strong></p>

  <h3>Git Info</h3>
  <p>Commit Id: <strong>{{clientInfo.commitId}}</strong></p>
  <p>Short Commit Id: <strong>{{clientInfo.shortCommitId}}</strong></p>
  <p>Commit Time: <strong>{{clientInfo.commitTime | date:'medium'}}</strong></p>

app.component.html

As mentioned in the Spring Boot section, I would not expose all this information just because it is available. Only extract the information you need and want to present to the user. Especially if this is JavaScript running on the public internet, you should be careful about what information you expose.

Automated Release

In this section, I will show you an example of an automated build process for an Angular application.

The script build-info-env.js we created in the previous step reads the version number from the package.json file and writes it into the environment file. You could manually increase the version number in package.json every time you release a version. However, a more convenient way is to let npm do that. npm provides the version command that increases the version number. For example, npm version patch increments the third level of a version number (e.g., 1.0.1 -> 1.0.2). With npm version minor and npm version major, you increment the minor and major parts of the version number. See the official documentation for more information.

With this command, we can now automate the version number increment step. In the scripts section of package.json, we add a new script.

    "release-patch": "npm --no-git-tag-version version patch && npm run build-prod"

package.json

npm version automatically creates a tag if you run this command in a clean Git repository. Because we are going to change the environment file afterward, we don't want to create a Git tag here, so we disable this behavior with --no-git-tag-version.

The release-patch script calls the script build-prod after incrementing the version number. But before we start the production build, we want to run our build-info-env.js script, which injects information into the environment file.

For this purpose, we add a prebuild-prod script. You don't have to explicitly call the pre and post scripts because npm automatically calls these scripts before and after a specific task.

    "prebuild": "node ./build-info-env.js",
    "postbuild": "node ./git-tag.js",

package.json

In this example, when you run npm run build, npm first calls prebuild, then build, and lastly postbuild.

The script git-tag.js is responsible for committing the changed file into Git and creating a Git tag.

const packageJson = require('./package.json');
const packageVersion = packageJson.version;
const simpleGit = require('simple-git')();

simpleGit
     .add('./*')
     .commit(`release v${packageVersion}`)
     .tag(['-a', `v${packageVersion}`, '-m', `release v${packageVersion}`]);
   //.push('origin', 'master');

git-tag.js

Now you have a completely automated build process, and whenever you want to release a patch version, you issue the following command from the shell:

npm run release-patch

For releasing a minor or major version, you could also add scripts into package.json or type the command manually into the shell.

// Minor
npm --no-git-tag-version version minor && npm run build
// Major
npm --no-git-tag-version version major && npm run build

This concludes the tutorial about exposing build and Git information in Spring Boot and Angular applications.

You can find the source code for all the examples presented in this blog post on GitHub:
https://github.com/ralscha/blog2019/tree/master/build-info