Java 25 segue com a Structured Concurrency em preview: dá para usar em produção?
A StructuredTaskScope do JEP 505 segue em quinta preview no Java 25, trocando ExecutorService e Future por um bloco que cancela e propaga erro sozinho, mas ainda depende da flag --enable-preview para compilar e rodar.

Nota da redação: este artigo usa a expressão 'agora' para discutir a adoção de StructuredTaskScope em produção, mas o Java 25 e a quinta preview da API (JEP 505) já estão em circulação há mais tempo do que o termo sugere, e o próprio JEP já aponta uma sexta preview (JEP 525) a caminho. O conteúdo técnico sobre a API, seu histórico e comportamento está confirmado na fonte; o leitor deve entender 'agora' como 'no estado atual da preview', não como lançamento recente.
A StructuredTaskScope do JEP 505 segue em quinta preview no Java↳Java42 conteúdosNovidades do Java 26 (para desenvolvedores)Dev (Back & Front) · mar 2026Visual Studio Code para Java: o guia completo (dicas, configuração e extensões)Dev (Back & Front) · out 2025Quarkus: Modernizando a linguagem Java para a era da nuvemDev (Back & Front) · nov 2020Ver tudo em Dev (Back & Front) → 25, trocando ExecutorService e Future por um bloco que cancela e propaga erro sozinho, mas ainda depende da flag --enable-preview para compilar e rodar.
A StructuredTaskScope do JEP 505 segue em quinta preview no Java 25, trocando ExecutorService e Future por um bloco que cancela e propaga erro sozinho, mas ainda depende da flag --enable-preview para compilar e rodar.
Da incubação à quinta preview: uma longa jornada
A Structured Concurrency acompanha o JDK desde a incubação na JDK 19 (JEP 428) e na JDK 20 (JEP 437), virou preview formal na JDK 21 (JEP 453) e vem sendo repreview a cada versão desde então: JDK 22 (JEP 462), JDK 23 (JEP 480), JDK 24 (JEP 499) e, no Java 25, soma a quinta rodada com o JEP 505. O JEP já relaciona um JEP 525 como sexta preview para o próximo ciclo, então quem acompanha a API sabe que ela ainda não está estabilizada mesmo depois de seis rodadas.
Isso importa para quem mantém serviço Java com ExecutorService e Future espalhados pelo código: a API que promete resolver cancelamento e vazamento de thread ainda está sob a flag --enable-preview, e mudou de forma relevante entre uma rodada e outra. Nesta versão, por exemplo, StructuredTaskScope deixou de ser aberta por construtor público e passou a usar métodos de fábrica estáticos como open(). Quem já tinha código de protótipo rodando em Java 24 precisa reescrever essa parte para compilar em Java 25.
O problema que a API ataca: concorrência sem estrutura
O JEP 505 parte de um exemplo clássico: um método handle() que dispara duas sub-tarefas concorrentes, findUser() e fetchOrder(), via ExecutorService.
Response handle() throws ExecutionException, InterruptedException {
Future<String> user = executor.submit(() -> findUser());
Future<Integer> order = executor.submit(() -> fetchOrder());
String theUser = user.get(); // Join findUser
int theOrder = order.get(); // Join fetchOrder
return new Response(theUser, theOrder);
}O problema não é o código em si, é o que acontece quando algo falha. Se findUser() lança exceção, fetchOrder() continua rodando em sua própria thread: isso é vazamento de thread, na definição literal do JEP. Se a thread que chama handle() for interrompida, a interrupção não se propaga para as sub-tarefas. E se findUser() demora enquanto fetchOrder() já falhou, handle() espera user.get() retornar antes de sequer saber que o outro lado quebrou, desperdiçando tempo. Nenhuma dessas três situações é um bug de lógica: é a ausência de uma relação pai-filho entre as tarefas, que existe só na cabeça de quem escreveu o código, não em tempo de execução.
Como fica o código com StructuredTaskScope
A mesma lógica, reescrita com a API proposta pelo JEP 505, fica assim:
Response handle() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // junta as sub-tarefas, propagando exceções
return new Response(user.get(), order.get());
}
}O fluxo de trabalho é sempre o mesmo: abrir o escopo com open(), disparar sub-tarefas com fork() (cada uma roda por padrão numa virtual thread), reunir tudo com join() e processar o resultado antes de fechar o escopo, normalmente via try-with-resources. Se uma sub-tarefa falhar, a outra é cancelada (interrompida) automaticamente, sem o try-finally manual que seria necessário com Future::cancel. Se a thread dona do escopo for interrompida antes ou durante o join(), todas as sub-tarefas são canceladas quando o bloco é fechado. É cancelamento por construção, não por disciplina do time.
O que mudou nesta rodada do Java 25
A mudança mais visível da quinta preview é estrutural: StructuredTaskScope não tem mais construtor público, só métodos de fábrica estáticos. O open() sem parâmetros cobre o caso comum (falhar se qualquer sub-tarefa falhar, esperar todas as outras terminarem com sucesso). Para políticas diferentes, existe a variante open(Joiner), que recebe um objeto Joiner configurando a política de junção; o próprio JEP deixa em aberto quais outras políticas e resultados podem ser implementados dessa forma, sem detalhar cada uma.
Em resumo: a troca de construtor por fábrica estática não é cosmética. Ela isola a criação do escopo atrás de uma interface (StructuredTaskScope agora é sealed interface), deixando espaço para o JDK adicionar variações de comportamento sem quebrar assinatura pública a cada preview, mesmo que o corpo do código que você escreve continue mudando de uma versão para outra.
Cancelamento automático, mas a responsabilidade é da sub-tarefa
O cancelamento feito pela StructuredTaskScope depende de interrupção de thread, o mesmo mecanismo que Thread.interrupt() sempre usou. O JEP é explícito: sub-tarefas que não respondem à interrupção, por bloquearem em uma chamada não interrompível, podem atrasar o fechamento do escopo indefinidamente. O método close() sempre espera as threads das sub-tarefas terminarem, mesmo que o escopo já esteja cancelado; a execução não continua além dele. Isso significa que trocar ExecutorService por StructuredTaskScope não livra o time de escrever código que trata InterruptedException corretamente, só torna o descaso mais visível quando a aplicação trava no fechamento do escopo.
A observabilidade↳Observabilidade11 conteúdosObservabilidade para APIs: os desafios e benefícios dessa abordagemDev (Back & Front) · jan 2025Falhas em Observabilidade afetam os Apps e a Segurança das OrganizaçõesDev (Back & Front) · nov 2023ADK Java 1.0: O Google quer que você pare de gambiarra Python no seu backendMarketing Tech · abr 2026Ver tudo em DevSecOps → também muda de categoria. Um thread dump de uma aplicação usando StructuredTaskScope mostra findUser() e fetchOrder() como filhas do escopo que as criou, em vez de aparecerem soltas e sem relação aparente entre si, como acontece hoje com ExecutorService. Para quem depura produção via thread dump às três da manhã, essa é a diferença entre reconstruir a árvore de chamadas na mão e já receber ela pronta.
Dá para ativar --enable-preview em produção agora?
O JEP 505 é explícito quanto a ser uma preview API, desabilitada por padrão. Para usá-la é preciso compilar com javac --release 25 --enable-preview Main.java, rodar com java --enable-preview Main, ou iniciar o jshell --enable-preview para experimentar. Nenhum desses comandos é segredo, mas o custo de ativar a flag em produção é maior do que parece.
- Instabilidade de assinatura: a própria mudança desta rodada (construtor público virando fábrica estática) já quebrou código escrito contra a preview da JDK 24. Não há garantia de que a sexta preview, referenciada no próprio JEP como JEP 525, não repita o padrão.
- Acoplamento de build: classes compiladas com
--enable-previewem geral só rodam na mesma versão major do JDK com a flag ativa, o que complica pipeline de CI/CD e distribuição de artefatos entre times com JDKs diferentes. - Escopo limitado: o próprio JEP deixa claro que não é objetivo substituir
ExecutorServiceouFuture, nem virar a API definitiva de concorrência estruturada. Código de fan-out/fan-in simples como o do exemplo se beneficia; pool de workers de longa duração, filas de trabalho ou cenários que não têm uma relação clara de pai-filho continuam exigindoExecutorService.
Veredito pragmático: vale estudar e prototipar StructuredTaskScope agora, inclusive em branches de laboratório ou serviços internos de baixo risco, porque o modelo mental (tarefa e sub-tarefas confinadas a um bloco léxico) é o caminho que o próprio time do OpenJDK está convergindo. Mas ativar --enable-preview em um serviço que atende tráfego real, com SLA e pipeline de release compartilhado entre vários times, ainda é apostar que a sexta preview não vai exigir outra reescrita. Para esse serviço, o caminho mais seguro continua sendo ExecutorService dentro de um try-with-resources, com cancelamento manual explícito nos blocos catch, até a API ser finalizada sem a flag.
Fonte: OpenJDK, JEP 505: Structured Concurrency (Fifth Preview) (https://openjdk.org/jeps/505).
Fonte: OpenJDK JEP 505 — Structured Concurrency (Fifth Preview)
Este artigo foi escrito por Bisneto Braga, colunista de back-end. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
GGUF no Hugging Face Transformers: quando compensa trocar o llama.cpp
O Transformers passou a ler arquivos GGUF direto do Hub, mas o ganho de memória que o llama.cpp entrega por padrão só aparece hoje para os modelos Qwen3.5 e depende de kernel específico. Fora disso, o que você ganha é conveniência, não economia de RAM.















