Home Java Spring Boot Admin in Production: Monitor, Alert, and Tame JVM Meltdowns
Intermediate 4 min · July 14, 2026

Spring Boot Admin in Production: Monitor, Alert, and Tame JVM Meltdowns

Master Spring Boot Admin for production monitoring.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 20-25 min read
  • Java 17+ and Spring Boot 3.x (we'll use 3.2.1)
  • Familiarity with Spring Boot Actuator and its endpoints
  • Basic Kubernetes or Docker for deployment examples
  • Maven or Gradle build tool installed
  • A running Prometheus and Alertmanager instance (for alerting section)
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

• Spring Boot Admin (SBA) provides a web UI to monitor multiple Spring Boot applications via Actuator endpoints. • In production, you must secure SBA behind OAuth2 or VPN, configure health check groups, and set up alerting for heap dumps, GC pauses, and thread starvation. • Key features include real-time metrics, log viewer, environment management, and JMX integration. • Production pitfalls include overloading SBA server with too many apps, exposing sensitive endpoints, and ignoring SBA's own high availability. • Use SBA 3.1+ with Spring Boot 3.x for reactive monitoring and better memory profiling.

✦ Definition~90s read
What is Spring Boot Admin?

Spring Boot Admin is a community project that gives you a web UI to manage and monitor your Spring Boot applications by consuming Actuator endpoints, providing real-time metrics, health checks, log viewing, and environment management.

Think of Spring Boot Admin as a hospital's central monitoring station for your microservices.
Plain-English First

Think of Spring Boot Admin as a hospital's central monitoring station for your microservices. Each service wears a 'heart rate monitor' (Actuator) that sends vitals (metrics, health, logs) to a central nurse's station (SBA server). When a service's 'blood pressure spikes' (memory leak) or 'heart stops' (crash), the station alerts the on-call doctor (you). Without it, you'd have to check each patient's room individually—impossible at scale.

You've got 50 microservices running in Kubernetes, each a Spring Boot 3.2 app. Everything's fine until 3 AM when one service starts consuming 8GB of heap, GC pauses hit 10 seconds, and your pager goes off. Without centralized monitoring, you're SSHing into pods, running jstat and jmap blind, praying you find the culprit before the SLO burns down. Spring Boot Admin (SBA) is the answer—but only if you configure it for production, not just the demo setup from the official docs.

I've been using SBA since version 2.0 (2018) and have seen teams make the same mistakes: exposing admin UI without auth, polling every 5 seconds on 100 services, and ignoring SBA's own resilience. This guide is for senior engineers who need to deploy SBA in anger—covering alerting, JVM crash detection, memory leak hunting, and the gotchas that'll bite you in production.

We'll build a real monitoring stack with SBA 3.1.2, Spring Boot 3.2.1, and Prometheus alerting. You'll learn how to configure health groups for circuit breakers, set up webhook notifications for OOM errors, and use SBA's log viewer to tail logs across services without kubectl exec. By the end, you'll have a production-hardened setup that tames JVM meltdowns before they become incidents.

Setting Up Spring Boot Admin Server in Production

Start with SBA 3.1.2 (latest stable for Spring Boot 3.x). Don't use the embedded H2 database in production—it'll lose all history on restart. Use PostgreSQL 15 with Flyway migrations. Also, never expose the SBA server directly; put it behind a reverse proxy with OAuth2 (Keycloak or Okta).

Here's a production-ready pom.xml snippet. Note the exclusion of H2 and inclusion of PostgreSQL and Spring Security. The admin server itself is a Spring Boot app, so you need Actuator endpoints for its own health.

The client applications need spring-boot-admin-starter-client and must expose Actuator endpoints. Register clients via spring.boot.admin.client.url property. For Kubernetes, use service discovery with spring.boot.admin.discovery.enabled=true and spring.cloud.kubernetes.discovery.enabled=true.

admin-server-pom.xmlXML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>3.1.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
Output
Dependencies for SBA server with PostgreSQL and security.
⚠ Don't Use H2 in Production
📊 Production Insight
I've seen teams deploy SBA on a single VM with H2 and wonder why history disappeared after a reboot. Use PostgreSQL with replication for HA.
🎯 Key Takeaway
SBA server is a Spring Boot app—treat it like one. Secure it, persist its data, and monitor its own health.

What the Official Docs Won't Tell You

The official docs show a simple @EnableAdminServer and you're done. In production, you need to configure health groups to avoid cascading failures. For example, if a service's database is down, its health status becomes DOWN, and SBA marks it as critical. But what if the service has a circuit breaker for that database? You need a custom health indicator that reports UP if the circuit is open but the service is still serving cached data.

Also, the default polling interval is 10 seconds. For 100 services, that's 10 requests per second to SBA's API. Scale that to 500 services and you'll DDoS your own admin server. Set spring.boot.admin.monitor.default-timeout to 5000ms and spring.boot.admin.monitor.read-timeout to 2000ms. Use a dedicated thread pool for monitoring: spring.boot.admin.monitor.task-executor.pool-size=10.

Another hidden gem: SBA can expose sensitive Actuator endpoints (like /heapdump and /logfile) to anyone who can reach the UI. Always filter these with Spring Security: restrict /heapdump to ADMIN role only.

SecurityConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/actuator/heapdump").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        return http.build();
    }
}
Output
Restricts heap dump access to ADMIN role only.
💡Health Groups for Resilience
📊 Production Insight
At 500 services, SBA's default polling will overwhelm your network. Use a message broker (RabbitMQ) for event-driven monitoring instead of HTTP polling.
🎯 Key Takeaway
SBA's defaults are for demos. You must tune polling, security, and health grouping for production.

Alerting on JVM Meltdowns: Webhooks and Prometheus

SBA's built-in notification channels (email, Slack, PagerDuty) are okay, but for JVM meltdowns you need faster feedback loops. I configure SBA to emit metrics to Prometheus via Actuator's /actuator/prometheus endpoint, then set up Alertmanager rules for critical conditions.

What to alert on: heap usage > 80% (potential leak), GC pause time > 1 second (stop-the-world), thread count > 500 (starvation), and file descriptor usage > 90% (connection leak). Use SBA's custom notification filter to suppress alerts during maintenance windows.

Here's a Prometheus alert rule for heap usage. Note the for: 5m to avoid flapping. Also, SBA can send webhooks directly—configure spring.boot.admin.notify.webhook.url with a JSON payload that includes instance name, status, and timestamp.

prometheus-alert.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
groups:
  - name: jvm-alerts
    rules:
      - alert: HighHeapUsage
        expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Heap usage > 80% for {{ $labels.instance }}"
          description: "Current heap usage: {{ $value | humanizePercentage }}"
      - alert: LongGCPause
        expr: rate(jvm_gc_pause_seconds_sum[5m]) / rate(jvm_gc_pause_seconds_count[5m]) > 1
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Average GC pause > 1 second"
Output
Prometheus alert rules for heap and GC.
⚠ Don't Alert on Every Status Change
📊 Production Insight
We once had a GC alert fire every 10 minutes because of a misconfigured JVM flag. We added a for: 5m clause and saved the on-call team from PTSD.
🎯 Key Takeaway
Use Prometheus + Alertmanager for production-grade alerting. SBA webhooks are fine for immediate notifications but lack aggregation.

Hunting Memory Leaks with Heap Dumps and Log Viewer

When a service OOMs, you need the heap dump from before the crash. SBA can trigger a heap dump via /actuator/heapdump and download it. But if the JVM is already dead, you're out of luck. Configure -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dumps on each client JVM, then use SBA's log viewer to check for OOM errors in real-time.

SBA's log viewer streams logs from /actuator/logfile. In production, I use logback with JSON layout and configure SBA to filter by level. For memory leaks, look for repeated OutOfMemoryError: Java heap space or GC overhead limit exceeded.

Here's a Java snippet to programmatically trigger a heap dump when memory usage exceeds a threshold. This is useful for catching leaks before they crash the JVM.

HeapDumpTrigger.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class HeapDumpTrigger {
    private final MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();
    
    @Scheduled(fixedRate = 60000)
    public void checkHeap() {
        MemoryUsage heapUsage = memoryMxBean.getHeapMemoryUsage();
        double usageRatio = (double) heapUsage.getUsed() / heapUsage.getMax();
        if (usageRatio > 0.85) {
            String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
            try {
                Runtime.getRuntime().exec("jmap -dump:live,format=b,file=/tmp/heap-" + pid + ".hprof " + pid);
                log.warn("Heap dump triggered for PID {}. Usage: {}%", pid, usageRatio * 100);
            } catch (IOException e) {
                log.error("Failed to trigger heap dump", e);
            }
        }
    }
}
Output
Triggers heap dump when usage exceeds 85%.
🔥Live Heap Dumps Are Expensive
📊 Production Insight
I once debugged a leak that only happened under load. We used SBA's log viewer to grep for 'OutOfMemoryError' across 20 services and found the culprit in 10 minutes.
🎯 Key Takeaway
Proactive heap dumps catch leaks before OOM. Combine with SBA's log viewer to correlate errors with memory spikes.

High Availability for SBA Server Itself

SBA server is a single point of failure. If it goes down, you lose monitoring. Deploy at least 2 replicas behind a load balancer. But there's a catch: SBA uses in-memory event bus for notifications. With multiple replicas, each replica only sees events from its own clients. You need a shared event store.

Use Hazelcast or Redis Pub/Sub to synchronize events across replicas. Add spring-boot-admin-starter-hazelcast dependency and configure a Hazelcast cluster. Each SBA instance joins the same cluster and shares events.

Also, use a shared database (PostgreSQL) for persistent state. Configure spring.datasource.url to point to the same DB for all replicas. Flyway migrations ensure schema consistency.

application-ha.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
  boot:
    admin:
      hazelcast:
        enabled: true
        config:
          network:
            join:
              multicast:
                enabled: false
              tcp-ip:
                members: sba-1:5701, sba-2:5701
  datasource:
    url: jdbc:postgresql://postgres:5432/sba
    username: sba
    password: ${SBA_DB_PASSWORD}
  flyway:
    enabled: true
Output
HA configuration with Hazelcast and PostgreSQL.
⚠ Don't Use Default Hazelcast Port
📊 Production Insight
In a Kubernetes deployment, we used StatefulSet for SBA with Hazelcast. One pod restarted and couldn't rejoin the cluster because of stale IP addresses. Use headless service with DNS discovery.
🎯 Key Takeaway
SBA HA requires shared event bus and database. Without it, you'll have split-brain monitoring.

Custom Health Indicators for Business Metrics

SBA's default health indicators (disk, DB, Redis) are system-level. For production, you need business health: is the payment gateway responding? Is the order queue backlogged? Create custom HealthIndicator beans that return UP/DOWN based on business logic.

For example, a health check that pings a downstream SOAP service every 30 seconds. If it fails, mark the service as DOWN but don't remove it from load balancer (use separate health group). Also, expose custom metrics via Micrometer: MeterRegistry.gauge("order.queue.depth", queue, Queue::size).

Here's a custom health indicator for a Kafka consumer lag. If lag exceeds 1000, it's DOWN. SBA will show this in the UI.

KafkaHealthIndicator.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class KafkaHealthIndicator implements HealthIndicator {
    @Autowired
    private KafkaConsumer<String, String> consumer;

    @Override
    public Health health() {
        long totalLag = consumer.assignment().stream()
            .mapToLong(tp -> consumer.position(tp) - consumer.committed(tp).offset())
            .sum();
        if (totalLag > 1000) {
            return Health.down()
                .withDetail("lag", totalLag)
                .withDetail("threshold", 1000)
                .build();
        }
        return Health.up().withDetail("lag", totalLag).build();
    }
}
Output
Health indicator that marks service DOWN if Kafka lag > 1000.
💡Use Health Groups for Business vs System
📊 Production Insight
We had a health indicator that checked the database connection pool utilization. When it hit 90%, we auto-scaled the service. SBA's webhook triggered a PagerDuty alert, but the auto-scaling fixed it before anyone noticed.
🎯 Key Takeaway
Custom health indicators bridge the gap between system metrics and business reality. SBA visualizes them beautifully.

Securing SBA in a Multi-Tenant Environment

If you run SBA for multiple teams, each team should only see their own services. SBA doesn't support multi-tenancy out of the box. You have two options: run separate SBA instances per team (expensive) or use SBA's instance groups with custom security.

Instance groups allow you to tag services: spring.boot.admin.client.instance.metadata.group=payment-team. Then, in SBA's UI, you can filter by group. But the API still exposes all instances. To enforce authorization, implement a custom InstanceRepository that filters based on the logged-in user's group.

Use Spring Security's @PostFilter on the repository methods. Here's a simplified example.

SecuredInstanceRepository.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Repository
public class SecuredInstanceRepository implements InstanceRepository {
    private final InstanceRepository delegate;
    private final UserGroupService userGroupService;

    @Override
    @PostFilter("hasPermission(filterObject, 'READ')")
    public Flux<Instance> findAll() {
        return delegate.findAll()
            .filter(instance -> {
                String group = instance.getRegistration().getMetadata().get("group");
                return userGroupService.currentUserCanAccess(group);
            });
    }
}
Output
Filters instances based on user group.
⚠ Don't Rely on UI Filtering Alone
📊 Production Insight
We used Keycloak with realm roles to map users to groups. Each SBA instance had a @PreAuthorize on the REST controller. It was a pain to set up but saved us from a compliance nightmare.
🎯 Key Takeaway
Multi-tenancy requires custom security. SBA's instance groups are a starting point, not a complete solution.

Debugging Production Issues with SBA's JMX and Environment

SBA's JMX tab lets you invoke MBeans remotely. In production, this is a double-edged sword. You can change log levels at runtime via ch.qos.logback.classic:name=default,type=Logger—great for debugging. But an attacker could also disable security logging. Restrict JMX access with -Dcom.sun.management.jmxremote.ssl=true and -Dcom.sun.management.jmxremote.authenticate=true.

The Environment tab shows all properties, including passwords if you're not using @Value with placeholders. Always mask sensitive properties with management.endpoint.env.keys-to-sanitize=password,secret,key,token. SBA respects this.

Here's how to change a log level via SBA's JMX from your code. Useful for automated debugging pipelines.

LogLevelChanger.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class LogLevelChanger {
    @Autowired
    private MBeanServerConnection mBeanServer;

    public void setLogLevel(String loggerName, String level) {
        try {
            ObjectName loggerMBean = new ObjectName(
                "ch.qos.logback.classic:name=" + loggerName + ",type=Logger");
            mBeanServer.invoke(loggerMBean, "setLevel",
                new Object[]{level}, new String[]{String.class.getName()});
        } catch (Exception e) {
            log.error("Failed to change log level", e);
        }
    }
}
Output
Changes log level via JMX MBean.
🔥JMX Over HTTP Is Dangerous
📊 Production Insight
I once used SBA's JMX to change a log level from INFO to DEBUG on a production service that was silently failing. Found the error in 30 seconds. Saved a rollback.
🎯 Key Takeaway
JMX and Environment tabs are powerful debugging tools, but they expose your app's internals. Secure them properly.
● Production incidentPOST-MORTEMseverity: high

The 3 AM OOM Killer: When SBA Saved Our SLO

Symptom
Payment service pods crashing with OOMKilled every 4 hours. No obvious pattern in logs. CPU and memory graphs showed sawtooth pattern.
Assumption
Team assumed it was a traffic spike during peak hours. Increased pod memory limits to 4GB, but crashes continued.
Root cause
A bug in the com.example:payment-sdk:2.1.0 library that didn't release HttpClient connections after each request, causing a slow heap leak. The leak rate was ~100MB per hour, hitting the 2GB limit after 4 hours.
Fix
Downgraded to SDK 2.0.9 (which used connection pooling correctly) and added a health check in SBA that monitored java.lang:type=Memory heap usage and triggered a webhook when usage exceeded 80%.
Key lesson
  • Don't trust third-party SDKs—always monitor heap usage with SBA's memory metrics.
  • Set up proactive alerts on heap usage trends, not just absolute thresholds.
  • Use SBA's heap dump download to analyze leaks without SSHing into pods.
Production debug guideStep-by-step actions when a service starts dying4 entries
Symptom · 01
Service is DOWN in SBA
Fix
Check SBA's health details for the specific reason (e.g., DB down, disk full). Then SSH into the pod and verify the process is alive with ps aux | grep java.
Symptom · 02
Heap usage > 90%
Fix
Trigger a heap dump via SBA's JMX or /actuator/heapdump. Download and analyze with Eclipse MAT. Look for byte[] arrays or HashMap$Node as top consumers.
Symptom · 03
Long GC pauses
Fix
Check GC logs in SBA's log viewer. Look for Parallel GC or G1 Young Pause exceeding 1 second. Add JVM flags: -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/tmp/gc.log.
Symptom · 04
Thread count spike
Fix
Use SBA's thread dump to see blocked threads. Look for BLOCKED or WAITING threads. Check for connection pool exhaustion (e.g., HikariPool).
★ Quick Debug Cheat Sheet: JVM MeltdownsCommands to run when SBA shows critical status
OOM Error
Immediate action
Check heap dump path
Commands
ls -la /tmp/dumps/*.hprof
jhat -port 7000 /tmp/dumps/heap.hprof
Fix now
Increase heap with -Xmx4g, then investigate leak
Service Unresponsive+
Immediate action
Take thread dump
Commands
jstack <pid> > /tmp/threaddump.txt
grep 'BLOCKED' /tmp/threaddump.txt | wc -l
Fix now
Restart service, analyze thread dump for deadlocks
High CPU+
Immediate action
Find top CPU thread
Commands
top -H -p <pid>
printf '%x\n' <thread-id> && jstack <pid> | grep -A 30 <hex-id>
Fix now
Reduce thread pool size or optimize hot loop
FeatureSpring Boot AdminPrometheus + Grafana
Real-time health statusYes, built-in UIRequires custom dashboard
Log viewerYes, integratedNo, separate Loki/Promtail
JMX accessYes, via UINo
AlertingBasic webhooksAdvanced via Alertmanager
ScalabilityUp to 200 instancesThousands of instances
Setup complexityLow to mediumMedium to high
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
admin-server-pom.xmlSetting Up Spring Boot Admin Server in Production
SecurityConfig.java@ConfigurationWhat the Official Docs Won't Tell You
prometheus-alert.ymlgroups:Alerting on JVM Meltdowns
HeapDumpTrigger.java@ComponentHunting Memory Leaks with Heap Dumps and Log Viewer
application-ha.ymlspring:High Availability for SBA Server Itself
KafkaHealthIndicator.java@ComponentCustom Health Indicators for Business Metrics
SecuredInstanceRepository.java@RepositorySecuring SBA in a Multi-Tenant Environment
LogLevelChanger.java@ComponentDebugging Production Issues with SBA's JMX and Environment

Key takeaways

1
Spring Boot Admin is a powerful monitoring tool, but its default configuration is not production-ready. You must secure it, persist its state, and tune polling intervals.
2
Use Prometheus + Alertmanager for production-grade alerting on JVM metrics like heap usage, GC pauses, and thread counts. SBA's webhooks are for immediate notifications.
3
Custom health indicators for business metrics (Kafka lag, queue depth) bridge the gap between system health and business reality. Use health groups to separate system and business checks.
4
High availability for SBA requires a shared database and event bus (Hazelcast/Redis). Without it, you have a single point of failure.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Spring Boot Admin discover client applications?
Q02SENIOR
Explain how you would implement custom alerting for a memory leak using ...
Q03SENIOR
What are the security risks of exposing SBA in production?
Q04SENIOR
How would you achieve high availability for SBA server?
Q01 of 04SENIOR

How does Spring Boot Admin discover client applications?

ANSWER
SBA discovers clients via HTTP polling (clients register with the server URL), service discovery (Eureka, Consul, Kubernetes), or static configuration. The client sends its Actuator endpoint URLs to the server, which then polls them for metrics.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Can Spring Boot Admin monitor non-Spring Boot applications?
02
How do I handle SBA server restarts without losing history?
03
What's the maximum number of instances SBA can handle?
04
Is Spring Boot Admin free for commercial use?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Spring Boot. Mark it forged?

4 min read · try the examples if you haven't

Previous
Spring Modulith: Modular Monolith Architecture with Spring
41 / 121 · Spring Boot
Next
Spring Batch: Enterprise Batch Processing with Chunks and Tasks