skip to content

What does JUnit 5's @Isolated annotation guarantee, and how does it differ from putting @ResourceLock on the same class?

level: middleimportance: should knowfreq 25%

answer

  1. class-level only; runs alone
  2. = exclusive lock on the global key
  3. name the resource → lock; can't name it → isolate
  4. barrier: drains and refills the pool
  5. SAME_THREAD is not isolation

basics

~20 s

@Isolated marks a test class to run exclusively: while it executes, no other test in the suite runs concurrently, even tests that share no resource with it. @ResourceLock only excludes tests declaring the same key. @Isolated is effectively an exclusive lock on a global resource, so it is the blunt instrument to use when the shared state cannot be named.

solid answer

~50 s

`@Isolated` is a class-level annotation meaning "run this class alone". Under parallel execution the engine will not run any other test concurrently with it — the rest of the suite drains, the isolated class runs, then normal parallelism resumes. Mechanically it is an exclusive lock on a single global resource key that every other test implicitly holds in shared mode, which is why it excludes everything. `@ResourceLock` is scoped: it only excludes tests that declare the *same key*. Two classes locking different keys still run in parallel. So the choice is about whether you can name the conflict. If a test mutates the JVM default locale, name it and lock that key — everything unrelated keeps running. If a test replaces a global agent, rewires a static logging pipeline, measures timing, or does something whose blast radius you cannot enumerate, `@Isolated` is honest. The cost is throughput: an isolated class is a suite-wide barrier, so a handful of them can flatten most of your parallel gain.

code

java · 11 lines
java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Isolated;

@Isolated("measures throughput; any concurrent test contaminates the timing")
class IngestThroughputTest {

    @Test
    void sustainsTenThousandEventsPerSecond() {
        assertTrue(Benchmark.measureEventsPerSecond() > 10_000);
    }
}

go deeper

for a junior

Say it makes a class run alone with nothing else concurrent, unlike a resource lock which only blocks tests sharing the same key.

for a middle

Explain that it is an exclusive lock on a global key, that it is class-level only, and give an example where no key can name the conflict.

for a senior

Quantify the barrier cost, insist on the reason string, and lay out the escalation order: remove sharing, then name a lock, then isolate.

for a principal

Treat isolated classes as a governed exception: review new ones, track the count, and consider moving measurement-style tests out of the unit suite entirely.

## What @Isolated says `@Isolated` is applied to a test class (top-level or nested) and declares that the class must be executed in isolation from all other tests. When parallel execution is enabled, the engine schedules it so that no other test runs while any of its tests are running. Sequentially it has no observable effect, because there is never anything to isolate from. It takes an optional string value used as the reason, and that string is worth filling in — six months later nobody remembers which piece of global state forced the isolation. ## How it relates to @ResourceLock The two mechanisms are the same machinery at different granularities. JUnit models shared things as *exclusive resources* identified by a key with an access mode. `@ResourceLock(key)` declares a lock on the key you name. `@Isolated` declares an exclusive lock on a single, special global key that every other node implicitly holds in shared mode — so the isolated class conflicts with the entire suite by construction. That gives a clean rule: - **You can name the shared state** → `@ResourceLock` with that key. Only tests touching the same thing wait; the rest of the suite runs at full speed. - **You cannot name it, or the list is unbounded** → `@Isolated`. Correct by construction, expensive by construction. A second difference is placement. `@ResourceLock` can go on a method or a class; `@Isolated` is class-level only. If a single method needs total exclusivity, the options are to move it into its own isolated class or to give it an exclusive lock on a key that everything else respects — usually a sign the design should be revisited. ## When @Isolated is the right call Realistic cases: - A test that installs or removes something process-wide: a security manager-style hook, a global agent, a `System.setOut` redirection combined with third-party code that captures the stream, a shared logging pipeline reconfiguration. - A test that **measures** something — throughput, latency, memory. Concurrent tests contaminate the measurement, and there is no key that names "CPU". - A test that resets a framework's global state, e.g. tearing down and rebuilding an application context or a connection pool that other tests hold references to. - A test that must observe an absence: "no other connections exist", "the cache is empty", "only my metrics were recorded". Any concurrent test can falsify these. The measurement case is the one that best illustrates why `@ResourceLock` cannot help: contention for CPU and memory is real interference with no key to name. ## The cost Isolation is a barrier. The scheduler has to drain in-flight work, run the isolated class alone, then refill the pool. Around each isolated class there is a period where much of the machine is idle. Three consequences: 1. **Isolated classes should be small and fast.** An isolated class that takes two minutes costs the suite roughly two minutes multiplied by your parallelism in wasted capacity. 2. **Their number matters more than their size.** Each one is a separate drain-and-refill cycle. 3. **They are contagious debt.** Once one exists, the next person with a mysterious flake copies the annotation instead of diagnosing. Requiring a reason string and reviewing new usages is a cheap guardrail. ## Alternatives to try first Before isolating, ask whether the test can own its state instead: pass configuration in rather than mutating a global; use a per-test temporary directory and an ephemeral port; use a fresh in-memory registry rather than the singleton; assert on your own recorded events rather than on a global absence. If those are impossible for a legitimate reason — the JVM really has one default locale — try a named resource lock next, since it preserves parallelism for everything unrelated. Reach for `@Isolated` last. ## Interaction with execution mode A point candidates often get wrong: `@Execution(ExecutionMode.SAME_THREAD)` is *not* a weaker `@Isolated`. Execution mode governs whether a node's children may overlap **each other**; it says nothing about the rest of the suite, so other classes still run concurrently with your same-thread class. If your goal is "nothing else runs while this executes", only exclusive-resource declarations — a lock on a key everyone respects, or `@Isolated` — achieve it. ## Version note `@Isolated` was introduced in JUnit 5.7; `@ResourceLock` has been available since 5.3. Before 5.7 the equivalent trick was to declare an exclusive lock on a global key by hand, which is exactly what `@Isolated` now expresses declaratively.

  • Can you apply @Isolated to a single test method?
    No — it targets types, so it applies to a test class, including a nested one. If exactly one method needs total exclusivity, the usual move is to extract it into its own small isolated class, which also keeps the barrier short. Wanting isolation for one method inside an otherwise parallel class is often a signal that the method is doing something process-wide that deserves separating anyway.
  • Ten classes in a suite are @Isolated. What does that do to the benefit of parallel execution?
    It largely erases it. Each isolated class forces the pool to drain, run alone, and refill, so around every one of them most threads sit idle; ten such barriers in a run can dominate the wall-clock time. The remedy is to audit them: most will turn out to touch one nameable resource and can be downgraded to a scoped resource lock, and a few will be genuine measurements that should perhaps live in a separate suite.

A resource lock is booking one meeting room; @Isolated is closing the whole office so you can run a fire drill.

saying these in an interview costs you the question

  • Thinking @Isolated only excludes tests that share a resource with it
  • Using @Execution(SAME_THREAD) and believing nothing else runs concurrently
  • Applying @Isolated as the default fix for any concurrency flake
  • Assuming @Isolated can be placed on a test method
  • Expecting @Isolated to change anything when parallel execution is disabled

context