Nesta última aula você olha para o caminho inteiro, do primeiro "Olá, mundo" às três trilhas. Também escreve um programa que faz o inventário dos seus projetos para montar um portfólio, vê para onde o Java está indo depois do Java 25 e sai com um plano concreto para os próximos meses.
Setenta e oito aulas atrás, este curso começou do zero: nenhuma linha de código, nenhuma suposição sobre o que você já sabia. De lá para cá, você escreveu um servidor HTTP a partir de sockets, colocou uma aplicação web com banco de dados no ar num servidor, fez um app de desktop e um de Android conversarem com a sua API, e publicou um jogo que outra pessoa baixa e joga sem instalar nada. Esta aula não traz tecnologia nova. Ela faz três coisas que costumam ficar de fora dos cursos: mostra o que tudo isso tem em comum, transforma o que você construiu em algo que outras pessoas conseguem ver, e aponta os próximos destinos.
O mapa do caminho percorrido
O curso teve uma espinha dorsal e três trilhas de extensão. Cada uma terminou num projeto que junta tudo o que veio antes:
| Parte | O que você construiu | As ideias que ficaram |
|---|---|---|
| Trilha principal (53 aulas) | a linguagem, do zero à concorrência; o Farol, um servidor HTTP escrito sobre sockets, com roteador, arquivos estáticos, keep-alive, limites e desligamento gracioso | tipos e objetos; records, tipos selados e pattern matching; coleções e streams; exceções e recursos; testes; threads virtuais; o protocolo HTTP lido byte a byte; jlink e jpackage |
| Trilha A (14 aulas): web, API e banco | as Trilhas da Ilha: SQL, JDBC, Spring Boot, JPA, Flyway, PostgreSQL, MySQL e MongoDB, a API REST de "Extensão — A API das Trilhas: REST, Validação, Erros e OpenAPI", segurança com JWT, testes com Testcontainers, o site com htmx e o deploy de "Extensão — Do Laptop ao Servidor: Docker, nginx e Deploy" | o que os frameworks fazem por dentro (montamos miniaturas de injeção de dependências, de contexto de persistência e de migrações); transações e concorrência no banco; nunca confiar na entrada |
| Trilha B (4 aulas): desktop e Android | "Extensão — Um App Desktop que Consome a API (MVVM)" em JavaFX, e "Extensão — Java no Android: o que Mudou com o Kotlin e um App que Consome a API" | separar a interface do estado (MVVM); a mesma API servindo vários clientes; threads e interface gráfica |
| Trilha C (7 aulas): jogos | do game loop à mão até A Ilha dos Blocos, o plataforma de "O Capstone dos Jogos — o Plataforma Completo e Exportado" | passo fixo; colisão; física própria e com Box2D; medir alocação; ciclo de vida de recursos nativos |
Olhando a tabela de longe, o que mais se repete não são as bibliotecas, porque as bibliotecas mudam. O que se repete são meia dúzia de princípios. Eles apareceram em todas as trilhas, com roupas diferentes:
- Regras longe da entrada e da saída. O
Roteadordo Farol, as regras das Trilhas da Ilha e aFasedo plataforma não conhecem sockets, HTTP nem GPU, e por isso foram testados em milissegundos. - Todo recurso tem dono. O
closedo socket, a conexão do banco devolvida ao pool, odisposeda textura, ofreedoPool. Os erros mais difíceis do curso, como a falha de segmentação do Box2D e o objeto liberado duas vezes, foram sempre de dono indefinido. - Desconfie da entrada. O cabeçalho HTTP malicioso, o caminho com
../, o SQL injetado e o arquivo de preferências editado à mão são o mesmo problema. - Meça antes de mudar. Os 74 bytes por passo do Asteroids, as 501 consultas do N+1, o pior quadro caindo exatamente numa pausa do coletor. Nenhuma dessas conclusões saiu de intuição.
- Entenda um nível abaixo. Escrevemos o HTTP antes do Spring, o
Proxyantes do Spring Data e o game loop antes do libGDX. Quem já viu por dentro usa o framework sem medo, e sabe onde procurar quando ele falha. - Pronto é quando outra pessoa consegue usar. O Farol virou uma imagem
jlink, as Trilhas foram para um servidor, e o jogo virou um executável.
Olá, mundo — de novo
O primeiro programa do curso foi uma classe Ola, com public static void main(String[] args) e System.out.println. Ainda na mesma aula, ele encolheu para a forma compacta, que continua sendo um programa Java 25 completo:
void main() {
IO.println("Olá, mundo!");
}
$ java OlaMundo.java
Olá, mundo!
Na primeira aula, cada palavra aqui era um ato de fé. Hoje você consegue explicar tudo o que está implícito:
- É um arquivo-fonte compacto: o compilador envolve o código numa classe sem nome.
- O
mainpode ser de instância, semstatice semString[] args, porque o lançador cria o objeto antes de chamá-lo. IOé uma classe dejava.lang, que está sempre importado.- O comando
java OlaMundo.javacompila em memória e executa, sem gerar nenhum.classno disco. - Por baixo, há o bytecode, a JVM, o interpretador e o JIT que depois compila o trecho quente, e o coletor que vai limpar a
Stringquando ninguém mais a usar.
A mesma linha, só que agora sem mistério. Se os acentos saírem como ? num terminal configurado sem UTF-8, rode com java -Dstdout.encoding=UTF-8 OlaMundo.java.
Um inventário da jornada
Um portfólio começa por saber o que você tem. Este programa percorre as pastas dos seus projetos e monta uma tabela em Markdown, pronta para um README, com o tamanho de cada projeto, quantos testes ele tem e alguns sinais do Java moderno que ele usa. Ele também é um arquivo-fonte compacto, e cabe tudo do curso num só lugar: records, streams, expressões regulares, try com recursos e exceções verificadas dentro de lambdas.
/// Inventário da Jornada: percorre as pastas dos seus projetos e monta uma tabela em Markdown
/// com o tamanho de cada um e alguns sinais do Java moderno que ele usa.
///
/// Uso: java Inventario.java Farol=~/cursos/farol Jogo=~/cursos/ilha-dos-blocos > PORTFOLIO.md
///
/// Arquivo-fonte compacto (Java 25): sem "public class", sem "public static", e o módulo
/// java.base inteiro já importado — como no primeiro "Olá, mundo" do curso.
/// O que medimos em cada projeto.
record Projeto(String nome, int arquivos, int linhas, int testes, int records, int selados, int switchesNovos) {
Projeto somar(Projeto o) {
return new Projeto("**Total**", arquivos + o.arquivos, linhas + o.linhas, testes + o.testes,
records + o.records, selados + o.selados, switchesNovos + o.switchesNovos);
}
}
/// O que medimos em cada arquivo (um Projeto é a soma deles).
record Contagem(int linhas, int testes, int records, int selados, int switchesNovos) { }
// padrões compilados uma vez: um record declarado, um tipo selado, um "case ... ->"
final Pattern RECORD = Pattern.compile("\\brecord\\s+\\w+\\s*[(<]");
final Pattern SELADO = Pattern.compile("\\bsealed\\s+(interface|class|abstract)");
final Pattern CASE_SETA = Pattern.compile("\\bcase\\b.*->");
final Set<String> IGNORAR = Set.of("target", "build", "out", "outt", ".git", ".gradle");
void main(String[] args) throws IOException {
if (args.length == 0) {
IO.println("uso: java Inventario.java Nome=pasta [Nome=pasta ...]");
return;
}
List<Projeto> projetos = new ArrayList<>();
for (String arg : args) {
String[] partes = arg.split("=", 2);
if (partes.length != 2) throw new IllegalArgumentException("esperado Nome=pasta, veio: " + arg);
projetos.add(medir(partes[0], Path.of(partes[1])));
}
projetos.sort(Comparator.comparingInt(Projeto::linhas).reversed()); // o maior primeiro
IO.println("| Projeto | Arquivos .java | Linhas de código | Testes | records | tipos selados | case -> |");
IO.println("|---|---:|---:|---:|---:|---:|---:|");
Projeto total = new Projeto("", 0, 0, 0, 0, 0, 0);
for (Projeto p : projetos) {
IO.println(linha(p));
total = total.somar(p);
}
if (projetos.size() > 1) IO.println(linha(total));
}
String linha(Projeto p) {
return "| %s | %d | %,d | %d | %d | %d | %d |".formatted(
p.nome(), p.arquivos(), p.linhas(), p.testes(), p.records(), p.selados(), p.switchesNovos());
}
Projeto medir(String nome, Path raiz) throws IOException {
if (!Files.isDirectory(raiz)) throw new NoSuchFileException(raiz.toString());
try (Stream<Path> caminhos = Files.walk(raiz)) { // o Stream segura a pasta aberta: feche-o
List<Contagem> contagens = caminhos
.filter(c -> c.toString().endsWith(".java"))
.filter(c -> !ignorado(raiz.relativize(c)))
.map(this::contar)
.toList();
return new Projeto(nome, contagens.size(),
contagens.stream().mapToInt(Contagem::linhas).sum(),
contagens.stream().mapToInt(Contagem::testes).sum(),
contagens.stream().mapToInt(Contagem::records).sum(),
contagens.stream().mapToInt(Contagem::selados).sum(),
contagens.stream().mapToInt(Contagem::switchesNovos).sum());
}
}
/// Pastas geradas pela compilação não são "seu código".
boolean ignorado(Path relativo) {
for (Path parte : relativo) if (IGNORAR.contains(parte.toString())) return true;
return false;
}
Contagem contar(Path arquivo) {
int linhas = 0, testes = 0, records = 0, selados = 0, cases = 0;
boolean emComentario = false;
try {
for (String bruta : Files.readAllLines(arquivo)) {
String s = bruta.strip();
if (emComentario) { // dentro de /* ... */
if (s.contains("*/")) emComentario = false;
continue;
}
if (s.startsWith("/*")) { emComentario = !s.contains("*/"); continue; }
if (s.isEmpty() || s.startsWith("//")) continue; // "///" (Markdown doc) também é comentário
linhas++;
if (s.startsWith("@Test")) testes++;
if (RECORD.matcher(s).find()) records++;
if (SELADO.matcher(s).find()) selados++;
if (CASE_SETA.matcher(s).find()) cases++;
}
} catch (IOException e) {
throw new UncheckedIOException("não consegui ler " + arquivo, e); // lambdas não lançam checked
}
return new Contagem(linhas, testes, records, selados, cases);
}
Alguns detalhes que foram assunto de aulas inteiras:
- Os comentários
///são o formato de documentação em Markdown do Java 23+, e o próprio inventário os trata como comentário. - O
Files.walkdevolve umStreamque mantém diretórios abertos. Sem otrycom recursos, cada projeto medido deixaria descritores de arquivo vazando até o coletor passar, se passar. contaré usado dentro de ummap, e lambdas não podem lançar exceções verificadas. AIOExceptioné embrulhada numaUncheckedIOException, que preserva a causa original.IGNORARimpede de contar código gerado. Sem ele, os.javaque o Maven ou o Gradle copiam paratarget/ebuild/seriam contados duas vezes.
Rodei o inventário em quatro dos projetos que construí para este curso:
java Inventario.java "Farol (servidor HTTP)=farol" "Painel desktop (JavaFX)=painel" \
"A Ilha dos Blocos (libGDX)=ilha-dos-blocos" "Asteroids=asteroids" > PORTFOLIO.md
| Projeto | Arquivos .java | Linhas de código | Testes | records | tipos selados | case -> |
|---|---|---|---|---|---|---|
| Farol (servidor HTTP) | 33 | 1,812 | 67 | 18 | 3 | 27 |
| A Ilha dos Blocos (libGDX) | 19 | 1,113 | 16 | 2 | 0 | 17 |
| Painel desktop (JavaFX) | 15 | 677 | 7 | 7 | 0 | 4 |
| Asteroids | 6 | 419 | 8 | 1 | 0 | 0 |
| Total | 73 | 4,021 | 98 | 28 | 3 | 48 |
Os separadores de milhar saíram com vírgula porque a máquina está em inglês. O %,d respeita o idioma do sistema, e num sistema em português sai 1.812. Com uma pasta que não existe, o programa para com NoSuchFileException: /nao/existe, em vez de gerar uma tabela com zeros enganosos.
Os números dizem pouco sozinhos. O que eles mostram é o formato do trabalho: o Farol tem um teste para cada 27 linhas, porque um servidor exposto à rede não pode ter comportamento adivinhado. Tem também 18 records e 3 hierarquias seladas, porque o protocolo HTTP é feito de dados imutáveis com poucas variantes. O jogo tem proporcionalmente menos testes, e é exatamente o ponto: só as regras (a física, o progresso, as fases) foram testadas, e a parte que desenha foi verificada com os olhos e com fotos.
Do inventário ao portfólio
Um recrutador, ou você mesmo daqui a um ano, vai gastar poucos minutos em cada projeto. Um repositório de portfólio precisa responder rápido a quatro perguntas:
- O que é isto? Uma frase e uma imagem: a captura do jogo, o site das Trilhas no ar, o
curlrespondido pelo Farol. - Como eu rodo? Um comando só.
mvn verify,./gradlew lwjgl3:run,docker compose upou o executável dojpackagepara baixar. - Funciona? Testes rodando automaticamente a cada push, com o selo verde no topo do README.
- Por que foi feito assim? Uma seção "Decisões" com duas ou três escolhas e o que foi trocado por elas. Por exemplo: "resolvemos a colisão um eixo de cada vez para evitar a ambiguidade das quinas" ou "PostgreSQL como banco principal pela busca textual em português". Essa seção separa quem copiou um tutorial de quem entendeu o problema.
A terceira pergunta é respondida por uma integração contínua. No GitHub, um arquivo em .github/workflows/testes.yml basta para um projeto Maven:
name: testes
on:
push:
branches: [ main ]
pull_request:
jobs:
testes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '25'
cache: maven
- name: Compilar e testar
run: mvn --batch-mode verify
A cada push, uma máquina limpa baixa o código, instala o Java 25 e roda todos os testes. É a versão automatizada da regra de "Extensão — Bancos de Verdade nos Testes: Testcontainers": se o teste só passa na sua máquina, ele não prova nada. Nos projetos com Testcontainers, a máquina do GitHub já tem Docker, e os testes contra um PostgreSQL de verdade rodam ali também.
Três projetos bem apresentados valem mais que trinta pela metade, e este curso deixa exatamente três de peso:
- o Farol, que mostra fundamentos: rede, protocolo, concorrência e segurança;
- as Trilhas da Ilha, que mostram o dia a dia do mercado: Spring, bancos, API, segurança e deploy;
- A Ilha dos Blocos, que mostra projeto de software fora do caminho comum, e um produto que se instala e se usa.
Como o Java continua andando
O Java que você aprendeu é o Java 25, lançado em setembro de 2025. Desde o Java 10, em 2018, sai uma versão nova a cada seis meses, e a cada dois anos uma delas recebe suporte longo (LTS): 17, 21 e 25. Se o ritmo se mantiver, a próxima LTS será o JDK 29, em setembro de 2027. Enquanto isso, as versões intermediárias trazem novidades que vão amadurecendo em preview.
O JDK 27 saiu em 15 de setembro de 2026, com nove JEPs. Três delas conversam direto com este curso:
- JEP 534, Compact Object Headers by Default. O cabeçalho dos objetos cai de 12 para 8 bytes na configuração padrão. Lembra da conta de bytes por partícula em "Sem Lixo no Loop — Pools, ECS e o Coletor nos Jogos"? Ela muda, e o exercício 4 refaz essa conta.
- JEP 523, Make G1 the Default Garbage Collector in All Environments. O G1 passa a ser o padrão também nas máquinas pequenas, onde antes a JVM escolhia o Serial.
- JEP 533, Structured Concurrency (Seventh Preview). O
StructuredTaskScopeque usamos nas aulas de concorrência continua em preview, ainda sendo refinado.
E o próximo grande passo já tem data: a JEP 401, Value Objects (Preview), foi marcada para o JDK 28. É o primeiro resultado do Projeto Valhalla, e são objetos sem identidade, que a JVM pode guardar "achatados" na memória, como os arrays de float que venceram a disputa de desempenho na aula de partículas, mas escritos como classes normais. Quando ela sair do preview, muitos records do Farol e do jogo serão candidatos naturais a virar value records.
Para acompanhar sem se afogar:
- openjdk.org/jeps é a fonte primária. Toda mudança da linguagem e da JVM começa numa JEP, com motivação, alternativas descartadas e exemplos.
- inside.java reúne os anúncios, podcasts e textos da equipe da Oracle que desenvolve o Java.
- dev.java tem os tutoriais oficiais, atualizados a cada versão.
- Um hábito simples: a cada versão nova, leia a lista de JEPs e rode os seus três projetos nela. Adicionar
'27'ao lado de'25'numa matriz dosetup-javafaz isso de graça no CI.
Os próximos destinos
Terminar o curso não é chegar. É ter o mapa para escolher o próximo lugar. Os caminhos mais naturais a partir daqui:
- Back-end e nuvem. A continuação direta da Trilha A: aprofundar o Spring (Spring Modulith para organizar monólitos, mensageria com Kafka ou RabbitMQ), observabilidade com OpenTelemetry, Kubernetes para quando um servidor com Docker não basta, e a leitura de Designing Data-Intensive Applications, de Martin Kleppmann, que é o livro sobre o que acontece quando os dados não cabem numa máquina.
- A JVM por dentro. Se as aulas de coletor, JIT e alocação foram as suas preferidas: o JFR a fundo, o JMH para benchmarks confiáveis e o código-fonte do próprio OpenJDK, que é aberto e bem comentado.
- Android. A Trilha B mostrou que o Android moderno fala Kotlin. Como Kotlin roda na mesma JVM e usa as mesmas bibliotecas, ele é o passo seguinte natural, e a transição leva semanas, não meses.
- Jogos. Continuar no libGDX (atlas de texturas, áudio, controles, as extensões FreeType e Box2DLights) ou levar os mesmos conceitos a um motor com editor. O game loop, o passo fixo e a disciplina de recursos são iguais em todos.
- Código aberto. O libGDX, o Spring, o próprio OpenJDK e milhares de bibliotecas menores aceitam contribuições. Comece por documentação, exemplos ou um bug marcado como "good first issue". Ler o código de outras pessoas e ter o seu revisado ensina o que curso nenhum ensina.
- Certificação. A Oracle oferece o exame Oracle Certified Professional: Java SE 25 Developer (1Z0-831). Ele cobra detalhes da linguagem e da biblioteca padrão num nível de minúcia que o dia a dia não cobra. Não substitui projetos, mas é um bom complemento se o seu mercado valoriza certificações.
Um plano possível para os próximos 90 dias, em vez de "estudar mais":
- Mês 1: publicar os três projetos com README, capturas, a seção "Decisões" e CI verde. Rodar o inventário e colocar a tabela num repositório de apresentação.
- Mês 2: escolher um destino da lista acima e fazer uma coisa nova nele, que o curso não fez. Por exemplo, a API das Trilhas publicando eventos no Kafka, ou uma terceira fase do jogo com inimigos.
- Mês 3: uma contribuição para um projeto de código aberto que você usou, e um texto (no seu site, no LinkedIn, onde for) explicando uma coisa que você aprendeu construindo. Explicar é a prova final de ter entendido.
Fechando a jornada
Quando este curso começou, a aula de abertura contou a história de uma linguagem criada no começo dos anos 1990 por uma equipe liderada por James Gosling, pensada para aparelhos eletrônicos e que acabou dominando servidores, celulares e, pelo caminho, um dos jogos mais vendidos da história. Trinta anos depois, o Java continua sendo reescrito por dentro (threads virtuais, records, pattern matching e, em breve, objetos de valor) sem quebrar o código que já existe. Essa combinação de mudança constante e compatibilidade é rara, e é a razão pela qual o que você aprendeu aqui vai continuar valendo por muitos anos.
Mas o que fica deste ano não é uma linguagem. É um jeito de trabalhar:
- ler a especificação antes de adivinhar;
- escrever o teste que prova a regra;
- medir antes de otimizar;
- saber quem é dono de cada recurso;
- desconfiar de toda entrada;
- considerar pronto só o que outra pessoa consegue usar.
Isso vale em Java, em Kotlin, em Go, no próximo framework que aparecer e no que ainda nem foi inventado.
O Farol foi o nome do primeiro grande projeto porque um farol não leva ninguém a lugar nenhum. Ele mostra onde estão as pedras, e o caminho quem escolhe é o navegante. Este curso tentou ser isso. O destino chegou, e ele é só o ponto de partida do próximo começo, que agora é seu.
Fontes e leituras recomendadas
- OpenJDK — JDK 27
- Inside.java — The Arrival of Java 27!
- Inside.java — JEP targeted to JDK 28: 401: Value Objects (Preview)
- Dev.java — Getting Started with Java Certification
- GitHub — actions/setup-java
- OpenJDK — JEP 512: Compact Source Files and Instance Main Methods
Exercícios
Exercício 1
Acrescente ao inventário uma coluna "threads virtuais", que conta as linhas que usam Thread.ofVirtual, Thread.startVirtualThread ou newVirtualThreadPerTaskExecutor. Quais partes do programa mudam?
Ver resposta
✓ Resposta: Mudam quatro pontos, sem mexer na estrutura. (a) Um padrão novo: final Pattern VIRTUAL = Pattern.compile("ofVirtual|startVirtualThread|newVirtualThreadPerTaskExecutor");. (b) Um componente novo, int virtuais, nos dois records, Contagem e Projeto, e a soma correspondente em somar. (c) Em contar, a linha if (VIRTUAL.matcher(s).find()) virtuais++; e o novo campo no return. (d) Em medir, mais um mapToInt(Contagem::virtuais).sum(), e em main/linha, a coluna nova no cabeçalho e no formatted. O compilador ajuda: ao acrescentar um componente ao record, todo new Projeto(...) com o número antigo de argumentos deixa de compilar, e nenhum lugar fica esquecido.
Exercício 2
O inventário tem um bug de contagem com linhas como /* ajuste fino */ int limite = 8;. Qual é o erro, e como corrigir?
Ver resposta
✓ Resposta: A linha começa com /* e contém */, então o código cai no if (s.startsWith("/*")), faz emComentario = false e executa continue. A declaração int limite = 8; que vem depois do comentário nunca é contada. A correção é olhar o que sobra depois do fechamento: if (s.startsWith("/*")) { int fim = s.indexOf("*/"); if (fim < 0) { emComentario = true; continue; } s = s.substring(fim + 2).strip(); if (s.isEmpty()) continue; }, e seguir com a contagem normal. Um contador realmente correto também precisaria ignorar // e /* dentro de strings. Para isso existem analisadores de verdade, como o do próprio javac (Compiler Tree API), e o exercício mostra por que eles existem.
Exercício 3
Por que medir usa try (Stream<Path> caminhos = Files.walk(raiz)), se streams comuns, como lista.stream(), nunca são fechados? O que aconteceria sem o try ao medir centenas de projetos?
Ver resposta
✓ Resposta: A maioria dos streams é só um cálculo sobre dados em memória e não segura nada. O Files.walk é diferente: ele abre diretórios no sistema operacional à medida que desce na árvore, e os DirectoryStreams abertos só são liberados quando o stream é fechado. Sem o try, cada chamada deixaria descritores abertos até o coletor rodar os limpadores, e o coletor não tem pressa, porque não sabe que descritores são escassos. Com centenas de projetos, o processo pode esbarrar no limite do sistema (Too many open files). É a mesma regra do curso inteiro: um recurso do sistema operacional tem dono, e o dono fecha.
Exercício 4
No JDK 27, com os cabeçalhos compactos ligados por padrão (JEP 534), o cabeçalho de um objeto cai de 12 para 8 bytes. Refaça a estimativa do exercício 1 de "Sem Lixo no Loop — Pools, ECS e o Coletor nos Jogos" para a versão "objetos" com o G1 (referências de 4 bytes, alinhamento em 8).
Ver resposta
✓ Resposta: O Vector2 passa de 12 + 8 = 20 → 24 bytes para 8 + 8 = 16 bytes, já alinhado. A Particula passa a ter 8 + 4 + 4 + 4 = 20 → 24 bytes, como antes, porque o alinhamento absorve a economia. O total por partícula cai de 72 para 24 + 2 × 16 = 56 bytes, e 56 × 1.500 = 84.000 bytes por quadro, em vez de 108.000: 22% menos alocação, sem mudar uma linha do jogo. É uma boa ilustração de como o Java melhora por baixo do código existente, e também um lembrete de que números de memória medidos numa versão precisam ser remedidos na seguinte. Medido no JDK 27: a versão "objetos" aloca 89.739 bytes por quadro com o G1, os 84.000 da conta mais os mesmos 5.739 bytes que não dependem das partículas.
Exercício 5
Você vai colocar as Trilhas da Ilha em produção num servidor da empresa. Use o Java 25 ou o JDK 27, lançado em setembro de 2026? Justifique, e diga como aproveitar o JDK 27 mesmo assim.
Ver resposta
✓ Resposta: Em produção, o Java 25. Ele é LTS, com correções de segurança por muitos anos, e o Spring, o Hibernate, os drivers e as imagens Docker estão validados nele. O JDK 27 é uma versão intermediária: a Oracle dá atualizações para ele só até março de 2027, quando ele é substituído pelo 28, e adotá-lo obriga a atualizar a cada seis meses. Isso é possível, mas é uma decisão de operação, não de gosto. Para aproveitar o 27 sem risco: ponha-o na matriz do CI (java-version: ['25', '27']), para descobrir cedo qualquer incompatibilidade e medir ganhos como os cabeçalhos compactos, e use-o nos projetos pessoais e de estudo. Quando o JDK 29 LTS sair, a migração vai ser um evento sem sustos, porque os testes já terão rodado em todas as versões do meio.