Representative interview topic

Coding Interview: How would you design safe one-time lazy initialization with Java 25 StableValue?

CodingHard
Offer.cc Editorial TeamPublished Updated

Question

How would you design safe one-time lazy initialization with Java 25 StableValue?

Prompt and use case

You maintain a highly concurrent service that must lazily create an expensive configuration object and publish a successful result only once. Use Java 25 StableValue and explain concurrency, failures, observability, preview-feature rollout, and rollback boundaries. The prompt tests Java concurrency, JMM publication reasoning, and judgment about modern JDK APIs.

What the interviewer is testing

  • Whether you correctly identify StableValue as a Java SE 25 preview API rather than a stable long-term contract.
  • Whether you distinguish a write-once container from an immutable object.
  • Whether you can explain failure and retry behavior for orElseSet, trySet, setOrThrow, and orElseThrow.
  • Whether you handle initialization exceptions, competing initializers, shutdown, and upgrades.

Questions to clarify before answering

  • After initialization fails, may the system retry, or does the first failure trip a circuit breaker?
  • Does construction have side effects, and must competing callers wait for one shared result?
  • Can the target runtime enable preview features, and does the release policy allow preview APIs?
  • Is the object truly immutable, or does it need additional encapsulation and lifecycle management?

A 30-second answer framework

I would treat StableValue as a container that can be successfully set at most once, not as an automatic thread-safety guarantee for the object inside it. I would fully construct and validate the value, then publish it with orElseSet; competing readers consume the published value. If failures are retryable, I would define retry limits, exception classes, and side-effect cleanup. If failure is terminal, I would use setOrThrow with startup validation. Because JDK 25 still marks this API preview, the rollout plan must include enablement flags, monitoring, rollback, and upgrade tests.

Step-by-step deep dive

1. The StableValue contract

StableValue stores one non-null value and allows it to be successfully set at most once. It offers read, try-set, and exception-throwing set operations so the runtime can recognize a stable value and optimize safe publication.

2. Difference from volatile and AtomicReference

volatile permits repeated writes, and AtomicReference supports updates and compare-and-set. StableValue directly expresses “never changes after a successful set.” It fits one-time configuration, parsed results, or shared handles, not refreshable or replaceable caches.

3. One-time lazy initialization code

java
import java.lang.StableValue;

final class CatalogHolder {
    private final StableValue<Catalog> catalog = StableValue.of();

    Catalog get() {
        return catalog.orElseSet(this::loadCatalog);
    }

    private Catalog loadCatalog() {
        return Catalog.loadFrom("/etc/catalog.json");
    }
}

The factory should finish every validation before returning. Never publish a partially initialized object and mutate it later.

4. Handling competing initialization

When several threads call orElseSet, only one value is successfully published and the others should read that value. The factory may run concurrently, so assume it can execute more than once and avoid irreversible side effects such as duplicate registration, charging, or file creation.

5. Exception and retry policy

If the factory throws, no value is installed and a later call may try again. Separate transient failures from deterministic configuration errors, then set backoff, attempt limits, and metrics. If retries are forbidden, catch the startup failure and use setOrThrow or terminate the process deliberately.

6. When to use trySet and setOrThrow

trySet reports whether this attempt won, which suits explicit contention control. setOrThrow throws when already set or when the value is invalid, which suits a startup path with one guaranteed writer. A reader can use orElseThrow to state that initialization must already have happened.

7. Publication visibility and object state

The container defines when a value becomes visible; it does not make the object internally thread-safe. Keep Catalog fields unchanged after construction with final fields, immutable collections, or controlled synchronization. If it owns mutable resources, define close and concurrent-access protocols.

8. Engineering a preview API rollout

JDK 25 documents StableValue as preview. Compilation and execution need the matching preview options, kept consistent across CI, container images, IDEs, and production scripts. Record a fallback implementation, compatibility matrix, and upgrade rollback steps before exposing the preview API at an irreversible boundary.

Trade-offs and boundaries

  • Use a cache or atomic reference when values must refresh, replace, or be evicted; StableValue has no such lifecycle operation.
  • If the factory has external side effects, competing calls can repeat them. Make the operation idempotent or coordinate it with a separate lock before publishing.
  • If callers must await initialization, use a Future at a higher layer; do not assume StableValue provides blocking coordination.
  • Preview performance claims require benchmarks covering cold start, contention, exceptions, and GC.

Implementation plan and evidence

  1. Compile a minimal implementation with JDK 25 preview enabled and verify orElseSet, trySet, and setOrThrow return values and exceptions.
  2. Run concurrency stress tests measuring factory invocations, successful publications, duplicate side effects, and read latency.
  3. Inject construction exceptions and timeouts to verify retry, backoff, alerting, and process-exit behavior.
  4. Validate startup flags, container JDK, monitoring, and rollback scripts in the target runtime.
  5. Use the Oracle API, JDK 25 migration notes, and JVM specification as review evidence.

Common mistakes and follow-up questions

Mistake 1: Calling StableValue an immutable object

It limits successful writes to the container; it does not freeze the object inside. State the object’s own immutability design.

Mistake 2: Assuming the factory runs once

Contention or failed retries can execute it multiple times. Explain idempotent side effects and invocation-count verification.

Mistake 3: Ignoring preview enablement

Compiling locally on JDK 25 does not prove production readiness. Check preview flags in compilation, startup, containers, and CI.

Follow-up: Why not keep volatile double-checked locking?

Volatile remains suitable for a refreshable reference. For explicit one-time publication, StableValue communicates intent better, but preview risk must be accepted and any benefit demonstrated with benchmarks.

Follow-up: How do you prove no duplicate side effects?

Add observable counters and idempotency keys to the factory, then verify counts, resource handles, and final-value consistency under contention, exceptions, and retries.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool