Java 27: 14 pytań rekrutacyjnych o nowości i migrację
Premiera Javy 27 jest planowana na 15 września 2026 r. Datę potwierdza zapowiedź zespołu Java w Oracle z 10 września. Ten artykuł powstał 13 września, przed wydaniem GA, i opisuje stan przygotowań do tej premiery.
Na rozmowie technicznej sama lista JEP-ów niewiele powie o Twoich umiejętnościach. Znacznie ciekawsze jest to, czy potrafisz wyjaśnić skutki zmiany: co stanie się z drugim wywołaniem HTTP po błędzie pierwszego, kiedy obliczy się leniwa wartość albo dlaczego aplikacja po zmianie JDK zużywa inaczej pamięć.
Poniżej znajdziesz 14 przykładowych pytań z odpowiedziami, dopytaniami i pułapkami. To autorska propozycja przygotowania do rozmowy na podstawie zmian w JDK, a nie statystyka pytań zadawanych przez firmy. Pytania o szczegóły implementacyjne najczęściej kieruje do kandydata prowadzący rozmowę programista lub architekt.
Premiera i zakres Javy 27
1. Czy Java 27 jest LTS i czy warto migrować na nią od razu?
Java 27 jest wydaniem non-LTS w planie wsparcia Oracle. Java 25 ma status LTS, a następny LTS jest planowany jako Java 29 we wrześniu 2027 r. LTS opisuje politykę utrzymania wydania; nie oznacza, że każda funkcja dostępna w danym JDK jest stabilnym API.
Dobra odpowiedź rekrutacyjna zaczyna się od potrzeb projektu. Zespół regularnie aktualizujący środowisko może rozważyć nowe wydanie, a projekt z rzadszym cyklem migracji może preferować LTS. W obu przypadkach sprawdź wsparcie swojej dystrybucji JDK oraz zgodność narzędzi i bibliotek.
Dopytanie: „Co musiałoby się poprawić, żebyś rekomendował migrację?”. Wskaż mierzalny cel, np. mniejsze zużycie pamięci przy tym samym obciążeniu, i sposób jego weryfikacji. Sam większy numer wersji nie stanowi uzasadnienia biznesowego.
2. Co rzeczywiście zmienia Java 27?
Zakres wydania obejmuje dziewięć JEP-ów. Warto odróżnić zmiany gotowe do zwykłego użycia od kolejnych iteracji API rozwijanych jako preview lub incubator. Takie rozróżnienie pozwala uniknąć błędu „jest w JDK, więc można bez zmian utrzymywać ten kod przez lata”.
| Obszar | Zmiana w JDK 27 | Status |
|---|---|---|
| Garbage collection | JEP 523: G1 domyślnie we wszystkich środowiskach HotSpot | Zmiana domyślnego zachowania |
| Pamięć obiektów | JEP 534: kompaktowe nagłówki domyślnie | Zmiana domyślnego zachowania |
| Diagnostyka | JEP 536: redakcja danych wrażliwych w JFR wewnątrz procesu | Funkcja bez preview |
| TLS 1.3 | JEP 527: hybrydowa wymiana kluczy postkwantowych | Funkcja bez preview |
| Leniwa inicjalizacja | JEP 531: Lazy Constants | Trzecie preview |
| Język | JEP 532: typy proste we wzorcach, instanceof i switch | Piąte preview |
| Współbieżność | JEP 533: Structured Concurrency | Siódme preview |
| Obliczenia wektorowe | JEP 537: Vector API | Dwunasty incubator |
| Kryptografia | JEP 538: PEM Encodings of Cryptographic Objects | Trzecie preview |
Źródła zakresu: komunikaty zespołu Java o JDK 27 oraz opis zmian runtime.
Pułapka: kompaktowe nagłówki nie debiutują dopiero w Java 27. Nowością jest ich domyślne włączenie. Podobnie kolejna odsłona preview nie oznacza automatycznie stabilizacji funkcji.
3. Czym różnią się preview, incubator i stabilna funkcja?
Preview pozwala sprawdzić rozwijane elementy języka lub API przed ich ostatecznym zatwierdzeniem. Incubator udostępnia rozwijane API w odrębnych modułach. Przy obu trzeba liczyć się ze zmianami; stabilne funkcje podlegają zwykłym zasadom zgodności platformy.
Kod korzystający z preview kompilujesz i uruchamiasz z odpowiednią flagą. Dla pliku Demo.java korzystającego z preview JDK 27 polecenia wyglądają tak:
javac --enable-preview --release 27 Demo.java
java --enable-preview Demo
Uruchom je na JDK 27. Samo dodanie flagi nie udostępni nowych API w JDK 21. Konfigurację preview muszą uwzględniać również testy i proces uruchamiania aplikacji. Dla modułu incubator Vector API używa się --add-modules jdk.incubator.vector; to inny mechanizm niż preview.
Dopytanie: „Czy po aktualizacji JDK wystarczy uruchomić stary plik class z preview?”. Nie zakładaj tego: zaplanuj rekompilację dla nowego wydania i sprawdzenie zmian API. Zasady preview opisuje dokumentacja Oracle.
Współbieżność — pytania o cykl życia zadań
4. Jaki problem rozwiązuje Structured Concurrency?
Współbieżność strukturalna (structured concurrency) wiąże czas życia podzadań (subtasks) z nadrzędnym zakresem pracy. W JDK 27 nadal jest to siódme preview, opisane przez JEP 533. Pomaga uporządkować oczekiwanie na wyniki, obsługę błędów i anulowanie.
Wyobraź sobie endpoint, który jednocześnie pobiera profil klienta i jego zamówienia. Jeżeli profil jest niedostępny, a bez niego odpowiedź nie ma sensu, trzeba określić los drugiego zadania. Samo wysłanie dwóch zadań do executora nie definiuje jeszcze tej polityki.
Dopytanie: „A jeśli zamówienia są opcjonalne?”. Najpierw ustal wymagania: być może błąd tego podzadania powinien dawać częściową odpowiedź. Polityka współbieżności powinna wynikać z kontraktu endpointu.
Przykład dla JDK 27 z włączonym preview: oba wyniki są potrzebne do odpowiedzi. Zapisz kod jako ProfileDemo.java; użyj poleceń z pytania 3, zastępując nazwę Demo nazwą ProfileDemo.
import java.util.List;
import java.util.concurrent.StructuredTaskScope;
public class ProfileDemo {
public static void main(String[] args) throws Exception {
try (var scope = StructuredTaskScope.open()) {
var customer = scope.fork(() -> "Anna");
var orders = scope.fork(() -> List.of("ORD-42"));
scope.join(); // Najpierw czekamy na wynik całej operacji.
System.out.println(customer.get() + ": " + orders.get());
}
}
}
Wynik to Anna: [ORD-42]. Lambdy zastępują tu wywołania usług. Przenieś odczyt wyniku przed join() albo zasymuluj wyjątek w podzadaniu i wyjaśnij, dlaczego nie można traktować wyniku jako gotowego.
5. Co stanie się, gdy podzadanie zakończy się błędem albo zignoruje przerwanie?
Domyślny zakres otwarty przez StructuredTaskScope.open() oczekuje sukcesu wszystkich podzadań. Błąd jednego powoduje anulowanie pozostałych, a join() zgłasza ExecutionException. Zamknięcie zakresu czeka na zakończenie uruchomionych w nim wątków.
Anulowanie (cancellation) nie jest przymusowym zabiciem kodu. Podzadanie ignorujące przerwanie może opóźniać zamknięcie zakresu. Te zachowania opisuje robocza dokumentacja API JDK 27 przygotowana przez zespół OpenJDK.
Scenariusz do przećwiczenia: usługa zewnętrzna przestaje odpowiadać. Zaproponuj timeout klienta HTTP, budżet czasu całego żądania i test, który sprawdza, czy praca rzeczywiście kończy się po anulowaniu. Wyjaśnij też, kto zobaczy błąd i czy ponowienie operacji jest bezpieczne.
W poprzednim przykładzie zastąp deklarację orders poniższym fragmentem. Wyjątek dotrze do właściciela zakresu przez join(); obsłuż go na granicy operacji zamiast zwracać pozornie poprawny wynik.
var orders = scope.fork(() -> {
throw new IllegalStateException("Usługa zamówień niedostępna");
});
To fragment do ProfileDemo, również wymagający preview JDK 27. Drugie zadanie mogło już się zakończyć przed błędem — anulowanie nie cofa wykonanych efektów.
6. Czy wirtualne wątki usuwają potrzebę limitowania współbieżności?
Nie. Wirtualne wątki (virtual threads) ułatwiają obsługę dużej liczby zadań czekających na I/O, ale nie zwiększają liczby rdzeni ani pojemności bazy danych. Nie są też nowością Javy 27: zostały sfinalizowane w Java 21. Ich cel i ograniczenia opisuje JEP 444.
Przykład: endpoint uruchamia wiele zapytań, a pula ma 30 połączeń. Więcej wątków nie zapewni dodatkowych połączeń. Nadal potrzebujesz limitów, kolejkowania lub odrzucania nadmiarowych żądań — zależnie od wymagań systemu.
Dopytanie: „Co zmierzysz po przejściu na virtual threads?”. Przygotuj odpowiedź o czasie oczekiwania na połączenie, przepustowości, p95/p99, liczbie timeoutów i obciążeniu usługi zależnej. Rozróżnij współbieżność (concurrency) od równoległego wykonywania obliczeń (parallelism).
Poniższy samodzielny przykład działa już na JDK 21+, bez preview. Semafor (semaphore) ogranicza dostęp do zasobu do dwóch operacji, chociaż executor tworzy wirtualny wątek dla każdego zadania.
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class LimitDemo {
public static void main(String[] args) {
var permits = new Semaphore(2);
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 6; i++) {
executor.submit(() -> {
permits.acquire();
try {
Thread.sleep(100); // Symulacja oczekiwania na I/O.
} finally {
permits.release();
}
return null;
});
}
}
}
}
release() wykonujemy tylko po udanym acquire(). Limit chroni zasób, lecz sam nie ogranicza liczby oczekujących zadań. W aplikacji trzeba też odebrać wyniki z Future i obsłużyć błędy; przykład skupia się na limicie.
Język i Lazy Constants — pytania o semantykę
7. Po co rozwija się wzorce dla typów prostych?
Rozszerzenie pozwala używać typów prostych (primitive types) w większej liczbie kontekstów dopasowania, a także rozszerza instanceof i switch. W Java 27 to piąte preview. Temat łączy pattern matching z pytaniem o to, czy konkretną wartość można przedstawić w innym typie bez utraty informacji.
Warto przećwiczyć różnicę między sprawdzeniem dopasowania a zwykłym rzutowaniem (casting). Rzutowanie int na byte może zmienić wartość. Nie traktuj więc wzorca jako skrótu oznaczającego „zrób dowolny cast”. Podstawy tego mechanizmu opisuje dokumentacja czwartego preview w Java 26; status piątego preview potwierdza komunikat dla JDK 27.
Dopytanie: „Czy szerszy typ zawsze przechowa liczbę dokładnie?”. Nie: np. float nie reprezentuje dokładnie wszystkich wartości int. To dobre miejsce na powtórzenie zakresów i precyzji typów, zamiast uczenia się samej składni.
Prześledź różnicę między rzutowaniem a dopasowaniem wzorca (pattern matching). Ten przykład wymaga preview JDK 27; zapisz go jako PrimitiveDemo.java.
public class PrimitiveDemo {
public static void main(String[] args) {
int value = 300;
System.out.println((byte) value); // 44: utrata informacji.
if (value instanceof byte b) {
System.out.println(b);
} else {
System.out.println("Poza zakresem byte");
}
}
}
Oczekiwany wynik to 44, a następnie Poza zakresem byte. Zmień wartość na 100: dopasowanie powiedzie się i druga instrukcja wypisze 100. Rekruter może w ten sposób sprawdzić, czy rozumiesz konwersję dokładną (exact conversion).
8. Czym LazyConstant różni się od Supplier i pola final?
Leniwa stała (lazy constant), reprezentowana przez LazyConstant, przechowuje wartość obliczaną przy pierwszym żądaniu, a po udanej inicjalizacji zwraca tę samą zawartość. Zwykły Supplier nie gwarantuje zapamiętywania wyniku. Samo final zabrania ponownego przypisania pola, ale nie zapewnia leniwej inicjalizacji (lazy initialization).
W JDK 27 Lazy Constants pozostają trzecim preview. Robocza dokumentacja LazyConstant dla JDK 27 opisuje bezpieczną inicjalizację przy współbieżnym dostępie. Stałość przechowywanej referencji nie czyni jednak mutowalnego obiektu (mutable object) niezmiennym (immutable).
Dopytanie: „Gdzie byś tego użył?”. Rozważ kosztowną, rzadko używaną konfigurację, która nie zmienia się przez czas życia komponentu. Dla danych wymagających odświeżania zaproponuj osobny mechanizm cache z jawną polityką unieważniania.
W przykładzie preview JDK 27 zobaczysz różnicę między ponownym obliczaniem a zapamiętaniem wyniku. Zapisz go jako LazyDemo.java.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class LazyDemo {
public static void main(String[] args) {
var calls = new AtomicInteger();
Supplier<Integer> supplier = calls::incrementAndGet;
var lazy = LazyConstant.of(supplier);
System.out.println(calls.get()); // 0
System.out.println(lazy.get()); // 1
System.out.println(lazy.get()); // 1
System.out.println(supplier.get()); // 2
}
}
Utworzenie lazy nie wywołuje dostawcy wartości. Bezpośrednie wywołanie supplier.get() nie korzysta z wartości zapamiętanej przez lazy.
9. Czy funkcja inicjalizująca LazyConstant wykona się zawsze tylko raz?
Udana inicjalizacja następuje najwyżej raz, lecz obliczenie może zakończyć się wyjątkiem. W takim przypadku stała pozostaje niezainicjalizowana, a kolejne get() może ponowić próbę. Zwrócenie null powoduje NullPointerException, zgodnie z dokumentacją API.
Pułapka: nie utożsamiaj tego z gwarancją dokładnie jednego wykonania efektu biznesowego. Jeżeli inicjalizator pobiera opłatę, a potem rzuca wyjątek, ponowienie jest problemem projektowym. Na rozmowie zaproponuj inicjalizator bez takich efektów albo wyjaśnij sposób zapewnienia idempotencji (idempotency).
Poniższy samodzielny RetryDemo.java (preview JDK 27) celowo kończy pierwszą próbę wyjątkiem. Licznik pozwala zobaczyć, że ponowienie rzeczywiście wykonuje inicjalizator jeszcze raz.
import java.util.concurrent.atomic.AtomicInteger;
public class RetryDemo {
public static void main(String[] args) {
var attempts = new AtomicInteger();
var lazy = LazyConstant.of(() -> {
if (attempts.incrementAndGet() == 1) {
throw new IllegalStateException("Pierwsza próba nieudana");
}
return "gotowe";
});
try {
lazy.get();
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
System.out.println(lazy.get()); // gotowe
System.out.println(attempts.get()); // 2
}
}
Błąd nie zamienia się w zapamiętaną wartość. To istotna różnica między jednokrotnym udanym przypisaniem a jednokrotnym wykonaniem kodu.
JVM i bezpieczeństwo — pytania o skutki operacyjne
10. Co zmienia domyślne użycie G1 we wszystkich środowiskach?
HotSpot wybierze G1 także tam, gdzie ograniczone zasoby wcześniej prowadziły do wyboru Serial GC. Jawny wybór innego kolektora nadal obowiązuje. JEP 523 nie usuwa Serial GC i nie oznacza, że G1 jest najlepszy dla każdego obciążenia.
Scenariusz: mała usługa dostaje nowy obraz JDK, a jej kod pozostaje bez zmian. Zaproponuj porównanie na identycznych limitach CPU i pamięci. Oddziel czas startu, zużycie pamięci procesu, przepustowość i opóźnienia; poprawa jednego wyniku może towarzyszyć pogorszeniu innego.
Dla istniejącej aplikacji app.jar porównaj dwa uruchomienia na JDK 27. Identyczny rozmiar sterty ogranicza jedną ze zmiennych eksperymentu; logowanie GC (GC logging) pokaże użyty kolektor i przebieg odśmiecania.
java -Xms256m -Xmx256m -Xlog:gc -jar app.jar
java -Xms256m -Xmx256m -XX:+UseSerialGC -Xlog:gc -jar app.jar
W pierwszym uruchomieniu pozostawiasz wybór domyślny, w drugim jawnie wybierasz Serial. Porównuj pomiary po rozgrzaniu JVM (warm-up), przy takim samym obciążeniu.
11. Czy kompaktowe nagłówki gwarantują określoną oszczędność pamięci?
Nie gwarantują stałego procentu oszczędności dla każdej aplikacji. Java 27 domyślnie włącza kompaktowe nagłówki obiektów (compact object headers), ograniczając ich narzut metadanych (metadata overhead). To zmiana konfiguracji funkcji sfinalizowanej wcześniej w JDK 25. Źródło: zmiany runtime JDK 27.
Dopytanie: „Jak sprawdzisz korzyść?”. Porównaj tę samą aplikację i dane przy kontrolowanej konfiguracji. Zestaw zajętość sterty (heap) po odśmiecaniu pamięci (garbage collection, GC) z pamięcią całego procesu. Nie wnioskuj o rachunku za kontenery wyłącznie z rozmiaru pojedynczego obiektu.
12. Czy redakcja danych w JFR oznacza, że każde nagranie można udostępnić bez sprawdzania?
Nie. JEP 536 dodaje w procesie maskowanie danych wrażliwych (data redaction) z obsługiwanych argumentów JVM, zmiennych środowiskowych i właściwości. Dostępne są filtry domyślne oraz własne. To konkretny mechanizm filtrowania, a nie dowód, że całe nagranie nie zawiera danych poufnych. Źródło: opis JFR w JDK 27.
Ćwiczenie rekrutacyjne: zespół zapisuje token pod niestandardową nazwą. Opisz test filtra na sztucznej wartości i weryfikację nagrania. Wskaż, że własne zdarzenia aplikacji wymagają świadomego doboru zapisywanych pól.
13. Co daje hybrydowa wymiana kluczy postkwantowych w TLS 1.3?
JEP 527 łączy klasyczną wymianę kluczy z ML-KEM — to hybrydowa postkwantowa wymiana kluczy (post-quantum hybrid key exchange). Celem jest ograniczenie zagrożenia polegającego na zbieraniu zaszyfrowanego ruchu dziś z zamiarem odszyfrowania go w przyszłości. JDK 27 wprowadza m.in. grupę X25519MLKEM768, opisaną w komunikacie OpenJDK Quality Outreach.
Korzystanie z tego mechanizmu zależy od negocjacji i konfiguracji TLS. Aktualizacja JDK nie jest dowodem, że każde połączenie używa hybrydowej wymiany kluczy. Nie oznacza też automatycznej wymiany wszystkich certyfikatów i algorytmów podpisu.
Dopytanie: „Co sprawdzisz przed wdrożeniem?”. Wskaż zgodność klienta, serwera i pośredników, konfigurację grup oraz pomiary zestawiania połączeń. Nie obiecuj pełnego bezpieczeństwa całego systemu na podstawie jednej zmiany protokołu.
Migracja — pytanie dla mida i seniora
14. Jak zaplanować migrację aplikacji z Javy 21 lub 25 na Javę 27?
Najpierw ustal cel migracji i kryteria jej powodzenia. Następnie oddziel zmianę środowiska uruchomieniowego od wprowadzania nowych konstrukcji do kodu. Taki podział ułatwia ustalenie przyczyny ewentualnej regresji.
Przykładowa odpowiedź kandydata:
- Sprawdzam wsparcie JDK 27 w używanych wersjach frameworka, narzędzia budującego, pluginów, agentów APM i bibliotek przetwarzających bytecode.
- Przeglądam konfigurację startową. W JDK 27 usunięto m.in.
-noverifyi-Xverify:none, więc sprawdzam również obrazy kontenerów i skrypty CI. Źródło: komunikat o usuniętych opcjach launchera. - Buduję aplikację i uruchamiam testy integracyjne obejmujące bazę, serializację, TLS i monitoring. Kod z preview traktuję jako osobny zakres wymagający sprawdzenia zgodności.
- Porównuję obciążenie na tych samych danych i zasobach. Zapisuję wartości bazowe dla błędów, p95/p99, CPU i pamięci.
- Wdrażam stopniowo, z progami wycofania zmiany (rollback). Upewniam się, że poprzedni artefakt i konfiguracja nadal pozwalają na rollback.
Dopytanie: „Testy przeszły, ale p99 wzrosło — co dalej?”. Nie zgaduj od razu, że winny jest GC. Sprawdź profil, limity kontenera, kolejki, zależności sieciowe i różnice konfiguracji. Pokaż, jak zawęzisz problem pomiarami.
Co powtórzyć przed rozmową
Priorytety dopasuj do ogłoszenia i poziomu stanowiska. Poniższy podział jest propozycją nauki, nie uniwersalnym wymaganiem rekruterów.
| Poziom | Proponowany nacisk |
|---|---|
| Junior | Fundamenty Javy, znaczenie LTS i preview, wyjaśnienie dwóch zmian własnymi słowami |
| Mid | Obsługa błędów podzadań, limity zasobów, leniwa inicjalizacja, zgodność zależności |
| Senior | Decyzja migracyjna, diagnostyka regresji, budżety czasu, kryteria wdrożenia i rollback |
Vector API i API kodowania PEM warto umieć umiejscowić na mapie wydania. Pierwsze dotyczy m.in. obliczeń SIMD, a drugie reprezentacji obiektów kryptograficznych w PEM. Nie poświęcaj im całej powtórki kosztem tematów wynikających z oferty pracy. Tło obliczeń wektorowych przedstawia materiał zespołu Java o Vector API.
Na koniec wybierz trzy pytania z artykułu i odpowiedz na każde na głos: jaki problem rozwiązuje zmiana, jakie ma ograniczenie i jak sprawdzisz ją w projekcie. Jeżeli odpowiedź kończy się na nazwie JEP-u, wróć do scenariusza praktycznego.
Fundamenty uporządkujesz z przewodnikiem pytań z Javy, współbieżność z pytaniami Java Concurrency, a cały zakres przygotowania z przewodnikiem rozmowy Java Backend.
Przygotuj się do rozmowy technicznej
Wybierz plan i zyskaj dostęp do 2300+ pytań rekrutacyjnych z 30+ technologii.