Java

1 out, 2025

Novidades no Java 25 (para desenvolvedores)

Publicidade

O Java 25 chega com uma forte mistura de qualidade de vida da linguagem, melhor inicialização, criação de perfil poderosa e APIs práticas.

Este post é um tour prático: seções curtas, código executável e cenários concretos onde você usará cada recurso em breve. Cada trecho é intencionalmente minimalista para que você possa copiar, adaptar e executar rapidamente.

🚀 Destaques do Java 25

  • ✅ Corpos de construção flexíveis (JEP 513)
  • 🧩 Padrões primitivos em instanceofswitch(JEP 507)
  • 📦 Programas compactos e importação de módulos (JEP 512 + JEP 511)
  • 🔄 Concorrência estruturada + valores com escopo (JEP 505 + JEP 506)
  • 🔐 API KDF e codificação PEM (JEP 510 + JEP 470)
  • 📊 Novas ferramentas de perfil JFR (JEP 520 + JEP 509)
  • ⚡ Inicialização mais rápida via cache AOT (JEP 514 + JEP 515)

Atualizações de linguagem e sintaxe

JEP 513: Corpos de construção flexíveis (✅ final)

👉 PEC 513

O que é: Você pode executar instruções antes de chamar super(...)ou this(...). Um novo construtor “prólogo” permite validar entradas e inicializar seus próprios campos com segurança antes de delegar na hierarquia.

Quando você vai usar: Validação à prova de falhas e estabelecimento da integridade da subclasse antes que uma superclasse possa chamar métodos substituíveis.

Antes (abordagem anterior: tinha que chamar superprimeiro, muitas vezes enviando a validação para uma fábrica ou fazendo isso depois super, arriscando um estado parcialmente inicializado se a superclasse usasse métodos substituíveis):

// BEFORE (pre-Java 25)
class Person {
    Person(String name) { init(); }
    void init() { /* might call overridable methods */ }
}

class Employee extends Person {
    private String payrollId; // cannot set before super

    Employee(String name, String payrollId) {
        super(name); // must be first line
        if (payrollId == null || !payrollId.matches("EMP-\\d{4}")) {
            throw new IllegalArgumentException("Invalid payrollId");
        }
        this.payrollId = payrollId; // set only after super
    }
}

Limitações antes:

  • A validação é executada somente após super, então o código da superclasse pode observar um estado de subclasse incompleto.
  • Soluções alternativas: fábricas estáticas, inicialização em duas fases ou validação duplicada anterior.

Depois (ID da folha de pagamento do funcionário validado e inicializado antes do construtor da Pessoa):

// src/main/java/com/loiane/people/Employee.java
package com.loiane.people;

class Person {
    final String name;
    Person(String name) { this.name = name; log("constructed: " + name); }
    void log(String msg) { System.out.println(msg); }
}

class Employee extends Person {
    final String payrollId;

    Employee(String name, String payrollId) {
        // Prologue: validate & set subclass state before calling super
        if (payrollId == null || !payrollId.matches("EMP-\\d{4}"))
            throw new IllegalArgumentException("Invalid payrollId");
        this.payrollId = payrollId; // allowed before super
        super(name); // delegation after establishing invariants
        log("ready: " + this.payrollId);
    }

    public static void main(String[] args) {
        new Employee("Ana", "EMP-1234");
    }
}

O que está acontecendo aqui?

  • Validação + configuração invariante acontecem no prólogo (antes super).
  • A atribuição de campo payrollIdé permitida com antecedência.
  • O estado da subclasse existe antes da execução da lógica da superclasse.

JEP 507: Padrões primitivos em instanceof e switch (🧪 terceira prévia)

👉 PEC 507

O que é: A correspondência de padrões agora lida com tipos primitivos em instanceofswitch. Você pode testar e vincular valores restritos com segurança, sem verificações manuais de intervalo; switchsuporta booleanlongfloatdoubletambém.

Quando você usará: Conversões numéricas mais seguras, análise expressiva e regras de negócios mais limpas.

Antes (fundição manual + ramificações separadas: mais texto padronizado e risco de alargamento acidental):

// BEFORE (pre-Java 25)
public class TierLegacy {
    static String tierFor(Object usage) {
        if (usage instanceof Integer) {
            int i = (Integer) usage;
            if (i < 1) return "FREE";
            if (i < 1_000) return "STARTER";
            if (i < 10_000) return "PRO";
            return "ENTERPRISE";
        } else if (usage instanceof Long) {
            long l = (Long) usage;
            if (l < 10_000) return "PRO"; else return "ENTERPRISE";
        } else if (usage instanceof Double) {
            double d = (Double) usage;
            int asInt = (int) d; // silent truncation risk
            if (asInt == d) {
                return tierFor(asInt); // re-box to Integer call
            }
            return d < 1_000.0 ? "STARTER" : "PRO";
        }
        return "UNKNOWN";
    }
}

Limitações antes:

  • Repetição de conversões e declarações de variáveis.
  • Possível truncamento silencioso ao restringir doublepara int.
  • Nenhum padrão de proteção em linha com switcha expressão.

Depois (níveis de preços com estreitamento preciso e proteções):

// src/main/java/com/loiane/billing/Tier.java
package com.loiane.billing;

public class Tier {
    static String tierFor(Object usage) {
        if (usage instanceof int i) {
            return switch (i) {
                case int v when v < 1 -> "FREE";
                case int v when v < 1_000 -> "STARTER";
                case int v when v < 10_000 -> "PRO";
                case int v -> "ENTERPRISE";
            };
        }
        if (usage instanceof long l) {
            return switch (l) {
                case long v when v < 10_000 -> "PRO";
                case long v -> "ENTERPRISE";
            };
        }
        if (usage instanceof double d) {
            // Bind only when d converts exactly to int (no silent truncation)
            if (d instanceof int exact) {
                return tierFor(exact);
            }
            return d < 1_000.0 ? "STARTER" : "PRO";
        }
        return "UNKNOWN";
    }

    public static void main(String[] args) {
        System.out.println(tierFor(0));        // FREE
        System.out.println(tierFor(999));      // STARTER
        System.out.println(tierFor(9_999));    // PRO
        System.out.println(tierFor(10_000L));  // ENTERPRISE (long path)
        System.out.println(tierFor(42.0));     // STARTER (double exact -> int)
    }
}

O que está acontecendo aqui?

  • instanceof int exactvincula-se apenas à conversão exata (sem perda silenciosa).
  • switchsobre longe guardas de valores deixam clara a intenção.

Como executar (prévia):

javac --enable-preview --release 25 -d out src/main/java/com/loiane/billing/Tier.java
java --enable-preview -cp out com.loiane.billing.Tier

Programas compactos e importações mais limpas

JEP 512: Arquivos de origem compactos + instância principal (✅ final)

👉 PEC 512

O que é: Você pode escrever um arquivo apenas com métodos/campos; o compilador o encapsula em uma classe implícita. void main()Pode ser um método de instância. O novo java.lang.IO facilita a E/S do console.

Quando você usará: Katas, scripts, ensinamentos, demonstrações rápidas.

Antes (classe explícita + main estático necessário):

// BEFORE (pre-Java 25)
public class HelloClassic {

    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Depois (fonte compacta simplificada Hello World):

// Name this file: Hello.java (compact source)
void main() {
    IO.println("Hello, World!");
}

O que está acontecendo aqui?

  • Nenhuma declaração de classe; void main()é o ponto de entrada.
  • IO.println(java.lang.IO) evita clichês.

Run:

java Hello.java

JEP 511: Declarações de importação de módulos (✅ final)

👉 PEC 511

O que é: import module <name>;importa todos os pacotes exportados de um módulo (e transitivos), até mesmo do código classpath.

Quando você vai usar: Prototipagem e aprendizado; extração rápida de APIs amplas (por exemplo, java.seem código modular).

Código (análise HTTP + JSON com menos importações):

// src/main/java/com/loiane/http/FetchTodo.java
package com.loiane.http;

import module java.base;
import module java.net.http;

public class FetchTodo {
    public static void main(String[] args) throws Exception {
        var client = java.net.http.HttpClient.newHttpClient();
        var req = java.net.http.HttpRequest.newBuilder(URI.create("https://jsonplaceholder.typicode.com/todos/1")).build();
        var res = client.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
        System.out.println(res.body());
    }
}

O que está acontecendo aqui?

  • Duas importações de módulos trazem todos java.net.httpos tipos necessários; ainda qualificamos os tipos para maior clareza.
  • Caso surja alguma ambiguidade, adicione uma importação específica para resolvê-la.

Concorrência e contexto

JEP 506 + JEP 505: Valores com escopo (✅ final) + Concorrência estruturada (🧪 quinta prévia)

👉 PEC 505 👉 PEC 506

O que é: Valores com escopo são uma alternativa mais segura e herdável a ThreadLocal. A simultaneidade estruturada fornece um ciclo de vida para grupos de tarefas.

Quando você usará: Propagação de IDs de solicitação, autenticação ou localidade em threads virtuais com cancelamento claro.

Código (correlação-id em um grupo de tarefas):

/ src/main/java/com/loiane/observability/Trace.java
package com.loiane.observability;

import java.lang.ScopedValue;
import java.util.concurrent.StructuredTaskScope; // preview

public class Trace {
    static final ScopedValue<String> CORR_ID = ScopedValue.newInstance();

    static void log(String msg) { System.out.println("[" + CORR_ID.orElse("-") + "] " + msg); }

    public static void main(String[] args) throws Exception {
        var id = java.util.UUID.randomUUID().toString();
        ScopedValue.where(CORR_ID, id).run(() -> {
            try (var scope = new StructuredTaskScope<Void>()) { // simplified example
                var a = scope.fork(() -> { log("load customer"); return null; });
                var b = scope.fork(() -> { log("load orders"); return null; });
                scope.join();
                log("done");
            }
        });
    }
}

O que está acontecendo aqui?

  • ScopedValue.where(CORR_ID, id).run(...)vincula o ID de correlação para todo o escopo da tarefa.
  • Threads virtuais (se usadas) herdam o valor do escopo sem vazamentos.

Executar (visualização para simultaneidade estruturada):

javac --enable-preview --release 25 -d out src/main/java/com/loiane/observability/Trace.java
java --enable-preview -cp out com.loiane.observability.Trace

Blocos de construção de segurança

JEP 510: API KDF (✅ final) com HKDF

👉 PEC 510

O que é: Padrão javax.crypto.KDFpara derivar chaves; inclui HKDF e especificações de parâmetros.

Quando você vai usar: Chaves de sessão, fluxos HPKE/KEM, chaves de semente.

Código (derivar material de chave AES para uma exportação):

// src/main/java/com/loiane/crypto/HkdfDemo.java
package com.loiane.crypto;

import javax.crypto.*;
import javax.crypto.spec.HKDFParameterSpec;
import java.security.spec.AlgorithmParameterSpec;

public class HkdfDemo {
    public static void main(String[] args) throws Exception {
        byte[] ikm = "seed-key-material".getBytes();
        byte[] salt = "salt".getBytes();
        byte[] info = "export-label".getBytes();

        KDF hkdf = KDF.getInstance("HKDF-SHA256");
        AlgorithmParameterSpec params = HKDFParameterSpec
                .ofExtract().addIKM(ikm).addSalt(salt)
                .thenExpand(info, 32);

        SecretKey key = hkdf.deriveKey("AES", params);
        System.out.println("Key algorithm: " + key.getAlgorithm());
        System.out.println("Key length: " + key.getEncoded().length);
    }
}

JEP 470: codificações PEM (🧪 visualização)

👉 PEC 470

O que é: APIs de primeira classe para ler/escrever PEM e DER com criptografia opcional.

Quando você vai usá-lo: Importar/exportar chaves sem bibliotecas de terceiros; manipular envelopes PEM de forma limpa.

Código (escreva um registro PEM simples de bytes):

// src/main/java/com/loiane/crypto/PemWrite.java
package com.loiane.crypto;

import java.nio.file.*;
import java.util.HexFormat;
import jdk.security.pem.*; // preview API

public class PemWrite {
    public static void main(String[] args) throws Exception {
        byte[] payload = HexFormat.of().parseHex("DEADBEEF");
        var rec = PEMEncoder.encode("TEST-DATA", payload);
        Files.writeString(Path.of("test-data.pem"), rec);
    }
}

Executar (prévia):

javac --enable-preview --release 25 -d out src/main/java/com/loiane/crypto/*.java
java --enable-preview -cp out com.loiane.crypto.PemWrite

Criação de perfil e observabilidade

JEP 520: Tempo e rastreamento do método JFR (✅ final)

👉 PEC 520

O que é: Métodos específicos de tempo e rastreamento por instrumentação de bytecode: sem necessidade de alterações no código. Configuração via CLI, arquivos de configuração ou JMX.

Quando você vai usar: Identifique inicializadores lentos, pontos críticos em estruturas ou vazamentos em ciclos de vida de recursos.

Experimente (rastreie HashMap::resizeenquanto executa seu aplicativo):

java '-XX:StartFlightRecording:jdk.MethodTrace#filter=java.util.HashMap::resize,filename=recording.jfr' -jar yourapp.jar
jfr print --events jdk.MethodTrace --stack-depth 20 recording.jfr

O JMX programático (rastreamento remoto sob demanda) também é suportado.

JEP 509: Criação de perfil de tempo de CPU JFR (experimental, Linux)

👉 PEC 509

O que é: Perfis precisos de tempo de CPU usando o temporizador de CPU do Linux; atribui tempo nativo aos quadros Java.

Quando você vai usar: Criação de perfil de produção com baixa sobrecarga; priorize o trabalho vinculado à CPU.

Início rápido:

java -XX:StartFlightRecording=jdk.CPUTimeSample#enabled=true,filename=cpu.jfr -jar yourapp.jar
jfr view cpu-time-hot-methods cpu.jfr

Inicialização e aquecimento

JEP 514: Criação de cache AOT em uma etapa (✅ final)

👉 PEC 514

O que é: -XX:AOTCacheOutput=app.aotexecuta uma invocação de treinamento e cria o cache em uma única etapa. Uso -XX:AOTCache=app.aotem produção.

Cenário: A etapa de CI treina a inicialização (rota HTTP típica + desserialização) e publica o artefato de cache. Os pods de produção são montados app.aotpara inicializações a frio consistentemente rápidas.

# Train and create cache (one-shot)
java -XX:AOTCacheOutput=app.aot -jar yourapp.jar --train

# Use cache in prod
java -XX:AOTCache=app.aot -jar yourapp.jar

JEP 515: Criação de perfil de método antecipado (✅ final)

👉 PEC 515

O que é: Os perfis de treinamento são armazenados no cache AOT para que o JIT seja otimizado mais cedo na inicialização.

Dica: Para infraestrutura dividida, defina opções somente de criação JDK_AOT_VM_OPTIONSse o treinamento e a criação ocorrerem em instâncias diferentes.

Notas

  • Os recursos de linguagem de visualização (padrões primitivos, simultaneidade estruturada) são necessários --enable-previewpara compilar e executar.
  • As adições do JFR permitem tanto amostragem (tempo de CPU) quanto tempo/rastreamento precisos; combine com o JMC para análise profunda.
  • Os fluxos de trabalho AOT de Leyden (AOTCacheOutput + criação de perfil de método) melhoram as inicializações a frio e reduzem o tempo de aquecimento.
  • Fique de olho nos JEPs de pré-visualização/incubadora; os detalhes (nomes de métodos, formatos de API secundários) ainda podem mudar um pouco até a versão final.

Use os recursos que se adequam ao seu caso de uso hoje e mantenha as visualizações no seu radar: elas já são úteis e estão quase prontas.

Referências

Boa codificação!