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 find in a menu some information about the version of the application.

About dialog from Notepad++

notepad++ about

This information is useful for the user if he wants to know if he runs the newest version. They are also useful for the developer when users send the version number together with error reports.

Exposing build information could also be useful for web applications. You, as the developer, could verify with a version number if the right package is installed on a production server. 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 works 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 are going to see 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, but first, we look at an example without Actuator.

Without Actuator

Spring Boot exposes build information like version number and build timestamp through the bean BuildProperties.

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 use 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, like version, group and artifact id. Spring Boot automatically reads this file during start-up and instantiates the BuildProperties singleton, that you can inject into any Spring-managed class.

  @Autowired
  private BuildProperties buildProperties;

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

Application.java

With a simple GET endpoint, you can expose the information. Only expose the information that you need. If you only need a version number, create a more slimmed-down GET endpoint.

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

Similar to build information, Spring Boot is also able to expose Git information, like branch name and commit id, if your project is under Git control. Similar to the 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.

  public GitProperties gitInfo() {
    return this.gitProperties;
  }

  @Autowired
  private Environment environment;

  @GetMapping("/profile-info")

Application.java


Another useful 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 they are 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. Maybe only expose the version number to all users and secure all other endpoints, so only the developers and operators of the service have access to.


With Actuator

With Actuator on the classpath, we have to configure even less, 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 by 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 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 has, by default, when you created the app with the CLI, 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 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 here the replace-in-file package that 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 then always now if you work 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 are going one step further and also expose Git information. For this, I install the simpleGit package. 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: 1700628854,
  shortCommitId: '60302d1',
  commitId: '60302d1d0670e687dbcac1bcb07a5debf3373583',
  commitTime: 1699039865
};

environment.prod.ts

You can then read these values in the TypeScript code

    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

<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 it to the user. Especially if this a JavaScript running on the public Internet, you should be careful what information you expose.

Automated Release

In this section, I 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 part 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",
    "release-patch": "npm --no-git-tag-version version patch && npm run build-prod"

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 create 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 find the source code for all the examples presented in this blog post on GitHub:
https://github.com/ralscha/blog2019/tree/master/build-info