Java 25 LTS i Java 26 — co naprawdę zmieniło się w Javie i czy warto migrować
Java 25 to nowe wydanie LTS, a Java 26 dołożyła HTTP/3 i kolejne usprawnienia startu aplikacji. Przechodzimy przez najważniejsze JEP-y, mierzalne zyski w produkcji i plan migracji z Java 17 i 21.
Przy projektach integracyjnych bardzo często trafiamy na systemy napisane w Javie: ERP-y, systemy magazynowe, bankowe API, wewnętrzne aplikacje korporacyjne sprzed dekady. Część z nich wciąż działa na Javie 8 lub 11. Część przeszła na 17 lub 21. I prawie wszędzie pada pytanie: czy przechodzić na Javę 25, a jeśli tak — kiedy i jak?
Java 25 ukazała się we wrześniu 2025 roku jako kolejne wydanie z długoterminowym wsparciem (LTS). W marcu 2026 dołączyła do niej Java 26. W tym artykule zbieramy zmiany, które mają realne znaczenie dla zespołów utrzymujących aplikacje produkcyjne — z pominięciem ciekawostek, które nie wpływają na codzienną pracę.
Kalendarz wydań w pigułce
Od 2017 roku Java wychodzi co pół roku — w marcu i we wrześniu. Co dwa lata jedno z wydań otrzymuje status LTS, czyli wieloletnie wsparcie od dostawców (Oracle, Eclipse Temurin, Amazon Corretto, Azul, Microsoft, Red Hat).
| Wersja | Data wydania | Status |
|---|---|---|
| Java 17 | wrzesień 2021 | LTS |
| Java 21 | wrzesień 2023 | LTS |
| Java 25 | wrzesień 2025 | LTS |
| Java 26 | marzec 2026 | wydanie krótkoterminowe |
| Java 27 | wrzesień 2026 (planowo) | wydanie krótkoterminowe |
| Java 29 | wrzesień 2027 (planowo) | kolejne LTS |
Dla większości firm praktyczna zasada brzmi: produkcja na LTS, wydania pośrednie do testowania nowych funkcji. Jeśli dziś działasz na Javie 17 lub 21, naturalnym celem migracji jest Java 25.
Co przyniosła droga od Javy 21 do 25
Jeśli przeskakujesz z 21 na 25, dostajesz wszystkie zmiany z wydań 22, 23, 24 i 25. Najważniejsze z nich, pogrupowane według tego, na co wpływają.
Wirtualne wątki bez przypinania (JEP 491)
Wirtualne wątki (Project Loom) weszły jako stabilna funkcja w Javie 21 i pozwalają obsłużyć dziesiątki tysięcy równoległych operacji I/O bez puli wątków i bez programowania reaktywnego. Miały jednak poważne ograniczenie: wirtualny wątek blokujący się wewnątrz bloku synchronized był „przypinany" do wątku platformowego. W praktyce biblioteki intensywnie korzystające z synchronized (np. starsze sterowniki JDBC czy klienci HTTP) potrafiły doprowadzić do zakleszczeń lub spadku wydajności.
Od Javy 24 ten problem zniknął. To jeden z najważniejszych argumentów za migracją z 21 — jeśli rezygnowaliście z wirtualnych wątków z powodu pinningu, warto do nich wrócić.
Scoped Values — następca ThreadLocal (JEP 506, finalne w 25)
ThreadLocal od lat służy do przekazywania kontekstu (np. zalogowanego użytkownika, ID żądania, transakcji) w głąb wywołań. Przy milionach wirtualnych wątków jest jednak kosztowny i podatny na błędy — wartość jest mutowalna i łatwo zapomnieć o jej wyczyszczeniu.
Scoped Values to niemutowalny kontekst o jasno określonym zasięgu:
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
void handle(Request request) {
User user = authenticate(request);
ScopedValue.where(CURRENT_USER, user).run(() -> processOrder(request));
}
void processOrder(Request request) {
User user = CURRENT_USER.get(); // dostępne w całym drzewie wywołań
// ...
}
Wartość jest widoczna tylko w obrębie wywołania run, nie da się jej nadpisać, a po zakończeniu znika automatycznie. Dla frameworków webowych i bibliotek do obserwowalności to spora zmiana na lepsze.
Structured Concurrency (JEP 505, podgląd w 25)
Współbieżność strukturalna traktuje grupę zadań wykonywanych równolegle jako jedną jednostkę pracy. Jeśli jedno zadanie się nie powiedzie, pozostałe są anulowane; jeśli anulowane zostanie zadanie nadrzędne, anulowane są wszystkie podzadania. Koniec z „osieroconymi" wątkami, które działają dalej po błędzie.
Response fetchDashboard(long customerId) throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
var customer = scope.fork(() -> crm.findCustomer(customerId));
var orders = scope.fork(() -> erp.findOrders(customerId));
scope.join(); // czeka na oba; błąd jednego anuluje drugi
return new Response(customer.get(), orders.get());
}
}
W Javie 25 i 26 to wciąż funkcja w wersji podglądowej (wymaga flagi --enable-preview), a API zmieniało się między wydaniami. Warto ją poznać, ale z użyciem w kodzie produkcyjnym radzimy poczekać na wersję finalną.
Szybszy start aplikacji — AOT cache (Project Leyden)
To zmiana, która dla wielu zespołów jest najbardziej odczuwalna. Projekt Leyden stopniowo przenosi część pracy wykonywanej przy starcie JVM (ładowanie i linkowanie klas, profilowanie metod) do wcześniejszego „przebiegu treningowego".
- Java 24 (JEP 483): cache załadowanych i zlinkowanych klas.
- Java 25 (JEP 514): prostsza obsługa — jedna flaga zamiast kilku kroków.
- Java 25 (JEP 515): w cache zapisywane są także profile metod, więc kompilator JIT szybciej osiąga pełną wydajność.
- Java 26 (JEP 516): cache obiektów działa z dowolnym garbage collectorem, w tym z ZGC.
W praktyce wygląda to tak:
# przebieg treningowy — aplikacja uruchamia się i wykonuje typowe operacje
java -XX:AOTCacheOutput=app.aot -jar app.jar
# produkcja — start z gotowym cache
java -XX:AOTCache=app.aot -jar app.jar
Dla aplikacji Spring Boot oznacza to wyraźnie krótszy czas startu — bez przepisywania kodu i bez ograniczeń kompilacji natywnej (GraalVM Native Image). W środowiskach kontenerowych, gdzie instancje są często uruchamiane i zatrzymywane (autoskalowanie, serverless), różnica jest bardzo odczuwalna.
Compact Object Headers (JEP 519) — mniej pamięci za darmo
Każdy obiekt w Javie ma nagłówek z metadanymi. Na 64-bitowej JVM zajmował on zwykle 12 bajtów; dzięki Compact Object Headers — 8 bajtów. Brzmi niewinnie, ale typowa aplikacja tworzy miliony małych obiektów. W benchmarkach opisanych w JEP-ie oszczędność sterty sięgała około 20%, a zużycie CPU również spadało.
W Javie 25 funkcja przestała być eksperymentalna. Włącza się ją flagą:
java -XX:+UseCompactObjectHeaders -jar app.jar
To jedna z niewielu optymalizacji, które nie wymagają żadnych zmian w kodzie. Po migracji warto ją przetestować na swoim obciążeniu — w środowiskach chmurowych mniej pamięci to po prostu mniejszy rachunek.
Mniej ceremonii dla prostych programów (JEP 512, 511)
Java przez lata była krytykowana za rozwlekłość prostych programów. Od Javy 25 skrypt lub narzędzie pomocnicze może wyglądać tak:
void main() {
String name = IO.readln("Jak masz na imię? ");
IO.println("Cześć, " + name + "!");
}
Bez deklaracji klasy, bez public static void main(String[] args). Do tego import modułów (import module java.base;) zastępuje kilkanaście pojedynczych importów. Dla doświadczonych zespołów to kosmetyka, ale przy skryptach narzędziowych, prototypach i nauce języka — bardzo przyjemna zmiana. W połączeniu z uruchamianiem programów wieloplikowych bezpośrednio z kodu źródłowego (JEP 458) Java staje się rozsądną alternatywą dla skryptów w Pythonie czy Bashu w zespołach, które na co dzień piszą w Javie.
Elastyczne konstruktory (JEP 513)
Przed wywołaniem super(...) lub this(...) można teraz wykonywać instrukcje — np. walidować argumenty. Koniec z obchodzeniem tego ograniczenia statycznymi metodami pomocniczymi:
public PositiveAmount(BigDecimal value) {
if (value.signum() <= 0) {
throw new IllegalArgumentException("Kwota musi być dodatnia");
}
super(value);
}
Pozostałe zmiany warte uwagi
- Stream Gatherers (JEP 485, finalne w 24) — własne operacje pośrednie w strumieniach, np. okna przesuwne czy grupowanie w partie. Wiele pętli, które „nie pasowały" do Stream API, da się teraz zapisać czytelnie.
- Foreign Function & Memory API (JEP 454, finalne w 22) — nowoczesny następca JNI do wywoływania bibliotek natywnych w C. Istotne np. przy integracji z bibliotekami AI i sprzętem.
- Generational ZGC i Generational Shenandoah — kolektory o bardzo krótkich pauzach, teraz w trybie generacyjnym, z lepszą przepustowością.
- Kryptografia postkwantowa (JEP 496, 497) — algorytmy ML-KEM i ML-DSA w standardowej bibliotece. Dla systemów, które muszą chronić dane przez wiele lat, to początek przygotowań do ery komputerów kwantowych.
- API funkcji wyprowadzania kluczy (JEP 510) — standardowe KDF, m.in. HKDF.
- Nowe możliwości JFR (JEP 509, 518, 520) — profilowanie czasu CPU, dokładniejsze próbkowanie i pomiar czasu wybranych metod bez modyfikowania kodu.
- Dokumentacja w Markdownie (JEP 467) — komentarze Javadoc można pisać w Markdownie zamiast HTML.
Co nowego w Javie 26
Java 26 z marca 2026 to wydanie krótkoterminowe, ale z kilkoma istotnymi zmianami:
- HTTP/3 w HttpClient (JEP 517) — wbudowany klient HTTP obsługuje protokół HTTP/3 oparty na QUIC. Przydatne przy komunikacji z nowoczesnymi API i w sieciach o wysokich opóźnieniach.
- AOT cache z dowolnym GC (JEP 516) — szybki start aplikacji również przy ZGC.
- Lepsza przepustowość G1 (JEP 522) — mniej synchronizacji między wątkami aplikacji a kolektorem.
- „Final ma znaczyć final" (JEP 500) — JVM zaczyna ostrzegać przy modyfikowaniu pól
finalprzez refleksję. Kolejny krok w stronę bezpieczniejszej i lepiej optymalizowanej platformy. Warto sprawdzić, czy Twoje biblioteki (serializacja, mockowanie, wstrzykiwanie zależności) nie korzystają z takich sztuczek. - Usunięcie Applet API (JEP 504) — symboliczne zamknięcie pewnej epoki.
- Kolejne podglądy — Structured Concurrency, Lazy Constants (dawniej Stable Values), wzorce z typami prostymi, kodowanie PEM oraz Vector API w inkubatorze.
Ekosystem: Spring Boot 4 i reszta
Sama JVM to połowa historii. Równie ważne jest to, co dzieje się w frameworkach:
- Spring Boot 4 i Spring Framework 7 (listopad 2025) — minimalną wersją pozostaje Java 17, ale framework jest projektowany pod Javę 25. Przejście na Jakarta EE 11, adnotacje null-safety JSpecify, wbudowane wersjonowanie API, deklaratywne klienty HTTP i bardziej modułowa autokonfiguracja. Migracja ze Spring Boot 3 wymaga pracy, ale znacznie mniej niż skok z 2 na 3.
- Quarkus i Micronaut — wspierają Javę 25 i od lat stawiają na szybki start aplikacji, co dobrze łączy się z AOT cache.
- Narzędzia budowania — Maven i Gradle w aktualnych wersjach obsługują Javę 25. Starsze wersje Gradle mogą wymagać aktualizacji, zanim w ogóle uruchomisz build.
Na co uważać przy migracji
Większość migracji z 17 lub 21 na 25 przebiega spokojnie, ale kilka rzeczy regularnie sprawia kłopoty:
- Biblioteki operujące na bajtkodzie. Lombok, Mockito, ByteBuddy, AspectJ, agenty APM — starsze wersje nie rozpoznają nowego formatu klas. Zaktualizuj je jako pierwsze.
sun.misc.Unsafe. Metody dostępu do pamięci wUnsafegenerują ostrzeżenia (JEP 498) i w przyszłości zostaną usunięte. Dotyczy to głównie starszych bibliotek — sprawdź, czy mają nowsze wersje.- Security Manager jest trwale wyłączony od Javy 24. Jeśli Twoja aplikacja go używała (rzadkie, ale zdarza się w starych systemach), potrzebne są zmiany.
- JNI i dostęp natywny — kolejne ostrzeżenia przy ładowaniu bibliotek natywnych bez jawnego zezwolenia.
- Brak 32-bitowego portu x86 (JEP 503) — jeśli gdzieś w firmie wciąż działa 32-bitowa JVM, Java 25 nie będzie opcją.
- Obrazy kontenerów. Zaktualizuj obraz bazowy, sprawdź ustawienia pamięci i ergonomię GC — domyślne zachowanie JVM w kontenerach poprawiało się z każdym wydaniem.
Plan migracji krok po kroku
Tak wygląda proces, który stosujemy przy migracjach:
- Inwentaryzacja. Wersja JDK, frameworków, bibliotek i narzędzi budowania.
jdepspokaże zależności od wewnętrznych API JDK, ajdeprscan— użycie przestarzałych API. - Aktualizacja zależności na obecnej wersji Javy. Najpierw podnieś biblioteki i frameworki, nie zmieniając JDK. Dzięki temu oddzielasz problemy z bibliotekami od problemów z JVM.
- Build i testy na Javie 25. Kompilacja z
--release 25, pełny zestaw testów, uruchomienie w środowisku testowym. Automatyczne recepty OpenRewrite potrafią wykonać dużą część mechanicznych zmian w kodzie. - Testy wydajności. Porównaj czas startu, zużycie pamięci, opóźnienia i przepustowość. To dobry moment, żeby przetestować Compact Object Headers i AOT cache.
- Wdrożenie stopniowe. Najpierw mniej krytyczne usługi, potem reszta. Monitoruj logi pod kątem nowych ostrzeżeń JVM.
- Modernizacja kodu. Dopiero gdy wszystko działa, zacznij korzystać z nowych funkcji: wirtualnych wątków, Scoped Values, wzorców w
switch, rekordów.
Czy warto migrować? Nasza rekomendacja
- Z Javy 8 lub 11: tak, i to jak najszybciej. To już nie kwestia nowych funkcji, tylko bezpieczeństwa, wsparcia i dostępności bibliotek. Skok jest duży, więc warto przejść przez niego etapami lub z pomocą narzędzi do automatycznej migracji.
- Z Javy 17: tak, zaplanuj migrację w ciągu najbliższych kwartałów. Zyskujesz wirtualne wątki bez pinningu, szybszy start i mniejsze zużycie pamięci — a Spring Boot 4 i tak skłania do aktualizacji.
- Z Javy 21: warto, zwłaszcza jeśli używasz wirtualnych wątków albo działasz w chmurze, gdzie pamięć i czas startu przekładają się bezpośrednio na koszty. Bez pośpiechu, ale w ramach normalnego cyklu utrzymania.
- Java 26 na produkcji: tylko jeśli macie proces regularnych aktualizacji co pół roku. W przeciwnym razie zostańcie przy 25 LTS.
Podsumowanie
Java 25 to najbardziej „odczuwalne" wydanie LTS od lat. Nie przez spektakularne zmiany w składni, tylko przez rzeczy, które widać w metrykach: wirtualne wątki, które wreszcie działają bez kompromisów, szybszy start dzięki AOT cache, mniej pamięci dzięki kompaktowym nagłówkom obiektów i prostszy model współbieżności z Scoped Values. Java 26 kontynuuje ten kierunek i dokłada HTTP/3.
Utrzymujesz system w Javie, który trzeba zintegrować z nowymi narzędziami, zautomatyzować lub zmigrować? W Nightcode zajmujemy się integracjami i rozbudową istniejących systemów — także tych, których nikt w firmie nie chce już dotykać. Porozmawiajmy.
Chcesz sprawdzić, jak to wygląda w Twojej firmie?
Pogadajmy 15 minut. Powiemy wprost, co ma sens, ile to kosztuje i od czego zacząć — albo policz sam, ile tracisz na ręcznej pracy.