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++
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>
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;
}
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>
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;
}
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()));
}
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';
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"
}
],
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)}`]
});
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);
}
}
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
};
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
};
}
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>
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"
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",
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');
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