Java 17 LTS – najważniejsze zmiany od Java 13 do Java 17

Dlaczego warto poznać zmiany w Java 17?

Java 17 to jedna z najważniejszych wersji języka wydanych w ostatnich latach. Jest to wydanie Long-Term Support (LTS), które przez długi czas było domyślnym wyborem dla nowych projektów tworzonych w Javie. Wiele firm zdecydowało się na migrację bezpośrednio z Java 11 do Java 17, pomijając pośrednie wersje 12–16.

Jeżeli do tej pory korzystałeś głównie z Java 8 lub Java 11, to po przejściu na Java 17 zauważysz, że kod może być znacznie krótszy, bardziej czytelny i wygodniejszy w utrzymaniu. W międzyczasie pojawiło się wiele nowych funkcjonalności, takich jak Records, Switch Expressions, Pattern Matching, Text Blocks czy Sealed Classes. Część z nich szybko stała się standardem w nowoczesnych projektach.

W tym artykule omówię najważniejsze zmiany wprowadzone od Java 13 do Java 17. Nie będę analizować każdej minimalnej zmiany, ponieważ wiele z nich dotyczy wewnętrznych usprawnień JVM lub zmian istotnych głównie dla twórców bibliotek. Skupię się natomiast na funkcjonalnościach, z których programiści korzystają na co dzień.

Na koniec dowiesz się, czy w 2026 roku warto jeszcze korzystać z Java 17, czy może lepiej od razu zdecydować się na nowszą wersję LTS.

Java 17 – czym jest wersja LTS?

LTS (Long-Term Support) oznacza wersję Javy, która otrzymuje długoterminowe wsparcie producenta. Od 2018 roku nowe wersje Javy publikowane są co sześć miesięcy. Dzięki temu nowe funkcjonalności trafiają do języka znacznie szybciej niż kiedyś, jednak nie każda wersja jest przeznaczona do długoterminowego wykorzystania. Wersje takie jak Java 13, 14, 15 czy 16 wprowadzały wiele interesujących zmian, ale ich okres wsparcia był stosunkowo krótki. Często służyły one jako etap przejściowy przed kolejną wersją LTS.

Java 17 została wydana we wrześniu 2021 roku i była kolejną wersją LTS po Java 11. Dla wielu organizacji oznaczało to idealny moment na aktualizację środowiska. Oprócz licznych usprawnień wydajności JVM oraz poprawek bezpieczeństwa, Java 17 wprowadziła również wiele funkcjonalności, które znacząco poprawiły komfort codziennej pracy programisty. Wiele z nich było wcześniej dostępnych jako funkcje Preview w wersjach 13–16, a dopiero w Java 17 lub wcześniejszych wydaniach zostały oficjalnie sfinalizowane.

W kolejnych sekcjach artykułu omówimy najważniejsze zmiany wprowadzone od Java 13 do Java 17 i pokażemy, jak wpływają one na codzienną pracę programisty.

Java 13

Najbardziej rozpoznawalną nowością były Text Blocks w wersji Preview. Funkcjonalność ta znacząco ułatwiła tworzenie wieloliniowych napisów, eliminując konieczność stosowania znaków ucieczki (\n) oraz ręcznego łączenia kolejnych fragmentów tekstu. W Java 13 pojawiły się również usprawnienia związane z wydajnością maszyny wirtualnej oraz mechanizmem Class Data Sharing (CDS), jednak były one skierowane głównie do twórców bibliotek i osób zajmujących się optymalizacją JVM.

Text Blocks (Preview)

Jedną z najbardziej oczekiwanych nowości w Java 13 było wprowadzenie Text Blocks jako funkcjonalności Preview (JEP 355). Jej głównym celem było uproszczenie pracy z wieloliniowymi napisami.

Szczególnie docenią to osoby, które często pracują z zapytaniami SQL, dokumentami XML, JSON czy HTML.

Warto jednak pamiętać, że w Java 13 była to jeszcze funkcjonalność Preview, dlatego należało ją włączyć przy pomocy opcji:

--enable-preview

Java 14

Java 14 została wydana 17 marca 2020 roku. Podobnie jak Java 13 nie była wersją LTS, jednak wprowadziła kilka funkcjonalności, które na stałe zmieniły sposób pisania kodu w Javie.

Najważniejszą nowością były Switch Expressions, które pozwoliły wykorzystać instrukcję switch jako wyrażenie zwracające wartość. Dzięki temu kod stał się krótszy, czytelniejszy i mniej podatny na błędy związane z pominięciem instrukcji break.

W tej wersji pojawiły się również Helpful NullPointerExceptions, znacznie ułatwiające diagnozowanie błędów związanych z wartością null. Warto wspomnieć także o rekordach (Records), które zostały udostępnione jako funkcjonalność Preview i zapowiadały jedną z największych zmian w kolejnych wersjach Javy.

Switch Expressions

Jedną z najważniejszych zmian wprowadzonych w Java 14 było rozszerzenie możliwości instrukcji switch. Do tej pory pełniła ona wyłącznie rolę instrukcji sterującej przepływem programu. Od Java 14 może być również wykorzystywana jako wyrażenie (expression), które zwraca wartość.

Przed wprowadzeniem tej funkcjonalności przypisanie wartości na podstawie instrukcji switch wymagało utworzenia dodatkowej zmiennej oraz użycia instrukcji break w każdej gałęzi.

String dayType;

switch (day) {
    case MONDAY:
    case TUESDAY:
    case WEDNESDAY:
    case THURSDAY:
    case FRIDAY:
        dayType = "Dzień roboczy";
        break;
    case SATURDAY:
    case SUNDAY:
        dayType = "Weekend";
        break;
    default:
        throw new IllegalArgumentException("Nieznany dzień");
}

Od Java 14 ten sam kod można zapisać znacznie krócej.

String dayType = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Dzień roboczy";
    case SATURDAY, SUNDAY -> "Weekend";
    default -> throw new IllegalArgumentException("Nieznany dzień");
};

Nowa składnia wykorzystuje operator ->, dzięki czemu nie ma potrzeby stosowania instrukcji break. Dodatkowo cały switch zwraca wartość, którą można od razu przypisać do zmiennej.

Zwracanie wartości z bloku kodu

Jeżeli wykonanie danej gałęzi wymaga więcej niż jednej instrukcji, można użyć bloku kodu oraz słowa kluczowego yield.

String message = switch (status) {
    case SUCCESS -> {
        System.out.println("Operacja zakończona pomyślnie.");
        yield "OK";
    }
    case ERROR -> {
        System.out.println("Wystąpił błąd.");
        yield "Błąd";
    }
    default -> throw new IllegalStateException("Nieznany status");
};

Słowo kluczowe yield określa wartość zwracaną przez daną gałąź instrukcji switch.

Records (Preview)

Java 14 wprowadziła również Records jako funkcjonalność Preview. Ich głównym celem było ograniczenie ilości powtarzalnego kodu w klasach przechowujących dane.

Zamiast ręcznie implementować konstruktor, gettery, metody equals(), hashCode() oraz toString(), programista mógł opisać klasę przy pomocy znacznie krótszej składni.

Java 15

Najważniejsza zmaina w tej wersji to oficjalne wprowadzenie Text Blocks, które po dwóch wersjach Preview stały się pełnoprawnym elementem języka Java. W Java 15 pojawiła się również pierwsza wersja Sealed Classes jako funkcjonalność Preview, zapowiadająca kolejne usprawnienia w modelowaniu hierarchii klas.

Text blocks

Wczxeśniej zapisanie większego fragmentu tekstu, na przykład kodu HTML, zapytania SQL czy dokumentu JSON, wymagało ręcznego dodawania znaków nowej linii (\n) oraz łączenia kolejnych fragmentów za pomocą operatora +.

Przykład:

String html =
        "<html>\n" +
        "    <body>\n" +
        "        <h1>Developer On The Go</h1>\n" +
        "    </body>\n" +
        "</html>";

Taki kod jest mało czytelny i wraz ze wzrostem liczby linii staje się coraz trudniejszy w utrzymaniu.

Java 15 wprowadziła możliwość zapisania tego samego tekstu w znacznie prostszej formie.

String html = """
        <html>
            <body>
                <h1>Developer On The Go</h1>
            </body>
        </html>
        """;

Kod jest zdecydowanie bardziej czytelny i przypomina wygląd oryginalnego dokumentu.

Sealed Classes (Preview)

Java 15 wprowadziła również Sealed Classes jako funkcjonalność Preview (JEP 360). Pozwalają one ograniczyć, które klasy mogą dziedziczyć po danej klasie lub implementować dany interfejs.

Ponieważ w Java 15 była to jeszcze funkcjonalność Preview, pełne omówienie Sealed Classes zostawię na rozdział poświęcony Java 17, gdzie rozwiązanie to zostało oficjalnie wprowadzone do języka.

Java 16

Java 16 została wydana 16 marca 2021 roku. Choć nie była wersją LTS, przyniosła kilka zmian, które do dziś są powszechnie wykorzystywane w nowoczesnych projektach.

Najważniejszą z nich było oficjalne wprowadzenie Records, które po dwóch wersjach Preview stały się pełnoprawnym elementem języka. W tej wersji pojawiło się również Pattern Matching for instanceof, pozwalające uprościć sprawdzanie typów i eliminujące konieczność ręcznego rzutowania obiektów.

Records

Przed Java 16 tworzenie prostych klas przechowujących dane wiązało się z koniecznością napisania dużej ilości powtarzalnego kodu. Nawet niewielka klasa zawierająca kilka pól wymagała implementacji konstruktora, getterów oraz często metod equals(), hashCode() i toString().

Przykład:

public class User {

    private final String firstName;
    private final String lastName;
    private final int age;

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        // ...
    }

    @Override
    public int hashCode() {
        // ...
    }

    @Override
    public String toString() {
        // ...
    }
}

Records pozwalają zapisać tę samą klasę w znacznie krótszej formie.

public record User(
        String firstName,
        String lastName,
        int age
) {
}

Na podstawie takiej deklaracji kompilator automatycznie generuje:

  • konstruktor,
  • metody dostępowe do pól,
  • equals(),
  • hashCode(),
  • toString().

Dzięki temu kod staje się krótszy, bardziej czytelny i łatwiejszy w utrzymaniu.

Kiedy warto używać rekordów?

Records najlepiej sprawdzają się jako klasy reprezentujące dane, które po utworzeniu nie powinny ulegać zmianie. Bardzo często wykorzystuje się je jako:

  • VO (Value Object),
  • odpowiedzi z API REST,
  • obiekty zwracane przez serwisy,
  • klasy reprezentujące wyniki zapytań do bazy danych,
  • modele zdarzeń przesyłanych przez Kafka lub RabbitMQ.

Warto pamiętać, że rekord nie jest zwykłym skrótem zapisu klasy. Jest to specjalny typ przeznaczony do modelowania niemutowalnych danych (immutable data). Jeżeli klasa posiada rozbudowaną logikę biznesową lub wymaga mutowalnego stanu, lepszym rozwiązaniem będzie klasyczna klasa.

Pattern Matching for instanceof

Drugą bardzo praktyczną nowością było Pattern Matching for instanceof.

Przed Java 16 sprawdzenie typu obiektu wymagało wykonania dwóch operacji: sprawdzenia typu oraz jego ręcznego rzutowania.

if (object instanceof User) {
    User user = (User) object;
    System.out.println(user.firstName());
}

Choć taki kod był poprawny, zawierał sporo powtarzalnych elementów. Wcześniej należało najpierw sprawdzać typ obiektu, a następnie wykonać rzutowanie, mimo że JVM już wiedziała, jaki jest jego typ.

Java 16 upraszcza ten zapis.

if (object instanceof User user) {
    System.out.println(user.firstName());
}

Po pozytywnym sprawdzeniu warunku zmienna user jest automatycznie dostępna wewnątrz bloku if z odpowiednim typem. Nie ma potrzeby wykonywania dodatkowego rzutowania.

Java 17

Java 17 została wydana 14 września 2021 roku i jest kolejną wersją Long-Term Support (LTS) po Java 11. Dla wielu firm stała się naturalnym wyborem podczas modernizacji istniejących aplikacji oraz rozpoczęcia nowych projektów.

Oprócz długoterminowego wsparcia Java 17 przyniosła również oficjalne wprowadzenie kilku funkcjonalności rozwijanych we wcześniejszych wydaniach. Najważniejszą z nich były Sealed Classes, które po dwóch wersjach Preview stały się pełnoprawnym elementem języka.

W tej wersji znalazły się także kolejne usprawnienia związane z wydajnością JVM, bezpieczeństwem oraz stabilnością platformy, dzięki czemu Java 17 przez wiele lat pozostawała najczęściej wybieraną wersją JDK w projektach komercyjnych.

Klasy zapieczętowane (Sealed Classes)

Jedną z najważniejszych nowości w Java 17 było oficjalne wprowadzenie Sealed Classes (JEP 409). Funkcjonalność ta pozwala ograniczyć, które klasy mogą dziedziczyć po danej klasie lub implementować określony interfejs.

Przed Java 17 każda publiczna klasa mogła zostać rozszerzona przez dowolną inną klasę.

public class Shape {
}

Oznaczało to, że każdy programista mógł utworzyć własną implementację:

public class Circle extends Shape {
}

W przypadku, gdy autor klasy chciał mieć pełną kontrolę nad możliwością jej rozszerzenia, Java 17 rozwiązuje ten problem.

public sealed class Shape
        permits Circle, Rectangle {
}

W tym przypadku jedynie klasy Circle oraz Rectangle mogą rozszerzać klasę Shape. Próba utworzenia innej klasy zakończy się błędem kompilacji.

Każda klasa dziedzicząca po klasie sealed musi dodatkowo określić swój sposób dalszego dziedziczenia przy pomocy jednego z trzech słów kluczowych:

  • final – dalsze dziedziczenie jest zabronione,
  • sealed – można wskazać kolejne dozwolone klasy,
  • non-sealed – ponownie otwiera możliwość dziedziczenia.
public sealed class Shape
        permits Circle, Rectangle {
}

public final class Circle extends Shape {
}

public non-sealed class Rectangle extends Shape {
}

Sealed Classes sprawdzają się szczególnie dobrze podczas modelowania zamkniętych zbiorów typów, takich jak figury geometryczne, typy płatności, role użytkowników czy stany zamówienia. Dzięki nim kompilator ma pełniejszą wiedzę o strukturze aplikacji, co poprawia bezpieczeństwo kodu oraz ułatwia jego rozwój.

Zmiany w Pattern Matching

Java 17 nie wprowadziła ostatecznej wersji kolejnych mechanizmów Pattern Matching, ale kontynuowała rozwój tej funkcjonalności poprzez Pattern Matching for switch dostępny jako Preview.

Usprawnienia platformy

Wśród najważniejszych usprawnień znalazły się między innymi:

  • dalszy rozwój kolektorów pamięci ZGC i G1GC,
  • liczne poprawki bezpieczeństwa,
  • optymalizacje działania maszyny wirtualnej JVM,
  • usunięcie przestarzałych i nieużywanych elementów platformy.

Najważniejsze zmiany od Java 13 do Java 17

WersjaNajciekawsze nowościCzy warto znać?
Java 13Text Blocks (Preview)⭐⭐⭐⭐☆
Java 14Switch Expressions, Helpful NPE⭐⭐⭐⭐⭐
Java 15Text Blocks (Final), Sealed Classes (Preview)⭐⭐⭐⭐☆
Java 16Records, Pattern Matching for instanceof⭐⭐⭐⭐⭐
Java 17Sealed Classes (Final)⭐⭐⭐⭐☆

Podsumowanie

Java 17 to jedna z najważniejszych wersji języka wydanych w ostatnich latach. Choć wiele funkcjonalności pojawiało się stopniowo już od Java 13, dopiero wydanie LTS sprawiło, że zaczęły być powszechnie wykorzystywane w projektach komercyjnych.

Największy wpływ na codzienną pracę programisty miały Switch Expressions, Text Blocks, Records, Pattern Matching for instanceof oraz Sealed Classes. Dzięki nim kod stał się krótszy, bardziej czytelny i łatwiejszy w utrzymaniu.

Dodaj komentarz