Java 26, wydana 17 marca 2026 r., kontynuuje półroczny cykl wydań Oracle jako pierwsze wydanie bez statusu LTS od czasu Javy 25. Zespoły w firmach o zachowawczych cyklach aktualizacji mogą pozostać przy Javie 25 (obecnej wersji LTS), ale Java 26 przynosi istotne usprawnienia w zakresie wydajności, bezpieczeństwa, ekspresyjności języka i nowoczesnej komunikacji sieciowej.
To wydanie obejmuje 10 JEP-ów: 5 finalnych, 4 w wersji preview i 1 inkubacyjny. W tym artykule omawiamy nowości Javy 26, ze szczególnym uwzględnieniem finalnych JEP-ów i ich wpływu na programowanie w Javie.
Uwaga: Pobierz OpenJDK 26 tutaj: https://jdk.java.net/26/
JVM / nowości w Javie 26
JEP 516: Ahead-of-Time Object Caching with Any GC
JEP 516 rozszerza AOT cache tak, aby można go było używać z dowolnym garbage collectorem, w tym z niskolatencyjnym Z Garbage Collectorem (ZGC). Obiekty Javy zapisane w pamięci podręcznej są przechowywane w neutralnym formacie, niezależnym od GC, a nie w formacie właściwym dla konkretnego GC.
Przed JEP 516 AOT cache przechowywał obiekty w formacie zgodnym bit po bicie z układem sterty kolektorów Serial, Parallel i G1, ale zasadniczo niezgodnym z ZGC. Programiści musieli więc wybierać między dwiema skutecznymi optymalizacjami:
- Użycie ZGC w celu zminimalizowania opóźnień ogonowych wywołanych przez GC (pauzy poniżej 1 ms).
- Użycie AOT cache w celu przyspieszenia startu (np. Spring PetClinic uruchamia się o 41% szybciej).
JEP 516 eliminuje ten kompromis, przechowując referencje do obiektów jako indeksy logiczne zamiast surowych adresów pamięci. Dzięki temu JVM może przy starcie strumieniowo wczytywać i materializować obiekty z pamięci podręcznej niezależnie od używanego GC.
Przed JEP 516
# GC-specific format — raw memory address stored in reference field value: 0x4002045278
Po JEP 516
# GC-agnostic format — logical index stored in reference field value: 5
JVM stosuje automatyczną heurystykę w momencie tworzenia pamięci podręcznej i na tej podstawie decyduje, czy użyć formatu strumieniowego (niezależnego od GC). Format ten jest wybierany, gdy podczas treningu zachodzi jedna z trzech sytuacji:
- użyto ZGC (
-XX:+UseZGC) - jawnie wyłączono Compressed OOPs (
-XX:-CompressedOops) - sterta była większa niż 32 GB
Format strumieniowy, niezależny od GC, możesz też wybrać jawnie za pomocą nowej opcji -XX:+AOTStreamableObjects w następujący sposób:
# Force streamable, GC-agnostic object caching java -XX:+AOTStreamableObjects -cp app.jar com.example.App
JEP 522: G1 GC: Improve Throughput by Reducing Synchronization
G1 jest domyślnym garbage collectorem Javy od wersji 9 i ma równoważyć opóźnienia i przepustowość. Aby wykonywać swoją pracę, G1 działa współbieżnie z aplikacją, co oznacza, że wątki aplikacji i wątki GC dzielą procesor i muszą się synchronizować. Ten narzut synchronizacji obniża przepustowość.
Aby wydajnie śledzić referencje między obiektami, G1 utrzymuje strukturę danych zwaną tablicą kart (card table), aktualizowaną przy każdej zmianie referencji obiektu. Wątki optymalizujące działające w tle przetwarzają tę tablicę, a bezpieczne wykonanie tej pracy wymaga synchronizacji z wątkami aplikacji, co prowadzi do wolniejszego i bardziej skomplikowanego kodu barier zapisu (write barrier).
JEP 522 wprowadza drugą tablicę kart eliminującą to wąskie gardło synchronizacji. Wątki aplikacji zapisują do jednej tablicy bez żadnej synchronizacji, a wątki optymalizujące pracują na drugiej. W razie potrzeby G1 atomowo zamienia obie tablice.
Efektem jest wzrost przepustowości o 5–15% w aplikacjach intensywnie modyfikujących pola referencyjne, a do tego nawet 5% dodatkowego zysku na architekturach x64 dzięki prostszemu kodowi barier zapisu.
Biblioteki podstawowe (Core Libs)
JEP 517: HTTP/3 for the HTTP Client API
JEP 517 aktualizuje java.net.http.HttpClient — wbudowany klient HTTP dodany w JDK 11 — tak, aby obsługiwał HTTP/3, następcę HTTP/2 ustandaryzowanego przez IETF w 2022 roku.
HTTP/2 był dużym postępem w stosunku do HTTP/1.1, ale nadal działa na TCP. Oznacza to, że pojedynczy utracony pakiet blokuje wszystkie strumienie aż do jego ponownej transmisji — to osławione blokowanie na początku kolejki (head-of-line blocking).
HTTP/3 rozwiązuje te problemy, opierając się na QUIC (Quick UDP Internet Connections), protokole transportowym opartym na UDP. W QUIC każdy strumień jest niezależny, więc utracony pakiet wpływa tylko na swój strumień.
Przed JEP 517 — HTTP/3 wymagał zależności zewnętrznej
// Required external library e.g. Netty, Jetty, or Quiche4j
QuicChannel quicChannel = QuicChannel.newBootstrap(channel)
.handler(new Http3ClientConnectionHandler())
.remoteAddress(new InetSocketAddress("example.com", 443))
.connect()
.get();
Po JEP 517 — HTTP/3 włącza się jedną linią w standardowym HttpClient
// Opt in at the client level — prefers HTTP/3, races HTTP/2 as fallback
var client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
// Or opt in at the request level — tries HTTP/3, falls back if unavailable
var request = HttpRequest.newBuilder(URI.create("https://example.com/api"))
.version(HttpClient.Version.HTTP_3)
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
Oba poziomy włączania mają różne strategie awaryjne. Ustawienie HTTP_3 w HttpRequest wysyła pierwsze żądanie przez HTTP/3 i wraca do HTTP/2 lub HTTP/1.1, jeśli połączenia HTTP/3 nie uda się nawiązać na czas. Natomiast ustawienie HTTP_3 w HttpClient uruchamia równolegle połączenie HTTP/3 i połączenie HTTP/2 lub HTTP/1.1, a następnie używa tego, które zostanie nawiązane jako pierwsze.
Domyślną wersją protokołu pozostaje HTTP/2, więc cały istniejący kod nadal działa bez zmian.
JEP 500: Prepare to Make Final Mean Final
JEP 500 przygotowuje ekosystem Java na przyszłe wydanie, które domyślnie zabroni modyfikowania pól final za pomocą głębokiej refleksji. Dzięki temu programy w Javie staną się bezpieczniejsze i potencjalnie szybsze.
Pola final odgrywają kluczową rolę w modelu pamięci Javy (Java Memory Model) od JDK 5: ich niezmienność stanowi podstawę bezpiecznej inicjalizacji obiektów w kodzie wielowątkowym. Niestety ta niezmienność koliduje też z działaniem bibliotek serializacji, które modyfikują pola, aby inicjalizować obiekty podczas deserializacji. Ten przypadek użycia był na tyle ważny, że w JDK 5 zmieniono API refleksji, aby umożliwić modyfikowanie pól final.
Przed JEP 500 poniższy kod kompilował się, działał i po cichu modyfikował pole final bez żadnego ostrzeżenia:
// Before JEP 500 — silent final field mutation, no warning
class Person {
final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
var person = new Person("Alice");
Field field = Person.class.getDeclaredField("name");
field.setAccessible(true);
field.set(person, "Bob"); // mutates a final field - no warning at all
System.out.println(person.getName()); // prints: Bob
Po JEP 500 ten sam kod domyślnie generuje ostrzeżenie w czasie wykonania:
WARNING: Final field name in class Person has been mutated reflectively
by class Application in unnamed module.
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning.
WARNING: Mutating final fields will be blocked in a future release.
Tym zachowaniem steruje nowa opcja --illegal-final-field-mutation działająca podobnie jak --illegal-access wprowadzona w JDK 9 oraz --illegal-native-access z JDK 24. Działa ona następująco:
--illegal-final-field-mutation=allowpozwala na modyfikację bez ostrzeżenia.--illegal-final-field-mutation=warnpozwala na modyfikację, ale wyświetla ostrzeżenie, gdy kod w danym module po raz pierwszy wykona niedozwoloną modyfikację pola final. Dla każdego modułu wyświetlane jest najwyżej jedno ostrzeżenie. Ten tryb jest domyślny w JDK 26. W przyszłym wydaniu zacznie być wycofywany, a ostatecznie zostanie usunięty.--illegal-final-field-mutation=debugdziała tak samo jakwarnz tą różnicą, że przy każdej niedozwolonej modyfikacji pola final wyświetlany jest zarówno komunikat ostrzegawczy, jak i ślad stosu.--illegal-final-field-mutation=denysprawia, żeField::setzgłasza wyjątekIllegalAccessExceptionprzy każdej niedozwolonej modyfikacji pola final. W przyszłym wydaniu ten tryb stanie się domyślny.
JDK 26 integruje ten mechanizm również z JDK Flight Recorder za pomocą nowego zdarzenia jdk.FinalFieldMutation rejestrującego każdą próbę modyfikacji wraz z pełnym śladem stosu. To przydatne narzędzie do audytu całych drzew zależności, zanim wejdzie w życie ostateczna blokada.
Oto na przykład, jak utworzyć nagranie JFR, a następnie wyświetlić zdarzenia jdk.FinalFieldMutation z tego nagrania:
$ java -XX:StartFlightRecording:filename=recording.jfr ... $ jfr print --events jdk.FinalFieldMutation recording.jfr
JEP 500 jest częścią szerszej inicjatywy Integrity by Default zamykającej kolejnymi JEP-ami niebezpieczne luki w API: silna enkapsulacja wewnętrznych elementów JDK (JEP 396/403), ograniczenie dynamicznego ładowania agentów (JEP 451), wycofanie dostępu do pamięci przez sun.misc.Unsafe (JEP 471/498), ograniczenie użycia JNI (JEP 472), a teraz także modyfikacja pól final.
JEP 504: Remove the Applet API
JEP 504 usuwa cały pakiet java.applet z JDK 26, który jest tym samym pierwszą wersją Javy dostarczaną bez Applet API — 10 lat po rozpoczęciu procesu jego wycofywania.
Klasa java.applet.Applet pojawiła się w JDK 1.0 w 1996 roku. W tamtym czasie większość zastosowań Javy dotyczyła apletów — małych programów osadzanych w przeglądarkach, które dodawały interaktywne możliwości nieosiągalne we wczesnym HTML. Cyklem życia tych programów zarządzał w całości pakiet java.applet i jego API.
Dla kodu, który używał Applet jako kontenera interfejsu użytkownika, alternatywy zapewnia API AWT. Dla kodu, który używał AudioClip do odtwarzania dźwięku, zalecanym zamiennikiem jest klasa javax.sound.SoundClip wprowadzona w JDK 25. Do wdrażania samodzielnych aplikacji służy narzędzie jpackage tworzące natywne instalatory dla danej platformy.
JEP-y w wersji preview i inkubacyjne w Javie 26
Ten artykuł skupia się na finalnych JEP-ach Javy 26 — funkcjach, których możesz już dziś bezpiecznie używać na produkcji — ale wydanie zawiera też kilka JEP-ów w wersji preview i inkubacyjnych. To funkcje wciąż rozwijane: wersje preview są prawie gotowe, ale mogą jeszcze zostać dopracowane, a inkubatory udostępniają API, aby zebrać wczesne opinie. Oto krótki przegląd tego, co nas czeka.
JEP 524: PEM Encodings of Cryptographic Objects (Second Preview)
Wprowadza API do kodowania i dekodowania obiektów kryptograficznych — kluczy publicznych, kluczy prywatnych, certyfikatów i list odwołanych certyfikatów — do i z powszechnie stosowanego formatu PEM (Privacy-Enhanced Mail), standardu de facto poza światem Javy. Dwie główne klasy to PEMEncoder i PEMDecoder, uzupełnione o DEREncodable — interfejs znacznikowy dodany do istniejących typów, takich jak X509Certificate.
To druga wersja preview, z kilkoma zmianami względem Javy 25: klasa PEMRecord zmienia nazwę na PEM i zyskuje nową metodę decode() – oprócz tego metody encryptKey w klasie EncryptedPrivateKeyInfo zmieniają nazwę na encrypt i przyjmują teraz obiekty DEREncodable zamiast wyłącznie PrivateKey, co pozwala szyfrować także obiekty KeyPair i PKCS8EncodedKeySpec – a klasa EncryptedPrivateKeyInfo zyskuje nowe metody getKeyPair do odszyfrowywania tekstu zakodowanego w PKCS#8, który zawiera PublicKey.
JEP 525: Structured Concurrency (Sixth Preview)
JEP 525 upraszcza programowanie współbieżne, traktując grupy powiązanych zadań działających w różnych wątkach jako pojedyncze jednostki pracy, co usprawnia obsługę błędów, anulowanie i obserwowalność. Centralnym elementem API jest StructuredTaskScope, który ogranicza czas życia podzadań do jasno określonego zakresu leksykalnego, dzięki czemu kończą się one sukcesem, błędem lub zostają anulowane razem — bez wycieków wątków i osieroconych zadań, typowych przy stosowaniu ExecutorService/Future w klasycznych wzorcach.
To szósta wersja preview, oparta na gruntownej przebudowie z JDK 25, która zastąpiła publiczne konstruktory statycznymi metodami fabrycznymi. JEP 525 skupia się na drobnych, stopniowych udoskonaleniach: nowe wywołanie zwrotne onTimeout() w Joiner pozwala niestandardowym joinerom reagować na przekroczenie limitu czasu i zwracać wyniki częściowe lub zastępcze, zamiast zawsze zgłaszać wyjątek; Joiner.allSuccessfulOrThrow() zwraca teraz bezpośrednio List<T> zamiast strumienia obiektów Subtask (uchwytów podzadań); anySuccessfulResultOrThrow() zmienia nazwę na anySuccessfulOrThrow() dla zwięzłości, a przeciążenie StructuredTaskScope.open() oczekuje teraz UnaryOperator<Configuration> zamiast Function dla ściślejszego typowania.
JEP 526: Lazy Constants (Second Preview)
JEP 526 wprowadza java.lang.LazyConstant<T> — niezmienny kontener wartości, inicjalizowany najwyżej raz, z gwarancją bezpieczeństwa wątkowego, który łączy bezpieczeństwo pól final z elastycznością odroczonej inicjalizacji. Rozwiązuje to długotrwały dylemat w Javie: pola final wymuszają niezmienność, ale wymagają natychmiastowej inicjalizacji w momencie konstrukcji, a pola modyfikowalne pozwalają na leniwą inicjalizację, ale kosztem gwarancji bezpieczeństwa wątkowego i optymalizacji JVM typu constant folding.
// Replaces double-checked locking, volatile fields, and holder class idioms private static final LazyConstant<ConnectionPool> POOL = LazyConstant.of(() -> new ConnectionPool(MAX_CONNECTIONS)); // Access — initializes on first call, cached forever after pool.get().acquire(); // Lazy collections — each slot initializes independently on first access static final List<OrderController> ORDERS = List.ofLazy(10, _ -> new OrderController());
JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)
Rozszerza dopasowywanie wzorców, instanceof oraz switch tak, aby typy prymitywne (np. int, long, float, double) mogły być używane we wszystkich kontekstach wzorców, a nie tylko typy referencyjne. Usuwa to tarcia przy łączeniu typów prymitywnych z nowoczesnym dopasowywaniem wzorców i ujednolica język.
// Primitive types in switch patterns (preview)
Object value = getSomeValue();
switch (value) {
case int i when i > 0 -> System.out.println("Positive int: " + i);
case long l -> System.out.println("Long: " + l);
case double d -> System.out.println("Double: " + d);
default -> System.out.println("Other: " + value);
}
To czwarta wersja preview — wcześniej funkcja była dostępna jako preview w Javie 23, 24 i 25.
JEP 529: Vector API (Eleventh Incubator)
Vector API pozwala w jawny sposób wyrażać obliczenia wektorowe w stylu SIMD odwzorowywane w czasie wykonania na optymalne instrukcje wektorowe procesora. To jedenasta inkubacja — API pozostaje w fazie inkubacji do czasu ukończenia Project Valhalla, który dostarczy typy wartościowe potrzebne do wydajnej finalizacji tego API.
Podsumowanie
Java 26 potwierdza stały, półroczny rytm rozwoju platformy. Finalne JEP-y przynoszą konkretne, gotowe do produkcji usprawnienia: szybszy start zgodny teraz z ZGC, lepszą przepustowość G1 bez dodatkowej konfiguracji, obsługę HTTP/3 oraz długo oczekiwane porządki w API. Funkcje w wersji preview i inkubacyjne — leniwe stałe, współbieżność strukturalna, wzorce z typami prymitywnymi i kodowanie PEM — zapowiadają dalsze dojrzewanie Project Loom, Project Amber, a w przyszłości także Project Valhalla.
Jak zawsze, korzystając już dziś ze stabilnych funkcji i obserwując te, które dopiero się pojawiają, zyskasz to, co najlepsze z obu światów: niezawodność i innowacyjność.
Czy Java 26 jest wersją LTS (Long-Term Support)?
Nie, Java 26 to standardowe wydanie funkcjonalne z 6-miesięcznym wsparciem. Aktualną wersją LTS pozostaje Java 25. Java 26 świetnie nadaje się do testowania najnowszych innowacji, zanim zostaną ustabilizowane w przyszłym wydaniu LTS.
Dlaczego modyfikacja pól final wywołuje teraz ostrzeżenie?
To element inicjatywy „Integrity by Default”. Możliwość modyfikowania niezmiennych pól przez refleksję podważa optymalizacje kompilatora i bezpieczeństwo danych. Ostatecznie ta praktyka zostanie całkowicie zablokowana (deny jako tryb domyślny).
Czy mogę używać HTTP/3 z dowolnym serwerem?
Klient HttpClient w Javie 26 obsługuje HTTP/3, ale serwer zdalny także musi obsługiwać ten protokół (oparty na QUIC/UDP). Jeśli serwer go nie obsługuje, klient Javy automatycznie przełączy się na HTTP/2 lub HTTP/1.1, w zależności od Twojej konfiguracji.
Jaka jest konkretna korzyść z AOT Object Caching dla ZGC?
Przed Javą 26 użytkownicy ZGC nie mogli korzystać z buforowania obiektów przy starcie. Teraz mogą połączyć ultraniskie opóźnienia ZGC (pauzy < 1 ms) ze znacznie szybszym uruchamianiem aplikacji (nawet o 41% w niektórych frameworkach).
Jakie są główne różnice między Javą 25 a Javą 26?
Java 25 jako wydanie LTS z 18 JEP-ami stawiała na stabilność, a Java 26 wprowadza 10 JEP-ów skupionych na nowoczesnej komunikacji sieciowej (HTTP/3) i dostrajaniu wydajności JVM. Java 26 zaostrza też bezpieczeństwo, ostrzegając przed modyfikacją pól final, która w Javie 25 wciąż odbywała się po cichu.
Czy warto przejść z Javy 25 na Javę 26?
Jeśli priorytetem jest dla Ciebie długoterminowa stabilność i wsparcie, zostań przy Javie 25 (LTS). Jeśli jednak Twoja aplikacja wymaga obsługi HTTP/3 albo korzystasz z ZGC i zależy Ci na szybszym starcie, przejście na Javę 26 da Ci natychmiastowe korzyści techniczne.