Spring Boot Micrometer e Prometheus: Métricas do Actuator em Produção
Integração do Spring Boot Micrometer com Prometheus no Spring Boot 4: modelo de acesso do Actuator, scrape config, métricas HTTP, health probes e regras de alerta.

O Spring Boot Actuator expõe endpoints de saúde, métricas e diagnóstico, e o Micrometer é a fachada de métricas por trás deles. Adicionar a dependência micrometer-registry-prometheus disponibiliza um endpoint /actuator/prometheus que o Prometheus coleta em um intervalo fixo. Os exemplos abaixo usam Spring Boot 4.1 (a versão GA atual, com a 4.2 em release candidate), Micrometer 1.16 e o cliente Java do Prometheus 1.x.
A integração do Spring Boot Micrometer com o Prometheus leva três passos: adicionar spring-boot-starter-actuator e micrometer-registry-prometheus, incluir prometheus em management.endpoints.web.exposure.include e apontar um scrape_config do Prometheus para /actuator/prometheus. As métricas de JVM, HTTP, HikariCP e sistema são publicadas sem código extra.
Configuração do Spring Boot Micrometer: Actuator e o Registry do Prometheus
Dependências Maven Necessárias
O starter do Actuator habilita os endpoints e traz o Micrometer. O registry do Prometheus formata cada meter no formato de exposição do Prometheus. O starter do AspectJ só é necessário para as anotações @Timed e @Counted.
<!-- pom.xml -->
<!-- Actuator + Micrometer + Prometheus (Spring Boot 4.x) -->
<dependencies>
<!-- Spring Boot Actuator: monitoring endpoints, includes Micrometer -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Registry Prometheus (Prometheus Java client 1.x) -->
<!-- Exposes metrics in Prometheus format at /actuator/prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- AspectJ for @Timed and @Counted -->
<!-- Spring Boot 4 renamed spring-boot-starter-aop to spring-boot-starter-aspectj -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
</dependencies>Duas armadilhas de versão aparecem em tutoriais antigos. Desde o Micrometer 1.13 (Spring Boot 3.3), o micrometer-registry-prometheus é construído sobre o cliente Java do Prometheus 1.x; o código 0.x antigo fica no módulo depreciado micrometer-registry-prometheus-simpleclient, que a documentação do Micrometer para Prometheus apresenta apenas como solução provisória. E no Spring Boot 4, um pom.xml que ainda declara spring-boot-starter-aop deixa de resolver: o guia de migração do Spring Boot 4.0 o renomeia para spring-boot-starter-aspectj.
Configuração dos Endpoints do Actuator
Por padrão, apenas o endpoint health é exposto via HTTP. O endpoint /actuator/prometheus existe assim que o registry está no classpath, mas retorna 404 até ser adicionado à lista de exposição.
O Spring Boot 3.4 substituiu as chaves liga/desliga (management.endpoints.enabled-by-default, management.endpoint.<id>.enabled) por um modelo de acesso, e as propriedades antigas foram depreciadas. O padrão opt-in define o acesso padrão como none e concede read-only endpoint por endpoint:
# application.yml
# Actuator configuration for production (Spring Boot 3.4+ access model)
management:
endpoints:
# Every endpoint is inaccessible unless granted below
access:
default: none
web:
exposure:
# Endpoints exposed over HTTP
# health, info, prometheus are the minimum for monitoring
include: health,info,prometheus,metrics,env,loggers
base-path: /actuator
endpoint:
health:
access: read-only
show-details: when-authorized
show-components: when-authorized
info:
access: read-only
prometheus:
access: read-only
metrics:
access: read-only
env:
access: read-only
# Mask sensitive values for anonymous callers
show-values: when-authorized
loggers:
# unrestricted allows POST to change a log level at runtime
access: unrestrictedExposição e acesso são dois controles separados. exposure.include decide se um endpoint é mapeado via HTTP; access decide o que o endpoint permite depois de mapeado. Um arquivo de propriedades que ainda usa enabled: true continua funcionando com um aviso de depreciação, e esse é o momento de migrar.
A opção show-details: when-authorized mostra os detalhes de saúde apenas para usuários autenticados com o papel adequado. Os endpoints em si são protegidos por um SecurityFilterChain dedicado:
// Securing Actuator endpoints (Spring Boot 4, Spring Security 7)
package com.example.monitoring.config;
import static org.springframework.security.web.access.IpAddressAuthorizationManager.hasIpAddress;
import org.springframework.boot.security.autoconfigure.actuate.web.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration(proxyBeanMethods = false)
public class ActuatorSecurityConfig {
@Bean
SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
// Health and info public for load balancers
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
// Prometheus accessible from the internal network only
.requestMatchers(EndpointRequest.to("prometheus")).access(hasIpAddress("10.0.0.0/8"))
// Other endpoints restricted to admins
.anyRequest().hasRole("ACTUATOR_ADMIN")
)
.httpBasic(basic -> {})
.build();
}
}No Spring Boot 4, o EndpointRequest foi movido para org.springframework.boot.security.autoconfigure.actuate.web.servlet, como mostra a referência de endpoints do Actuator. A DSL Java AuthorizedUrl de authorizeHttpRequests não tem método hasIpAddress: a regra por IP passa por IpAddressAuthorizationManager.hasIpAddress, disponível desde o Spring Security 6.3.
Métricas Customizadas com Micrometer
Counters, Gauges e Timers Aplicacionais
O Micrometer oferece diversos tipos de métricas adequados a casos de uso distintos. Counters medem eventos cumulativos, gauges medem valores instantâneos e timers medem a duração de operações.
// Custom business metrics service
package com.example.monitoring.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Service;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
@Service
public class OrderMetricsService {
// Counter for orders created
private final Counter ordersCreatedCounter;
// Timer to measure processing duration
private final Timer orderProcessingTimer;
// Atomic value for pending orders gauge
private final AtomicInteger pendingOrdersCount = new AtomicInteger(0);
public OrderMetricsService(MeterRegistry registry) {
// Exported as orders_created_total: the _total suffix is added by the registry
this.ordersCreatedCounter = Counter.builder("orders.created")
.description("Total number of orders created")
.tag("channel", "web")
.register(registry);
// Timer with histogram buckets: percentiles computed server-side in PromQL
this.orderProcessingTimer = Timer.builder("orders.processing.duration")
.description("Order processing duration")
.publishPercentileHistogram()
.register(registry);
// Gauge linked to atomic value
// Read on each scrape
Gauge.builder("orders.pending.count", pendingOrdersCount, AtomicInteger::get)
.description("Number of orders pending processing")
.register(registry);
}
public void recordOrderCreated() {
ordersCreatedCounter.increment();
pendingOrdersCount.incrementAndGet();
}
public void recordOrderProcessed(Runnable processingLogic) {
// Measures execution duration
orderProcessingTimer.record(processingLogic);
pendingOrdersCount.decrementAndGet();
}
public <T> T recordOrderProcessedWithResult(Supplier<T> processingLogic) {
return orderProcessingTimer.record(processingLogic);
}
}Dois detalhes de nomenclatura importam com o registry do Prometheus. Os nomes de meters do Micrometer usam pontos e são convertidos para snake case (orders.processing.duration vira orders_processing_duration_seconds). Os counters recebem o sufixo _total automaticamente, então nomear um counter como orders.created.total só adiciona ruído.
publishPercentiles(0.5, 0.95, 0.99) calcula os percentis dentro de cada JVM, e esses valores não podem ser agregados entre instâncias. publishPercentileHistogram() publica buckets que o histogram_quantile() agrega sobre toda a frota, e é a opção certa quando o backend é o Prometheus.
Anotações @Timed e @Counted
Para evitar código repetitivo, o Micrometer disponibiliza anotações que instrumentam métodos via AspectJ.
// Automatic instrumentation with annotations
package com.example.monitoring.service;
import io.micrometer.core.annotation.Counted;
import io.micrometer.core.annotation.Timed;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private final PaymentValidator paymentValidator;
public PaymentService(PaymentValidator paymentValidator) {
this.paymentValidator = paymentValidator;
}
// @Timed creates a Timer
// Measures each call and publishes count, sum, max and histogram buckets
@Timed(
value = "payment.process.duration",
description = "Payment processing duration",
histogram = true
)
public PaymentResult processPayment(PaymentRequest request) {
// Call through another bean: the proxy intercepts it
paymentValidator.validate(request);
return executePayment(request);
}
// @Counted increments a counter on each call
// Useful for discrete events
@Counted(
value = "payment.refunds",
description = "Total number of refunds"
)
public void refundPayment(String transactionId) {
// Refund logic
}
}# application.yml
# Enables TimedAspect, CountedAspect and the other Micrometer annotation aspects
management:
observations:
annotations:
enabled: trueDesde o Spring Boot 3.2, declarar manualmente os beans TimedAspect e CountedAspect não é mais necessário: definir management.observations.annotations.enabled=true com spring-boot-starter-aspectj no classpath faz a autoconfiguração deles. A propriedade tem false como padrão, o que explica a maioria dos relatos do tipo "meu @Timed não faz nada".
As anotações @Timed e @Counted só funcionam em métodos públicos de beans Spring chamados através do proxy. Uma chamada de um método para outro na mesma classe ignora o proxy e não é instrumentada, e uma anotação em método privado é ignorada. Anotar um controller Spring MVC que já é instrumentado gera observações duplicadas.
Pronto para mandar bem nas entrevistas de Spring Boot?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Endpoints de Saúde Customizados
Health Indicators de Negócio
Os Health Indicators verificam o estado das dependências externas e dos componentes críticos do negócio. O Spring Boot já oferece indicadores padrão para bancos de dados, Redis e outros serviços comuns. No Spring Boot 4, a API de saúde fica em um módulo próprio, spring-boot-health, e Health e HealthIndicator passaram de org.springframework.boot.actuate.health para org.springframework.boot.health.contributor.
// Health indicator for payment gateway (Spring Boot 4 packages)
package com.example.monitoring.health;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import java.time.Duration;
import java.time.Instant;
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
private final RestClient restClient;
private final String gatewayHealthUrl;
public PaymentGatewayHealthIndicator(RestClient.Builder restClientBuilder) {
this.restClient = restClientBuilder.build();
this.gatewayHealthUrl = "https://api.payment-gateway.com/health";
}
@Override
public Health health() {
Instant start = Instant.now();
try {
// Call gateway health endpoint
var response = restClient.get()
.uri(gatewayHealthUrl)
.retrieve()
.toBodilessEntity();
Duration responseTime = Duration.between(start, Instant.now());
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("responseTime", responseTime.toMillis() + "ms")
.withDetail("statusCode", response.getStatusCode().value())
.build();
} else {
return Health.down()
.withDetail("statusCode", response.getStatusCode().value())
.withDetail("reason", "Unexpected status code")
.build();
}
} catch (Exception e) {
Duration responseTime = Duration.between(start, Instant.now());
return Health.down()
.withDetail("error", e.getClass().getSimpleName())
.withDetail("message", e.getMessage())
.withDetail("responseTime", responseTime.toMillis() + "ms")
.build();
}
}
}Esse indicador aparece em /actuator/health sob o nome paymentGateway: o nome do bean sem o sufixo HealthIndicator.
Grupos de Saúde para Kubernetes
O Spring Boot 4 habilita as probes de liveness e readiness por padrão, em todas as plataformas e não apenas quando detecta o Kubernetes. Os grupos /actuator/health/liveness e /actuator/health/readiness existem sem nenhuma configuração, e declarar manualmente beans LivenessStateHealthIndicator ou ReadinessStateHealthIndicator é desnecessário. A configuração só é necessária para adicionar componentes a um grupo ou definir grupos customizados:
# application.yml
# Health groups configuration for Kubernetes
management:
endpoint:
health:
probes:
# true by default in Spring Boot 4
enabled: true
# Also serve the groups at /livez and /readyz on the main server port
add-additional-paths: true
group:
# Readiness: can the application receive traffic?
readiness:
include: readinessState,db
# Custom group for critical dependencies
critical:
include: db,paymentGateway
show-details: when-authorizedO grupo de liveness deve ficar limitado a livenessState. Colocar um banco de dados ou um gateway externo na liveness faz o Kubernetes reiniciar todos os pods quando essa dependência cai, o que transforma uma indisponibilidade parcial em total. Dependências externas ficam na readiness, e só quando a aplicação não consegue atender nenhuma requisição sem elas.
As probes do Kubernetes então apontam para os endpoints dedicados:
# kubernetes-deployment.yml
# Kubernetes probes configuration
spec:
containers:
- name: order-service
livenessProbe:
httpGet:
path: /livez
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3Com add-additional-paths: true, as probes continuam na porta da aplicação mesmo quando o Actuator roda em uma porta de gerenciamento separada, de modo que a probe detecta um servidor principal com falha.
Integração do Spring Boot com Prometheus e Grafana
Configuração do Scraping no Prometheus
O Prometheus coleta as métricas consultando periodicamente o endpoint /actuator/prometheus. A configuração de scraping define os alvos e o caminho das métricas, já que o Prometheus usa /metrics por padrão.
# prometheus.yml
# Prometheus configuration for Spring Boot
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'spring-boot-apps'
metrics_path: '/actuator/prometheus'
scrape_interval: 10s
static_configs:
- targets:
- 'order-service:9090'
- 'payment-service:9090'
- 'inventory-service:9090'
# Relabeling to strip the port from the instance label
relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+):\d+'
replacement: '${1}'
# Kubernetes service discovery
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Only scrape pods with annotation
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)Os alvos usam a porta 9090 porque a configuração de produção mais abaixo move o Actuator para uma porta de gerenciamento separada. Sem management.server.port, o alvo do scraping é a porta da aplicação.
Métricas do Spring Boot Actuator Publicadas por Padrão
O Actuator com Micrometer expõe métricas de JVM, processo, HikariCP e HTTP sem nenhum código. Estas consultas PromQL cobrem as que são monitoradas primeiro em produção:
# PromQL queries for JVM monitoring
# Heap memory usage, summed across heap pools
sum by (instance) (jvm_memory_used_bytes{area="heap"})
# Heap usage percentage
sum by (instance) (jvm_memory_used_bytes{area="heap"})
/ sum by (instance) (jvm_memory_max_bytes{area="heap"}) * 100
# Live threads
jvm_threads_live_threads
# Garbage collection: seconds spent paused per second
rate(jvm_gc_pause_seconds_sum[5m])
# GC pauses per minute
rate(jvm_gc_pause_seconds_count[1m]) * 60
# CPU used by the JVM process
process_cpu_usage
# Active database connections
hikaricp_connections_active
# Connection pool utilization
hikaricp_connections_active / hikaricp_connections_max * 100jvm_memory_used_bytes{area="heap"} retorna uma série por pool de memória (Eden, Survivor, Old Gen com G1). Dividir pelo máximo sem sum by (instance) produz razões por pool que parecem erradas em um dashboard.
Um bean MeterBinder adiciona gauges de nível de aplicação que as métricas padrão não cobrem:
// Additional JVM metrics
package com.example.monitoring.metrics;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
@Component
public class CustomJvmMetrics implements MeterBinder {
@Override
public void bindTo(MeterRegistry registry) {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
// System load average
Gauge.builder("system.load.average", osBean, OperatingSystemMXBean::getSystemLoadAverage)
.description("System load average over 1 minute")
.register(registry);
// Available processors count
Gauge.builder("system.cpu.count", osBean, OperatingSystemMXBean::getAvailableProcessors)
.description("Number of available processors")
.register(registry);
// Application uptime
Gauge.builder("application.uptime.seconds",
ManagementFactory.getRuntimeMXBean(),
bean -> bean.getUptime() / 1000.0)
.description("Application uptime in seconds")
.register(registry);
}
}system.cpu.count e process.uptime já são publicados pelos binders nativos do Micrometer. O exemplo mostra o padrão, e um binder real deve registrar apenas o que falta nas métricas padrão.
Dashboards Grafana Prontos
O Grafana oferece dashboards da comunidade para Spring Boot que funcionam com as métricas padrão do Actuator. Um dashboard escrito à mão usa os tipos de painel timeseries e stat:
{
"annotations": {
"list": []
},
"panels": [
{
"title": "Request Rate",
"type": "timeseries",
"targets": [
{
"expr": "sum by (method, uri, status) (rate(http_server_requests_seconds_count{application=\"$application\"}[5m]))",
"legendFormat": "{{method}} {{uri}} - {{status}}"
}
]
},
{
"title": "Response Time P99",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.99, sum by (le, method, uri) (rate(http_server_requests_seconds_bucket{application=\"$application\"}[5m])))",
"legendFormat": "{{method}} {{uri}}"
}
]
},
{
"title": "Error Rate",
"type": "stat",
"targets": [
{
"expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\",status=~\"5..\"}[5m])) / sum(rate(http_server_requests_seconds_count{application=\"$application\"}[5m])) * 100"
}
]
}
]
}Muitos dashboards copiados de tutoriais antigos ainda usam os tipos de painel graph e singlestat. O Singlestat foi removido no Grafana 8, e o Grafana 12 removeu totalmente o suporte ao AngularJS, então esses painéis só são renderizados após a migração para timeseries e stat.
Para importar um dashboard da comunidade: Grafana > Dashboards > New > Import, e então informar o ID 4701 (JVM Micrometer) ou 12900 (SpringBoot APM). Vale conferir os tipos de painel após a importação: revisões antigas dos dashboards da comunidade ainda podem conter painéis Angular que o Grafana 12 precisa migrar.
Alertas com Prometheus
Regras de Alerta Essenciais
As regras de alerta do Prometheus disparam notificações quando as métricas ultrapassam limites críticos.
# alerting-rules.yml
# Alert rules for Spring Boot applications
groups:
- name: spring-boot-alerts
rules:
# Alert if application is down
- alert: ApplicationDown
expr: up{job="spring-boot-apps"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Application {{ $labels.instance }} is down"
description: "{{ $labels.instance }} has been down for more than 1 minute"
# Alert on HTTP error rate
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (application)
/
sum(rate(http_server_requests_seconds_count[5m])) by (application)
> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate on {{ $labels.application }}"
description: "Error rate is {{ $value | humanizePercentage }}"
# Alert on P99 latency, aggregated per application
- alert: HighLatency
expr: |
histogram_quantile(0.99,
sum by (le, application) (rate(http_server_requests_seconds_bucket[5m]))
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "High latency on {{ $labels.application }}"
description: "P99 latency is {{ $value | humanizeDuration }}"
# Heap memory alert
- alert: HighHeapUsage
expr: |
sum by (instance) (jvm_memory_used_bytes{area="heap"})
/ sum by (instance) (jvm_memory_max_bytes{area="heap"})
> 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "High heap memory usage on {{ $labels.instance }}"
description: "Heap usage is at {{ $value | humanizePercentage }}"
# Database connection pool exhausted alert
- alert: DatabaseConnectionPoolExhausted
expr: |
hikaricp_connections_active
/ hikaricp_connections_max
> 0.9
for: 2m
labels:
severity: critical
annotations:
summary: "Database connection pool nearly exhausted"
description: "{{ $value | humanizePercentage }} of connections in use"
# Excessive GC alert
- alert: HighGCPause
expr: |
rate(jvm_gc_pause_seconds_sum[5m])
/ rate(jvm_gc_pause_seconds_count[5m])
> 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "High GC pause time"
description: "Average GC pause is {{ $value | humanizeDuration }}"Esses alertas cobrem os problemas mais comuns em produção: disponibilidade, performance e recursos. A regra HighLatency precisa de sum by (le, ...): sem ele, o histogram_quantile calcula um P99 por série (por URI, método e status), e um único endpoint lento com três chamadas dispara o alerta.
Métricas HTTP e de Banco de Dados no Spring Boot
Instrumentação Automática de Requisições HTTP
O Spring Boot instrumenta toda requisição de entrada do Spring MVC e do WebFlux por meio da Observation API e publica o timer http.server.requests, com as tags method, uri, status, outcome e exception.
# application.yml
# HTTP metrics configuration
management:
metrics:
distribution:
# Histogram buckets for server-side percentiles
percentiles-histogram:
http.server.requests: true
# SLO buckets: count requests faster than each boundary
slo:
http.server.requests: 100ms, 500ms, 1s, 2s
tags:
# Common tags added to every meter
application: ${spring.application.name}
environment: ${spring.profiles.active:default}As interfaces WebMvcTagsContributor e WebMvcTagsProvider encontradas em muitos exemplos foram removidas no Spring Boot 3.0. Tags customizadas agora são adicionadas estendendo DefaultServerRequestObservationConvention, como documenta a referência de métricas do Spring Boot:
// Adds custom tags to http.server.requests (Spring Boot 3.0+)
package com.example.monitoring.config;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
@Component
public class TenantObservationConvention extends DefaultServerRequestObservationConvention {
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
HttpServletRequest request = context.getCarrier();
String userId = request.getHeader("X-User-Id");
String tenantId = request.getHeader("X-Tenant-Id");
// Keep the default tags and add two bounded ones
return super.getLowCardinalityKeyValues(context).and(
KeyValue.of("user.type", userId != null ? "authenticated" : "anonymous"),
KeyValue.of("tenant", tenantId != null ? tenantId : "default")
);
}
}Apenas key-values de baixa cardinalidade viram labels do Prometheus. Uma tag tenant é aceitável com algumas dezenas de tenants e vira um problema de cardinalidade com milhares. Valores de alta cardinalidade (ID de usuário, ID de pedido) pertencem a getHighCardinalityKeyValues, que alimenta apenas os traces. O lado de tracing da Observation API é detalhado em observabilidade do Spring Boot com OpenTelemetry.
Métricas HikariCP e de Consultas SQL
As métricas do pool do HikariCP são expostas automaticamente com o prefixo hikaricp, com tag pelo nome do pool, ao lado dos gauges genéricos jdbc.connections. Definir o nome do pool deixa a tag legível:
# application.yml
# HikariCP pool configuration
spring:
datasource:
hikari:
# Becomes the "pool" tag on hikaricp_* metrics
pool-name: OrderServicePool
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000O tempo por consulta não é coberto pelo Spring Boot. O projeto datasource-micrometer envolve o DataSource e cria uma observação por conexão, consulta e result set, o que gera tanto métricas quanto spans de trace. A linha 2.x é voltada ao Spring Boot 4, a linha 1.x ao Spring Boot 3:
<!-- pom.xml -->
<!-- JDBC observations: 2.x for Spring Boot 4, 1.x for Spring Boot 3 -->
<dependency>
<groupId>net.ttddyy.observation</groupId>
<artifactId>datasource-micrometer-spring-boot</artifactId>
<version>2.0.1</version>
</dependency>Isso substitui a abordagem manual de envolver o DataSource em um ProxyDataSourceBuilder com um listener escrito à mão, que contornava a autoconfiguração de DataSource do Spring Boot e precisava ser mantida em sincronia a cada atualização do Boot. Consultas lentas costumam indicar um padrão N+1, tratado em soluções para o problema N+1 no Spring Data JPA.
Boas Práticas para Produção
Cardinalidade das Métricas
Uma cardinalidade excessiva degrada o desempenho do Prometheus. Cada combinação única de tags gera uma série temporal distinta.
// AVOID: unbounded cardinality
package com.example.monitoring.antipattern;
@Service
public class AntiPatternHighCardinality {
private final MeterRegistry registry;
// BAD: userId creates one series per user
public void trackUserAction(String userId, String action) {
Counter.builder("user.actions")
.tag("userId", userId) // Millions of possible values
.tag("action", action)
.register(registry)
.increment();
}
}// Controlled cardinality
package com.example.monitoring.bestpractice;
@Service
public class GoodPracticeCardinality {
private final MeterRegistry registry;
// GOOD: user category instead of ID
public void trackUserAction(User user, String action) {
Counter.builder("user.actions")
.tag("userType", user.getSubscriptionType()) // FREE, PREMIUM, ENTERPRISE
.tag("action", action)
.register(registry)
.increment();
}
// GOOD: let a timer histogram bucket durations
public void trackResponseTime(long responseTimeMs) {
Timer.builder("response.time")
.publishPercentileHistogram()
.register(registry)
.record(Duration.ofMillis(responseTimeMs));
}
}Um Timer com buckets de histograma responde melhor às perguntas de latência do que um counter com uma tag bucket feita à mão: o PromQL consegue calcular qualquer percentil a partir dos buckets, e os limites dos buckets permanecem consistentes entre serviços.
Configuração Pronta para Produção
# application-production.yml
# Production configuration (Spring Boot 4)
management:
server:
# Separate port for management endpoints, not exposed publicly
port: 9090
endpoints:
access:
default: none
web:
exposure:
include: health,info,prometheus
endpoint:
health:
access: read-only
show-details: when-authorized
probes:
add-additional-paths: true
info:
access: read-only
prometheus:
access: read-only
observations:
annotations:
enabled: true
metrics:
distribution:
percentiles-histogram:
http.server.requests: true
minimum-expected-value:
http.server.requests: 1ms
maximum-expected-value:
http.server.requests: 30s
tags:
application: ${spring.application.name}
environment: production
version: ${app.version:unknown}Com access.default: none, endpoints como env, beans, configprops e mappings ficam inacessíveis sem precisar listá-los um a um. management.prometheus.metrics.export.enabled tem true como padrão, e uma configuração step não tem efeito no Prometheus: é um registry baseado em pull, e o intervalo de scraping é definido no prometheus.yml.
Erros Comuns com Actuator e Prometheus
| Sintoma | Causa | Correção |
|---|---|---|
/actuator/prometheus retorna 404 | Endpoint fora da lista de exposição, ou registry ausente | Adicionar prometheus em management.endpoints.web.exposure.include e verificar se micrometer-registry-prometheus está no classpath |
| O target do Prometheus mostra "server returned HTTP status 401" | O Spring Security protege todos os endpoints exceto health | Liberar prometheus para a rede do scraper ou configurar basic auth no scrape config |
@Timed não gera nenhuma métrica | management.observations.annotations.enabled é false por padrão, ou o AspectJ está ausente | Definir a propriedade como true e adicionar spring-boot-starter-aspectj |
spring-boot-starter-aop não encontrado após o upgrade | Renomeado no Spring Boot 4 | Usar spring-boot-starter-aspectj |
O import de Health ou HealthIndicator falha no Boot 4 | Pacote movido para org.springframework.boot.health.contributor | Atualizar os imports |
Avisos de depreciação em management.endpoint.*.enabled | Modelo liga/desliga substituído no Spring Boot 3.4 | Usar management.endpoints.access.default e management.endpoint.<id>.access |
| Percentis do dashboard muito diferentes da realidade | Valores de publishPercentiles com média entre pods | Usar publishPercentileHistogram() e histogram_quantile() |
Perguntas de Entrevista sobre Spring Boot Micrometer
O Actuator aparece na maioria das entrevistas de Spring Boot para vagas pleno e sênior. As respostas que separam um perfil experiente de um júnior:
- Diferença entre liveness e readiness: a liveness diz ao Kubernetes para reiniciar o pod, a readiness diz para parar de rotear tráfego para ele. Um candidato que coloca o banco de dados no grupo de liveness nunca passou por uma indisponibilidade com essa configuração.
- Por que Micrometer e não o cliente do Prometheus diretamente: o Micrometer é uma fachada. O mesmo código de
CountereTimerexporta para Prometheus, Datadog ou OTLP com a troca de uma dependência, e a instrumentação do próprio Spring (HTTP, pool JDBC, caches) é escrita sobre ele. - Percentis no cliente versus histogramas: percentis calculados no cliente não podem ser agregados entre instâncias, histogramas podem. É a pegadinha mais comum sobre o assunto.
- O que é cardinalidade e como ela derruba o Prometheus: uma série temporal por combinação única de labels; uma tag com ID de usuário multiplica o uso de memória pelo número de usuários.
- Como proteger o Actuator: limitar a exposição, definir níveis de acesso, rodar o gerenciamento em uma porta separada e protegê-lo com um
SecurityFilterChaindedicado.
Mais perguntas desse tipo estão reunidas no módulo de perguntas de entrevista sobre Spring Boot Actuator.
Fontes
- Spring Boot 4.0 Migration Guide:
spring-boot-starter-aoprenomeado paraspring-boot-starter-aspectj, probes habilitadas por padrão, novo módulospring-boot-health - Spring Boot 3.4 Release Notes:
enabled-by-defaulteendpoint.<id>.enableddepreciados em favor do modelo de acesso - Referência de endpoints do Spring Boot Actuator: propriedades de acesso, pacote do
EndpointRequest, health indicators emorg.springframework.boot.health.contributor, grupos de probes e caminhos adicionais - Referência de métricas do Spring Boot:
DefaultServerRequestObservationConvention, métricas do HikariCP ejdbc.connections, depreciação do simpleclient - Referência de observabilidade do Spring Boot:
management.observations.annotations.enablede a exigência do AspectJ - Documentação do Micrometer para Prometheus: registry sobre o cliente Java do Prometheus 1.x e o módulo legado simpleclient
- datasource-micrometer no GitHub: 2.x para Spring Boot 4, 1.x para Spring Boot 3
- Grafana: remoção do AngularJS no Grafana 12: painéis Graph e Singlestat substituídos por Time series e Stat
- Releases do Spring Boot: 4.1.2 como versão GA atual, 4.2.0-RC1 em pré-lançamento
O que Levar de Spring Boot Micrometer e Prometheus
- Três passos para um scraping funcional: starter do Actuator,
micrometer-registry-prometheus,prometheusna lista de exposição - Modelo de acesso desde a 3.4:
management.endpoints.access.default: nonemaisread-onlypor endpoint substitui as flagsenableddepreciadas - Mudanças do Spring Boot 4:
spring-boot-starter-aspectj, API de saúde emorg.springframework.boot.health.contributor,EndpointRequestemorg.springframework.boot.security.autoconfigure.actuate.web.servlet, probes ativas por padrão - Anotações precisam de uma chave:
management.observations.annotations.enabled=true, sem beanTimedAspectmanual - Tags HTTP via Observation API: estender
DefaultServerRequestObservationConvention, manter labels de baixa cardinalidade - Histogramas em vez de percentis no cliente:
publishPercentileHistogram()esum by (le)em todohistogram_quantile - A liveness fica local: dependências externas vão para a readiness ou para um grupo customizado, nunca para a liveness
Essas configurações dão a um serviço Spring Boot 4 métricas que agregam corretamente entre pods, endpoints que não vazam nada para a rede pública e alertas que disparam em incidentes reais, e não por causa de uma única URI lenta.
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Você saberia encontrar o bug em Spring Boot?
Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Escrito por
Anthony Fillion-MailletFundador da SharpSkill
Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.
Atualizado em 26 de outubro de 2026
Tags
Compartilhar
Artigos relacionados

Observabilidade no Spring Boot em 2026: OpenTelemetry, Tracing Distribuído e Perguntas de Entrevista
Guia completo sobre observabilidade no Spring Boot com OpenTelemetry e tracing distribuído. Configuração OTLP, Observation API, propagação de contexto e perguntas técnicas para entrevistas backend.

Logs estruturados no Spring Boot em 2026: JSON para produção com Logback e OpenTelemetry
Guia completo de logs estruturados no Spring Boot 4.x. Suporte JSON nativo, starter OpenTelemetry, tracing MDC e integração ELK Stack para observabilidade em produção.

Vagas freelance de desenvolvedor Java Spring Boot em 2026: onde encontrar contratos, valores diários e preparação
Guia completo sobre vagas freelance de Java Spring Boot em 2026: plataformas para encontrar contratos, valores diários na Europa, habilidades exigidas e preparação para entrevistas técnicas.