In most desktop applications, you can find the application's version number in an "About" dialog.
About dialog from Notepad++

The same information is useful in web applications. It helps users report problems, helps operators verify which build is deployed, and makes it much easier to confirm that a Service Worker or deployment pipeline picked up the newest version.
In this tutorial, we will expose build and Git information from a Spring Boot backend and an Angular frontend.
Spring Boot ¶
Spring Boot already knows how to expose build information. You only have to make that metadata available during the build.
If you use Maven, configure the Spring Boot Maven plugin to generate META-INF/build-info.properties and add the Git commit ID plugin so the build also creates git.properties.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
</plugin>
When the application starts, Spring Boot reads these files and creates BuildProperties and GitProperties beans automatically.
In the sample application, both beans are injected into the main controller, which exposes three small endpoints: one for build information, one for Git information, and one for the active and default profiles.
private final BuildProperties buildProperties;
private final GitProperties gitProperties;
private final Environment environment;
public Application(BuildProperties buildProperties, GitProperties gitProperties,
Environment environment) {
this.buildProperties = buildProperties;
this.gitProperties = gitProperties;
this.environment = environment;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@GetMapping("/build-info")
public BuildProperties buildInfo() {
return this.buildProperties;
}
@GetMapping("/git-info")
public GitProperties gitInfo() {
return this.gitProperties;
}
@GetMapping("/profile-info")
public Map<String, Object> profileInfo() {
return Map.of("activeProfiles",
String.join(",", this.environment.getActiveProfiles()), "defaultProfiles",
String.join(",", this.environment.getDefaultProfiles()));
}
If you only want to expose the version number, return just that value instead of the full object.
@GetMapping("/version")
public String version() {
return this.buildProperties.getVersion();
}
Only expose the information that is actually useful. A public version endpoint is often fine, but detailed Git metadata is usually more appropriate for developers and operators.
With Actuator ¶
If you already use Spring Boot Actuator, you can also expose build and Git metadata through the info endpoint.
Only the health endpoint is exposed over HTTP by default, so you have to opt in to the endpoints you need explicitly.
management.endpoints.web.exposure.include=health,info,env
management.info.git.mode=full
With this configuration in place, BuildProperties and GitProperties appear under /actuator/info.
Actuator also has an env endpoint, but it exposes much more than the active profile. If all you need is profile information, a dedicated endpoint like the one in the sample application is the better choice.
Angular ¶
Angular does not generate build metadata for you, but the CLI's environment files make it straightforward to add.
The sample application keeps its environment values in src/environments. The application code imports the base environment file, and the production build replaces that file with environment.prod.ts.
import {environment} from '../environments/environment';
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
The production environment file stores the values we want to surface in the UI.
import {Env} from './env';
export const environment: Env = {
production: true,
serverURL: 'http://localhost:8080',
version: '0.0.1',
buildTimestamp: 1773987576,
shortCommitId: '5145160',
commitId: '5145160dec08e87765640b59a1fba7d7a83d3500',
commitTime: 1773985782
};
The development environment file contains matching fields so the application can render the same UI in development mode.
export const environment: Env = {
production: false,
serverURL: 'http://localhost:8080',
version: 'DEVELOPMENT',
buildTimestamp: null,
shortCommitId: null,
commitId: null,
commitTime: null
};
Generating build metadata ¶
The sample project uses a small Node.js script that reads the package version, fetches the latest Git commit, and updates environment.prod.ts before the production build runs.
Install the Git helper library first:
npm install --save-dev simple-git
Then add a script like this:
const fs = require('node:fs/promises');
const path = require('node:path');
const packageJson = require('./package.json');
const {simpleGit} = require('simple-git');
const environmentProdPath = path.join(__dirname, 'src', 'environments',
'environment.prod.ts');
function replaceEnvironmentValue(content, key, value) {
return content.replace(new RegExp(`(${key}: )([^\n]+?)(,?)$`, 'm'),
(_, prefix, __, trailingComma) => `${prefix}${value}${trailingComma}`);
}
async function main() {
const git = simpleGit(__dirname);
const log = await git.log({maxCount: 1});
const latestCommit = log.latest;
if (!latestCommit) {
throw new Error('Unable to read the latest Git commit');
}
let environmentFile = await fs.readFile(environmentProdPath, 'utf8');
environmentFile = replaceEnvironmentValue(environmentFile, 'version',
`'${packageJson.version}'`);
environmentFile = replaceEnvironmentValue(environmentFile, 'buildTimestamp',
Math.floor(Date.now() / 1000));
environmentFile = replaceEnvironmentValue(environmentFile, 'shortCommitId',
`'${latestCommit.hash.slice(0, 7)}'`);
environmentFile = replaceEnvironmentValue(environmentFile, 'commitId',
`'${latestCommit.hash}'`);
environmentFile = replaceEnvironmentValue(environmentFile, 'commitTime',
Math.floor(new Date(latestCommit.date).getTime() / 1000));
await fs.writeFile(environmentProdPath, environmentFile);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The script writes these values into the production environment file:
versionbuildTimestampshortCommitIdcommitIdcommitTime
After that, the Angular application can read the values directly from the environment object.
readonly clientInfo: ClientInfo = {
version: environment.version,
buildTimestamp: environment.buildTimestamp ? environment.buildTimestamp * 1000 : null,
shortCommitId: environment.shortCommitId,
commitId: environment.commitId,
commitTime: environment.commitTime ? environment.commitTime * 1000 : null
};
readonly info$: Observable<{ build: BuildInfo, git: GitInfo, profile: ProfileInfo }> =
forkJoin({
build: this.httpClient.get<BuildInfo>(`${environment.serverURL}/build-info`),
git: this.httpClient.get<GitInfo>(`${environment.serverURL}/git-info`),
profile: this.httpClient.get<ProfileInfo>(`${environment.serverURL}/profile-info`)
});
And the template can 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>
As with the backend, only publish the metadata that helps your users or your operations workflow.
Automated Release ¶
The sample Angular project uses the npm version lifecycle to automate releases.
build-prod updates environment.prod.ts and runs the production build. The release-* scripts delegate to npm version, and the version lifecycle script rebuilds the application and stages the generated files before npm creates the release commit and tag.
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"build-prod": "node ./build-info-env.js && ng build --configuration production",
"serve-dist": "ws --hostname localhost -d dist/app/browser -p 1234 -o --log.format stats",
"release-patch": "npm version patch",
"release-minor": "npm version minor",
"release-major": "npm version major",
"version": "npm run build-prod && git add package.json src/environments/environment.prod.ts",
"format": "npx prettier --write ."
},
This gives you a clean release flow:
npm run release-patch
For minor and major releases, run one of these commands:
npm run release-minor
npm run release-major
If you ever want to bump the version number without creating the Git commit and tag that npm version normally generates, use npm version --no-git-tag-version ... instead. The npm version documentation covers that workflow in more detail.
This concludes the tutorial about exposing build and Git information in Spring Boot and Angular applications.
You can find the source code for all examples presented in this blog post on GitHub.