
Question bank
Java SE 21 Developer
45 Java SE 21 Developer questions with the correct answer and a written explanation, organised across 11 exam domains.
- 45 questions
- 11 domains
- Foundation level
Question index
Jump straight to any question. Answers and explanations are inline.
- Arrays and Collections · 1, 2, 3, 4
- Concurrency · 5, 6, 7, 8, 9, 10, 11
- Controlling Program Flow · 12, 13
- Handling Date, Time, Text, Numeric and Boolean Values · 14, 15, 16, 17
- Handling Exceptions · 18, 19, 20, 21
- I/O and NIO.2 · 22, 23, 24
- Localization · 25, 26
- Modules and Deployment · 27, 28, 29
- Object-Oriented Concepts · 30, 31, 32, 33, 34, 35, 36, 37, 38
- Streams and Lambda Expressions · 39, 40, 41, 42, 43
- Supplementary Topics · 44, 45
Arrays and Collections
4 questions in this domain
- Question 1Single Choice
What must be called on a Thread object to begin separate concurrent execution?
- ACall the run() method directly.
- BCall the start() method once.Correct answer
- CCreate the Thread subclass.
- DOverride the toString() method.
Why this is the answer
Calling start() makes the JVM schedule a new thread; run() is invoked inside that new thread. Calling run() directly does not launch concurrency.
- Question 2Multiple Select
Which statements are true about calling run() versus start() on a Thread object? Select all that apply.
- AA direct run() call executes the thread body in the current thread.Correct answer
- BA direct start() call creates a new execution context and runs the task asynchronously.Correct answer
- CCalling start() more than once on the same Thread object is valid.
- DA direct run() call always starts a new daemon thread.
Why this is the answer
run() is synchronous when invoked directly; start() schedules a new thread. A Thread cannot be restarted, so a second start() throws IllegalThreadStateException.
- Question 3Interactive
The code block submits a Callable that throws an exception, then calls Future.get() inside a try-catch. What is the class name printed by the code?
- Ajava.lang.IllegalStateException
- Bjava.util.concurrent.ExecutionExceptionCorrect answer
- Cjava.lang.RuntimeException
- Djava.lang.Exception
Why this is the answer
submit() stores task failure in the Future. Future.get() surfaces the failure as ExecutionException, with IllegalStateException as its cause.
- Question 4Single Choice
Why should a ReentrantLock acquired inside a method be unlocked in a finally block?
- ALock the object again before exiting the method.
- BPlace the unlock() call in a finally block.Correct answer
- CRely on synchronized to release the lock automatically.
- DUse System.gc() to force lock release.
Why this is the answer
Explicit Lock does not use synchronized's automatic monitor release; unlock() must be called, and finally ensures it runs whether the guarded code succeeds or throws.
Concurrency
7 questions in this domain
- Question 5Single Choice
In Java, what is the result of creating a Thread object without calling start()?
- AIt represents the thread, but execution is not started.Correct answer
- BIt immediately invokes the run() method.
- CIt allocates a new running thread.
- DIt throws an exception until start() is called.
Why this is the answer
A Thread object is a handle to the execution state. The actual task runs only after start() is called.
- Question 6Multiple Select
Which two statements are true when calling a Thread's run() method directly instead of using start()?
- AIt executes run() in the current thread.Correct answer
- BIt does not create a new thread of execution.Correct answer
- CIt starts a new daemon thread.
- DIt can be called only once after start().
Why this is the answer
Directly calling run() invokes its body synchronously in the caller's thread and does not start a separate thread.
- Question 7Interactive
Refer to the code block. What is the behavior of the operation that retrieves the Future's result?
- AIt blocks until the result is available and then returns it.Correct answer
- BIt returns a default value if the result is not ready.
- CIt throws an exception only if the task was cancelled.
- DIt polls the task once and returns immediately.
Why this is the answer
Future.get() is blocking: it waits until the task completes and then returns the result or throws an execution-related exception.
- Question 8Single Choice
Why can extending Thread make it harder to reuse a task class?
- AA Java class can extend only one superclass.Correct answer
- BThread methods cannot be overridden.
- CExtending Thread prevents execution on a virtual thread.
- DThread must be subclassed inside its own file.
Why this is the answer
Java allows only one superclass, so extending Thread prevents the task class from extending another useful class.
- Question 9Multiple Select
Which two statements correctly compare the execute() and submit() methods of ExecutorService?
- Aexecute() returns void, while submit() returns a Future.Correct answer
- BBoth execute() and submit() can capture the exception in a Future.
- CAn exception from execute() cannot be read through Future.get().Correct answer
- Dsubmit() rethrows task exceptions immediately on the caller's thread.
Why this is the answer
execute() has no Future, so async exceptions are not captured for later retrieval; submit() returns a Future that can hold the exception.
- Question 10Interactive
Refer to the code block. Which condition must be true before the await call is made?
- AThe associated Condition's Lock must be held.Correct answer
- BA synchronized monitor must be held.
- CThe lock must already be unlocked.
- DThe thread must be marked as a daemon thread.
Why this is the answer
Condition.await() must be called while holding the Lock associated with that Condition, just as wait() requires a synchronized monitor.
- Question 11Single Choice
What is the relationship between virtual threads and ExecutorService in Java 21?
- AExecutorService is still used for per-task virtual thread execution.Correct answer
- BVirtual threads make ExecutorService unnecessary.
- CExecutorService can only run platform threads.
- DVirtual threads require a fixed-size thread pool.
Why this is the answer
Virtual threads change how threads are created, but ExecutorService remains useful, for example via newVirtualThreadPerTaskExecutor().
Controlling Program Flow
2 questions in this domain
- Question 12Single Choice
What happens when you instantiate a Thread object in Java?
- AImmediately executes run() on the current thread.
- BStarts the new execution flow immediately.
- CCreates Thread instance; execution flow starts on start().Correct answer
- DBlocks until another thread calls notify().
Why this is the answer
Creating a Thread object does not start a new execution; the new flow begins only when start() is called. Calling run() directly executes synchronously, so a Thread object is not the same as running code.
- Question 13Multiple Select
Which statements correctly describe CompletableFuture methods for controlling flow? (Select all that apply.)
- A.thenAccept() returns CompletableFuture<Void>.Correct answer
- B.thenApply() returns CompletableFuture<Void>.
- C.allOf() returns CompletableFuture<Void> after all inputs finish.Correct answer
- D.allOf() collects input results into one future.
Why this is the answer
thenAccept() consumes a result and returns CompletableFuture<Void>; allOf() also returns CompletableFuture<Void> and does not combine values. thenApply() transforms a result, and allOf() never bundles individual results into a result-bearing future.
Handling Date, Time, Text, Numeric and Boolean Values
4 questions in this domain
- Question 14Single Choice
Which statement correctly describes the relationship between a Thread object and the actual execution it may perform?
- ACalling start() schedules separate execution.Correct answer
- BConstructing a Thread starts it automatically.
- CCalling run() creates a new thread.
- DConstructing a Thread invokes run() immediately.
Why this is the answer
Creating a Thread only creates an object; start() schedules the separate execution, while run() alone runs in the current thread.
- Question 15Multiple Select
Which statements about ExecutorService methods and Future behavior are true? Choose all that apply.
- Asubmit() returns a Future.Correct answer
- Bexecute() returns a Future.
- CFuture.get() may block waiting for the result.Correct answer
- DFuture.get() is always non-blocking.
Why this is the answer
submit() returns a Future that can represent the task result, while execute() returns void. Calling get() can block until the computation completes.
- Question 16Interactive
Based on the supplied code, what is the type of the variable that receives the returned value?
- ACompletableFuture<Void>Correct answer
- BCompletableFuture<String>
- CString
- Dvoid
Why this is the answer
The final call applies a Consumer for side effects and returns a CompletableFuture<Void>, not a future containing the original result.
- Question 17Single Choice
Which statement is true about CompletableFuture.allOf()?
- AIt returns CompletableFuture<Void> after all complete.Correct answer
- BIt returns all results in a collection.
- CIt returns the first future's value.
- DIt requires a combining function.
Why this is the answer
CompletableFuture.allOf() returns a CompletableFuture<Void> that completes when all supplied futures complete; it does not combine their values.
Handling Exceptions
4 questions in this domain
- Question 18Single Choice
Which statement accurately describes the relationship between a Thread object and the execution of its run() method?
- ACreating a Thread object immediately begins execution of that thread.
- BCalling start() creates a new execution path that calls run() asynchronously.Correct answer
- CThe run() method, when called directly, executes in a new thread.
- DThe Thread object itself is the actual operating-system thread.
Why this is the answer
A Thread object is only a handle. Without start(), no separate thread is created. The start() method asynchronously schedules run() to execute in a new thread, while calling run() directly is just an ordinary method call in the current thread.
- Question 19Multiple Select
A developer submits tasks to an ExecutorService. Which two statements are true about exception handling with execute() and submit()?
- AIf a task submitted with submit() throws an exception, the underlying cause is exposed when get() is called on the returned Future.Correct answer
- BTasks submitted with execute() do not return a Future, so their exceptions are not surfaced to the caller through Future.get().Correct answer
- CChecked exceptions from tasks submitted with submit() are wrapped in RuntimeException before being returned by get().
- DBoth execute() and submit() store any thrown exception until the thread that submitted the task calls get().
Why this is the answer
submit() returns a Future that stores the failure; get() throws an ExecutionException whose cause is the original exception. execute() returns void, so no Future exists, and task exceptions propagate to the worker thread instead of being delivered through Future.get().
- Question 20Interactive
The code shows a task submitted to an ExecutorService, followed by Future.get() inside a try-catch block. If the Callable throws IllegalStateException(\"boom\"), what value is printed by e.getCause().getMessage()?
- AboomCorrect answer
- BIllegalStateException: boom
- CExecutionException: boom
- DException in thread ... java.lang.IllegalStateException: boom
Why this is the answer
Future.get() throws ExecutionException when the task completed with an exception. The original IllegalStateException is stored as the cause, so e.getCause().getMessage() returns exactly \"boom\".
- Question 21Single Choice
Why should lock.unlock() be called in a finally block when using ReentrantLock?
- AA finally block ensures the lock is released after normal completion or after an exception.Correct answer
- BA finally block prevents the lock from being reentrantly acquired.
- CA finally block causes the lock to be released only when no exception is thrown.
- DA finally block is optional because ReentrantLock releases itself automatically when the protected block exits.
Why this is the answer
Unlike synchronized blocks, explicit Lock implementations do not automatically release the lock. Putting unlock() in a finally block guarantees that it executes on both the normal path and the exception path, preventing the lock from being held indefinitely.
I/O and NIO.2
3 questions in this domain
- Question 22Single Choice
Which statement correctly describes the behavior of Future.get() when it is called before a submitted task has finished?
- AFuture.get() is non-blocking.
- BFuture.get() blocks until completion.Correct answer
- CFuture.get() cancels unfinished tasks.
- DFuture.get() returns null when idle.
Why this is the answer
Future.get() is a blocking call. It waits until the task completes before returning the result, and it can throw ExecutionException if the task failed.
- Question 23Multiple Select
According to the playbook, which two statements are correct about exception handling in ExecutorService execute() and submit()?
- Aexecute() cannot return a Future.Correct answer
- Bsubmit() returns a Future.Correct answer
- Cexecute() always hides task exceptions.
- Dsubmit() ignores task exceptions.
Why this is the answer
execute() returns void and cannot store a failure; submit() returns a Future, so Future.get throws ExecutionException for failed tasks.
- Question 24Interactive
Consider the code block that creates a Thread with a Runnable and then calls the displayed method. What is the result?
- ATask runs synchronously in the current thread.Correct answer
- BA separate thread asynchronously executes the task.
- CThe code does not compile.
- DThe task is submitted to a thread pool.
Why this is the answer
Calling run() directly invokes the task synchronously in the caller's thread. No separate thread is started.
Localization
2 questions in this domain
- Question 25Single Choice
Which statement correctly describes creating a Thread object relative to the underlying system thread or asynchronous execution?
- AStarts a new execution automatically
- BExecution begins only after start() is calledCorrect answer
- CCalling run() invokes it in a new thread
- DThread object and actual execution are identical
Why this is the answer
Creating a Thread object only creates an instance. Execution begins only when start() is invoked, while calling run() directly runs synchronously and does not start a new thread.
- Question 26Multiple Select
Which two statements accurately describe the behavior of thenAccept and thenApply methods on CompletableFuture instances?
- AthenAccept returns CompletableFuture<Void>Correct answer
- BthenApply returns a CompletableFuture with transformed resultCorrect answer
- CthenApply returns CompletableFuture<Void>
- DthenAccept transforms the result and returns a CompletableFuture
Why this is the answer
thenAccept is for consuming results and returns CompletableFuture<Void>; thenApply transforms results and returns a CompletableFuture containing the transformed value.
Modules and Deployment
3 questions in this domain
- Question 27Single Choice
In Java concurrency, what is the effect of merely creating an instance of the Thread class?
- AThe JVM allocates a new operating-system thread and begins executing the associated task.
- BA Thread object exists, but no execution begins until start() is called.Correct answer
- CThe run() method is automatically invoked in the current thread.
- DThe thread immediately enters the TIMED_WAITING state.
Why this is the answer
Creating a Thread object only creates an instance; no execution begins until start() is invoked. Calling run() would execute synchronously in the calling thread, which is a common trap.
- Question 28Multiple Select
Which two statements are true about invoking run() directly on a Thread object instead of calling start()?
- AThe code inside run() executes on the current calling thread.Correct answer
- BThe code inside run() executes synchronously, not on a new thread.Correct answer
- CA new thread is started and run() executes on that new thread.
- DThe thread automatically changes from NEW to TIMED_WAITING.
Why this is the answer
Directly calling run() executes it as an ordinary method in the current thread. To launch a new thread of execution, start() must be used instead.
- Question 29Interactive
Inspect the Java code in the code block. What is true about the call to future.get()?
- AIt immediately returns 42 even if the task has not completed because the lambda already supplied the value.
- BIt blocks until the task completes, and then returns the integer 42.Correct answer
- CIt returns a Future that will be completed later.
- DIt prevents the ExecutorService from ever shutting down.
Why this is the answer
Future.get() without a timeout blocks until the task is complete and then returns the result, so the main thread obtains 42 after submission.
Object-Oriented Concepts
9 questions in this domain
- Question 30Single Choice
What happens when a Thread object is created?
- AIt only creates a Thread instance.Correct answer
- BIt immediately starts a new thread.
- CIt automatically calls run() in parallel.
- DIt schedules the task on the main thread.
Why this is the answer
Creating a Thread object only creates an instance. The start() method must be called to create and run a new execution thread.
- Question 31Multiple Select
Which statements about Thread.run() are true? Select all that apply.
- ADirect run() calls execute synchronously.Correct answer
- BAlways call run() to launch a thread.
- Crun() can be overridden in a subclass.Correct answer
- Dstart() causes run() to run on another thread.Correct answer
Why this is the answer
Calling run() directly executes it in the caller's thread. The start() method creates the new call stack and then invokes run() on the new thread.
- Question 32Interactive
Given the code block, what output is printed to the console?
- Atask mainCorrect answer
- Bmain task
- Ctask task main
- DNo output
Why this is the answer
The code calls t.run() directly, so the Runnable executes synchronously in the main thread. Therefore, task prints before main.
- Question 33Single Choice
Which statement about Future.get() is correct?
- AIt blocks until a result is available.Correct answer
- BIt returns immediately with the task result.
- CIt never blocks the calling thread.
- DIt returns a null Future if the task succeeds.
Why this is the answer
Future.get() is not a non-blocking poll. It blocks the calling thread until the asynchronous task completes and the result is available.
- Question 34Multiple Select
Which statements about execute() and submit() are true? Select all that apply.
- Asubmit() accepts Callable and returns Future.Correct answer
- BFuture.get() exposes failures from submitted tasks.Correct answer
- CTask exceptions from execute() reach the uncaught handler.Correct answer
- Dexecute() returns a Future like submit().
Why this is the answer
Unlike execute(), submit() accepts Callable and returns Future. Failures from submitted tasks are visible through Future.get(). Direct execute() routes exceptions to an uncaught handler.
- Question 35Interactive
Review the code block. Which statements are correct? Select all that apply.
- AThe text ready is printed before shutdown is called.Correct answer
- Bget() blocks until the task is complete.Correct answer
- Csubmit() returns a Future for the task.Correct answer
- DnewFixedThreadPool creates virtual thread tasks.
Why this is the answer
submit() returns a Future, and get() blocks until the task finishes. After the value is printed, the finally block calls shutdown().
- Question 36Single Choice
Which practice is essential when using an explicit Lock object?
- ACall unlock() in a finally block.Correct answer
- BUse synchronized blocks with every explicit lock.
- CLet the JVM release the Lock automatically.
- DRelease the Lock only at program shutdown.
Why this is the answer
An explicit Lock has no automatic release. Placing unlock() in a finally block guarantees that the lock is released even if an exception occurs.
- Question 37Multiple Select
Which statements about Java object monitors are true? Select all that apply.
- AA monitor exists for each Java object.Correct answer
- BAll monitors in the application are shared as one.
- CSynchronizing on the same object blocks competing threads.Correct answer
- DA monitor checks every field access automatically.
Why this is the answer
Every Java object has an intrinsic monitor. Threads must synchronize on the same object to block one another; a monitor does not protect fields automatically.
- Question 38Interactive
Review the code block. Which precondition is required for the method call inside the try block?
- AThe current thread must hold the Lock.Correct answer
- BThe current thread must also call Thread.sleep().
- CThe current thread must be a daemon thread.
- DThe current thread must use a synchronized method.
Why this is the answer
The blocking call requires the current thread to own the associated Lock. It is the lock-based counterpart of wait(), not a synchronized-method requirement.
Streams and Lambda Expressions
5 questions in this domain
- Question 39Single Choice
When a Thread object is created, which invocation starts the separate execution of its task?
- ACall start().Correct answer
- BCall run().
- CCall join().
- DCall yield().
Why this is the answer
A new Thread object is not running. The start() method creates the new call stack and then invokes run() on that stack; calling run() directly executes it in the caller's thread.
- Question 40Multiple Select
While working with an ExecutorService, which statements correctly contrast execute() and submit() regarding return values and exceptions?
- Asubmit() returns a Future.Correct answer
- Bexecute() returns a Future.
- Cexecute() accepts Runnable only.Correct answer
- Dsubmit() accepts Callable tasks.Correct answer
Why this is the answer
submit() accepts both Runnable and Callable and returns a Future for cancellation and result retrieval. execute() accepts Runnable, returns void, and does not expose a Future for that task.
- Question 41Interactive
The source shows a CompletableFuture chain. What static type is inferred for the result variable?
- AVoid
- BFuture<Void>
- CCompletableFuture<String>
- DCompletableFuture<Void>Correct answer
Why this is the answer
thenAccept() takes a Consumer and returns CompletableFuture<Void>; it does not transform the value. Only a stage such as thenApply() would return CompletableFuture<String>.
- Question 42Single Choice
Which Java inheritance rule prevents a class from extending both Thread and another parent class?
- AIt cannot extend another class.Correct answer
- BIt cannot override run().
- CIt cannot be declared final.
- DIt cannot implement an interface.
Why this is the answer
Java supports single inheritance for classes. Once a class extends Thread, it cannot extend another class, although it may implement multiple interfaces and override run().
- Question 43Multiple Select
What happens when a thread calls get() on a Future that has not yet completed?
- AIt returns immediately with null.
- BIt blocks until the result is ready.Correct answer
- CIt can throw ExecutionException.Correct answer
- DIt can throw InterruptedException.Correct answer
Why this is the answer
Future.get() blocks until the computation completes, if necessary. It throws ExecutionException when the task has failed and InterruptedException if the waiting thread is interrupted.
Supplementary Topics
2 questions in this domain
- Question 44Single Choice
Which statement correctly describes how exception handling differs between execute(Runnable) and submit(Callable<T>) tasks?
- Aexecute() returns a Future, but submit() does not return any handle to the task.
- BExceptions from execute() are not available to the caller; submit() captures failures in its returned Future.Correct answer
- CBoth execute() and submit() throw ExecutionException synchronously at the call site.
- DBoth methods require InterruptedException to be handled at the call site.
Why this is the answer
execute() is void, so task exceptions are not returned to the caller. submit() returns a Future, and the exception is revealed when Future.get() throws ExecutionException.
- Question 45Multiple Select
Which statements about CompletableFuture methods are true? (Choose all that apply.)
- AthenApply() is used to consume a result and returns CompletableFuture<Void>.
- BthenAccept() consumes a stage's result and returns CompletableFuture<Void>.Correct answer
- CallOf() returns a CompletableFuture<Void> that completes when all provided CompletableFutures complete.Correct answer
- Dexceptionally() can recover from exceptions thrown by stages chained after it.
Why this is the answer
thenAccept() consumes the result and returns CompletableFuture<Void>. allOf() does not aggregate results; it returns a CompletableFuture<Void> that completes when all inputs complete. exceptionally() only handles exceptions from previous stages, not downstream stages.
Frequently asked questions
Everything about the Java SE 21 Developer question bank and how to use it.