Exposing JVM metrics over Actuator in Spring Boot 4 

Most times, to investigate business issues in our enterprise Java apps, we often configure first class logging which is invaluable for us to find what’s going wrong in our application. But some issues are not visible. Issues like high memory usage, CPU spikes, thread blocking etc. These notorious issues creep into our applications and often become what we call “the silent killers” to our high throughput applications.

It is imperative that developers are aware of business issues via standard logging as well as JVM related issues. For this, we need a way to peek inside the JVM at run time periodically and get real time updates of its state. Failing which, you may wake up on a Sunday morning and find your application is hung up on prod – not an ideal way to start your week, is it? To avoid such unpleasant scenarios, we need a way to expose the JVM metrics periodically and be able to expose, query and visualise the values. If the values exceed a certain limit, we must raise an alert to our support teams to take necessary actions.

At a high level, there are several ways to do this and two broad methods: the pull model and the push model. In the pull model, an external process polls the metrics from our app. As you guessed it – in push, the app transmits the data over the network. Now the question which may be running through your mind is: how do we export these metrics and what is this process which collects metrics and queries over the exposed data. We searched for the answer for the first question in this article. For the second one, we may have an article in the near future.

Understanding the Spring Boot Actuator

To expose metrics of a Spring Boot application, there exists a native way offered by the Spring team. In the Spring Initializr dropdown, you’ll find a dependency called spring boot actuator.

Actuator is an API offering in Spring Boot applications which is used for exposing application JVM metrics over HTTP endpoints. This is ready-made and eliminates the need for developers to write these boilerplate REST APIs themselves. Actuator exposes 10+ endpoints out of the box. Some of these endpoints are loggers, health, info, heapdump, threaddump etc. We will explore all the important endpoints in this article. We will also create our own custom endpoint in actuator extending its core features. If this sounds interesting to you, to make it better , at the end we will develop a sample Spring Boot 4 application using Java 25 (which is the latest Java LTS version at the time of writing this article) and create a memory issue in it. Using actuator we will procure the application’s heapdump on the fly and analyse the issue in a tool called HeapHero. The scope of this article is exposing the JVM metrics. Monitoring the metrics and visualising them is a separate topic altogether.

What metrics does the Actuator expose?

The Actuator works using a tool called Micrometer under the hood. It exposes application JVM metrics like cpu usage, heap usage, thread information, server disk space, application metadata, log levels, Spring beans related information, tomcat session information, open process files and more. These metrics often serve as the first signal when diagnosing issues such as sudden CPU spikes in Spring Boot applications, before deeper analysis is performed.

The primary basic offering for getting started with Actuator is the health API. It returns the status of our application as UP/DOWN. Using application performance issues like spike CPU usage, peak memory, high thread spawning. We can also toggle log level in a live production application without JVM restart. We can carry out operations like fetching thread and heap dumps by invoking a simple REST API. Actuator will auto recognise the dependent services of our application like database, kafka etc and provide their status in the overall health as well. As you can see, for the above features actuator is clearly a hero in Java production Spring ecosystem due to the features it offers out of the box. These features make Actuator a powerful tool for monitoring Java applications in today’s world. Operations’ teams across many top organisations in the world poll metrics over actuator and visualise them in readily available dashboards.

Spring Boot Actuator metrics configuration

Introducing and configuring Actuator in our Spring Boot JVM application is a cakewalk. We just add the below dependency in our pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

You will see this log in the application console:

2026-01-01T20:54:17.377+05:30  INFO 5098 --- [test-app] [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'

Lets head to the browser and hit the actuator endpoint : http://localhost:8080/actuator

You will see an output like:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "health": {
      "href": "http://localhost:8080/actuator/health",
      "templated": false
    },
    "health-path": {
      "href": "http://localhost:8080/actuator/health/{*path}",
      "templated": true
    }
  }
}

Let’s hit the health endpoint : http://localhost:8080/actuator/health

The output shows the status as up:

{
  "groups": [
    "liveness",
    "readiness"
  ],
  "status": "UP"
}

By default, actuator does not expose all endpoints due to security concerns. It is a standard practice to expose only a few API endpoints declaratively in application.properties/ yaml file. The actuator needs to be well secured and authenticated since it can expose critical information to anyone who has access to it. In the wrong hands of a hacker, an actuator if not well protected could expose application metadata and provide extra clues and ideas about the system.

For learning purposes, let’s expose all the endpoints. Add the below property in your app:

management.endpoints.web.exposure.include=*

The output in browser after hitting the actuator endpoint would be:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8080/actuator/beans",
      "templated": false
    },
    "conditions": {
      "href": "http://localhost:8080/actuator/conditions",
      "templated": false
    },
    "configprops": {
      "href": "http://localhost:8080/actuator/configprops",
      "templated": false
    },
    "configprops-prefix": {
      "href": "http://localhost:8080/actuator/configprops/{prefix}",
      "templated": true
    },
    "env-toMatch": {
      "href": "http://localhost:8080/actuator/env/{toMatch}",
      "templated": true
    },
    "env": {
      "href": "http://localhost:8080/actuator/env",
      "templated": false
    },
    "info": {
      "href": "http://localhost:8080/actuator/info",
      "templated": false
    },
    "logfile": {
      "href": "http://localhost:8080/actuator/logfile",
      "templated": false
    },
    "loggers-name": {
      "href": "http://localhost:8080/actuator/loggers/{name}",
      "templated": true
    },
    "loggers": {
      "href": "http://localhost:8080/actuator/loggers",
      "templated": false
    },
    "threaddump": {
      "href": "http://localhost:8080/actuator/threaddump",
      "templated": false
    },
    "sbom": {
      "href": "http://localhost:8080/actuator/sbom",
      "templated": false
    },
    "sbom-id": {
      "href": "http://localhost:8080/actuator/sbom/{id}",
      "templated": true
    },
    "scheduledtasks": {
      "href": "http://localhost:8080/actuator/scheduledtasks",
      "templated": false
    },
    "mappings": {
      "href": "http://localhost:8080/actuator/mappings",
      "templated": false
    },
    "health": {
      "href": "http://localhost:8080/actuator/health",
      "templated": false
    },
    "health-path": {
      "href": "http://localhost:8080/actuator/health/{*path}",
      "templated": true
    },
    "metrics": {
      "href": "http://localhost:8080/actuator/metrics",
      "templated": false
    },
    "metrics-requiredMetricName": {
      "href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
      "templated": true
    }
  }
}

Understanding the endpoints exposed by Spring Boot Actuator

/info : Information like application name, version, git commit id, development team email etc is exposed over this endpoint

/health : overall health of the application

/loggers : Exposes the log levels of all classes inside this application. By using a POST API, we can mutate the log level of a specify class to debug an issue and later restore it back to its former log level

/threaddump : performs thread dump and downloads the file

/mappings : exposes all the information about REST endpoints inside this application, typically created by developers

/beans : all the beans loaded by this application and the interrelationship among them

/metrics  : list of all metrics it can expose. Simply pick a metric and append in the URL like a path variable to expose its value

/scheduledtasks : any scheduled application crons created programmatically using @Scheduled

/env : information related to environment variables

/configprops : the properties configured by the developer in application.properties/yaml files. 

Config props and environment variables are masked by default, to view them  use:

management.endpoint.configprops.show-values=ALWAYS
management.endpoint.env.show-values=ALWAYS

Let’s code an interesting case study about actuators. For simulation purposes, we create a simple program that caches some data which should be evicted later. There are multiple mechanisms of caching, I am going for a simple in-memory concurrent hashmap. It will store a key and value – both of type String. Ideally these entries of the cache must be evicted after an expiry period/timeout duration. For bug demonstration purposes, assume the cache is never getting evicted and the map grows inevitably. In high load applications, the cache will quickly get populated and grow uncontrollably, leading to high memory consumption. For this I am going to write some threads that will simulate concurrent user requests and dump data into this cache. As more and more time passes, the high number of requests are going to fill the map with millions of entries. The memory footprint of this map will be quite large, making it one of the “dominator objects”. If the map size approaches the -Xmx value of the program, the application will slow down and ultimately crash with an out of memory error once the value reaches the threshold -Xmx set.

Below code represents the setup for above intended scenario:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.LongStream;

@Component
public class TestProgram {
    
    AtomicLong atomicLong = new AtomicLong(0);
    Map<String, String> map = new ConcurrentHashMap<>();

    @Scheduled(fixedRate = 1)
    public void scheduled() {
        long i = atomicLong.getAndIncrement();
        LongStream.range(i, i + 1000)
                .parallel()
                .forEach(j -> map.put("key" + i, "value" + i));
    }

    @Scheduled(fixedRate = 1000)
    public void scheduled2() {
        System.out.println("Map size : " + map.size());
    }
}

As you can see from the above JSON response, the heapdump is not present by default. It needs to be explicitly allowed. We can do that by adding the property:

management.endpoint.heapdump.access=read_only

Now you see an extra endpoint been exposed:

Fig: Enabling the heapdump actuator endpoint 

Let’s hit this endpoint in our browser. We notice a file gets downloaded. This file is the heapdump of our application. This fill contains all answers to our memory related questions. This file is not human readable (unless you are an expert). Let’s leverage a visualisation tool HeapHero to see what is wrong in our application. Psst : We already know about that hashmap not getting its keys evicted, but this tool will do some wonders on its neat looking UI. Refer this article to know more about memory leaks

At around 100k entries in the map, I triggered the heapdump by invoking the API : http://localhost:8080/actuator/heapdump in my browser. On uploading the file on heaphero, we get the report:

Fig: Heapdump analysis report showing a heap size of 28MB

Fig: Heapdump analysis report pointing to the hashmap we created

Fig: Pie chart showing 50% of heap is used by our hashmap alone

Conclusion

In modern enterprise Java applications, application logs alone are no longer sufficient to diagnose production issues. While logs help explain what the application is doing, they fail to reveal how the JVM itself is behaving under load. Problems such as memory leaks, excessive thread creation, CPU saturation, and GC pressure typically remain invisible.

Spring Boot Actuator provides a production-ready mechanism to expose JVM and application metrics with no minimum or almost no effort. With a few application changes, developers gain deep runtime visibility into heap usage, thread states, CPU metrics, application health, and internal Spring metadata. The ability to dynamically inspect metrics, change log levels, and trigger thread or heap dumps over HTTP makes Actuator an invaluable tool in the Spring ecosystem.

The case study demonstrated how a tiny harmless in-memory cache can quietly evolve into a serious memory issue. By exposing the /heapdump endpoint and analyzing the dump using HeapHero, we were able to identify the suspect ConcurrentHashMap as a dominant object retaining memory indefinitely. This highlights that Actuator does not just help detect problems, it also significantly reduces the time required to understand their root cause. In summary, Spring Boot Actuator is the backbone of monitoring enterprise Java applications and we cannot imagine a Spring Boot application not running with Actuator configured today.

One thought on “Exposing JVM metrics over Actuator in Spring Boot 4 

Add yours

Share your Thoughts!

Up ↑

Index

Discover more from yCrash

Subscribe now to keep reading and get access to the full archive.

Continue reading