Java Novidades do 17 ao 25
O Java ganha uma versão nova a cada seis meses, e a cada dois anos uma delas é LTS (com suporte longo): 17, 21 e 25. Esta aula é um mapa do que mudou entre o Java 17 e o Java 25, o que este guia ensina. Cada novidade tem o selo da versão em que ficou pronta, e as principais têm um exemplo rodado no JDK 25 da máquina da Ana.
- A diferença entre um recurso final, um preview e um incubator, e como ligar um preview (
--enable-preview), com as mensagens reais. - Uma tabela, versão por versão, das novidades de linguagem e de biblioteca do 17 ao 25.
- Exemplos curtos das novidades que mais mudam o dia a dia:
void main()eIO,import module, construtores flexíveis,getFirst()/reversed(), o_sem nome e os scoped values. - O que aconteceu com os String templates (
STR."…"): foram retirados.
Se você quer saber o que é LTS e qual versão instalar, veja antes Versões e LTS. Tudo nesta página foi conferido no laboratório:
$ java -version openjdk version "25.0.4.1" 2026-08-18 LTS OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS) OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing)
A ideia antes do código: final, preview e incubator
Pense numa receita nova num restaurante. Antes de entrar no cardápio, ela é servida como prato do dia, para os clientes provarem e darem opinião; o chef pode mudar a receita ou até tirá-la de vez. Só depois de aprovada ela entra no cardápio fixo.
O Java faz igual. Cada mudança é descrita num documento chamado JEP (JDK Enhancement Proposal, "proposta de melhoria do JDK"), com um número. E passa por estágios:
| Estágio | O que quer dizer | Posso usar? |
|---|---|---|
| Final (permanente) | Entrou no cardápio fixo. Não muda mais. | Sim, normalmente. |
| Preview (prévia) | Prato do dia: completo, mas pode mudar ou sumir na próxima versão. | Só ligando a opção --enable-preview. Bom para experimentar, não para produção. |
| Incubator (incubadora) | Uma biblioteca ainda em teste, num módulo separado jdk.incubator.…. | Só adicionando o módulo à mão. |
Tentando usar um preview sem a opção
No JDK 25, os padrões com tipos primitivos (como case int n) ainda são preview. Veja o que o javac diz:
public class Main {
public static void main(String[] args) {
int nota = 8;
String r = switch (nota) {
case int n when n >= 7 -> "aprovado";
case int n -> "reprovado";
};
System.out.println(r);
}
}
$ javac Main.java
Main.java:5: error: primitive patterns are a preview feature and are disabled by default.
case int n when n >= 7 -> "aprovado";
^
(use --enable-preview to enable primitive patterns)
1 error
"Padrões primitivos são um recurso em prévia e estão desligados por padrão (use --enable-preview para ligar)." Para APIs em prévia, a mensagem é parecida: error: StructuredTaskScope is a preview API and is disabled by default.
Ligando o preview
A opção precisa ir nos dois lugares: no javac (junto com --release, a versão exata) e no java:
$ javac --enable-preview Main.java error: --enable-preview must be used with either -source or --release $ javac --release 25 --enable-preview Main.java Note: Main.java uses preview features of Java SE 25. Note: Recompile with -Xlint:preview for details. $ java Main Erro: o método main não foi encontrado na classe Main; defina o método main como: public static void main(String[] args) ou uma classe de aplicativo JavaFX deve expandir java.lang.UnsupportedClassVersionError: Preview features are not enabled for Main (class file version 69.65535). Try running with '--enable-preview' $ java --enable-preview Main aprovado $ java --enable-preview Main.java « modo arquivo-fonte: compila e roda numa tacada aprovado
Repare na terceira tentativa: compilou com preview, mas rodou sem a opção. A mensagem do lançador sai embaralhada (começa com o aviso de main em português e termina no meio da frase), mas o final diz o que importa: "recursos em prévia não estão ligados para Main; tente rodar com --enable-preview". O número 69.65535 é a versão do arquivo .class: 69 é o Java 25, e 65535 é a marca de "usa preview".
Um .class compilado com preview do Java 25 só roda no Java 25 (nem no 26). Por isso, este guia ensina apenas recursos finais, e a tabela abaixo marca os previews do JDK 25 separadamente.
A tabela: do Java 17 ao 25
Linguagem = o que muda na forma de escrever código; API = classes e métodos novos na biblioteca; ferramentas = java, javac, javadoc… Só aparecem os recursos finais de cada versão (o número entre parênteses é o da JEP).
| Versão | Linguagem | API e ferramentas |
|---|---|---|
| Java 17 set/2021 · LTS |
Classes seladas: sealed, permits, non-sealed (409) → aula.Chegaram entre o 11 e o 17 e você já usa: switch com setas (14) → aula; text blocks """ (15) → aula; records (16) → aula; instanceof com padrão (16) → aula. |
Novos geradores de números aleatórios, RandomGenerator (356). As partes internas do JDK ficam sempre trancadas para reflexão (403) → aula. |
| Java 18 mar/2022 |
— | UTF-8 é o padrão para ler e escrever texto em qualquer sistema (400). Servidor web simples de linha de comando, jwebserver (408). Exemplos de código no Javadoc com @snippet (413). |
| Java 19 e Java 20 set/2022, mar/2023 |
Nenhum recurso de linguagem final: foram as versões de "prato do dia" das threads virtuais, dos record patterns e dos padrões no switch, que ficaram prontos no 21. | |
| Java 21 set/2023 · LTS |
Padrões no switch, com when e case null (441); record patterns (440) → aula. |
Threads virtuais (444) → aula. Coleções sequenciadas: getFirst(), getLast(), reversed(), addFirst() (431) → exemplo abaixo. |
| Java 22 mar/2024 |
Variáveis e padrões sem nome: _ (456) → exemplo abaixo. |
Rodar programas de vários arquivos direto com java Main.java (458). API de funções e memória externas (Foreign Function & Memory), para chamar código C sem JNI (454). |
| Java 23 set/2024 |
— | Comentários de documentação em Markdown, com /// (467) → aula. Os String templates, que estavam em preview, foram retirados (veja abaixo). |
| Java 24 mar/2025 |
— | Stream gatherers: operações intermediárias novas, como Gatherers.windowFixed (485) → streams. API para ler e gerar arquivos .class (484). Threads virtuais não ficam mais "presas" em synchronized (491). Criptografia resistente a computadores quânticos, ML-KEM e ML-DSA (496, 497). |
| Java 25 set/2025 · LTS |
Arquivos-fonte compactos e void main() de instância, com a classe IO (512) → exemplo abaixo e Olá, Mundo. import module (511). Corpos de construtor flexíveis: código antes do super(…) (513). |
Scoped values (506) → exemplo abaixo. Cabeçalhos de objeto compactos, que economizam memória (519). API de derivação de chaves, KDF (510). |
Em preview (ou incubadora) no JDK 25
Conferido no laboratório: estes não funcionam sem --enable-preview no JDK 25.
| Recurso | Estágio no 25 | Mensagem sem a opção |
|---|---|---|
Tipos primitivos em padrões, instanceof e switch (507) | 3ª prévia | primitive patterns are a preview feature and are disabled by default. |
Concorrência estruturada, StructuredTaskScope (505) | 5ª prévia | StructuredTaskScope is a preview API and is disabled by default. |
Valores estáveis, StableValue (502) | 1ª prévia | StableValue is a preview API and is disabled by default. |
| Codificação PEM de chaves e certificados (470) | 1ª prévia | (API em prévia) |
Vector API, jdk.incubator.vector (508) | incubadora (10ª) | (módulo separado) |
As novidades na prática
void main() e IO Java 25+
A mudança mais visível para quem começa. Desde sempre, o menor programa Java precisava de uma classe, de public static void main(String[] args) e de System.out.println. No Java 25, um arquivo pode ter só o método main, sem classe em volta (um arquivo-fonte compacto), e a classe IO ("entrada e saída") oferece IO.println, IO.print e IO.readln:
void main() {
IO.println("Olá, Ana!");
IO.println("2 + 3 = " + somar(2, 3));
}
int somar(int a, int b) {
return a + b;
}
$ java Ola.java Olá, Ana! 2 + 3 = 5 $ javac Ola.java && java Ola Olá, Ana! 2 + 3 = 5 $ ls Ola.class Ola.java
Por baixo, o Java cria uma classe chamada Ola (o nome do arquivo) para você. Funciona com java Ola.java e também com javac + java. Outra vantagem: um arquivo compacto já enxerga toda a biblioteca básica (List, ArrayList, Map…) sem nenhum import. As duas formas valem no Java 25:
| Clássica (todas as versões) | Compacta Java 25+ |
|---|---|
public class Ola { | void main() { |
Em projetos de verdade, com pacotes e várias classes, a forma clássica continua sendo a regra. A compacta brilha em programas pequenos, scripts e no aprendizado.
Missão: reescreva este programa no formato compacto do Java 25: apague a classe, deixe só void main() e troque System.out.println por IO.println. A saída deve continuar Olá, Java 25!.
public class Main {
public static void main(String[] args) {
System.out.println("Olá, Java 25!");
}
}
void main() {
IO.println("Olá, Java 25!");
}
import module Java 25+
Em vez de uma linha de import para cada classe, você importa um módulo inteiro. java.base é o módulo com o básico do Java (java.util, java.time, java.io…):
import module java.base;
public class Main {
public static void main(String[] args) {
List<String> frutas = new ArrayList<>(List.of("maçã", "uva"));
Map<String, Integer> estoque = new HashMap<>();
estoque.put("maçã", 10);
System.out.println(frutas + " " + estoque + " " + LocalDate.of(2026, 10, 16));
}
}
$ javac Main.java && java Main
[maçã, uva] {maçã=10} 2026-10-16
List, ArrayList, Map, HashMap e LocalDate vieram de três pacotes diferentes com uma linha só.
Código antes do super(…) Java 25+
Até o Java 24, a chamada super(…) tinha de ser a primeira linha do construtor. Agora dá para validar ou preparar os argumentos antes, desde que não se use o objeto que ainda está sendo construído (this):
class Pessoa {
String nome;
Pessoa(String nome) { this.nome = nome; }
}
class Cliente extends Pessoa {
Cliente(String nome) {
if (nome == null || nome.isBlank()) {
throw new IllegalArgumentException("nome vazio");
}
super(nome.strip());
}
}
public class Main {
public static void main(String[] args) {
System.out.println(new Cliente(" Ana ").nome);
new Cliente(" ");
}
}
$ javac Main.java && java Main Ana Exception in thread "main" java.lang.IllegalArgumentException: nome vazio at Cliente.<init>(Main.java:9) at Main.main(Main.java:18)
O primeiro cliente teve os espaços removidos com strip() antes de chegar à classe mãe; o segundo foi recusado antes mesmo de a Pessoa ser construída (<init> no rastro é como o Java chama um construtor).
Coleções sequenciadas Java 21+
Listas, LinkedHashMap, TreeSet e outras coleções que têm uma ordem ganharam métodos com nomes óbvios:
import java.util.ArrayList;
import java.util.List;
import java.util.LinkedHashMap;
public class Main {
public static void main(String[] args) {
List<String> fila = new ArrayList<>(List.of("Ana", "Bruno", "Carla"));
System.out.println(fila.getFirst());
System.out.println(fila.getLast());
System.out.println(fila.reversed());
fila.addFirst("Zé");
System.out.println(fila);
LinkedHashMap<String, Integer> notas = new LinkedHashMap<>();
notas.put("Ana", 9);
notas.put("Bruno", 7);
System.out.println(notas.firstEntry() + " " + notas.lastEntry());
}
}
$ javac Main.java && java Main Ana Carla [Carla, Bruno, Ana] [Zé, Ana, Bruno, Carla] Ana=9 Bruno=7
| Antes do 21 | Java 21+ |
|---|---|
lista.get(0) | lista.getFirst() |
lista.get(lista.size() - 1) | lista.getLast() |
Copiar e chamar Collections.reverse(copia) | lista.reversed() (uma "visão" ao contrário, sem copiar) |
lista.add(0, x) | lista.addFirst(x) |
Missão: complete as três linhas usando getFirst(), getLast() e reversed().
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> fila = new ArrayList<>(List.of("Ana", "Bruno", "Carla", "Davi"));
System.out.println("Primeiro: ");
System.out.println("Último: ");
System.out.println("Ao contrário: ");
}
}
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> fila = new ArrayList<>(List.of("Ana", "Bruno", "Carla", "Davi"));
System.out.println("Primeiro: " + fila.getFirst());
System.out.println("Último: " + fila.getLast());
System.out.println("Ao contrário: " + fila.reversed());
}
}
O _ sem nome Java 22+
Quando o Java exige uma variável que você não vai usar, escreva _ (sublinhado). Fica claro para quem lê que aquilo foi ignorado de propósito:
import java.util.List;
public class Main {
public static void main(String[] args) {
int total = 0;
for (String _ : List.of("a", "b", "c")) {
total++;
}
IO.println("Itens: " + total);
try {
Integer.parseInt("dez");
} catch (NumberFormatException _) {
IO.println("Não é número");
}
}
}
$ java Main.java Itens: 3 Não é número
Também vale em padrões: case Ponto(int x, _) (veja Pattern Matching) e em lambdas: (_, valor) -> …. Repare que o IO.println também funciona dentro de uma classe comum.
Scoped values Java 25+
Um ScopedValue ("valor com escopo") guarda uma informação, como o usuário logado, que fica visível para todos os métodos chamados dentro de um trecho, sem precisar passá-la de parâmetro em parâmetro. Fora do trecho, ela some. É a substituta moderna do ThreadLocal, pensada para conviver bem com as threads virtuais:
public class Main {
static final ScopedValue<String> USUARIO = ScopedValue.newInstance();
static void registrar(String acao) {
System.out.println(USUARIO.get() + " fez: " + acao);
}
public static void main(String[] args) {
ScopedValue.where(USUARIO, "ana").run(() -> registrar("venda"));
ScopedValue.where(USUARIO, "bruno").run(() -> registrar("estoque"));
System.out.println("Fora: " + USUARIO.isBound());
}
}
$ javac Main.java && java Main ana fez: venda bruno fez: estoque Fora: false
O método registrar não recebeu o usuário como parâmetro, mas soube quem era. Depois do run, o valor não está mais ligado (isBound() = "está ligado?" deu false). É um recurso avançado: guarde o nome para quando trabalhar com servidores.
Stream gatherers Java 24+
O método gather permite operações novas no meio de um stream, como agrupar os itens de dois em dois:
List<List<Integer>> pares = Stream.of(1, 2, 3, 4, 5).gather(Gatherers.windowFixed(2)).toList(); System.out.println(pares);
$ javac Main.java && java Main [[1, 2], [3, 4], [5]]
O que não entrou: String templates
Você pode encontrar na internet artigos sobre String templates, uma forma de colocar variáveis dentro de textos: STR."Olá, \{nome}!". Eles foram preview no Java 21 e no 22 e, depois das opiniões recebidas, os responsáveis decidiram que o desenho não estava bom: o recurso foi retirado no Java 23 e não existe no 25, nem com --enable-preview:
public class Main {
public static void main(String[] args) {
String nome = "Ana";
String s = STR."Olá, \{nome}!";
System.out.println(s);
}
}
$ javac Main.java
Main.java:4: error: illegal escape character
String s = STR."Olá, \{nome}!";
^
1 error
$ java --enable-preview Main.java
Main.java:4: error: illegal escape character
String s = STR."Olá, \{nome}!";
^
1 error
error: compilation failed
Para o Java 25, \{ é só um escape desconhecido. Para montar textos, continue usando concatenação, String.format / formatted ou StringBuilder. Essa história mostra para que serve o estágio de preview: experimentar sem prometer.
Quando dá errado
… is a preview feature and is disabled by default
Você usou um recurso que, na sua versão, ainda é preview. Conserto: confira se não há uma forma final de fazer o mesmo; se quiser só experimentar, compile com javac --release 25 --enable-preview e rode com java --enable-preview.
--enable-preview must be used with either -source or --release
O javac precisa saber a versão exata, porque o preview de uma versão não vale na outra. Conserto: acrescente --release 25.
Preview features are not enabled for Main (class file version 69.65535)
O .class foi compilado com preview, mas o java rodou sem a opção. Conserto: java --enable-preview Main.
Um recurso novo num Java velho
Se alguém rodar seu void main() num Java 21, ou getFirst() num Java 17, vai ver erros de compilação, porque aquela versão não conhece o recurso. É por isso que as aulas marcam o selo Java 21+: confira a versão com java -version e, no Maven ou Gradle, a versão configurada no projeto (veja Maven).
Referência rápida: recurso → versão final
| Recurso | Final desde | Aula |
|---|---|---|
var | Java 10 | var |
switch com setas e yield | Java 14 | switch com Setas |
Text blocks """ | Java 15 | Text Blocks |
| Records | Java 16 | Records |
instanceof com padrão | Java 16 | Pattern Matching |
| Classes seladas | Java 17 | Classes Seladas |
Padrões no switch, record patterns | Java 21 | Pattern Matching |
| Threads virtuais | Java 21 | Threads Virtuais |
getFirst(), getLast(), reversed() | Java 21 | esta |
_ sem nome | Java 22 | esta |
Javadoc em Markdown /// | Java 23 | Javadoc |
| Stream gatherers | Java 24 | esta |
void main(), IO, arquivo compacto | Java 25 | esta e Olá, Mundo |
import module | Java 25 | esta |
Código antes do super(…) | Java 25 | esta |
ScopedValue | Java 25 | esta |
Padrões primitivos, StructuredTaskScope, StableValue | (preview no 25) | — |
String templates STR."…" | (retirado no 23) | — |
Exercícios
1. Complete com a opção que liga os recursos em prévia:
2. Complete para pegar o último elemento da lista (Java 21+):
3. Complete o programa compacto do Java 25:
4. O que aconteceu com os String templates (STR."Olá, \{nome}")?
5. Quais versões são LTS entre as estudadas aqui?
6. Por que este guia evita ensinar recursos em preview?
Missão final: num arquivo compacto do Java 25 (só void main(), sem classe e sem import), acrescente a venda 25 no fim com addLast, mostre a última venda com getLast() e a lista ao contrário com reversed(), usando IO.println.
void main() {
var vendas = new ArrayList<>(List.of(30, 45, 12, 80));
// acrescente 25 no fim e mostre as duas linhas
}
void main() {
var vendas = new ArrayList<>(List.of(30, 45, 12, 80));
vendas.addLast(25);
IO.println("Última venda: " + vendas.getLast());
IO.println("Mais recentes primeiro: " + vendas.reversed());
}
Saída real da solução:
$ java Main.java Última venda: 25 Mais recentes primeiro: [25, 80, 12, 45, 30]
Resumo
- Uma versão nova a cada 6 meses; LTS a cada 2 anos: 17, 21, 25.
- Recursos passam por preview (precisa de
--enable-previewnojavac, com--release, e nojava) antes de virar finais. Sem a opção:… is a preview feature and is disabled by default. - 17: classes seladas. 21: padrões no
switch, record patterns, threads virtuais,getFirst/reversed. 22:_. 24: gatherers. 25:void main()+IO,import module, código antes dosuper,ScopedValue. - No JDK 25 ainda são preview: padrões primitivos,
StructuredTaskScope,StableValue. - String templates foram retirados (Java 23).
Próxima aula: Threads, o começo da concorrência.