Příprava na backendový pohovor z Javy

Otázky a odpovědi na pohovor z Javy 2026

Sada 30 otázek na pohovor z Javy pro backendové vývojáře, od základů jazyka a chování JVM až po kolekce, Stream API a souběžnost.

Spustit zkušební pohovor z JavyKreditní karta není potřeba. K dispozici je 1 bezplatná lekce.
Procvičování technických pohovorů v angličtiněRežim pro lidi, pro které angličtina není rodným jazykem, určený k procvičování technických pohovorů.

Java a JVM

1Co je Java a proč je považována za platformně nezávislou?

Java je staticky typovaný univerzální programovací jazyk. Zdrojový kód se kompiluje do bajtkódu (bytecode) a bajtkód běží na JVM (Java Virtual Machine). Stejný soubor .class nebo .jar může obvykle běžet na různých operačních systémech bez rekompilace, pokud je k dispozici kompatibilní JVM. Nejedná se o absolutní nezávislost: nativní knihovny, cesty specifické pro danou platformu, systémové příkazy a chování operačního systému mohou přesto způsobit, že aplikace bude závislá na platformě.

Java source -> javac -> bytecode -> JVM -> machine code
Zkusit odpovědět na tuto otázku s trenérem AI

2Jaký je rozdíl mezi JVM, JRE a JDK?

JVM (Java Virtual Machine) načítá a spouští bajtkód (bytecode) a spravuje paměť, vlákna a úklid paměti (garbage collection). JRE (Java Runtime Environment) historicky znamenalo JVM a k tomu knihovny potřebné ke spuštění aplikací. JDK (Java Development Kit) zahrnuje běhové prostředí a vývojářské nástroje, jako jsou javac, java, debugger, javadoc, jar a diagnostické nástroje. V moderních distribucích je samostatné JRE méně běžné, protože aplikace jsou často dodávány s vlastním obrazem běhového prostředí (custom runtime image).

JDK = development tools + runtime
Runtime = JVM + standard libraries
Zkusit odpovědět na tuto otázku s trenérem AI

3Jak JVM (Java Virtual Machine) vykonává kód v Javě a proč je zapotřebí kompilátor JIT (Just-In-Time compilation)?

Kompilátor `javac` překládá zdrojový kód do bajtkódu (bytecode). JVM načítá třídy a začne interpretovat nebo vykonávat instrukce bajtkódu. Často vykonávané části kódu kritické z hlediska výkonu jsou za běhu detekovány a zkompilovány JIT kompilátorem do optimalizovaného strojového kódu. JIT dokáže provádět inlining metod, optimalizovat cykly, provádět analýzu úniku (escape analysis) a odstraňovat některé alokace. To je důvod, proč se Java aplikace mohou po zahřátí (warm-up) zrychlit, a proč jsou mikrotesty výkonu bez použití nástroje JMH často zavádějící.

Zkusit odpovědět na tuto otázku s trenérem AI

4Jak funguje ClassLoader?

ClassLoader načítá definice tříd do JVM (Java Virtual Machine). Běžné zavaděče jsou Bootstrap, Platform, Application a vlastní zavaděče. Třída je identifikována nejen svým plně kvalifikovaným názvem, ale také instancí ClassLoader, která ji načetla, takže dvě třídy se stejným názvem z různých zavaděčů mohou být odlišné typy. Java obvykle používá delegování na rodiče (parent delegation): zavaděč se nejprve zeptá svého rodiče, aby se snížilo riziko nahrazení tříd platformy.

Loading
Linking: verification, preparation, resolution
Initialization
Zkusit odpovědět na tuto otázku s trenérem AI

5Jak je uspořádána paměť JVM (Java Virtual Machine)?

Klíčové oblasti paměti JVM zahrnují haldu (heap) pro objekty a pole, zásobníky Javy (Java stacks) s rámci volání (call frames) pro každé vlákno, Metaspace pro metadata tříd, mezipaměť kódu (code cache) pro kód zkompilovaný pomocí JIT (Just-In-Time compilation), registr PC (Program Counter) a nativní zásobníky. Vyhněte se zjednodušování, že primitivní typy jsou vždy uloženy na zásobníku a objekty vždy na haldě: primitivní položka je uložena uvnitř svého objektu a JIT escape analýza může odstranit alokace paměti nebo změnit reprezentaci objektu.

Zkusit odpovědět na tuto otázku s trenérem AI

6Jak funguje GC (Garbage Collector)?

Garbage Collector (GC) uvolňuje paměť obsazenou objekty, které již nejsou dosažitelné z kořenů GC (GC Roots). Tyto kořeny zahrnují lokální proměnné aktivních metod, statické položky, aktivní vlákna a reference JNI (Java Native Interface). Mnoho nástrojů pro správu paměti spoléhá na zjištění, že většina nových objektů má krátkou životnost, a proto je halda (heap) často rozdělena na mladou a starou generaci (young and old generations). GC nezavírá soubory nebo spojení deterministicky; externí prostředky by měly být uzavírány pomocí bloku `try-with-resources`.

try (var stream = Files.newInputStream(path)) {
    // use stream
}
Zkusit odpovědět na tuto otázku s trenérem AI

OOP a typy

7Které čtyři principy OOP (Object-Oriented Programming) Java podporuje?

Java podporuje zapouzdření (encapsulation), abstrakci, dědičnost a polymorfismus. Zapouzdření skrývá vnitřní stav za veřejné API (Application Programming Interface). Abstrakce se zaměřuje na nezbytné chování. Dědičnost vytváří nový typ na základě existujícího typu. Polymorfismus umožňuje kódu pracovat s různými implementacemi prostřednictvím sdíleného kontraktu. V praxi se často dává přednost kompozici před dědičností, protože vytváří nižší míru provázanosti (coupling).

interface PaymentService {
    void pay(BigDecimal amount);
}

class CardPaymentService implements PaymentService {
    @Override
    public void pay(BigDecimal amount) {
        // payment implementation
    }
}
Zkusit odpovědět na tuto otázku s trenérem AI

8Jaký je rozdíl mezi přetěžováním (overloading) a překrýváním (overriding) metod?

Přetěžování (overloading) znamená, že několik metod má stejný název, ale různé parametry; kompilátor vybírá metodu na základě statických typů argumentů. Překrývání (overriding) znamená, že podtřída nahrazuje implementaci instanční metody z rodičovské třídy; dynamické vazování (runtime dispatch) vybírá metodu na základě skutečného typu objektu. Statické metody jsou skryty (hidden), nikoliv překryty, a soukromé (private) metody nejsou překryty, protože se nedědí jako přístupné členy.

void send(String message) {}
void send(String message, int priority) {}

class Animal {
    void speak() { System.out.println("Animal"); }
}

class Dog extends Animal {
    @Override
    void speak() { System.out.println("Dog"); }
}
Zkusit odpovědět na tuto otázku s trenérem AI

9Jaký je rozdíl mezi rozhraním (interface) a abstraktní třídou?

Rozhraní definuje kontrakt a může obsahovat abstraktní metody, výchozí metody (default methods), statické metody, konstanty a privátní pomocné metody. Abstraktní třída může mít navíc stav instance, konstruktory, chráněné atributy (protected fields), sdílenou implementaci a abstraktní nebo konkrétní metody. Třída může dědit (extend) pouze z jedné třídy, ale může implementovat více rozhraní. Rozhraní použijte pro nezávislý kontrakt a abstraktní třídu pro úzce související typy se sdílenou implementací a stavem.

interface Repository<T> {
    Optional<T> findById(long id);
}
Zkusit odpovědět na tuto otázku s trenérem AI

10Co znamená klíčové slovo `final`?

Proměnná označená jako `final` může mít hodnotu přiřazenou pouze jednou, ale reference typu `final` neznamená, že je odkazovaný objekt neměnný (immutable). Metodu označenou jako `final` nelze přepsat (override). Třídu označenou jako `final` nelze dědit (extend). Klíčové slovo `final` sděluje záměr programátora a omezuje změny referencí nebo dědičnost, ale samo o sobě neposkytuje kompletně neměnný model ani bezpečnost pro použití ve více vláknech (thread safety).

final int maxAttempts = 3;

final List<String> names = new ArrayList<>();
names.add("Alice"); // allowed

public final void validate() {}

public final class Token {}
Zkusit odpovědět na tuto otázku s trenérem AI

11Co jsou to typy `record` v Javě?

Typ `record` poskytuje kompaktní syntaxi pro neměnné datové nosiče (immutable data carriers). Kompilátor automaticky generuje privátní položky `final`, konstruktor, přístupové metody (accessors), `equals`, `hashCode` a `toString`. Typy `record` jsou užitečné pro DTO (Data Transfer Object), hodnotové objekty (value objects) a výsledky metod. Nejsou náhradou za každou doménovou entitu: validaci lze sice přidat v kompaktním konstruktoru, ale pro složité měnitelné chování, odložené načítání (lazy loading) a modely založené na dědičnosti se typy `record` obvykle nehodí.

public record User(long id, String name) {}

public record Order(List<String> items) {
    public Order {
        items = List.copyOf(items);
    }
}
Zkusit odpovědět na tuto otázku s trenérem AI

12Jak v Javě vytvoříte neměnný (immutable) objekt?

Označte třídu jako `final` nebo pečlivě omezte její dědičnost, položky nastavte jako `private final`, vyhněte se metodám typu setter a provádějte validaci v konstruktoru. Pro měnitelné vstupy vytvářejte defenzivní kopie (defensive copies) a vracejte kopie nebo neupravitelné (unmodifiable) pohledy. Hluboká neměnnost (deep immutability) vyžaduje, aby veškerý vnořený stav byl také neměnný. Položky s modifikátorem `final` pomáhají bezpečnému zveřejnění (safe publication), ale objekt může být stále logicky měnitelný, pokud zpřístupní měnitelnou kolekci nebo měnitelný vnořený objekt.

public final class User {
    private final String name;
    private final List<String> roles;

    public User(String name, List<String> roles) {
        this.name = name;
        this.roles = List.copyOf(roles);
    }

    public List<String> getRoles() {
        return roles;
    }
}
Zkusit odpovědět na tuto otázku s trenérem AI

Reference, řetězce a porovnávání

13Předává Java parametry hodnotou (pass by value) nebo referencí (pass by reference)?

Java vždy předává argumenty hodnotou. U primitivních typů se kopíruje samotná hodnota. U objektů se kopíruje hodnota reference. Metoda může změnit stav objektu prostřednictvím zkopírované reference, ale nemůže nahradit referenci volajícího jiným objektem. Tento rozdíl vysvětluje, proč může být změna členské proměnné (field) pro volajícího viditelná, zatímco přiřazení nového objektu samotnému parametru nikoliv.

void change(int value) {
    value = 100;
}

void rename(User user) {
    user.setName("Kate");
}

void replace(User user) {
    user = new User("New");
}
Zkusit odpovědět na tuto otázku s trenérem AI

14Jaký je rozdíl mezi == a equals()?

U primitivních typů porovnává operátor == jejich hodnoty. U objektových referencí == kontroluje, zda obě reference ukazují na stejný objekt. equals() je metoda, která by měla porovnávat logickou rovnost, pokud ji třída implementuje správně. Třídy jako String, Integer, LocalDate a mnoho hodnotových tříd metodu equals překrývá. U vlastních doménových objektů musí metoda equals odrážet zamýšlenou sémantiku identity nebo hodnoty.

int a = 10;
int b = 10;
System.out.println(a == b); // true

String first = new String("java");
String second = new String("java");

System.out.println(first == second);      // false
System.out.println(first.equals(second)); // true
Zkusit odpovědět na tuto otázku s trenérem AI

15Jaký je kontrakt mezi metodami equals a hashCode?

Pokud jsou dva objekty shodné podle metody equals, musí vracet stejný hashCode. Opačně to neplatí: různé objekty mohou mít stejný hashCode. Metoda equals by měla být reflexivní, symetrická, tranzitivní, konzistentní a pro hodnotu null by měla vracet false. Porušení tohoto kontraktu poškodí chování v kolekcích jako HashMap, HashSet a dalších kolekcích založených na hašování: objekt se může stát nenalezitelným, přestože je v kolekci přítomen.

@Override
public boolean equals(Object object) {
    if (this == object) return true;
    if (!(object instanceof User other)) return false;
    return id == other.id;
}

@Override
public int hashCode() {
    return Long.hashCode(id);
}
Zkusit odpovědět na tuto otázku s trenérem AI

16Proč je String neměnný (immutable)?

`String` je neměnný z důvodu bezpečnosti, předvídatelnosti a výkonu. Neměnnost umožňuje internování řetězců (string interning), bezpečné sdílení mezi vlákny, spolehlivé cachování `hashCode` a bezpečné použití v cestách, URL (Uniform Resource Locator), názvech tříd a hodnotách citlivých z hlediska bezpečnosti. Operace jako `concat`, `replace` a `substring` vytvářejí nové řetězce. Od Java 9 může `String` interně používat `byte[]` s kompaktní reprezentací, ale veřejný kontrakt neměnnosti zůstává stejný.

String value = "Java";
value.toUpperCase();

System.out.println(value); // Java

String a = "java";
String b = "java";

System.out.println(a == b); // often true due to interning
Zkusit odpovědět na tuto otázku s trenérem AI

17Jaký je rozdíl mezi String, StringBuilder a StringBuffer?

`String` je neměnný (immutable), takže opakované zřetězení v cyklech může vytvořit mnoho dočasných objektů, ačkoliv kompilátor dokáže jednoduché případy optimalizovat. `StringBuilder` je měnitelný (mutable) a preferovaný pro vytváření řetězců v jediném vlákně. `StringBuffer` je podobný, ale synchronizovaný, takže je obvykle pomalejší a relevantní hlavně pro starší API (Application Programming Interface), která vyžadují bezpečnost z hlediska vláken (thread-safe). Pro formátování mohou být `String.format` nebo API ve stylu šablon přehlednější, ale nejsou vždy rychlejší.

StringBuilder builder = new StringBuilder();

for (String item : items) {
    builder.append(item).append(',');
}

String result = builder.toString();

String message = "Hello, " + name;
Zkusit odpovědět na tuto otázku s trenérem AI

Generické typy a kolekce

18Co jsou generika a co je to mazání typů (type erasure)?

Generika umožňují v kódu vyjádřit typově bezpečné kontejnery a API, jako je `List<String>`. Během překladu kompilátor kontroluje typy a vkládá přetypování tam, kde je to potřeba. Během běhu programu je většina informací o generických typech odstraněna (mazání typů), takže `List<String>` a `List<Integer>` sdílejí stejnou třídu v době běhu. Z tohoto důvodu nemůžete přímo vytvořit `new T()`, používat parametry primitivních typů nebo spolehlivě kontrolovat typ pomocí `instanceof List<String>`.

List<String> names = new ArrayList<>();
names.add("Alice");

List<? extends Number> producer;
List<? super Integer> consumer;
Zkusit odpovědět na tuto otázku s trenérem AI

19Co je to Java Collections Framework?

Collections Framework je sada rozhraní a implementací pro ukládání a zpracování skupin objektů. Mezi základní rozhraní patří List, Set, Queue, Deque a Map. Implementace zahrnují ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap a PriorityQueue. Kolekci vybírejte podle vzorce přístupu, požadavků na řazení, jedinečnosti, složitosti vyhledávání, paměťové režie a potřeb souběžnosti (concurrency).

List<String> list = new ArrayList<>();
Set<Long> ids = new HashSet<>();
Map<Long, User> usersById = new HashMap<>();
Deque<String> queue = new ArrayDeque<>();
Zkusit odpovědět na tuto otázku s trenérem AI

20Jak funguje HashMap?

HashMap ukládá záznamy do přihrádek (buckets) vybraných na základě hashe klíče. Při vyhledávání vypočítá hash, najde příslušnou přihrádku a porovná klíče pomocí metody equals. Kolize jsou řešeny uvnitř přihrádky; v moderní Javě mohou být dlouhé řetězce převedeny na vyvážený strom (treeified), pokud jsou splněny určité podmínky. HashMap umožňuje jeden null klíč a null hodnoty, není bezpečná pro přístup z více vláken (thread-safe) a může změnit svou velikost (resize), když je překročena prahová hodnota faktoru zatížení (load factor).

Map<Long, User> users = new HashMap<>();
users.put(user.id(), user);

User found = users.get(user.id());
Zkusit odpovědět na tuto otázku s trenérem AI

21Jaký je rozdíl mezi HashMap, LinkedHashMap, TreeMap a ConcurrentHashMap?

HashMap poskytuje rychlý neuspořádaný přístup a není bezpečný pro přístup z více vláken (thread-safe). LinkedHashMap zachovává pořadí vkládání nebo pořadí přístupu a je užitečný pro mezipaměti typu LRU (Least Recently Used). TreeMap udržuje klíče seřazené podle přirozeného pořadí nebo pomocí rozhraní Comparator a jeho operace mají časovou složitost O(log n). ConcurrentHashMap je navržen pro souběžný přístup s využitím segmentované nebo interní synchronizace, neumožňuje použití null klíčů ani hodnot a při vyšší zátěži je vhodnější než synchronizedMap.

map.computeIfAbsent(key, ignored -> loadValue());
Zkusit odpovědět na tuto otázku s trenérem AI

22Jaký je rozdíl mezi ArrayList a LinkedList?

ArrayList je vnitřně reprezentován polem. Poskytuje rychlý indexovaný přístup, kompaktní uspořádání paměti a efektivní iteraci, ale vkládání doprostřed vyžaduje posouvání prvků. LinkedList je obousměrně spojový seznam. Vkládání je výpočetně levnější, pokud je uzel již znám, ale indexovaný přístup má složitost O(n), paměťová režie je vyšší a lokalita mezipaměti (cache locality) je horší. V mnoha reálných aplikacích je ArrayList rychlejší, a to i v případě, že dochází k určitým operacím vkládání.

Zkusit odpovědět na tuto otázku s trenérem AI

23Jaký je rozdíl mezi Comparable a Comparator?

Comparable definuje přirozené pořadí uvnitř třídy pomocí metody compareTo. Comparator je samostatný objekt nebo lambda výraz, který definuje externí řazení. Comparable je užitečný v případě, kdy má typ jedno zřejmé výchozí pořadí. Comparator je vhodnější, když existuje více možností řazení, například podle jména, data nebo priority. Metody compare a compareTo musí být konzistentní a ideálně kompatibilní s metodou equals, pokud se používají v seřazených množinách nebo mapách.

class User implements Comparable<User> {
    private int age;

    @Override
    public int compareTo(User other) {
        return Integer.compare(age, other.age);
    }
}

Comparator<User> byName =
    Comparator.comparing(User::getName);

Comparator<User> byAgeThenName =
    Comparator.comparingInt(User::getAge)
              .thenComparing(User::getName);
Zkusit odpovědět na tuto otázku s trenérem AI

Výjimky a prostředky

24Jaký je rozdíl mezi kontrolovanými (checked) a nekontrolovanými (unchecked) výjimkami?

Kontrolované výjimky dědí ze třídy Exception, ale ne z RuntimeException, a kompilátor vyžaduje, aby je volající zpracoval nebo deklaroval. Nekontrolované výjimky dědí z RuntimeException a nejsou vyžadovány v signaturách metod. Kontrolované výjimky se hodí pro zotavitelné situace na rozhraní API (Application Programming Interface). Nekontrolované výjimky jsou vhodné pro programátorské chyby, neplatný stav a porušení kontraktu. V kódu moderních služeb se týmy často vyhýbají nadměrnému používání kontrolovaných výjimek, protože se hlučně šíří napříč vrstvami aplikace.

void load() throws IOException {
    // ...
}

throw new IllegalArgumentException("Invalid id");
Zkusit odpovědět na tuto otázku s trenérem AI

25Jaký je rozdíl mezi klíčovými slovy throw, throws a blokem try-with-resources?

Klíčové slovo throw okamžitě vyvolá výjimku. Klíčové slovo throws deklaruje možnou výjimku v signatuře metody. Konstrukce try-with-resources automaticky uzavírá prostředky typu AutoCloseable v opačném pořadí, než v jakém byly otevřeny. Pokud hlavní blok i metoda close vyvolají výjimky, druhá z nich je zachována jako potlačená výjimka (suppressed exception). Toto řešení je bezpečnější a přehlednější než ruční používání bloků finally v případě, že pracujete s několika prostředky najednou.

throw new IllegalArgumentException("Invalid id");

void readFile() throws IOException {}

try (var reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}
Zkusit odpovědět na tuto otázku s trenérem AI

Stream API a funkcionální styl

26Co je Stream API?

Stream API umožňuje kódu zpracovávat sekvence deklarativním způsobem. Průběžné (intermediate) operace, jako jsou filter, map a sorted, jsou líné (lazy); koncové (terminal) operace, jako jsou toList, collect, reduce a forEach, spouštějí samotné zpracování. Proud (stream) není kolekce, obvykle se používá pouze jednou a funguje nejlépe s čistými funkcemi bez vedlejších účinků. Metoda parallelStream nezaručuje rychlejší běh a může naopak zhoršit výkon nebo narušit správnost fungování.

List<String> names = users.stream()
    .filter(User::isActive)
    .sorted(Comparator.comparing(User::getName))
    .map(User::getName)
    .toList();
Zkusit odpovědět na tuto otázku s trenérem AI

27Co je třída Optional a kdy byste ji měli použít?

Optional<T> explicitně reprezentuje absenci výsledku a funguje dobře jako návratový typ. Mezi užitečné metody patří map, flatMap, filter, orElse, orElseGet a orElseThrow. Metoda orElse vyhodnocuje svůj argument dychtivě (eagerly), zatímco dodavatel (supplier) v metodě orElseGet se spustí pouze tehdy, když hodnota chybí. Použití Optional se obvykle nedoporučuje pro atributy entit, parametry metod nebo serializované objekty typu DTO (Data Transfer Object), pokud k tomu neexistuje pádný důvod.

Optional<User> user = repository.findById(id);

String name = user
    .map(User::getName)
    .orElse("Unknown");

optional.orElse(expensiveOperation());
optional.orElseGet(() -> expensiveOperation());
Zkusit odpovědět na tuto otázku s trenérem AI

Souběžnost

28Co zaručují klíčová slova synchronized, volatile a atomické třídy?

Klíčové slovo synchronized zajišťuje vzájemné vyloučení (mutual exclusion) a garanci uspořádání typu happens-before (událost předchází jiné). Klíčové slovo volatile zaručuje viditelnost nejnovějších zápisů a omezuje změnu pořadí instrukcí, ale nezajišťuje atomicitu složených operací. Atomické třídy poskytují jednotlivé atomické operace, jako je například incrementAndGet. Použijte zámky (locks) nebo synchronized pro několik souvisejících změn, volatile pro jednoduché stavové příznaky a AtomicInteger nebo LongAdder pro čítače v prostředí s vysokou mírou souběžnosti (contention).

synchronized (lock) {
    counter++;
}

private volatile boolean running = true;

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
Zkusit odpovědět na tuto otázku s trenérem AI

29Co je to souběh (race condition), uváznutí (deadlock) a bezpečnost z hlediska vláken (thread safety)?

K souběhu dochází, když výsledek závisí na nepředvídatelném pořadí provádění vláken. K uváznutí dojde, když vlákna navždy čekají na prostředky držené jinými vlákny. Pro dosažení bezpečnosti z hlediska vláken omezte sdílený měnitelný stav, udržujte konzistentní pořadí zamykání, zkraťte kritické sekce, vyhněte se volání externího kódu pod zámky a používejte neměnné (immutable) objekty, souběžné kolekce, časové limity (timeouts) nebo předávání zpráv. Bezpečnost z hlediska vláken je vlastností celého protokolu pro přístup ke stavu aplikace.

Zkusit odpovědět na tuto otázku s trenérem AI

30Jaký je rozdíl mezi ExecutorService, CompletableFuture a virtuálními vlákny (virtual threads)?

ExecutorService spravuje fond vláken (thread pool) a provádění úloh. CompletableFuture pomáhá skládat asynchronní operace. Virtuální vlákna jsou odlehčená vlákna JVM (Java Virtual Machine), která se hodí pro velké množství blokujících I/O (Input/Output) úloh. Zjednodušují model jednoho vlákna na požadavek (thread-per-request), ale nezrychlují práci náročnou na procesor (CPU-bound), neodstraňují limity databází nebo externích prostředků ani automaticky nezajišťují bezpečnost měnitelného stavu pro více vláken.

try (var executor = Executors.newFixedThreadPool(4)) {
    Future<Integer> future = executor.submit(() -> calculate());
    int result = future.get();
}

CompletableFuture<User> userFuture =
    CompletableFuture.supplyAsync(this::loadUser)
        .thenApply(this::enrichUser)
        .exceptionally(error -> fallbackUser());

try (var executor =
         Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> handleRequest());
}
Zkusit odpovědět na tuto otázku s trenérem AI