Free Java 17 1Z0-829 Practice Exam: Oracle Java SE 17 Developer
Try 50 free Oracle Java SE 17 Developer (Java 17 1Z0-829) questions across the exam domains, with explanations, then continue with IT Mastery practice.
This free full-length Java 17 1Z0-829 practice exam includes 50 original IT Mastery questions across the exam domains.
These are original IT Mastery practice questions. They are not official Oracle questions, copied live-exam content, or exam dumps. Use them to preview question style and explanation depth before continuing with mixed sets, topic drills, and timed mocks in IT Mastery.
Count note: this page uses the full-length practice count maintained in the Mastery exam catalog. Some certification vendors publish total questions, scored questions, duration, or unscored/pretest-item rules differently; always confirm exam-day rules with the sponsor.
Try the IT Mastery web app for a richer interactive practice experience with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Exam snapshot
- Practice target: Java 17 1Z0-829
- Practice-set question count: 50
- Time limit: 90 minutes
- Practice style: mixed-domain diagnostic run with answer explanations
Full-length exam mix
| Domain | Weight |
|---|---|
| Handling Date, Time, Text, Numeric, and Boolean Values | 11% |
| Controlling Program Flow | 6% |
| Utilizing Java Object-Oriented Approach | 20% |
| Handling Exceptions | 7% |
| Working with Arrays and Collections | 10% |
| Working with Streams and Lambda Expressions | 10% |
| Packaging, Deploying, and Java Platform Module System | 6% |
| Managing Concurrent Code Execution | 10% |
| Using Java I/O API | 6% |
| Accessing Databases Using JDBC | 4% |
| Implementing Localization | 3% |
| Logging API and Standard Annotations | 7% |
Use this as one diagnostic run. IT Mastery gives you timed mocks, topic drills, analytics, code-reading practice where relevant, and interactive practice.
Practice questions
Questions 1-25
Question 1
Topic: Handling Exceptions
A developer is refactoring a method so Java 17 try-with-resources closes the selected reader. The current code does not compile and must be fixed without opening an extra unclosed reader. Which refactor is best?
static int first(Path path, boolean utf16) throws IOException {
Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);
if (utf16) {
reader = Files.newBufferedReader(path, StandardCharsets.UTF_16);
}
try (reader) {
return reader.read();
}
}
Options:
A. Choose the charset first, create
readeronce, then usetry (reader).B. Keep the code but change the header to
try (Reader r = reader).C. Add
finaltoreaderand keep the conditional reassignment.D. Keep the reassignment and change only
Readertovar.
Best answer: A
Explanation: Java 17 allows an existing local variable in a try-with-resources header only if that variable is final or effectively final. The original reader is reassigned, so try (reader) is illegal. Selecting the charset first lets the code create one effectively final reader and then reuse it safely.
A try-with-resources resource may be a declaration or a reference to an existing final or effectively final variable. A variable is not effectively final if it is reassigned after initialization. In the original method, reader may be assigned once for UTF-8 and then reassigned for UTF-16, so it cannot be used as try (reader). The best refactor is to compute the charset first, then create reader exactly once and use that variable in the resource list.
Declaring a new resource variable from the reassigned reader can compile, but it does not fix the extra unclosed reader opened before reassignment.
- Final modifier fails because a
finallocal variable cannot be conditionally reassigned. - New resource variable closes only the current reference and can leave the earlier opened reader unclosed.
- Using
varchanges type inference only; it does not make a reassigned variable effectively final.
Question 2
Topic: Working with Arrays and Collections
A defect appears in this Java 17 code. The method must be able to store a Double value in the same array.
class Metrics {
static void update(Number[] values) {
values[0] = 3.5;
}
public static void main(String[] args) {
Integer[] values = {1, 2, 3};
update(values);
System.out.println(values[0]);
}
}
Which refactor is the best fix?
Options:
A. Declare
Number[] values = new Integer[] {1, 2, 3};B. Create a
Number[]:Number[] values = {1, 2, 3}; update(values);C. Change
updateto acceptObject[]and keepInteger[].D. Keep
Integer[]and callupdate((Number[]) values);
Best answer: B
Explanation: Java arrays are covariant, so an Integer[] can be passed where a Number[] is expected. However, the array still has runtime type Integer[], so storing a Double causes an ArrayStoreException. Creating an actual Number[] fixes the runtime incompatibility.
The core issue is the difference between an array’s declared type and its runtime component type. In the original code, update(Number[]) accepts the call because Integer[] is a subtype of Number[]. But the object is still an Integer[], and arrays check stores at runtime. Assigning 3.5 boxes to a Double, which cannot be stored in an Integer[], so the program fails with ArrayStoreException.
Declaring and creating a Number[] gives the array a runtime component type of Number, allowing both Integer and Double elements. Merely changing the reference type or adding a cast does not change the actual array object.
- Cast-only fix fails because casting the reference does not change the runtime array type.
- Declared-type trap still creates an
Integer[], so storing aDoubleremains invalid. - Object parameter widens the method parameter but does not make an
Integer[]able to store non-Integerobjects.
Question 3
Topic: Working with Arrays and Collections
Which statement correctly describes Java 17 generic collections that use raw types and wildcard bounds?
Options:
A. A raw
Listprevents heap pollution because generic type checks are performed at runtime.B. A raw
Listcan cause unchecked warnings and heap pollution;List<? extends Number>cannot accept addedIntegervalues.C.
List<? super Integer>returnsIntegerfromget()without a cast because it acceptsIntegervalues.D.
List<? extends Number>accepts anyNumbersubtype because all elements are readable asNumber.
Best answer: B
Explanation: Java generics use compile-time checks with type erasure, so raw types bypass important generic checking and can create heap pollution. An extends wildcard is producer-oriented: you can read values as the upper bound, but you cannot safely add specific subtype values such as Integer.
A raw type such as List removes the generic type argument from compile-time checking. Assigning or writing through a raw reference can produce unchecked warnings, and adding the wrong element type can pollute an object that is also viewed as a List<String> or another parameterized type. With List<? extends Number>, the actual list might be a List<Integer>, List<Double>, or some other subtype list, so adding an Integer is not type-safe. The only universally safe value to add is null. In contrast, List<? super Integer> can accept Integer values, but values read from it have type Object.
- Runtime checks misconception fails because generic element types are mostly enforced at compile time, not retained for normal runtime collection checks.
- Extends mutation misconception fails because readable as
Numberdoes not mean writable with everyNumbersubtype. - Super read misconception fails because a
superwildcard supports safe writes but only guaranteesObjectwhen reading.
Question 4
Topic: Implementing Localization
Assume needed imports exist. A report stores event times as Instant. For a French-speaking reviewer, the report must use French month/currency formatting, but the displayed event time must be the UTC calendar date and clock time for the instant. Which replacements for the placeholders meet both requirements?
var event = Instant.parse("2024-01-01T00:30:00Z");
var total = 1234.56;
var dateFmt = DateTimeFormatter.ofPattern("MMM d, uuuu HH:mm", /* date locale */)
.withZone(/* date zone */);
var moneyFmt = NumberFormat.getCurrencyInstance(/* money locale */);
System.out.println(dateFmt.format(event));
System.out.println(moneyFmt.format(total));
Options:
A. Date locale
Locale.FRANCE; date zoneZoneId.of("UTC"); money localeLocale.FRANCEB. Date locale
Locale.US; date zoneZoneId.of("UTC"); money localeLocale.FRANCEC. Date locale
Locale.FRANCE; date zoneZoneId.of("UTC"); money localeLocale.USD. Date locale
Locale.FRANCE; date zoneZoneId.of("Europe/Paris"); money localeLocale.FRANCE
Best answer: A
Explanation: Locale affects presentation conventions such as month names, currency symbols, and separators. ZoneId affects how an Instant is converted into displayed calendar date and clock time. The requirement needs French formatting with a UTC temporal projection.
Locale-sensitive formatting and time-zone-sensitive temporal behavior are separate concerns. In this code, Locale.FRANCE makes MMM use French month text and makes NumberFormat.getCurrencyInstance() use French currency formatting conventions. The ZoneId supplied to DateTimeFormatter.withZone() determines which local date and time fields are computed from the Instant. To display the instant as UTC time, the formatter must use ZoneId.of("UTC"), not the reviewer’s regional zone.
Using Europe/Paris would still represent the same instant, but it would display Paris local clock time rather than UTC clock time.
- Paris zone fails because it changes the displayed clock time for the instant to Paris local time.
- US date locale fails because
MMMwould use US English month text instead of French text. - US money locale fails because the currency formatting would use US conventions rather than French conventions.
Question 5
Topic: Working with Streams and Lambda Expressions
A service needs a method that returns the doubled length of each name as a List<Integer>. The length calculation should use a primitive stream before collecting.
static List<Integer> sizes(List<String> names) {
// choose one return statement
}
Which return statement meets the requirement?
Options:
A.
return names.stream().map(String::length).map(n -> n * 2).boxed().toList();B.
return names.stream().mapToInt(String::length).map(n -> n * 2).boxed().toList();C.
return names.stream().mapToInt(String::length).map(n -> n * 2).toList();D.
return names.stream().mapToInt(String::length).boxed().mapToInt(n -> n * 2).toList();
Best answer: B
Explanation: The correct pipeline crosses from an object stream to a primitive stream with mapToInt, performs primitive int mapping, then crosses back with boxed(). In Java 17, toList() is available on Stream<T>, not on IntStream.
Object streams and primitive streams have separate APIs. Stream<String>.mapToInt(String::length) returns an IntStream, whose map method takes an IntUnaryOperator and still returns an IntStream. To collect into List<Integer> using toList(), the primitive stream must be converted back to Stream<Integer> with boxed(). Calling boxed() on a regular Stream<Integer> is invalid, and calling toList() directly on IntStream is invalid in Java 17. The key distinction is the stream type at each step, not the lambda arithmetic.
- Object stream confusion fails because
boxed()is not a method onStream<Integer>. - Primitive terminal mismatch fails because
mapToIntreturns anIntStream, which does not havetoList()in Java 17. - Box too early fails because converting back to
IntStreambefore collection again leaves notoList()method available.
Question 6
Topic: Working with Streams and Lambda Expressions
A batch job assigns shard numbers. The method must create a stream containing the primitive int values 1 through 5, inclusive, and pass it to an API that requires an IntStream. With java.util.* and java.util.stream.* imported, which declaration correctly applies the Java SE 17 stream source rule?
Options:
A.
IntStream ids = List.of(1, 2, 3, 4, 5).stream();B.
IntStream ids = IntStream.range(1, 5);C.
IntStream ids = IntStream.rangeClosed(1, 5);D.
IntStream ids = Stream.of(1, 2, 3, 4, 5);
Best answer: C
Explanation: Primitive range streams use IntStream.range() or IntStream.rangeClosed(). Because the requirement includes both 1 and 5 and needs a primitive IntStream, rangeClosed(1, 5) matches the bounds and stream type.
Java provides primitive stream factories for numeric ranges. IntStream.range(startInclusive, endExclusive) excludes the second argument, while IntStream.rangeClosed(startInclusive, endInclusive) includes it. Since the stream must contain exactly 1, 2, 3, 4, and 5 as primitive int values, the inclusive range factory is the direct match. Factory methods such as Stream.of(1, 2, 3) and collection stream() methods create object streams, such as Stream<Integer>, not IntStream. Those streams can be converted with mapToInt(Integer::intValue), but they are not directly assignable to IntStream.
- Exclusive upper bound fails because
range(1, 5)produces 1 through 4 only. - Object factory stream fails because
Stream.of(1, 2, 3, 4, 5)producesStream<Integer>. - Collection stream fails because
List.of(...).stream()also produces a boxedStream<Integer>, not anIntStream.
Question 7
Topic: Handling Exceptions
A developer writes the following Java 17 code. No imports are needed. What is the result of compiling and running it?
class MissingConfigException extends Exception {}
class BadConfigException extends RuntimeException {}
public class App {
static void load(boolean present)
throws MissingConfigException, BadConfigException {
if (!present) throw new MissingConfigException();
throw new BadConfigException();
}
static void start() {
load(true);
}
public static void main(String[] args) {
start();
}
}
Options:
A. Compilation fails;
start()must handleMissingConfigException.B. It compiles, then throws
BadConfigException.C. Compilation fails;
start()must handle both exception types.D. It compiles and exits normally.
Best answer: A
Explanation: The compile-time rule is based on the declared exception type, not on the argument value or expected runtime path. Because load() declares the checked exception MissingConfigException, start() must catch it or declare it. BadConfigException is unchecked and does not require handling.
Checked exceptions must be either caught or declared by any method that can invoke code declaring them. Here, MissingConfigException extends Exception, so it is checked. The call load(true) is still treated as a call to a method that declares throws MissingConfigException; the compiler does not ignore that declaration based on the literal argument. BadConfigException extends RuntimeException, so it is unchecked and may be caught, declared, or ignored.
The key takeaway is that checked-exception handling is enforced from the method signature at compile time.
- Both exception types is too broad because unchecked exceptions do not have to be caught or declared.
- Runtime
BadConfigExceptionignores that compilation is checked before runtime behavior. - Exits normally is not possible because the code does not compile as written.
Question 8
Topic: Controlling Program Flow
Which statement is a correct Java 17 rule for a switch expression that uses arrow labels and is used to initialize a variable?
Options:
A. Arrow-labeled cases fall through unless they end with
break.B. It must be exhaustive, and an arrow block must
yielda value or throw.C. An arrow block returns its value with
break value;.D. A
defaultlabel is required for every switch expression.
Best answer: B
Explanation: A Java 17 switch expression must be exhaustive and produce a value compatible with the target type, unless the selected path throws. With arrow labels, a block body does not implicitly return its last expression; it must use yield to provide the result.
A switch expression is an expression, so Java must be able to determine a result for the assignment. Arrow rules do not fall through. A rule may directly provide an expression, throw an exception, or use a block. When a block is used to produce the expression’s value, the block uses yield value;. Exhaustiveness is also required, but a default is not always mandatory, such as when all constants of an enum are covered.
- Fall-through assumption fails because arrow-labeled rules do not fall through to later cases.
- Old break syntax fails because Java 17 uses
yield, notbreak value;, for switch expression results. - Mandatory default is too broad because exhaustive enum switches can omit
default.
Question 9
Topic: Working with Streams and Lambda Expressions
A developer wants to process primitive int values without boxing. Given this Java 17 code, which declaration can replace the comment so the program compiles and prints 12?
import java.util.function.*;
import java.util.stream.*;
public class Report {
public static void main(String[] args) {
// insert declaration here
var total = IntStream.of(1, 2, 3)
.map(calc)
.sum();
System.out.println(total);
}
}
Options:
A.
IntFunction<Integer> calc = n -> n * 2;B.
Function<Integer, Integer> calc = n -> n * 2;C.
IntUnaryOperator calc = n -> n * 2;D.
UnaryOperator<Integer> calc = n -> n * 2;
Best answer: C
Explanation: IntStream uses primitive functional interfaces, not the generic boxed interfaces used by Stream<T>. The map() method on IntStream expects an IntUnaryOperator, so doubling 1, 2, 3 produces 2, 4, 6, and the sum is 12.
Primitive streams have specialized method signatures to avoid boxing. For IntStream, map() is declared to take an IntUnaryOperator, whose functional method accepts an int and returns an int. Java does not automatically convert a variable typed as Function<Integer, Integer> or UnaryOperator<Integer> into an IntUnaryOperator, even if the lambda body looks compatible. IntFunction<Integer> also takes an int, but it returns an object and is used with APIs such as mapToObj(), not IntStream.map().
The key rule is to match the functional interface type required by the stream method’s signature.
- Boxed unary operator fails because
UnaryOperator<Integer>is for object values, notIntStream.map(). - Generic function fails because
Function<Integer, Integer>matches boxedStream<Integer>operations, not primitiveIntStream.map(). - Int-to-object function fails because
IntFunction<Integer>returns an object and fitsmapToObj(), notmap().
Question 10
Topic: Managing Concurrent Code Execution
A service keeps a read-mostly list of listeners. One thread iterates over the list to send notifications while other threads may occasionally add or remove listeners. Which Java 17 collection rule best supports safe iteration without caller-side locking?
Options:
A.
ArrayListfail-fast iterators make concurrent iteration safe.B.
CopyOnWriteArrayListiterators use a stable snapshot.C.
ConcurrentHashMapiterators block updates until completion.D.
Collections.synchronizedListsynchronizes iteration automatically.
Best answer: B
Explanation: CopyOnWriteArrayList is designed for read-mostly cases where iteration should be safe while occasional updates occur. Its iterators are snapshot-based, so they do not require external locking and do not fail because another thread modifies the list.
Concurrent collection choice depends on both update safety and iterator behavior. CopyOnWriteArrayList creates a new internal array on each structural modification, and its iterator reads the array snapshot that existed when the iterator was created. This makes iteration safe without caller-side locking, but updates are relatively expensive, so it fits read-mostly workloads.
The closest alternative, Collections.synchronizedList, synchronizes individual method calls but requires the caller to manually synchronize on the list while iterating.
- Fail-fast confusion fails because
ArrayListiterators are not a concurrency safety mechanism. - Synchronized wrapper trap fails because synchronized wrappers still require external synchronization during iteration.
- Map iterator behavior fails because
ConcurrentHashMapiterators are weakly consistent, not locking iterators.
Question 11
Topic: Handling Date, Time, Text, Numeric, and Boolean Values
A scheduler stores deadlines as dates only. This code fails while adding one grace day, and the design should remain date-only:
import java.time.*;
class Scheduler {
public static void main(String[] args) {
var due = LocalDate.of(2024, 2, 28);
var grace = Duration.ofDays(1);
System.out.println(due.plus(grace));
}
}
It throws java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds. What is the best fix?
Options:
A. Change
duetoLocalTime.of(0, 0).B. Use
Duration.ofHours(24)forgrace.C. Use
Period.ofDays(1)forgrace.D. Call
due.plusSeconds(grace.toSeconds()).
Best answer: C
Explanation: LocalDate represents a date without time, so it supports date-based adjustments such as days, months, and years. Duration is time-based and applies seconds and nanoseconds, which a LocalDate cannot handle.
The Date-Time API separates date-based amounts from time-based amounts. Period represents years, months, and days, making it appropriate for adjusting LocalDate. Duration represents an exact amount of time in seconds and nanoseconds; even Duration.ofDays(1) means exactly 24 hours, not a calendar day amount. When LocalDate.plus(Duration) is called, the duration tries to add seconds, producing the unsupported unit exception. For a date-only deadline, Period.ofDays(1) keeps the model date-based and produces 2024-02-29.
- Twenty-four hours still uses
Duration, so it remains time-based and still tries to add seconds toLocalDate. - Changing to
LocalTimeloses the actual date, so it does not meet the date-only deadline requirement. - Adding seconds is not available on
LocalDate; seconds require a time-bearing type such asLocalDateTimeorInstant.
Question 12
Topic: Managing Concurrent Code Execution
A metrics class is used by many worker tasks. Assume main declares any checked exceptions and that imports exist. The program sometimes prints a number less than 100000.
class Metrics {
private int completed;
void done() { completed++; }
int get() { return completed; }
}
var metrics = new Metrics();
var pool = Executors.newFixedThreadPool(4);
var futures = new ArrayList<Future<?>>();
for (int i = 0; i < 100000; i++)
futures.add(pool.submit(metrics::done));
for (var f : futures) f.get();
pool.shutdown();
System.out.println(metrics.get());
Which change most directly fixes the concurrency problem?
Options:
A. Call
Thread.sleep()before printing.B. Synchronize only the
get()method.C. Use
AtomicIntegerand callincrementAndGet().D. Declare
completedasvolatile.
Best answer: C
Explanation: The problem is a lost update caused by the non-atomic completed++ operation. AtomicInteger provides an atomic read-modify-write operation, so concurrent worker tasks cannot overwrite each other’s increments.
completed++ looks like one operation, but it is a read, an addition, and a write. When multiple threads execute it concurrently, two threads can read the same old value and both write back the same new value, losing one increment. Waiting on each Future ensures the submitted tasks have completed before printing, but it does not make the increments atomic. AtomicInteger.incrementAndGet() is designed for this kind of shared counter and provides a thread-safe atomic update without explicit locking.
The key distinction is visibility versus atomicity: seeing a value is not enough if the update itself can be interleaved.
- Volatile field improves visibility, but
completed++still remains a non-atomic read-modify-write. - Synchronized reader only does not protect concurrent writes in
done()from racing with each other. - Sleeping first does not create atomicity or a reliable happens-before relationship for the increment operation.
Question 13
Topic: Using Java I/O API
A developer serializes a Login object to a byte array and immediately deserializes it using the same class definitions. Assume the helper methods correctly use ObjectOutputStream and ObjectInputStream.
class Settings implements java.io.Serializable {
private static final long serialVersionUID = 1L;
String theme;
Settings(String theme) { this.theme = theme; }
}
class Login implements java.io.Serializable {
private static final long serialVersionUID = 1L;
String user = "guest";
transient String token = "none";
Settings settings = new Settings("light");
}
Before serialization, the object is changed to user = "Mia", token = "T7", and settings.theme = "dark". Which comparison is correct after deserialization?
Options:
A. Deserialization fails unless
serialVersionUIDis auto-generated.B.
userandsettings.themeare restored;tokenisnull.C. Only
useris restored;settingsbecomesnull.D. All three values are restored, including
token.
Best answer: B
Explanation: Serialization saves non-transient instance fields, including references to other serializable objects. The transient field is not saved, so after deserialization its value is the default for its type. The declared serialVersionUID supports version compatibility and does not need to be auto-generated.
Java object serialization records the state of non-transient instance fields. Here, user is a normal String field, and settings refers to a Settings object that also implements Serializable, so both values are part of the serialized object graph. The token field is marked transient, so its value is skipped. During deserialization, the Login constructor and field initializers are not used to restore token; it gets the default value for String, which is null. A matching declared serialVersionUID is valid and commonly used to control compatibility checks.
- Reference field skipped is wrong because serializable referenced objects are included in the serialized object graph.
- Transient value restored is wrong because
transientexcludes the field value from serialization. - Auto-generated UID required is wrong because an explicitly declared
serialVersionUIDis valid and preferred.
Question 14
Topic: Accessing Databases Using JDBC
A developer is troubleshooting this JDBC update. The done column is SQL BOOLEAN, and id is SQL INTEGER; the team wants type-compatible bindings and no SQL concatenation. It throws java.sql.SQLException: Parameter index out of range (0 < 1) before executing the update.
String sql = "UPDATE task SET done = ? WHERE id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(0, "true");
ps.setInt(1, taskId);
ps.executeUpdate();
}
Which change is the best fix?
Options:
A. Use
ps.setBoolean(1, true);andps.setInt(2, taskId);B. Use
ps.setString(1, "true");andps.setInt(2, taskId);C. Use
ps.setBoolean(0, true);and keepps.setInt(1, taskId);D. Change the SQL placeholders to
?0and?1.
Best answer: A
Explanation: PreparedStatement parameter indexes start at 1, not 0. The first ? is for done, so it should be bound at index 1 with a boolean-compatible setter, and the second ? should be bound at index 2 with setInt.
JDBC binds PreparedStatement parameters by the position of each ? placeholder, starting with index 1. In this SQL statement, done = ? is the first placeholder and id = ? is the second. Because done is a SQL BOOLEAN, setBoolean(1, true) is the type-compatible binding. Because id is a SQL INTEGER, setInt(2, taskId) binds the second placeholder correctly.
The exception occurs before execution because index 0 is invalid for JDBC parameter binding. Changing the placeholder text or relying on string conversion does not address the one-based indexing rule and the requested type-compatible binding.
- Zero-based indexes fails because JDBC parameter positions start at 1, not 0.
- Text boolean binding fixes the index but relies on conversion instead of binding a boolean value.
- Numbered placeholders fails because standard JDBC
PreparedStatementplaceholders are plain?, not?0or?1.
Question 15
Topic: Utilizing Java Object-Oriented Approach
A team is adding the Truck(String id) constructor. It must call the Vehicle(int) constructor with value 18 and then store id. Which replacement for // INSERT CODE correctly applies Java constructor chaining rules?
class Vehicle {
Vehicle(int wheels) {}
}
class Truck extends Vehicle {
private String id;
Truck() {
this("fleet");
}
// INSERT CODE
}
Options:
A.
Truck(String id) { this.id = id; }B.
Truck(String id) { super(18); this.id = id; }C.
Truck(String id) { this.id = id; super(18); }D.
Truck(String id) { this(); this.id = id; }
Best answer: B
Explanation: A constructor may explicitly call either this(...) or super(...), and that call must be the first statement. Here, the required superclass constructor is Vehicle(int), so super(18) must appear first before assigning id.
Java constructor chaining requires the first statement of a constructor to be either an explicit same-class constructor call, this(...), or an explicit superclass constructor call, super(...). If neither appears, the compiler inserts super(). In this class, Vehicle has only Vehicle(int), so an implicit no-argument super() would not compile. The constructor that starts with super(18) correctly invokes the available superclass constructor and then performs normal instance initialization.
A this(...) call would delegate to another Truck constructor, not directly to Vehicle(int), and it can create a constructor cycle here because Truck() already calls Truck(String).
- Late super call fails because
super(18)cannot appear after an assignment. - Same-class delegation fails here because
Truck()already delegates toTruck(String), creating a constructor cycle. - Omitted constructor call fails because the compiler would insert
super(), butVehiclehas no no-argument constructor.
Question 16
Topic: Accessing Databases Using JDBC
Assume the imports shown are complete. At runtime, no registered JDBC driver accepts URLs beginning with jdbc:inventory:. What happens when this program is run?
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DbPing {
public static void main(String[] args) throws SQLException {
var url = "jdbc:inventory://db.example.com:5432/app";
try (Connection con =
DriverManager.getConnection(url, "app", "secret")) {
System.out.print(con.isValid(2));
}
}
}
Options:
A. It throws
IllegalArgumentExceptionbecause the URL includes a host.B. It prints
trueafter creating a connection from the URL.C. It does not compile because
Class.forName()is missing.D. It compiles, then
getConnection()throwsSQLException.
Best answer: D
Explanation: The code compiles because the JDBC API calls and imports are valid. At runtime, DriverManager.getConnection() must find a registered driver that accepts the connection URL. If none does, it throws a SQLException before the try block body runs.
DriverManager does not create a JDBC driver merely from the text of a URL. It asks registered drivers whether they accept the supplied JDBC URL, then uses the matching driver to attempt the connection. In this scenario, no registered driver accepts jdbc:inventory:..., so DriverManager.getConnection() fails with a SQLException, commonly reported as no suitable driver found. The call to con.isValid(2) is never reached.
Modern JDBC drivers are often auto-registered when present, so an explicit Class.forName() is not a compile-time requirement. The key issue is whether an appropriate driver is available and accepts the URL.
- URL creates driver fails because the URL selects among registered drivers; it does not instantiate an unavailable driver.
- Missing
Class.forName()fails because the program can compile and modern JDBC drivers can auto-register when present. - Host in URL fails because JDBC URLs commonly include host and port details; that is not an
IllegalArgumentExceptionrule.
Question 17
Topic: Controlling Program Flow
An application needs the method below to compile on Java SE 17 and return a String for every possible int input. Which replacement for the comment correctly uses an arrow-label switch expression?
static String label(int code) {
// replacement goes here
}
Options:
- A. ```java return switch (code) { case 1 -> “one”; case 2 -> { “two”; } default -> “other”; };
- B. ```java
return switch (code) {
case 1 -> "one";
case 2 -> "two";
};
- C. ```java return switch (code) { case 1 -> “one”; case 2 -> { break “two”; } default -> “other”; };
- D. ```java
return switch (code) {
case 1 -> "one";
case 2 -> {
yield "two";
}
default -> "other";
};
Best answer: D
Explanation: A Java 17 switch expression must be exhaustive and must produce a compatible result from each selected arm. An arrow arm can directly provide an expression result, but an arrow block must use yield to provide the switch expression’s value.
In Java 17, an arrow-label switch expression is itself an expression, so it must produce a value compatible with the target type or complete abruptly. A simple arrow expression supplies the result directly. When the right side is a block, Java does not use the block’s last expression as a value; the block must execute yield with the result. Because the selector type is int, the listed case constants cannot cover every possible value, so a default arm is needed for exhaustiveness. break with a value is not valid Java 17 syntax for producing a switch expression result.
- Block without yield fails because a switch-expression block does not return its final expression automatically.
- Break with value fails because Java 17 uses
yield, notbreak, to supply a switch expression result. - Missing default fails because a switch expression over
intis not exhaustive with only two constant labels.
Question 18
Topic: Packaging, Deploying, and Java Platform Module System
A team is migrating an application to JPMS. The third-party JAR lib/acme-util-1.0.jar contains package com.acme.util, has no module-info.class, and has no Automatic-Module-Name manifest entry.
module com.store.app {
requires acme.util;
}
Compile command includes:
--class-path lib/acme-util-1.0.jar
--module-path mods
Error:
module not found: acme.util
Which is the best next fix?
Options:
A. Run the application with
java -jarinstead.B. Change the requirement to
requires com.acme.util;.C. Add
exports com.acme.util;to the app module.D. Put the library JAR on the module path.
Best answer: D
Explanation: The JAR is currently on the class path, so it is part of the unnamed module and cannot satisfy a requires directive. Placing a non-modular JAR on the module path makes it an automatic module. Its derived name is based on the JAR file name, so acme-util-1.0.jar can be required as acme.util.
JPMS resolves requires clauses only from modules observable on the module path or system modules. A regular JAR on the class path is not a named module; it belongs to the unnamed module and is not found by requires acme.util. When the same non-modular JAR is placed on the module path, Java treats it as an automatic module. With no Automatic-Module-Name, the name is derived from the JAR file name, so acme-util-1.0.jar becomes acme.util after removing the version and converting separators. The key distinction is class path versus module path, not the package name inside the JAR.
- Package name confusion fails because
requiresuses module names, not package names such ascom.acme.util. - Wrong export location fails because the consuming app module cannot export a package from the library JAR.
- Classpath launch fails because
java -jardoes not make the dependency a named module for JPMS resolution.
Question 19
Topic: Working with Arrays and Collections
A developer is troubleshooting this Java 17 code. The intent is to publish a read-only snapshot of the current task names.
var tasks = new ArrayList<>(List.of("parse", "load"));
List<String> published = Collections.unmodifiableList(tasks);
tasks.add("index");
System.out.println(published);
published.set(0, "skip");
At runtime, it prints [parse, load, index] and then throws UnsupportedOperationException. Which option best explains the cause and fix?
Options:
A.
tasksbecame immutable; remove thetasks.add("index")call.B.
publishedis a view; useList.copyOf(tasks)for a snapshot.C.
publishedis fixed-size; usenew ArrayList<>(published)instead.D.
publishedallowsset; replacesetwithadd.
Best answer: B
Explanation: Collections.unmodifiableList(tasks) does not create an independent immutable collection. It creates an unmodifiable view backed by tasks, so later changes to tasks are visible through published. Use List.copyOf(tasks) when the requirement is a read-only snapshot.
The key distinction is between an unmodifiable view and an immutable snapshot. Collections.unmodifiableList(tasks) prevents mutations through the returned reference, so published.set(...) throws UnsupportedOperationException. However, the wrapper still reads from the original tasks list, so tasks.add("index") changes what published displays. List.copyOf(tasks) creates a new unmodifiable list containing the current elements, so later structural changes to tasks are not reflected in that returned list. The closest trap is thinking “unmodifiable” means “independent immutable copy,” but here it only blocks mutation through the wrapper.
- Fixed-size confusion fails because
Collections.unmodifiableList()is not the same asArrays.asList()and is still backed by the original list. - Immutable source confusion fails because wrapping
tasksdoes not make the originalArrayListimmutable. - Mutation method confusion fails because both
setandaddare mutating operations blocked by the unmodifiable wrapper.
Question 20
Topic: Managing Concurrent Code Execution
Given the following Java 17 program, which statement about the printed line is guaranteed? Assume the program is run without interruption.
import java.util.concurrent.atomic.AtomicInteger;
public class CounterDemo {
static final int N = 100_000;
static final AtomicInteger atomic = new AtomicInteger();
static volatile int vol;
static int plain;
static void work() {
for (int i = 0; i < N; i++) {
atomic.incrementAndGet();
vol++;
plain++;
}
}
public static void main(String[] args) throws Exception {
var t1 = new Thread(CounterDemo::work);
var t2 = new Thread(CounterDemo::work);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(atomic.get() + " " + vol + " " + plain);
}
}
Options:
A. Only the first value is guaranteed to be
200000.B. No value is guaranteed to be
200000.C. All three values are guaranteed to be
200000.D. The first two values are guaranteed to be
200000.
Best answer: A
Explanation: The atomic counter uses an atomic read-modify-write operation, so both threads contribute all increments. A volatile int provides visibility for individual reads and writes, but ++ is still not atomic. The ordinary shared field also has unsynchronized concurrent updates.
The core distinction is atomicity versus visibility. AtomicInteger.incrementAndGet() performs the increment as one atomic operation, so after both threads complete, atomic.get() is guaranteed to be 200000. The expression vol++ performs a volatile read and a volatile write, but the combined read-modify-write sequence can interleave with another thread and lose an update. The ordinary plain++ has the same lost-update problem, without even volatile visibility between worker threads. The join() calls ensure the main thread observes completed thread actions, but they do not make the increments atomic. The closest trap is treating volatile as a replacement for an atomic counter; it is not.
- Volatile increment fails because
vol++is not a single atomic operation. - Join visibility fails because
join()does not prevent lost updates between the worker threads. - All unsafe fails because
AtomicIntegerprovides an atomic increment method.
Question 21
Topic: Handling Date, Time, Text, Numeric, and Boolean Values
Inside a method, a developer needs a constant for a counter value of 2,147,483,648, exactly one more than Integer.MAX_VALUE. The code must compile in Java SE 17 without casts. Which declaration correctly applies Java’s primitive literal rules?
Options:
A.
var count = 2_147_483_648;B.
int count = 2_147_483_648L;C.
long count = 2_147_483_648;D.
long count = 2_147_483_648L;
Best answer: D
Explanation: Decimal integer literals are int by default unless they have an L suffix. The value 2,147,483,648 is outside the int range, so it must be written as a long literal to compile.
Java checks an integer literal’s type before considering the assignment target. A decimal integer literal with no suffix is an int, and it must fit in the int range from -2,147,483,648 to 2,147,483,647. The value in the stem is one greater than the maximum int, so the unsuffixed literal is invalid even when assigned to a long or used with var. Adding L makes the literal a long, and long can store the value. A long literal cannot be assigned to an int without a narrowing cast.
- Long target only fails because assigning to
longdoes not rescue an unsuffixed literal that is too large forint. - Int target with L fails because a
longliteral cannot be assigned tointwithout narrowing. - Var inference fails because
varcannot infer a type from an invalid unsuffixed integer literal.
Question 22
Topic: Logging API and Standard Annotations
A Java 17 service configures its audit logger as follows:
import java.util.logging.*;
class Audit {
private static final Logger LOG = Logger.getLogger(Audit.class.getName());
static {
var handler = new ConsoleHandler();
LOG.setUseParentHandlers(false);
LOG.setLevel(Level.WARNING);
handler.setLevel(Level.INFO);
LOG.addHandler(handler);
}
static void run() {
LOG.info("created");
LOG.warning("retry");
}
}
The requirement is to publish both messages through the added ConsoleHandler. Do not re-enable parent handlers. Which refactor is the best fix?
Options:
A. Add another
ConsoleHandlerwith levelINFO.B. Change the logger level to
Level.INFO.C. Change the handler level to
Level.ALL.D. Change the handler level to
Level.WARNING.
Best answer: B
Explanation: The INFO record is blocked by the Logger level before it reaches the handler. Since the handler already allows INFO and higher, lowering the logger threshold to Level.INFO lets both info() and warning() messages be published.
In java.util.logging, both the Logger and each Handler apply level filtering. A record must first be loggable by the logger. Only then is it passed to the attached handlers, where each handler applies its own level. Here, LOG.setLevel(Level.WARNING) rejects the INFO message immediately, even though the handler is set to Level.INFO. Changing the logger level to Level.INFO allows both INFO and WARNING records to reach the handler, and the handler level already permits both.
The key takeaway is that a handler cannot publish a record the logger has already filtered out.
- Raising handler threshold to
WARNINGwould continue to publish only the warning message. - Lowering only the handler to
ALLdoes not help because the logger still rejectsINFOrecords. - Adding another handler does not bypass the logger-level filter, so the
INFOrecord still never reaches any handler.
Question 23
Topic: Utilizing Java Object-Oriented Approach
Given this Java 17 code, which statement is true?
class Counter {
private int value;
Counter(int value) { this.value = value; }
void add(int x) { value += x; }
void add(int... xs) {
for (var x : xs) value += x;
}
}
public class Test {
public static void main(String[] args) {
var c = new Counter(1);
c.add();
c.add(2);
System.out.println(c.value);
}
}
Options:
A. It does not compile because
valueis private inCounter.B. It compiles and prints
3.C. It does not compile because
c.add()is ambiguous.D. It does not compile because
varcannot inferCounter.
Best answer: A
Explanation: The compilation failure is caused by encapsulation, not by var or varargs. The field value is declared private, so code in the separate Test class cannot access it directly even though the classes are in the same package.
In Java, private members are accessible only inside the class that declares them. Here, Counter.value can be read or modified by methods of Counter, such as add, but Test.main cannot access c.value directly. The local variable declaration var c = new Counter(1); is valid because var is allowed for local variables with an initializer. The overloads are also valid: c.add() invokes the varargs method with zero arguments, and c.add(2) chooses the fixed-arity add(int) method as the more specific match. A public accessor such as get() would be needed to print the value from Test.
- Local inference fails as a distractor because
varcan infer the typeCounterfrom the initializer. - Varargs ambiguity fails because zero arguments can only match
add(int...), notadd(int). - Printed result fails because compilation stops before any runtime output due to private field access.
Question 24
Topic: Using Java I/O API
A developer is troubleshooting a utility that must display attributes of a symbolic link itself, not its target. The file system supports symbolic links. logs/current.log is a symbolic link to logs/app.log, and logs/app.log is a regular file of size 1,024 bytes. The code compiles but reports attributes for the target file.
Path p = Path.of("logs/current.log");
BasicFileAttributes a =
Files.readAttributes(p, BasicFileAttributes.class);
System.out.println(a.isSymbolicLink());
System.out.println(a.isRegularFile());
System.out.println(a.size());
Which next fix best matches the intended behavior?
Options:
A. Replace
BasicFileAttributeswithPosixFileAttributes.B. Add
LinkOption.NOFOLLOW_LINKStoreadAttributes.C. Use
p.toRealPath()before reading attributes.D. Call
p.normalize()before reading attributes.
Best answer: B
Explanation: NIO.2 file attribute reads follow symbolic links by default. To inspect the symbolic link itself, pass LinkOption.NOFOLLOW_LINKS to Files.readAttributes() so the returned BasicFileAttributes describe the link rather than its target.
The core issue is symbolic link handling in NIO.2 attribute reads. Files.readAttributes(path, BasicFileAttributes.class) follows links unless told otherwise, so the attributes describe logs/app.log. Adding LinkOption.NOFOLLOW_LINKS changes the lookup to the link object at logs/current.log, allowing isSymbolicLink() to reflect the link itself.
The fixed call is:
BasicFileAttributes a = Files.readAttributes(
p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
Path cleanup or choosing a more specific attribute interface does not change the default link-following behavior.
- Path normalization only removes redundant name elements; it does not affect symbolic link traversal.
- Real path lookup typically resolves the link to its target, which is the opposite of the intent.
- POSIX attributes add owner, group, and permission data where supported, but they still follow links unless
NOFOLLOW_LINKSis used.
Question 25
Topic: Utilizing Java Object-Oriented Approach
A Java 17 application must model a set of workflow states: DRAFT, REVIEW, APPROVED, and REJECTED. The set is fixed by the business process, and each state may later need fields and methods such as a display label. Which construct best fits this model?
Options:
A. A class with public
StringconstantsB. An enum with one constant per state
C. A sealed interface with one implementation per state
D. A record with a
String namecomponent
Best answer: B
Explanation: A Java enum is the best model when the valid values are a fixed set of named constants. Enum constants are type-safe instances, and an enum can include fields, constructors, and methods for behavior such as display labels.
Java enums are intended for closed sets of named values such as workflow states, days, directions, or categories. Each enum constant is a singleton instance of the enum type, so variables cannot accidentally hold unrelated strings. An enum can also declare fields, constructors, and methods, which supports adding a display label or behavior without changing the modeling approach.
The key takeaway is that a fixed set of named constants is a direct enum use case, not a string-based or open-ended value model.
- String constants allow invalid or misspelled values because any
Stringcan be assigned. - Record value represents data but does not restrict values to the listed workflow states.
- Sealed hierarchy can model a closed set of types, but it is unnecessarily complex for simple named constants.
Questions 26-50
Question 26
Topic: Handling Exceptions
Assume this Java 17 code is compiled and run. No imports are required. What is the result?
public class App {
static String call(int n) {
try {
if (n == 1) return "try";
throw new IllegalArgumentException("try");
} finally {
if (n == 1) throw new IllegalStateException("finally");
return "finally";
}
}
public static void main(String[] args) {
try { System.out.print(call(1)); }
catch (Exception e) { System.out.print(e.getClass().getSimpleName()); }
System.out.print(",");
try { System.out.print(call(2)); }
catch (Exception e) { System.out.print(e.getClass().getSimpleName()); }
}
}
Options:
A. It prints
try,finally.B. It prints
try,IllegalArgumentException.C. The code does not compile.
D. It prints
IllegalStateException,finally.
Best answer: D
Explanation: A finally block runs before a pending return or exception from the try block is completed. If the finally block itself returns or throws, that later abrupt completion replaces the earlier one. That is why the first call reports an exception and the second call returns finally.
The core rule is that finally can override a pending result from try. In call(1), the try block starts to return try, but the finally block throws IllegalStateException, so the caller observes that exception. In call(2), the try block throws IllegalArgumentException, but the finally block returns finally, so that exception is discarded and the returned value is printed.
Returning or throwing from finally is legal, but it can mask earlier returns and exceptions.
- Ignored finally fails because the
tryreturn incall(1)is replaced by the exception thrown fromfinally. - Original exception kept fails because the
returninfinallyforcall(2)masks theIllegalArgumentException. - Compilation concern fails because Java allows
returnandthrowstatements in afinallyblock.
Question 27
Topic: Utilizing Java Object-Oriented Approach
A request-processing library has this Java 17 class. Audit tokens may be cached, and cached tokens must not keep any Request instance alive. The token value must use the supplied request’s id.
class Request {
private final String id;
Request(String id) { this.id = id; }
class AuditToken {
String value() { return id; }
}
static AuditToken tokenFor(Request request) {
return new AuditToken(); // compile-time error
}
}
Which refactor is the best correction?
Options:
A. Make
idstatic so a static nestedAuditTokencan read it.B. Make
AuditTokenstatic, store a finalid, and returnnew AuditToken(request.id).C. Return
request.new AuditToken()fromtokenFor.D. Keep the inner class and call
new Request(request.id).new AuditToken().
Best answer: B
Explanation: A non-static inner class instance requires an enclosing instance and keeps an implicit reference to it. Since cached tokens must not keep any Request alive, the best refactor is a static nested class that copies the needed id value.
The core issue is the difference between a non-static inner class and a static nested class. new AuditToken() is invalid in the static method because AuditToken is a non-static inner class and requires an enclosing Request instance. Qualifying construction with a Request would compile, but the resulting token would keep an implicit reference to that Request. Since the requirement says cached tokens must not keep any Request instance alive, the nested type should be made static and given its own copied state, such as a final id field initialized from request.id.
The key takeaway is that fixing the syntax alone is not enough when the inner class lifecycle is also part of the requirement.
- Qualified inner creation fixes the compile error but leaves an implicit reference to the supplied
Request. - New outer instance still creates an inner object tied to a
Request, just a different one. - Static shared id loses per-request state because all requests would share the same field.
Question 28
Topic: Managing Concurrent Code Execution
A service uses the following methods to build a list of uppercase IDs. The source List is not modified while either method runs, and the required result is a deterministic list containing all mapped IDs in source encounter order.
class Report {
static List<String> a(List<String> ids) {
var out = new ArrayList<String>();
ids.parallelStream()
.map(String::toUpperCase)
.forEach(out::add);
return out;
}
static List<String> b(List<String> ids) {
return ids.parallelStream()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
}
Which statement correctly applies the Java SE 17 parallel stream rule?
Options:
A. Both methods are safe for this goal.
B. Only
a()is safe for this goal.C. Only
b()is safe for this goal.D. Neither method is safe for this goal.
Best answer: C
Explanation: Parallel stream operations should avoid unsafe mutation of shared external state. Method a() passes ArrayList::add to forEach, allowing concurrent mutation and nondeterministic ordering. Method b() uses a collector-based reduction, which is the safe aggregation pattern for this goal.
The core rule is that stream pipeline actions should be non-interfering and should not rely on unsafely shared mutable state. In a(), multiple worker threads may call out.add(...) on the same ArrayList, which is not thread-safe, and forEach does not preserve encounter order in a parallel stream. In b(), collect(Collectors.toList()) is a reduction operation: the stream framework manages accumulation and combines partial results safely. Because the source is an ordered List and the pipeline is not made unordered, the collected result preserves encounter order. The key takeaway is to aggregate parallel stream results with collectors rather than external mutation.
- External mutation fails because
forEach(out::add)mutates one unsynchronizedArrayListfrom parallel tasks. - Both safe fails because
a()has both a data-race risk and noforEachencounter-order guarantee. - Neither safe fails because the collector-based reduction in
b()is a safe way to produce the list.
Question 29
Topic: Utilizing Java Object-Oriented Approach
A payroll system must represent employee work status as exactly four values: ACTIVE, ON_LEAVE, RETIRED, and TERMINATED. Each value needs a display label and a method that determines whether the employee can receive assignments. The set is fixed by company policy and should not be extended by client code. Which Java model best fits this scenario?
Options:
A. A
sealed interface WorkStatuswith one class per statusB. A
record WorkStatus(String label, boolean assignable)C. A
class WorkStatuswithpublic static final StringconstantsD. An
enum WorkStatuswith constants, fields, and methods
Best answer: D
Explanation: An enum is the clearest Java model for a fixed set of named constants. Java enums are type-safe and can include constructors, fields, and methods, so they can store the label and assignment behavior for each status.
Java enums are designed for cases where the valid values are known, named, and fixed at compile time. Each enum constant is a singleton instance of the enum type, and the enum can define a private constructor, instance fields, and methods. That fits the payroll statuses because client code should choose only among the declared constants, not create new values.
String constants do not provide type safety, records represent data carriers that can usually be instantiated with arbitrary values, and a sealed hierarchy is more complex than needed for simple fixed named constants.
- String constants fail because any
Stringcan still be passed where a status-like value is expected. - Record values fail because records model data, not a closed list of named constants.
- Sealed hierarchy can restrict implementations, but it is not the simplest direct model for enum-like constants.
Question 30
Topic: Handling Date, Time, Text, Numeric, and Boolean Values
A test fails with StringIndexOutOfBoundsException when this Java 17 code runs. The constructor capacity was chosen to leave room for the final !.
public class Demo {
public static void main(String[] args) {
var tag = new StringBuilder(8);
tag.append("ab")
.insert(2, "cd")
.delete(0, 1)
.reverse()
.insert(8, "!");
System.out.println(tag);
}
}
What is the best cause or next fix?
Options:
A. Insert at
tag.length()or useappend("!").B. Use
delete(0, 0)because the end index is inclusive.C. Move
reverse()earlier because it creates a new builder.D. Increase the initial capacity to at least 9.
Best answer: A
Explanation: StringBuilder capacity is only allocated storage, not current content length. Chained methods mutate the same builder, and valid insert positions are from 0 through the current length(). After the chain, index 8 is outside the valid range.
StringBuilder operations such as append, insert, delete, and reverse mutate the same object and usually return that object for chaining. Here, the content changes from "ab" to "abcd", then to "bcd", then to "dcb"; its length is 3. The constructor argument 8 sets initial capacity, but it does not create eight characters or make index 8 valid. An insertion offset must be between 0 and length() inclusive, so the final operation should insert at tag.length() or simply append "!".
- Capacity confusion fails because increasing capacity does not increase the valid index range.
- Reverse behavior fails because
reverse()mutates and returns the sameStringBuilder, not a separate builder. - Delete endpoint fails because
delete(start, end)uses an exclusive end index, sodelete(0, 1)removes the first character.
Question 31
Topic: Utilizing Java Object-Oriented Approach
An order service needs an enum Priority whose constants each store an SLA value in hours and expose it through an hours() method. Which declaration is valid in Java SE 17?
Options:
- A. ```java enum Priority { LOW(48), HIGH(4) private final int hours; Priority(int hours) { this.hours = hours; } int hours() { return hours; } }
- B. ```java
enum Priority {
private final int hours;
LOW(48), HIGH(4);
Priority(int hours) { this.hours = hours; }
int hours() { return hours; }
}
- C. ```java enum Priority { LOW(48), HIGH(4); private final int hours; public Priority(int hours) { this.hours = hours; } int hours() { return hours; } }
- D. ```java
enum Priority {
LOW(48), HIGH(4);
private final int hours;
Priority(int hours) { this.hours = hours; }
int hours() { return hours; }
}
Best answer: D
Explanation: An enum may define fields, constructors, and methods, but its constants must be declared before other members. When constants are followed by fields or methods, the constant list must end with a semicolon. Enum constructors cannot be public or protected.
Java enum declarations have a special member order: enum constants, if present, must come first. If the enum has additional fields, constructors, or methods after the constants, the constant list is terminated with a semicolon. Each constant may pass arguments to an enum constructor, such as LOW(48), and the constructor initializes instance fields for that constant. Enum constructors are only used by the enum constants themselves, so they cannot be declared public or protected; omitting the modifier is valid. The valid declaration satisfies all of these rules while providing an instance method that returns the stored value.
- Fields first fails because enum constants must appear before fields, constructors, and methods.
- Public constructor fails because enum constructors cannot be declared
publicorprotected. - Missing semicolon fails because fields and methods follow the constant list.
Question 32
Topic: Packaging, Deploying, and Java Platform Module System
A module com.shop.domain contains package com.shop.order. Module com.shop.ui must compile code that imports public types from com.shop.order. Module com.shop.mapper must access private fields in that package reflectively at runtime. No other named module should receive either kind of access. Assuming consumer modules declare needed dependencies, which directives belong in com.shop.domain’s module-info.java?
Options:
A.
exports com.shop.order to com.shop.ui, com.shop.mapper;B.
exports com.shop.order to com.shop.ui;andopens com.shop.order to com.shop.mapper;C.
opens com.shop.order to com.shop.ui;andexports com.shop.order to com.shop.mapper;D.
opens com.shop.order to com.shop.ui, com.shop.mapper;
Best answer: B
Explanation: JPMS separates public type access from deep reflective access. A qualified exports directive limits compile-time access to named target modules, while a qualified opens directive limits runtime reflective access to named target modules.
In JPMS, exports and opens solve different access problems. exports com.shop.order to com.shop.ui lets only com.shop.ui compile against public types in that package. opens com.shop.order to com.shop.mapper lets only com.shop.mapper perform deep reflection, such as accessing private fields with reflection APIs. Using both directives on the same package is valid when the access needs are different for different modules. The key distinction is that opens does not replace exports for normal compilation, and exports does not grant private reflective access.
- Swapped directives fail because
opensdoes not letcom.shop.uicompile imports, andexportsdoes not allow private-field reflection. - Exporting both modules gives public type access to
com.shop.mapperbut still blocks deep reflection. - Opening both modules allows reflective access more broadly than needed and still does not support
com.shop.uicompile-time imports.
Question 33
Topic: Managing Concurrent Code Execution
An audit service stores records in priority order. Assume no other thread writes to standard output.
var records = List.of("A", "B", "C", "D");
Which pipeline is guaranteed by Java SE 17 to print BC in that order, even though the stream is parallel?
Options:
A.
records.parallelStream().skip(1).limit(2).forEachOrdered(System.out::print);B.
records.parallelStream().skip(1).limit(2).findAny().ifPresent(System.out::print);C.
records.parallelStream().unordered().skip(1).limit(2).forEachOrdered(System.out::print);D.
records.parallelStream().skip(1).limit(2).forEach(System.out::print);
Best answer: A
Explanation: A List stream has a defined encounter order. For an ordered stream, skip and limit select elements using that order, and forEachOrdered preserves it even when the stream runs in parallel.
Encounter order is the source-defined traversal order of a stream. List.of("A", "B", "C", "D") supplies an ordered stream, so skip(1).limit(2) selects B and C in that order unless the stream is made unordered. In a parallel stream, forEach may perform actions in any order, but forEachOrdered performs actions according to the encounter order when one exists. findAny is also intentionally flexible and is not a way to process all selected elements. The key rule is that ordered intermediate selection does not make forEach ordered; the terminal operation still matters.
- Plain
forEachmay print the selected elements in either order when the stream is parallel. - Calling
unordered()removes the requirement thatskipandlimitrefer to the original list positions. - Using
findAnyprints at most one selected element and does not guarantee the first one.
Question 34
Topic: Handling Date, Time, Text, Numeric, and Boolean Values
A scheduling service stores user-entered local times with region-based time zones. In America/New_York, on March 10, 2024, clocks jump from 02:00 to 03:00, and the offset changes from -05:00 to -04:00.
var zone = ZoneId.of("America/New_York");
var local = LocalDateTime.of(2024, 3, 10, 2, 30);
var zdt = local.atZone(zone);
Which statement about zdt correctly applies the Java SE 17 Date-Time API rule?
Options:
A. It is
2024-03-10T02:30-05:00[America/New_York].B. It is
2024-03-10T02:30-04:00[America/New_York].C. It throws
DateTimeException.D. It is
2024-03-10T03:30-04:00[America/New_York].
Best answer: D
Explanation: The local time 02:30 falls inside the supplied daylight-saving gap. LocalDateTime.atZone(ZoneId) does not keep an impossible wall-clock time; it adjusts forward to 03:30 and uses the offset after the gap.
A LocalDateTime has no offset or time zone, so converting it with atZone applies the zone rules. When the local time is nonexistent because of a daylight-saving gap, Java resolves it by shifting the local time forward by the length of the gap. Here, 02:30 is in the skipped hour, so it becomes 03:30. The stem states that the offset after the jump is -04:00, giving 2024-03-10T03:30-04:00[America/New_York].
The key takeaway is that atZone resolves a gap rather than preserving an invalid local time.
- Earlier offset fails because
02:30does not exist with offset-05:00on that date. - Later offset unchanged fails because Java does not keep the nonexistent
02:30local time. - Exception assumption fails because
atZoneadjusts gap times instead of throwing.
Question 35
Topic: Packaging, Deploying, and Java Platform Module System
Which statement correctly describes a Java 17 JPMS rule for compiling code in one named module that refers to a public class in another named module?
Options:
A. The source module must read the target module, and the target package must be exported to it.
B. An
opensdirective alone gives normal compile-time access to the package.C. Duplicate
requiresdirectives are accepted and treated as one dependency.D. The module path order selects one package when resolved modules split a package.
Best answer: A
Explanation: JPMS separates module readability from package accessibility. Code in one named module can compile against a public type in another named module only when the source module reads the target module and the target exports that package appropriately.
In Java 17, requires establishes readability between modules, but readability is not enough by itself. The referenced type must also be public, and its package must be exported by the target module, either unqualified or qualified to the source module. An opens directive is mainly for reflective access at run time and does not replace exports for normal compilation.
The key takeaway is that missing readability and non-exported packages produce different JPMS access problems, but ordinary cross-module compilation needs both readability and export.
- Opens confusion fails because
opensdoes not provide ordinary compile-time access to package types. - Module path order fails because JPMS does not resolve split-package conflicts by choosing the first module.
- Duplicate dependency fails because duplicate
requiresdirectives in a module declaration are not valid source.
Question 36
Topic: Working with Streams and Lambda Expressions
An engineer wants text scores converted to Score records in numeric order. This Java 17 code fails to compile at the middle pipeline line:
import java.util.*;
record Score(Integer value) {}
public class Demo {
public static void main(String[] args) {
var result = List.of("10", "2", "30").stream()
.map(Integer::parseInt)
.map(Integer::compareTo)
.map(Score::new)
.toList();
System.out.println(result);
}
}
Which change is the best fix?
Options:
A. Use
.map(i -> new Score(i))for the last operation.B. Use
.map(Integer::valueOf)for the first operation.C. Use
.map(Integer.valueOf(0)::compareTo)for the middle operation.D. Use
.sorted(Integer::compareTo)for the middle operation.
Best answer: D
Explanation: The middle method reference is the problem. Integer::compareTo is an unbound instance reference, so it needs both the receiver and the comparison argument, matching Comparator<Integer> for sorted, not Function<Integer, R> for map.
A method reference is resolved using its target functional interface. map needs a one-argument Function, but Integer::compareTo refers to an instance method where the receiver is unbound: it behaves like (a, b) -> a.compareTo(b). That shape matches Comparator<Integer>, which is what sorted expects. The surrounding references are fine: Integer::parseInt is a static reference used as a Function<String, Integer>, and Score::new is a constructor reference used as a Function<Integer, Score>. The key is choosing the stream operation whose target type matches the method reference shape.
- Bound reference trap fails because
Integer.valueOf(0)::compareTocompiles as a one-argument function but maps scores to comparison results instead of sorting them. - Static reference trap fails because
Integer::parseIntis already a valid static method reference in thismapcall. - Constructor trap fails because replacing
Score::newwith an equivalent lambda does not address the incompatible middle reference.
Question 37
Topic: Logging API and Standard Annotations
A Java 17 service uses java.util.logging. The catch block must compile and include both an explanatory message and the caught exception in the log record, without assuming any handler or level configuration will display it.
import java.io.IOException;
import java.util.logging.Logger;
class Importer {
private static final Logger LOGGER =
Logger.getLogger(Importer.class.getName());
void run() {
try {
load();
} catch (IOException ex) {
LOGGER.warning("Import failed", ex);
}
}
void load() throws IOException { throw new IOException("disk"); }
}
Assume any needed java.util.logging imports can be added. Which refactor is best?
Options:
A. Use
LOGGER.warning(ex.toString());B. Use
LOGGER.setLevel(Level.WARNING); LOGGER.warning("Import failed");C. Use
LOGGER.log(Level.WARNING, "Import failed", ex);D. Use
LOGGER.log(Level.WARNING, ex);
Best answer: C
Explanation: Logger.warning() is a convenience method for logging a message, not a message plus a Throwable. To attach the exception to the log record, use log(Level, String, Throwable). Whether that record is displayed is controlled outside this call by logger and handler configuration.
The Java Logging API provides convenience methods such as warning(String) for simple messages. When code needs to record an exception object, the appropriate method is Logger.log(Level level, String msg, Throwable thrown). That call creates a log record containing the severity, message, and associated exception. The method call should not assume anything about console output, handler setup, or effective logging level; those are configuration concerns separate from using the correct logging API.
The key takeaway is to choose the overload that captures the Throwable, not a convenience method that only logs text.
- String-only logging loses the exception object by converting it to text, so the
Throwableis not attached to the record. - Missing overload fails because
Loggerhas nolog(Level, Throwable)method. - Changing logger level solves a different problem and still does not attach the caught exception to the log record.
Question 38
Topic: Working with Streams and Lambda Expressions
A developer needs to turn nested batch data into one stream and then append a marker. Which replacement for the comment compiles and produces [red, blue, green, audit]?
var batches = List.of(
List.of("red", "blue"),
List.of("green"));
Stream<String> result = // replacement
System.out.println(result.toList());
Options:
A.
Stream.concat(batches.stream().flatMap(List::stream), Stream.of("audit"));B.
Stream.concat(batches.stream().map(List::stream), Stream.of("audit"));C.
Stream.concat(batches.stream(), Stream.of(List.of("audit")));D.
Stream.of(batches.stream().flatMap(List::stream), Stream.of("audit"));
Best answer: A
Explanation: The nested lists must first be decomposed into String elements. flatMap(List::stream) produces a Stream<String>, and Stream.concat joins that stream with Stream.of("audit") to produce the required single stream.
map preserves one output element per input element, so mapping each list to List::stream creates a stream whose elements are streams. flatMap is used when each input element supplies another stream and those streams must be flattened into one stream. After flattening batches into a Stream<String>, Stream.concat(first, second) creates a lazily concatenated stream containing all elements from the first stream followed by all elements from the second stream. The declared type Stream<String> also matters: both arguments to concat must combine to produce a compatible stream of strings.
- Using
mapleaves nestedStream<String>values rather than producing the requiredStringelements. - Using
Stream.ofcreates a stream containing two stream objects, not a concatenated stream of their elements. - Concatenating lists produces a
Stream<List<String>>, not the required flattenedStream<String>.
Question 39
Topic: Handling Exceptions
Consider this Java 17 code. Which statement correctly compares what process() must do for the two exception types?
class Batch {
static void load() throws java.io.IOException {
throw new java.io.IOException("missing file");
}
static void validate(String s) {
if (s == null) throw new IllegalArgumentException("bad input");
}
static void process() {
load();
validate(null);
}
}
Options:
A. Catch or declare
IllegalArgumentException;IOExceptionmay be ignored.B. Ignore both exception types.
C. Catch or declare both exception types.
D. Catch or declare
IOException;IllegalArgumentExceptionmay be ignored.
Best answer: D
Explanation: Java requires checked exceptions to be handled or declared by a method that can throw them. load() declares IOException, so process() must catch or declare it. IllegalArgumentException is unchecked, so process() can compile without mentioning it.
The core distinction is checked versus unchecked exceptions. A checked exception, such as java.io.IOException, must be caught in the current method or declared in that method’s throws clause. An unchecked exception, such as IllegalArgumentException, extends RuntimeException, so the compiler does not require it to be caught or declared. This does not mean the unchecked exception cannot occur; it only means Java does not enforce compile-time handling for it. In the snippet, the compile-time problem in process() is the call to load(), not the call to validate(null).
- Both required fails because unchecked exceptions do not require catch or declaration.
- Reversed rule fails because
IOExceptionis checked and cannot be ignored byprocess(). - Ignore both fails because the declared
IOExceptionfromload()must be handled or declared.
Question 40
Topic: Controlling Program Flow
Which statement describes a valid Java 17 loop rule?
Options:
A.
for(;;)is valid and loops indefinitely unless exited.B.
while(0)is valid because0means false.C.
while(false)compiles and simply skips its body.D.
do { } while(false)skips its body entirely.
Best answer: A
Explanation: Java allows a basic for statement to omit its initializer, condition, and update clauses. When the condition is omitted, it is treated as true, so for(;;) is a valid infinite loop unless break, return, or an exception exits it.
A Java for loop has three optional parts: initializer, condition, and update. If the condition is absent, Java treats it as true, making for(;;) a valid infinite loop. By contrast, while and do-while conditions must have type boolean or Boolean; Java does not use numeric truth values such as 0 or 1. A while(false) loop body is unreachable when the condition is the constant expression false, so it does not compile as a simple skipped loop. A do-while loop checks its condition after the body, so its body runs at least once.
- Skipped while body fails because
while(false)makes the loop body unreachable at compile time. - Do-while timing fails because a
do-whilebody runs before its condition is tested. - Numeric condition fails because Java loop conditions are not C-style numeric truth tests.
Question 41
Topic: Handling Date, Time, Text, Numeric, and Boolean Values
A Java 17 method should return values such as true:A-ready. The current code fails to compile.
static String label(boolean active, char code) {
String suffix = "-ready";
return active + ':' + code + suffix;
}
Which replacement for the return statement is the best fix?
Options:
A.
return (active && code == 'A') + ":" + suffix;B.
return active + ":" + code + suffix;C.
return active + ':' + String.valueOf(code) + suffix;D.
return (active ? code : ':') + suffix;
Best answer: B
Explanation: The compile-time problem is the first + operation: boolean + char is not legal. Using ":" makes the first operation string concatenation, so Java converts the boolean and char values to text and preserves the intended output format.
In Java, + is evaluated left to right. It performs string concatenation only when at least one operand of that particular + operation is a String. In the original expression, active + ':' is evaluated first, and a boolean cannot be added to a char. Changing the delimiter from the character literal ':' to the string literal ":" makes the first operation boolean + String, which is valid string concatenation. After that, the rest of the expression is also concatenation.
The key takeaway is that adding a later String does not fix an earlier invalid + operation.
- Late conversion fails because
active + ':'is checked beforeString.valueOf(code)is reached. - Boolean test compiles but changes the meaning by testing whether
codeis'A'instead of including the code value. - Ternary character compiles but drops the
activetext and the required colon separator.
Question 42
Topic: Working with Arrays and Collections
What is the result of compiling and running the following Java 17 code?
import java.util.*;
public class Demo {
public static void main(String[] args) {
List<String> fixed = Arrays.asList("A", "B");
List<String> view = Collections.unmodifiableList(fixed);
List<String> copy = List.copyOf(fixed);
fixed.set(0, "X");
System.out.print(view + " ");
System.out.print(copy + " ");
try {
fixed.add("C");
} catch (RuntimeException e) {
System.out.print(e.getClass().getSimpleName());
}
}
}
Options:
A. The code throws
UnsupportedOperationExceptionatfixed.set(0, "X").B.
[X, B] [A, B] UnsupportedOperationExceptionC.
[X, B] [X, B] UnsupportedOperationExceptionD.
[A, B] [A, B] UnsupportedOperationException
Best answer: B
Explanation: The code compiles and prints the changed fixed-size list through the unmodifiable view. List.copyOf() made a separate unmodifiable copy before the change, so it still shows the original contents. Adding to the fixed-size list throws UnsupportedOperationException.
Arrays.asList() returns a fixed-size list backed by an array: element replacement with set() is allowed, but structural changes such as add() or remove() are not. Collections.unmodifiableList(fixed) returns a view-backed wrapper, so changes made through the original fixed reference are visible through view. List.copyOf(fixed) creates an unmodifiable copy that is not backed by the original list, so later fixed.set(0, "X") does not change copy.
The key distinction is fixed-size versus unmodifiable versus view-backed copy behavior.
- Snapshot wrapper is wrong because
Collections.unmodifiableList()does not copy the list; it wraps the original list. - Copy as view is wrong because
List.copyOf()does not reflect later element replacement in the source list. - Set prohibition is wrong because
Arrays.asList()allows replacing existing elements withset().
Question 43
Topic: Using Java I/O API
Assume the current directory is writable, contains no symbolic links named a.txt or b.txt, and all necessary imports exist. What is the result of running this code?
public class Test {
public static void main(String[] args) throws Exception {
Path a = Path.of("a.txt");
Path b = Path.of("b.txt");
Files.writeString(a, "A");
Files.writeString(b, "B");
Files.copy(a, b, StandardCopyOption.REPLACE_EXISTING);
Files.writeString(b, "C", StandardOpenOption.APPEND);
Files.writeString(a, "D", StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE);
System.out.print(Files.readString(b));
}
}
Options:
A. It prints
DC.B. It throws
FileAlreadyExistsExceptionat thecopy()call.C. It prints
AC.D. It throws
FileAlreadyExistsExceptionbefore printing.
Best answer: D
Explanation: The copy call succeeds because REPLACE_EXISTING allows b.txt to be overwritten with the contents of a.txt. The append call then adds C to b.txt, but the later CREATE_NEW write targets an existing a.txt, causing FileAlreadyExistsException before anything is printed.
NIO.2 file options control whether an existing target is accepted, replaced, appended to, or rejected. The first two writeString() calls use default behavior, creating or truncating the files. Files.copy(a, b, REPLACE_EXISTING) replaces b.txt with the contents of a.txt, so b.txt becomes A. Files.writeString(b, "C", APPEND) appends to b.txt, making it AC. However, Files.writeString(a, "D", CREATE_NEW, WRITE) requires that a.txt not already exist. Since a.txt still exists after a copy operation, this call throws FileAlreadyExistsException. The final print statement is never reached.
- Append result is tempting because
b.txtdoes becomeAC, but the later exception prevents printing it. - Create-new overwrite is wrong because
CREATE_NEWrejects an existing file instead of replacing it. - Copy failure is wrong because
REPLACE_EXISTINGallows the existing targetb.txtto be replaced.
Question 44
Topic: Logging API and Standard Annotations
An import job should fail when load() cannot read its configuration. During troubleshooting, a test run logs a SEVERE message with a stack trace and then prints DONE, so the scheduler records the run as successful. What is the best next fix?
import java.io.*;
import java.util.logging.*;
class ImportJob {
private static final Logger LOG =
Logger.getLogger(ImportJob.class.getName());
static void load() throws IOException {
throw new IOException("missing config");
}
public static void main(String[] args) {
try {
load();
} catch (IOException e) {
LOG.log(Level.SEVERE, "Import failed", e);
}
System.out.println("DONE");
}
}
Options:
A. Use
LOG.severe(e.getMessage())instead ofLOG.log(...).B. Declare
mainwiththrows IOExceptionand rethrowe.C. Move
System.out.println("DONE")into afinallyblock.D. Replace the logger call with
System.out.println(e).
Best answer: B
Explanation: A Logger call records diagnostic information; it does not handle, rethrow, or convert an exception into a failed process. To stop normal completion, the code must explicitly change control flow, such as by rethrowing the caught exception from main.
Java logging, exception handling, and standard output are separate mechanisms. The catch block consumes the IOException and only writes a log record, so execution continues after the catch block and DONE is printed. If the job must fail, the catch block should log the useful diagnostic details and then rethrow the exception, with main declaring throws IOException or otherwise returning a failure status. Printing to System.out is only text output and does not signal failure to Java control flow or to a scheduler by itself.
The key takeaway is that logging can document a failure, but exception handling decides whether execution continues.
- Printing instead fails because
System.out.println(e)only writes text and still lets execution continue. - Changing log API fails because
LOG.severe(e.getMessage())logs less exception detail and still does not fail the job. - Using finally fails because a
finallyblock would makeDONEprint even more reliably after the exception path.
Question 45
Topic: Utilizing Java Object-Oriented Approach
In Java 17, consider this record declaration:
public record Book(String title, int pages) {
public Book {
if (pages < 1) throw new IllegalArgumentException();
title = title.strip();
}
}
Which statement describes the record correctly?
Options:
A. It compiles only if
equals,hashCode, andtoStringare declared.B. It fails because records require accessors named
getTitle()andgetPages().C. It compiles; the compact constructor may reassign component parameters before automatic field assignment.
D. It fails because the compact constructor must assign
this.titleandthis.pages.
Best answer: C
Explanation: The record compiles because the body is a compact canonical constructor. In a compact constructor, component parameters are available for validation or normalization, and the compiler performs the final field assignments after the constructor body completes.
A record header declares the record components. For each component, Java creates a private final field and a public accessor named exactly like the component, such as title() and pages(). A compact constructor is a shortened form of the canonical constructor: it has no parameter list, but the component parameters are in scope in its body. The constructor may validate values or reassign parameters such as title = title.strip();. It must not explicitly assign the component fields with this.title = ...; the compiler inserts those assignments after the compact constructor body. Generated equals, hashCode, and toString are also provided unless explicitly declared.
- Explicit field assignment fails because compact constructors rely on compiler-inserted assignments to component fields.
- Bean-style accessors fail because record accessors are named
title()andpages(), notgetTitle()andgetPages(). - Manual generated members fails because records automatically provide
equals,hashCode, andtoString.
Question 46
Topic: Working with Arrays and Collections
What is the result of compiling and running this Java 17 code?
import java.util.*;
public class Demo {
public static void main(String[] args) {
int[] nums = {3, 1, 2};
Arrays.sort(nums);
var list = Arrays.asList(nums);
nums[0] = 9;
System.out.println(list.size() + " " + Arrays.toString(list.get(0)));
}
}
Options:
A.
3 [1, 2, 3]B.
3 [9, 2, 3]C. The code does not compile.
D.
1 [9, 2, 3]
Best answer: D
Explanation: The code compiles and prints one list element: the int[] array itself. Arrays.asList() does not box a primitive array into individual Integer elements. After sorting, changing nums[0] is visible through the list because the list stores the same array reference.
Arrays.asList(T...) creates a fixed-size list backed by the provided reference elements. With int[] nums, the single argument is the primitive array object, so the inferred list type is List<int[]>, not List<Integer>. Arrays.sort(nums) first changes the array to [1, 2, 3]. The later assignment nums[0] = 9 mutates the same array object stored as the list’s only element. Therefore list.size() is 1, and Arrays.toString(list.get(0)) prints the current contents of that array. The key takeaway is that primitive arrays are not expanded or boxed by Arrays.asList().
- Boxing assumption fails because
Arrays.asList()does not turnint[]into threeIntegerelements. - Copy assumption fails because the list stores the same array reference, so later array mutations are visible.
- Compilation concern fails because
list.get(0)is anint[], which is valid forArrays.toString(int[]).
Question 47
Topic: Utilizing Java Object-Oriented Approach
What is the result of compiling and running the following Java 17 code? No imports are needed.
public class Report {
public static void main(String[] args) {
int bonus = 2;
StringBuilder log = new StringBuilder("S");
class Worker {
void add() { log.append(bonus); }
}
Runnable task = new Runnable() {
public void run() { log.append(":").append(bonus); }
};
log.append("T");
bonus++;
new Worker().add();
task.run();
System.out.println(log);
}
}
Options:
A. It prints
ST3:3.B. It prints
ST2:2.C. It does not compile because
bonusis not effectively final.D. It does not compile because
logis mutated after capture.
Best answer: C
Explanation: Local and anonymous classes may capture local variables only if those variables are final or effectively final. The variable bonus is captured inside both nested class bodies and later modified with bonus++, so the code fails to compile.
A local variable captured by a local class or anonymous class must not be reassigned or incremented anywhere in its scope after initialization. Here, bonus is used inside Worker.add() and inside the anonymous Runnable, but bonus++ changes the local variable, so it is not effectively final. By contrast, log is still effectively final because the reference variable is assigned only once; mutating the StringBuilder object does not reassign log.
The key distinction is between changing a captured local variable and changing the state of an object referenced by that variable.
- Runtime outputs fail because code that violates capture rules is rejected before execution.
- Captured value assumption fails because Java requires effective finality rather than allowing later local-variable changes.
- Mutable object trap fails because calling
append()mutates the object, not the captured local referencelog.
Question 48
Topic: Utilizing Java Object-Oriented Approach
Which Java 17 record declaration is valid? Assume each option is considered as a separate top-level declaration.
Options:
A.
record Point(int x, int y) extends Object { }B.
record Point(int x, int y) { int z; }C.
record Point(int x, int y) { static int count; int sum() { return x + y; } }D.
record Point(int x, int y) { { System.out.println(x); } }
Best answer: C
Explanation: Java records have strict rules about their bodies. A record can declare static members and instance methods, but it cannot declare extra instance fields, instance initializer blocks, or an explicit superclass.
A record declaration automatically defines private final fields for its record components, along with accessors and other standard members. In the valid declaration, count is a static field, which records may have, and sum() is an instance method that can read the component fields x and y. A record cannot add its own non-static instance field such as z, cannot use an instance initializer block, and cannot include an extends clause because every record implicitly extends java.lang.Record.
The key distinction is that records restrict additional instance state, not all additional members.
- Extra instance state fails because a record body cannot declare a new non-static field such as
z. - Initializer block fails because records cannot declare instance initializer blocks.
- Explicit superclass fails because records implicitly extend
java.lang.Recordand cannot useextends Object.
Question 49
Topic: Utilizing Java Object-Oriented Approach
A team wants this Java 17 record to reject null or blank account IDs and store the stripped value. The record currently fails to compile. Which refactor is the best fix while keeping the compact constructor form?
public record AccountId(String value) {
public AccountId {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("missing id");
}
this.value = value.strip();
}
}
Options:
A. Keep
this.value = value.strip();in the compact constructorB. Add
private String value;and assign that fieldC. Replace the last line with
value = value.strip();D. Add a setter and call it from the constructor
Best answer: C
Explanation: Record component fields are final and are not assigned directly inside a compact constructor. The compact constructor body works with the implicit constructor parameters, and Java assigns the component fields after the body completes.
A compact constructor is a canonical constructor without an explicit parameter list. Inside its body, the component names such as value refer to the implicit constructor parameters, which may be validated or reassigned for normalization. After the compact constructor body finishes, the compiler assigns the record’s final component fields from those parameter values. Therefore, assigning value = value.strip(); stores the normalized value, while this.value = ... is not allowed in the compact constructor body.
The key takeaway is to normalize the parameter, not the record component field.
- Direct field assignment fails because compact constructors cannot assign to the record component fields.
- Extra instance field fails because records cannot add arbitrary instance fields to bypass components.
- Setter-based mutation fails because record components are final and records are not designed for component mutation.
Question 50
Topic: Logging API and Standard Annotations
Assume this Java 17 source file is compiled with the shown import, and warnings are not treated as errors. What is the result?
import java.util.List;
class Report {
@SafeVarargs
void printAll(List<String>... groups) {
for (var group : groups)
System.out.print(group.size());
}
public static void main(String[] args) {
new Report().printAll(List.of("a"), List.of("b", "c"));
}
}
Options:
A. It does not compile because generic varargs are prohibited.
B. It does not compile because
printAllis overridable.C. It compiles but throws
ClassCastException.D. It compiles and prints
12.
Best answer: B
Explanation: The decisive rule is the valid use of @SafeVarargs. The method is varargs, but it is an instance method that is not final, static, or private, so the annotation is rejected at compile time.
@SafeVarargs documents that a varargs method or constructor does not perform unsafe operations on its varargs parameter. Java restricts this annotation to declarations that cannot be overridden: constructors, final methods, static methods, and private methods. In the snippet, printAll is a package-access instance method and can be overridden, so applying @SafeVarargs causes a compile-time error before main can run.
Generic varargs themselves are not automatically illegal; without this invalid annotation, the code could compile with an unchecked warning. The key issue is the annotation’s permitted target and intent.
- Generic varargs ban is wrong because generic varargs may compile, often with unchecked warnings.
- Runtime output is unreachable because compilation fails before
mainruns. - ClassCastException is not reached and is not caused by this loop.
Continue in the web app
Use IT Mastery for interactive Java 17 1Z0-829 practice with mixed sets, timed mocks, topic drills, explanations, and progress tracking.