Java 26: o que são Lazy Constants e por que elas aposentam o double-checked locking (JEP 526)

Published: (April 3, 2026 at 01:28 PM EDT)
6 min read
Source: Dev.to

Source: Dev.to

O que é o JEP 526 do Java 26?

O JEP 526 introduz a classe java.lang.LazyConstant: um contêiner que guarda um valor imutável, inicializado no máximo uma vez.

VersãoNome da featureObservação
Java 25Stable Values (JEP 502)API mais verbosa, ainda em preview
Java 26Lazy Constants (JEP 526)API simplificada, segunda preview

O problema que ela resolve é antigo:

  • final garante imutabilidade, mas exige inicialização imediata.
  • Campos não‑final permitem inicialização tardia, mas perdem as garantias de concorrência.

LazyConstant combina os dois mundos: você inicializa quando quiser, mas apenas uma vez, com a mesma segurança de um final.

Por que o double‑checked locking existe e qual é o problema?

Todo desenvolvedor Java já escreveu algo assim:

public class MetricRegistry {

    private static volatile MetricRegistry instance;

    public static MetricRegistry getInstance() {
        if (instance == null) {
            synchronized (MetricRegistry.class) {
                if (instance == null) {
                    instance = new MetricRegistry();
                }
            }
        }
        return instance;
    }
}

Funciona, mas exige:

  • volatile
  • Dois testes de null
  • Um bloco synchronized

Quem lê o código pela primeira vez costuma ficar confuso. A alternativa com classe interna estática é mais segura, mas ainda é um work‑around. Nenhuma dessas opções é uma abstração de primeira classe para o problema real: “inicialize isso uma vez, quando precisar, de forma segura”.

Como o LazyConstant funciona na prática?

Imagine um serviço de pagamento com um cliente HTTP caro de criar, que só faz sentido existir se a rota for chamada:

public class PaymentService {

    private final LazyConstant httpClient =
        LazyConstant.of(() -> HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5))
            .build());

    public String charge(String orderId, BigDecimal amount) {
        HttpClient client = httpClient.get();   // primeira chamada cria o cliente
        // usa o client para chamar o gateway

    }
}
  • O HttpClient é criado na primeira chamada a httpClient.get().
  • Nas chamadas subsequentes a get(), a JVM devolve o mesmo valor (pode até aplicar constant‑folding).
  • Thread‑safe por construção, sem synchronized nem volatile.

O que mudou do Java 25 para o Java 26 no JEP 526?

  • Java 25 – API com métodos de baixo nível (setOrThrow, trySet, orElseSet), transformando o recurso em um primitivo de sincronização.
  • Java 26 – Mantém apenas LazyConstant.of(supplier). Você fornece a função de inicialização e a JVM cuida do resto.

Factories “lazy” para coleções

As factory methods para listas e mapas lazy mudaram de lugar, saindo de StableValue para List e Map diretamente:

// Lista lazy com 10 slots, cada um inicializado de forma independente
List processors = List.ofLazy(10, _ -> new OrderProcessor());

// Mapa lazy com chaves definidas, valores inicializados sob demanda
Map limiters = Map.ofLazy(
    Set.of("payments", "refunds", "reports"),
    key -> RateLimiter.create(requestsPerSecond(key))
);

Cada slot da lista e cada entry do mapa tem seu próprio ciclo de inicialização. O slot payments pode existir sem que refunds tenha sido criado.

Qual é a diferença entre LazyConstant e um campo final normal?

CaracterísticafinalLazyConstant
Momento da inicializaçãoNo construtor (imediato)Na primeira chamada a .get()
Necessidade de sincronizaçãoNão (já é thread‑safe)Internamente thread‑safe
Custo de criaçãoPago sempre, mesmo que nunca usadoPago somente se for usado
FlexibilidadeValor deve estar pronto na construçãoPode depender de recursos que ainda não existem
// final obriga inicialização imediata
private final ExpensiveClient client = new ExpensiveClient(); // roda no construtor

// LazyConstant inicializa na primeira chamada a .get()
private final LazyConstant client =
    LazyConstant.of(ExpensiveClient::new);

Depois de inicializado, a JVM pode tratar o LazyConstant como constante e aplicar as mesmas otimizações de um final estático.

O que preciso saber antes de usar Lazy Constants no Java 26?

  • Ainda é preview – para compilar e rodar, habilite o preview:
javac --enable-preview --release 26 PaymentService.java
java --enable-preview PaymentService
  • LazyConstant não é Serializable. Caso precise serializar o objeto, ele não funcionará.
  • O valor armazenado é imutável, mas o objeto interno pode ser mutável. O LazyConstant garante que a referência é atribuída uma única vez; o que você faz com o objeto depois é responsabilidade sua.

Quando usar (e quando não usar) LazyConstant?

Use quando:

  • O objeto é caro de criar e nem sempre necessário.
  • Precisa ser thread‑safe sem gerenciar sincronização manualmente.
  • Exemplos: conexões de banco, clientes HTTP, caches de configuração, registradores de métricas.

Não use quando:

  • Um final simples resolve o problema.
  • Precisa de serialização.
  • Quer controle exato do momento da inicialização (por exemplo, antes de um ponto específico de inicialização da aplicação).

LazyConstant garante que a inicialização ocorre antes do primeiro .get(), mas não em um ponto arbitrário que você escolha.

Por que isso importa agora, sendo ainda preview?

O double‑checked locking está em produção em milhares de sistemas Java. Não porque alguém goste da complexidade, mas porque não havia alternativa melhor. O JEP 526 é a resposta do OpenJDK a esse problema.

A feature ainda pode mudar antes de ser finalizada, e a segunda preview serve exatamente para coletar feedback. Contudo, a direção está clara: a plataforma quer tratar a inicialização “lazy” como um primitivo de primeira classe, eliminando a necessidade de padrões boilerplate e propensos a erros.

LazyConstant – Abstração de Primeira Classe

Use a feature como abstração de primeira classe, com suporte do compilador e da JVM, em vez de uma “receita de código” que cada desenvolvedor implementa à sua maneira. Experimentar agora com --enable-preview é a forma mais direta de entender o impacto no seu código antes que a feature chegue como estável.

Perguntas frequentes sobre LazyConstant e JEP 526 no Java 26

LazyConstant é thread‑safe?

Sim. A inicialização única vale em ambientes multithread sem precisar de synchronized ou volatile.

Posso usar LazyConstant com Serializable?

Não. Se o objeto precisa de serialização, use final ou outro mecanismo.

LazyConstant substitui o padrão Singleton com classe interna estática?

Para a maioria dos casos, sim. O resultado é o mesmo, mas o código fica mais direto e a intenção fica explícita no tipo.

Preciso de Java 26 para usar essa feature?

Sim, com --enable-preview. O JEP 526 está em second preview e ainda não é estável.

O que acontece se a função de inicialização lançar uma exceção?

A exceção é propagada para quem chamou .get() e o LazyConstant não fica marcado como inicializado. A próxima chamada tenta novamente.

Sobre o autor

Eu sou o Luis De Llamas, Developer Advocate na act digital, Oracle ACE e IBM Champion.

Se quiser acompanhar mais conteúdo sobre Quarkus, Java e o que rola no ecossistema, me encontra aqui:

0 views
Back to Blog

Related posts

Read more »

Book review: JAVA HOW LOW CAN YOU GO

Overview I liked JAVA HOW LOW CAN YOU GO: Low‑latency design for RFQ and high‑frequency trading covering Java 24+ and beyond because it goes beyond normal Java...

Global Variable VS Local Variable

Global Variable in Java Java does not support true global variables. Instead, it uses class‑level variables, which behave similarly. Types of Class‑Level Varia...