ScopedValue: O Sucessor Moderno do ThreadLocal no Java 21+
Com a chegada das Virtual Threads no Java 21, a escalabilidade atingiu um novo patamar, permitindo que aplicações gerenciem milhões de threads simultaneamente. No entanto, essa nova densidade expôs fraquezas em ferramentas veteranas, como o ThreadLocal. Para resolver esses problemas, o Java introduziu o ScopedValue (finalizado no Java 25 via JEP 506), uma funcionalidade fundamental para a concorrência moderna.
Neste artigo, vamos explorar por que o ThreadLocal se tornou um gargalo, como o ScopedValue oferece uma alternativa mais segura e eficiente, e como ele se integra ao ecossistema de Virtual Threads e Structured Concurrency.
📦 Repositório de referência: github.com/guigomes91/virtual-threads
O Problema: As Deficiências do ThreadLocal
O ThreadLocal permite que dados sejam armazenados localmente em uma thread, mas possui três falhas críticas de design que se agravam com Virtual Threads:
- Mutabilidade Descontrolada: Qualquer componente com acesso à variável pode chamar
set(), tornando o rastreio de alterações e o debug extremamente complexos. - Vazamentos de Memória (Memory Leaks): Os dados persistem enquanto a thread estiver viva, a menos que
remove()seja chamado explicitamente. Em ambientes de longa duração, esquecer de limpar essas variáveis causa vazamentos graves. - Herança Cara: Quando uma thread pai cria threads filhas, todas as variáveis
ThreadLocalsão copiadas para as novas threads. Com milhões de Virtual Threads, essa duplicação de dados esgota rapidamente o heap da JVM.
Exemplo do problema com ThreadLocal
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// ⚠️ Padrão problemático com ThreadLocal</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>RequestContext</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ThreadLocal&lt;String&gt; USER_ID = <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">new</span></span> <span class=<span class="hljs-string">"hljs-title class_"</span>>ThreadLocal</span>&lt;&gt;();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>handleRequest</span><span class=<span class="hljs-string">"hljs-params"</span>>(String userId)</span> {
USER_ID.set(userId);
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">try</span></span> {
processRequest();
} <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">finally</span></span> {
USER_ID.remove(); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// esqueceu isso? -&gt; memory leak</span></span>
}
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>processRequest</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Qualquer código pode chamar set() e corromper o estado</span></span>
USER_ID.set(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;hacker&quot;</span>); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// ⚠️ mutação silenciosa e perigosa</span></span>
System.out.println(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;Processando para: &quot;</span> + USER_ID.get());
}
}
A Solução: ScopedValue
O ScopedValue surge como uma alternativa de "via de mão única" para compartilhar dados imutáveis entre componentes e suas sub-tarefas de forma segura.
Os Pilares do ScopedValue
| Característica | ThreadLocal | ScopedValue |
|---|---|---|
| Mutabilidade | set() livre |
Imutável após where() |
| Tempo de vida | Lifetime da thread | Escopo da tarefa |
| Herança | Cópia completa (cara) | Referência compartilhada (grátis) |
| Compatibilidade | Pool de threads | Virtual Threads nativas |
| Risco de leak | Alto | Nenhum |
- Imutabilidade: Não existe o método
set(). Uma vez vinculado viawhere(), o valor permanece constante durante todo o escopo de execução. - Tempo de Vida Delimitado: O valor é vinculado apenas à execução de um método específico. Assim que a tarefa termina, o valor é invalidado automaticamente, eliminando o risco de memory leaks.
- Eficiência de Memória: Threads filhas (especialmente dentro de um
StructuredTaskScope) compartilham a mesma referência do valor da thread pai, sem cópias custosas.
Como Implementar na Prática
1. Declaração
Geralmente declarado como campo estático e final, com acesso restrito para garantir encapsulamento.
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>SecurityContext</span> {
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// private: apenas esta classe pode fazer bind e leitura</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;String&gt; USER_ID = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// public: expõe acesso de leitura para outros componentes de forma controlada</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> String <span class=<span class="hljs-string">"hljs-title function_"</span>>currentUserId</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> USER_ID.get();
}
}
2. Vinculação e Execução (Binding)
A vinculação define onde e por quanto tempo o valor estará disponível. É possível encadear múltiplos valores.
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Vinculando um único valor</span></span>
ScopedValue.where(USER_ID, <span class=<span class="hljs-string">"hljs-string"</span>>&quot;getcaramelo&quot;</span>)
.run(() -&gt; processarRequisicao());
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Encadeando múltiplos valores</span></span>
ScopedValue.where(USER_ID, <span class=<span class="hljs-string">"hljs-string"</span>>&quot;getcaramelo&quot;</span>)
.where(REQUEST_ID, UUID.randomUUID().toString())
.where(TENANT_ID, <span class=<span class="hljs-string">"hljs-string"</span>>&quot;acme-corp&quot;</span>)
.run(() -&gt; processarRequisicao());
3. Recuperação Segura
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Verificação antes do acesso</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">if</span></span> (USER_ID.isBound()) {
System.out.println(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;Usuário atual: &quot;</span> + USER_ID.get());
}
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Valor padrão caso não esteja vinculado (Java 25: orElse não aceita null)</span></span>
<span class=<span class="hljs-string">"hljs-type"</span>>String</span> <span class=<span class="hljs-string">"hljs-variable"</span>>userId</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> USER_ID.orElse(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;anonimo&quot;</span>);
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Lança exceção se não estiver vinculado</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">try</span></span> {
<span class=<span class="hljs-string">"hljs-type"</span>>String</span> <span class=<span class="hljs-string">"hljs-variable"</span>>id</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> USER_ID.get();
} <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">catch</span></span> (NoSuchElementException e) {
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// ScopedValue não foi vinculado neste escopo</span></span>
}
4. Retornando valores com call()
Além de run() (que retorna void), use call() quando precisar de um resultado:
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// run() -&gt; void</span></span>
ScopedValue.where(USER_ID, <span class=<span class="hljs-string">"hljs-string"</span>>&quot;getcaramelo&quot;</span>)
.run(() -&gt; salvarAuditoria());
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// call() -&gt; retorna valor</span></span>
<span class=<span class="hljs-string">"hljs-type"</span>>String</span> <span class=<span class="hljs-string">"hljs-variable"</span>>resultado</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> ScopedValue.where(USER_ID, <span class=<span class="hljs-string">"hljs-string"</span>>&quot;getcaramelo&quot;</span>)
.call(() -&gt; buscarPerfil());
ScopedValue em Fluxos Reais
Exemplo 1: TravelApp com Structured Concurrency
No cenário de um sistema de viagens, o ScopedValue organiza o fluxo de dados entre microserviços concorrentes de forma limpa, sem passar parâmetros por toda a pilha de chamadas.
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>TravelService</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;String&gt; LOC = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;String&gt; DEST = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> TravelOffer <span class=<span class="hljs-string">"hljs-title function_"</span>>fetchTravelOffers</span><span class=<span class="hljs-string">"hljs-params"</span>>(String origem, String destino)</span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">throws</span></span> Exception {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> ScopedValue.where(LOC, origem)
.where(DEST, destino)
.call(() -&gt; {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">try</span></span> (<span class=<span class="hljs-string">"hljs-type"</span>><span class="hljs-keyword">var</span></span> <span class=<span class="hljs-string">"hljs-variable"</span>>scope</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">new</span></span> <span class=<span class="hljs-string">"hljs-title class_"</span>>StructuredTaskScope</span>.ShutdownOnFailure()) {
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// As sub-tarefas herdam LOC e DEST automaticamente</span></span>
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// sem nenhuma cópia de memória</span></span>
<span class=<span class="hljs-string">"hljs-type"</span>><span class="hljs-keyword">var</span></span> <span class=<span class="hljs-string">"hljs-variable"</span>>ridesharing</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> scope.fork(<span class=<span class="hljs-string">"hljs-built_in"</span>><span class="hljs-built_in">this</span></span>::fetchRidesharing);
<span class=<span class="hljs-string">"hljs-type"</span>><span class="hljs-keyword">var</span></span> <span class=<span class="hljs-string">"hljs-variable"</span>>publicTransport</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> scope.fork(<span class=<span class="hljs-string">"hljs-built_in"</span>><span class="hljs-built_in">this</span></span>::fetchPublicTransport);
<span class=<span class="hljs-string">"hljs-type"</span>><span class="hljs-keyword">var</span></span> <span class=<span class="hljs-string">"hljs-variable"</span>>taxi</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> scope.fork(<span class=<span class="hljs-string">"hljs-built_in"</span>><span class="hljs-built_in">this</span></span>::fetchTaxi);
scope.join().throwIfFailed();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> buildOffer(
ridesharing.get(),
publicTransport.get(),
taxi.get()
);
}
});
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> RidesharingQuote <span class=<span class="hljs-string">"hljs-title function_"</span>>fetchRidesharing</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// LOC e DEST disponíveis sem parâmetros!</span></span>
<span class=<span class="hljs-string">"hljs-type"</span>>String</span> <span class=<span class="hljs-string">"hljs-variable"</span>>from</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> LOC.get();
<span class=<span class="hljs-string">"hljs-type"</span>>String</span> <span class=<span class="hljs-string">"hljs-variable"</span>>to</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> DEST.get();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> ridesharingApi.quote(from, to);
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> TransportQuote <span class=<span class="hljs-string">"hljs-title function_"</span>>fetchPublicTransport</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> transitApi.routes(LOC.get(), DEST.get());
}
}
Exemplo 2: Segurança em APIs Web (contexto de autenticação)
Padrão comum em frameworks web para propagar o usuário autenticado sem injeção de dependência manual.
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>SecurityFilter</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;Principal&gt; PRINCIPAL = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>doFilter</span><span class=<span class="hljs-string">"hljs-params"</span>>(HttpRequest request, FilterChain chain)</span> {
<span class=<span class="hljs-string">"hljs-type"</span>>Principal</span> <span class=<span class="hljs-string">"hljs-variable"</span>>principal</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> authenticate(request);
ScopedValue.where(PRINCIPAL, principal)
.run(() -&gt; chain.doFilter(request));
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Após o run(), PRINCIPAL é automaticamente invalidado</span></span>
}
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>OrderService</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> Order <span class=<span class="hljs-string">"hljs-title function_"</span>>createOrder</span><span class=<span class="hljs-string">"hljs-params"</span>>(OrderRequest req)</span> {
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Acessa o principal sem receber por parâmetro</span></span>
<span class=<span class="hljs-string">"hljs-type"</span>>Principal</span> <span class=<span class="hljs-string">"hljs-variable"</span>>user</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> SecurityFilter.PRINCIPAL.get();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">if</span></span> (!user.hasRole(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;CUSTOMER&quot;</span>)) {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">throw</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">new</span></span> <span class=<span class="hljs-string">"hljs-title class_"</span>>AccessDeniedException</span>(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;Acesso negado para: &quot;</span> + user.getName());
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> orderRepository.save(<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">new</span></span> <span class=<span class="hljs-string">"hljs-title class_"</span>>Order</span>(req, user.getName()));
}
}
Exemplo 3: Rastreamento distribuído (Tracing)
Propagação de traceId para logs estruturados em sistemas distribuídos.
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>TracingContext</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;String&gt; TRACE_ID = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;String&gt; SPAN_ID = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>withNewTrace</span><span class=<span class="hljs-string">"hljs-params"</span>>(Runnable task)</span> {
ScopedValue.where(TRACE_ID, UUID.randomUUID().toString())
.where(SPAN_ID, UUID.randomUUID().toString())
.run(task);
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> String <span class=<span class="hljs-string">"hljs-title function_"</span>>currentTraceId</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> TRACE_ID.orElse(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;sem-trace&quot;</span>);
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> String <span class=<span class="hljs-string">"hljs-title function_"</span>>currentSpanId</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">return</span></span> SPAN_ID.orElse(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;sem-span&quot;</span>);
}
}
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Uso em qualquer camada da aplicação</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>PaymentService</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> <span class=<span class="hljs-string">"hljs-type"</span>>Logger</span> <span class=<span class="hljs-string">"hljs-variable"</span>>log</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> LoggerFactory.getLogger(PaymentService.class);
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>processPayment</span><span class=<span class="hljs-string">"hljs-params"</span>>(Payment payment)</span> {
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Log automaticamente enriquecido com o trace</span></span>
log.info(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;[traceId={}] [spanId={}] Processando pagamento de {}&quot;</span>,
TracingContext.currentTraceId(),
TracingContext.currentSpanId(),
payment.amount());
}
}
Exemplo 4: Rebinding - Sobrescrita Controlada de Escopo
Em alguns cenários, uma chamada aninhada precisa de um valor diferente no mesmo ScopedValue. O rebinding cria um novo escopo sem afetar o escopo externo.
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;String&gt; ROLE = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>demonstrate</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
ScopedValue.where(ROLE, <span class=<span class="hljs-string">"hljs-string"</span>>&quot;USER&quot;</span>).run(() -&gt; {
System.out.println(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;Escopo externo: &quot;</span> + ROLE.get()); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// &quot;USER&quot;</span></span>
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Rebinding: cria um escopo interno com valor diferente</span></span>
ScopedValue.where(ROLE, <span class=<span class="hljs-string">"hljs-string"</span>>&quot;ADMIN&quot;</span>).run(() -&gt; {
System.out.println(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;Escopo interno: &quot;</span> + ROLE.get()); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// &quot;ADMIN&quot;</span></span>
});
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Escopo externo é restaurado automaticamente</span></span>
System.out.println(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;Escopo externo restaurado: &quot;</span> + ROLE.get()); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// &quot;USER&quot;</span></span>
});
}
Conexão com Virtual Threads: Por Que Isso Importa
O repositório guigomes91/virtual-threads demonstra como evitar thread pinning usando ReentrantLock em vez de synchronized. O ScopedValue é o par natural dessa estratégia: enquanto o ReentrantLock libera a carrier thread durante bloqueios, o ScopedValue garante que o contexto seja propagado corretamente para os milhões de Virtual Threads sem explosão de memória.
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// ✅ Padrão completo: Virtual Thread + ReentrantLock + ScopedValue</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>ModernRequestHandler</span> {
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">static</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> ScopedValue&lt;RequestContext&gt; CTX = ScopedValue.newInstance();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">final</span></span> <span class=<span class="hljs-string">"hljs-type"</span>>Lock</span> <span class=<span class="hljs-string">"hljs-variable"</span>>lock</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">new</span></span> <span class=<span class="hljs-string">"hljs-title class_"</span>>ReentrantLock</span>();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>handle</span><span class=<span class="hljs-string">"hljs-params"</span>>(HttpRequest request)</span> {
<span class=<span class="hljs-string">"hljs-type"</span>><span class="hljs-keyword">var</span></span> <span class=<span class="hljs-string">"hljs-variable"</span>>context</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> RequestContext.from(request);
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// 1. Propaga contexto imutável via ScopedValue</span></span>
ScopedValue.where(CTX, context).run(() -&gt; {
lock.lock(); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// 2. ReentrantLock evita pinning da Virtual Thread</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">try</span></span> {
processWithContext(); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// carrier thread é liberada durante I/O</span></span>
} <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">finally</span></span> {
lock.unlock();
}
});
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// 3. Contexto invalidado automaticamente após o run()</span></span>
}
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>processWithContext</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span> {
<span class=<span class="hljs-string">"hljs-type"</span>>RequestContext</span> <span class=<span class="hljs-string">"hljs-variable"</span>>ctx</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> CTX.get();
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// ... lógica de negócio usando o contexto</span></span>
}
}
Histórico de Evolução e Status no Java 25
| Java | JEP | Status |
|---|---|---|
| 20 | 429 | Incubator |
| 21 | 446 | 1º Preview |
| 22 | 464 | 2º Preview |
| 23 | 481 | 3º Preview |
| 24 | 487 | 4º Preview |
| 25 (LTS) | 506 | ✅ Finalizado |
A única mudança na finalização: ScopedValue.orElse() não aceita mais null como argumento. A API chegou estável, sem flags de preview.
Quando NÃO usar ScopedValue
ScopedValue não é um substituto direto para todo caso de ThreadLocal. Há cenários em que ThreadLocal ainda é apropriado:
- Estado mutável por design: Se você precisa acumular dados ao longo de uma thread (ex.: contadores, listas que crescem),
ThreadLocalainda faz sentido. - Integração com código legado: Frameworks como Spring e Hibernate usam
ThreadLocalinternamente. A migração deve ser gradual. - Escopos verdadeiramente abertos: Se o valor precisa sobreviver além do escopo da chamada de método (ex.: dados mantidos entre requisições na mesma thread de um pool),
ScopedValuenão é a ferramenta certa.
Conclusão
O ScopedValue não é apenas uma melhoria incremental, é um componente fundamental para a arquitetura de sistemas modernos em Java. Ao forçar a imutabilidade e restringir o tempo de vida dos dados ao escopo da tarefa, ele oferece a segurança e a leveza necessárias para que as Virtual Threads alcancem seu potencial máximo de vazão sem comprometer a estabilidade da memória.
Com o Java 25 (LTS) finalizando a API, não há mais razão para usar --enable-preview. É hora de considerar o ScopedValue como o padrão para propagação de contexto em qualquer código novo.
Referências
- JEP 506: Scoped Values (Final)
- JEP 444: Virtual Threads
- JEP 505: Structured Concurrency (Final)
- Repositório: guigomes91/virtual-threads
- Java Coding Problems, Second Edition — Anghel Leonard
Publicado por: Guilherme Gomes - 24/05/2026 17:27
Caramelo.dev