You want a JUnit 5 Assertions.assertAll check over a list whose size is only known at runtime. Which overloads accept something other than varargs, and what interface must each lambda implement?
answer
- six overloads: varargs / Collection / Stream, ± heading
- Executable: void execute() throws Throwable
- not Runnable — checked exceptions allowed
- stream consumed once, run sequentially
- assert inside the lambda, don't just evaluate
basics
~20 sBesides Executable varargs, assertAll accepts a Collection<Executable> and a Stream<Executable>, each with an optional leading heading String. The lambdas implement org.junit.jupiter.api.function.Executable — void execute() throws Throwable — so they may call methods that throw checked exceptions.
solid answer
~40 s`assertAll` has six overloads: `Executable...`, `Collection<Executable>` and `Stream<Executable>`, each also in a form taking a leading `String` heading. For a runtime-sized check you map the data to executables: ```java assertAll("line items", orders.stream().map(o -> () -> assertTrue(o.total().signum() > 0))); ``` `Executable` is a functional interface in `org.junit.jupiter.api.function` with `void execute() throws Throwable`. Because it declares `Throwable`, a lambda can call checked-exception-throwing code inline with no try/catch — that is what distinguishes it from `Runnable`. Practical points: the stream is consumed once (a reused or already-consumed stream throws `IllegalStateException`); executables are run sequentially even if the stream is parallel-flavoured, since assertAll drives the iteration; and null executables are rejected. If instead you want one *reported result per input*, that is a job for parameterized or dynamic tests, not for a group.
code
java · 9 lines@Test
void everyRowHasPositiveTotal() {
List<Order> orders = repository.findAll();
assertAll("orders",
orders.stream().map(o ->
() -> assertTrue(o.total().signum() > 0,
() -> "non-positive total for order " + o.id())));
}go deeper
Know that lambdas passed to assertAll are Executable instances and that collections and streams are accepted as well as varargs.
Explain the Throwable-declaring signature, the inference/cast issue when mapping, and one-shot stream consumption.
Weigh a generated group against parameterized tests on reporting granularity, and insist on identity-carrying messages so an N-line aggregate is diagnosable.
Set the team rule for when data-driven verification becomes its own test dimension versus staying an in-test group, factoring in CI report noise and flake triage.
## The overload set `org.junit.jupiter.api.Assertions` exposes `assertAll` in three input shapes, each with and without a heading: - `assertAll(Executable... executables)` and `assertAll(String heading, Executable... executables)` - `assertAll(Collection<Executable> executables)` and `assertAll(String heading, Collection<Executable> executables)` - `assertAll(Stream<Executable> executables)` and `assertAll(String heading, Stream<Executable> executables)` The varargs form is what you write by hand for a fixed set of checks. The `Collection` and `Stream` forms exist for the case the question describes: the number of checks depends on data available only at runtime. ## Executable All three shapes take `org.junit.jupiter.api.function.Executable`, a functional interface whose single method is `void execute() throws Throwable`. Two consequences: - A lambda body can call anything, including methods declaring checked exceptions, without wrapping in try/catch — the framework catches the throwable and turns it into a collected failure. This is why JUnit defines its own type rather than reusing `Runnable`, whose `run()` declares no exceptions. - The lambda returns nothing; you assert inside it rather than returning a boolean. `() -> user.isActive()` does not compile as an executable body used for assertion — you must write `() -> assertTrue(user.isActive())`. This is a real mistake candidates make: a lambda that merely evaluates an expression asserts nothing and the group passes vacuously. ## Building executables from data The common pattern maps each element to an executable: ```java List<Executable> checks = users.stream() .map(u -> (Executable) () -> assertNotNull(u.email(), () -> "missing email for " + u.id())) .toList(); assertAll("emails", checks); ``` The cast (or an explicit `Executable` type parameter) is often needed because the compiler cannot infer the functional interface for a bare lambda inside `map`. Passing the stream directly avoids materialising the list: ```java assertAll("emails", users.stream().map(u -> () -> assertNotNull(u.email()))); ``` Here inference works because the target type of `map` is fixed by the `Stream<Executable>` parameter. ## Stream semantics The stream is a one-shot resource: assertAll consumes it, so you cannot pass the same stream twice, and passing an already-consumed one throws `IllegalStateException` from the JDK, not an assertion failure. Execution is sequential and ordered — assertAll iterates and invokes; it does not fan the executables out to a thread pool, so assertions are safe with non-thread-safe fixtures. A stream marked parallel gives you nothing here. The collection/stream must not contain `null` entries; JUnit validates and fails fast on them, since a null executable is always a programming error. ## When a group is the wrong tool A group produces *one* test result. If you want each input to appear as its own named result — visible individually in the CI report, individually re-runnable — that is what parameterized and dynamic tests are for; assertAll is deliberately the cheaper option that stays inside a single test method. Choose a group when the elements are observations about one outcome ("every returned row has a positive total") and a per-input test when each element is really its own case. Also keep messages useful: with N generated checks, the aggregate lists N lines, and `expected: <true> but was: <false>` repeated five times identifies nothing. Give each generated assertion a message carrying the element identity, ideally as a supplier so it costs nothing on the passing path.
- Why does JUnit define Executable instead of reusing java.lang.Runnable?Runnable.run() declares no checked exceptions, so any lambda calling checked-exception code would need a try/catch that turns a real error into noise. Executable.execute() declares throws Throwable, so test code reads naturally and the framework decides how to report whatever escapes.
- When would you use a parameterized test instead of an assertAll group over a collection?When each input deserves its own reported result — a distinct name in the CI report, independent re-run, and independent pass/fail history. A group collapses everything into one test result, which is right for several observations of one outcome and wrong for many independent cases.
saying these in an interview costs you the question
- Believing assertAll only accepts varargs
- Writing lambdas that evaluate a boolean instead of asserting, so the group passes vacuously
- Assuming grouped executables run in parallel
- Reusing an already-consumed Stream and blaming JUnit for the IllegalStateException
- Generating N checks with no per-element message, making the aggregate unreadable