Você tem dashboards impecáveis. Traces no Jaeger, métricas no Grafana, logs indexados no Loki. Tudo verde. Mas a aplicação está lenta de um jeito que nenhum span consegue explicar. CPU a 80% no pod, latência subindo, e nenhum trace apontando o culpado. Isso aconteceu comigo mais de uma vez como Tech Leader, e a resposta que eu precisava não estava nas três ferramentas que eu já tinha.
O problema é que traces, métricas e logs respondem perguntas diferentes: onde a lentidão aconteceu, o que o sistema fez, e por que algo foi logado. Mas nenhum deles responde: qual método está consumindo CPU nesse momento? Pois bem, o OpenTelemetry acabou de lançar o 4o sinal que resolve exatamente isso, e o melhor de tudo: o coletor de dados já está no seu JDK desde o Java 11. Bora ver como funciona.
Você tem dashboards impecáveis. Traces no Jaeger, métricas no Grafana, logs indexados no Loki. Tudo verde. Mas a aplicação está lenta de um jeito que nenhum span consegue explicar. CPU a 80% no pod, latência subindo, e nenhum trace apontando o culpado. Isso aconteceu comigo mais de uma vez como Tech Leader, e a resposta que eu precisava não estava nas três ferramentas que eu já tinha.
O problema é que traces, métricas e logs respondem perguntas diferentes: onde a lentidão aconteceu, o que o sistema fez, e por que algo foi logado. Mas nenhum deles responde: qual método está consumindo CPU nesse momento? Pois bem, o OpenTelemetry acabou de lançar o 4o sinal que resolve exatamente isso, e o melhor de tudo: o coletor de dados já está no seu JDK desde o Java 11. Bora ver como funciona.
Os 3 sinais que você já conhece (e o gap que eles deixam)
O OpenTelemetry sempre dividiu a observabilidade em três sinais:
- Traces: o caminho de uma requisição pelo sistema. Responde "onde a lentidão aconteceu?" num nível de serviço e span.
- Metrics: contadores e medidas do estado do sistema. Responde "o que está acontecendo?" em termos de throughput, latência percentil e uso de recursos.
- Logs: eventos discretos com contexto. Responde "o que foi registrado nesse momento?" para debugging e auditoria.
O problema aparece num cenário específico: você abre o trace e vê um span que demorou 3 segundos. O span aponta para o seu serviço de pedidos. Mas dentro daquele span, qual método da JVM consumiu esses 3 segundos? Garbage collector? Um loop interno? Uma chamada de serialização? Os traces não chegam nesse nível. Métricas também não. Logs, idem.
Pra resolver isso, times precisavam de ferramentas separadas: Java Mission Control, async-profiler, YourKit, JProfiler. Ferramentas que ficavam fora do ecossistema OTel, com integração manual e correlação impossível com os outros três sinais. E aí você ficava com dois mundos separados: o mundo OTel e o mundo do profiler.
O 4o sinal veio para acabar com esse gap.
O que é o OTel Profiling e por que o JFR é a peça-chave
Em março de 2026, o OpenTelemetry lançou o sinal de Profiling em Public Alpha. A ideia central é simples: coletar amostras contínuas do que a JVM está fazendo (quais métodos estão na call stack naquele microsegundo) e exportar esses dados via OTLP para o mesmo backend que já recebe seus traces, métricas e logs.
Para Java, a fonte de dados é o Java Flight Recorder (JFR). E aqui está a parte que muita gente não sabe: o JFR não é uma ferramenta externa. Ele é parte da JVM padrão desde o Java 11, incluído no OpenJDK sem custo adicional de licença. Cada instância Java que você roda em produção já tem um profiler embarcado esperando para ser ligado.
O JFR coleta:
- Amostras de CPU (quais métodos estavam na stack a cada N milissegundos)
- Alocação de objetos na heap (quais classes estão alocando mais memória)
- Contention de locks (quais threads estão esperando por synchronized)
- Eventos de Garbage Collector
- Latência de I/O e operações de rede a nível de JVM
Com o SDK de Profiling do OTel, esses dados saem via OTLP direto pro Grafana Pyroscope, Polar Signals, ou qualquer backend que suporte o formato de profiling do OTel. E o mais importante: correlacionados com o trace_id. Você abre um trace lento e clica em "ver profile" do mesmo intervalo de tempo. Isso antes era impossível sem ferramentas proprietárias.
Como configurar na prática: Spring Boot + OTel Profiling + JFR
Bora para o que interessa. A configuração básica requer três coisas: habilitar o JFR no seu processo Java, adicionar o SDK de profiles ao classpath, e configurar o exportador OTLP. Veja como fica em um projeto Spring Boot com o Java Agent do OTel:
Dependências no pom.xml (o SDK ainda está em alpha, então use versão explícita):
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>1.48.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>opentelemetry-jfr-profiler</artifactId>
<version>1.48.0-alpha</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp-profiles</artifactId>
<version>1.48.0-alpha</version>
</dependency>
Agora o setup programático. Você registra o profiler no SDK junto com os outros providers de trace e metrics:
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.contrib.jfr.profiler.JfrProfilerProvider;
import io.opentelemetry.exporter.otlp.profiles.OtlpGrpcProfilesExporter;
public class OtelConfig {
public static OpenTelemetry initOtel() {
// Exportador de profiles via OTLP — mesmo endpoint dos traces
OtlpGrpcProfilesExporter profileExporter = OtlpGrpcProfilesExporter.builder()
.setEndpoint("http://otel-collector:4317")
.build();
// Provider JFR: configura amostragem a cada 20ms
JfrProfilerProvider jfrProvider = JfrProfilerProvider.builder()
.setProfilesExporter(profileExporter)
.setSamplingInterval(Duration.ofMillis(20)) // 50 amostras/s
.build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider()) // seu provider de traces existente
.setMeterProvider(meterProvider()) // seu provider de métricas existente
.setProfilerProvider(jfrProvider) // 4o sinal registrado aqui
.buildAndRegisterGlobal();
}
}
Num projeto com Spring Boot 3.x+, você pode registrar isso como um @Bean de configuração. O framework vai usar o SDK global automaticamente para todos os seus traces e spans:
@Configuration
public class ObservabilityConfig {
@Bean
public OpenTelemetry openTelemetry() {
return OtelConfig.initOtel();
}
}
Com essa configuração, o JFR começa a amostrar a stack de todas as threads a cada 20ms e exportar os profiles via OTLP. Se seu backend é o Grafana com Pyroscope ou Tempo, você consegue correlacionar um trace span diretamente com o flamegraph do profiler daquele intervalo de tempo.
Usando via Java Agent (zero-code para apps existentes)
Tem uma forma ainda mais fácil, sem tocar no código: usar o Java Agent do OTel com a extensão de profiling. Ideal para aplicações legadas ou em containers que você não quer modificar.
Baixe o agent com o contrib incluído e passe as flags na inicialização da JVM:
java \
-javaagent:opentelemetry-javaagent-all.jar \
-Dotel.service.name=meu-servico-pedidos \
-Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
-Dotel.jfr.enabled=true \
-Dotel.jfr.sampling.interval=20ms \
-jar meu-servico.jar
O parâmetro -Dotel.jfr.enabled=true liga o profiler JFR sem alterar uma linha de código. O agent cuida de tudo: inicializar o JFR recording, coletar as amostras e exportar via OTLP no mesmo fluxo dos traces.
Bora ver um exemplo de como fica o Dockerfile num ambiente de produção:
FROM eclipse-temurin:21-jre-alpine
COPY opentelemetry-javaagent-all.jar /app/otel-agent.jar
COPY target/meu-servico.jar /app/meu-servico.jar
ENV JAVA_OPTS="-javaagent:/app/otel-agent.jar \
-Dotel.service.name=${SERVICE_NAME} \
-Dotel.exporter.otlp.endpoint=${OTEL_ENDPOINT} \
-Dotel.jfr.enabled=true \
-Dotel.jfr.sampling.interval=20ms"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/meu-servico.jar"]
Simples assim. Sem dependência nova no código da aplicação. O JFR que já existe na JVM começa a alimentar o pipeline OTel automaticamente.
O que muda no seu dia a dia como dev Java
A mudança mais prática é que agora você tem uma linha do tempo unificada. Antes, o fluxo de debugging de um problema de performance era mais ou menos assim:
- Detectar pelo alerta de latência (metrics)
- Abrir o trace para entender o caminho da requisição (traces)
- Pegar timestamp do span problemático
- Conectar no pod e rodar async-profiler ou JFR manualmente no mesmo período
- Cruzar os dados na mão, na tentativa
Com o 4o sinal, o passo 4 e 5 somem. O backend OTel (Grafana Tempo + Pyroscope, por exemplo) exibe um botão "ver flame graph" direto no trace lento. Você clica, e o profiler te mostra qual método estava consumindo CPU naquele exato intervalo de tempo do span.
Casos de uso que realmente fazem diferença:
- CPU spike sem causa óbvia em trace: o flame graph mostra imediatamente se é GC, serialização JSON ou um loop interno ineficiente
- Comparação antes/depois de refatoração: dois profiles lado a lado mostram a diferença real em alocação de objetos
- Diagnóstico de thread contention: o JFR captura eventos de lock e mostra quais threads estão esperando por qual monitor
- Validação de migração para Virtual Threads: confirmar que não há thread pinning (synchronized bloqueando carrier thread) nas libs utilizadas
Nas empresas onde trabalhei com stack Java em produção, boa parte do tempo de investigação de incidentes ia para correlação manual de dados de profiler com traces. Com o 4o sinal, isso vira trabalho de segundos. Não é hype, é tempo de engenharia de volta para o que importa.
Quando não usar ainda:
- Ambientes com Java 11+ mas onde o overhead de profiling em 100% das requisições é inaceitável (ajuste o sampling interval ou use recording sob demanda)
- Backends OTel que ainda não suportam o formato de profiles alpha (verifique compatibilidade do Grafana Pyroscope e do seu Tempo antes)
- Produção crítica sem teste prévio em staging, já que é Public Alpha (o formato do sinal ainda pode mudar antes do GA)
Por que isso importa para entrevistas e progressão de carreira
Observabilidade saiu de "diferencial" para "requisito" nas vagas sênior em 2025 e 2026. Mas tem um detalhe que os JDs não falam claramente: a maioria das empresas ainda mede observabilidade nos três sinais. Poucos times chegaram no 4o.
Saber configurar e usar OTel Profiling em produção, correlacionar flamegraphs com traces e extrair insights de JFR coloca você num percentil pequeno de desenvolvedores. Não porque é difícil (como você acabou de ver, não é), mas porque pouquíssimas pessoas sabem que existe.
Nos próximos 12 meses, com o sinal saindo de alpha para GA, isso vai aparecer em requisitos de vagas da mesma forma que "configurar OpenTelemetry" aparece hoje. Early adopter tem vantagem.
Pra quem já trabalha com observabilidade na equipe: mostrar que você implementou o 4o sinal antes do time pedir é exatamente o tipo de iniciativa que diferencia sênior de pleno. O detalhe de ter correlação trace-profile num incidente real vai aparecer de forma natural numa review.
FAQ — Perguntas frequentes
O OTel Profiling funciona com qualquer JVM ou só com HotSpot?
O JFR é nativo da JVM HotSpot (OpenJDK, Oracle JDK, GraalVM no modo JVM padrão). GraalVM Native Image não suporta JFR, então o profiler OTel via JFR não funciona em executáveis nativos. Para apps Quarkus ou Spring Native, outras abordagens de profiling são necessárias.
Qual o overhead do JFR em produção?
O JFR foi desenhado para ter overhead mínimo, tipicamente abaixo de 1-2% de CPU com configuração de profiling contínuo. Em comparação, profilers de instrumentação de bytecode tradicionais podem chegar a 10-30% de overhead. O intervalo de amostragem de 20ms é um bom equilíbrio; em aplicações muito sensíveis a latência, você pode aumentar para 50ms ou 100ms.
Preciso de licença comercial para usar o JFR?
Não. Desde o Java 11 (OpenJDK), o JFR é open source e gratuito. Antigamente era uma feature paga do Oracle JDK. Hoje, qualquer distribuição OpenJDK 11+ inclui o JFR sem custo adicional de licença.
O OTel Profiling está pronto para produção?
Está em Public Alpha, o que significa que a API e o formato do sinal ainda podem mudar antes do GA. O SDK funciona, mas evite dependência de versão fixa em sistemas de missão crítica sem plano de atualização. Para ambientes de staging e desenvolvimento, use sem receio.
Quais backends suportam o formato de profiles do OTel hoje?
Grafana Pyroscope é o mais maduro na integração com OTel Profiles. Polar Signals também tem suporte nativo. O Grafana Tempo tem integração trace-profile ainda em desenvolvimento. Verifique a documentação do seu backend antes de subir em produção.
Links e referências
- OpenTelemetry Profiles Enters Public Alpha (blog oficial, março 2026)
- OTel Profiling Signal — documentação oficial
- Mais artigos Java no Meu Universo Nerd
- Como configurar OpenTelemetry do zero com Spring Boot
- Observabilidade moderna com Java: traces, métricas e logs
Você já usa OTel no seu stack? Ou ainda está no fase de "vou configurar isso um dia"? Conta nos comentários como está sua observabilidade hoje e se o 4o sinal resolve algum problema real que você tem no dia a dia.
Na próxima semana a gente vai falar do Arconia Framework, um projeto que unifica Micrometer e OpenTelemetry no Spring Boot numa única dependência. Apresentado no Spring I/O 2026, é outra peça que a maioria dos times ainda não conhece.