skip to content

A Mockito deep-stub chain still fails: one call in the middle returns null, and another throws ClassCastException on a method declared to return a generic type variable T. What are the limits of deep stubbing that explain both?

level: middleimportance: should knowfreq 22%

answer

  1. mockable return type → child mock; otherwise default value
  2. String / Class / wrappers / primitives end the chain
  3. type variable T erases to its bound → ClassCastException
  4. generic metadata survives for List<Order>-style declarations
  5. Mockito 5 inline mock maker: final types traversable

basics

~20 s

A deep stub only creates a child mock when the return type is mockable; otherwise it falls back to the plain default value, so String returns null and int returns 0, ending the chain. For a method returning a type variable, erasure leaves only the bound (often Object), so the child mock is of the wrong type and the cast fails.

solid answer

~60 s

Deep stubbing is not magic — it just asks "can I mock this return type?" **Non-mockable return types end the chain.** Primitives get their zero value, and types Mockito refuses to mock — notably `String`, `Class` and the primitive wrappers — fall back to the plain empty value, i.e. `null`. So `a.b().name().length()` NPEs at `name()` even though the mock is deep. **Generics are limited by erasure.** Mockito resolves generic return types where the type information survives (a field or method declared `List<Order>` is fine), but a method declared `<T> T get(String key)` erases to its bound. Mockito then creates a mock of that bound — `Object` — and assigning it to `Order` throws `ClassCastException` at the call site, often inside your own `when(...)` line. Also remember the chain is only as deep as your mocks: the intermediate types must themselves be mockable. Since Mockito 5 the inline mock maker is the default, so `final` classes in the middle of a chain are mockable; on Mockito 4 with the subclass mock maker a `final` return type ends the chain at `null`. When you hit these, stub that link explicitly with a real value instead.

code

java · 9 lines
java
interface Registry { <T> T get(String key); }

Registry registry = mock(Registry.class, Answers.RETURNS_DEEP_STUBS);
// ClassCastException: the erased return type is Object
// when(registry.<Order>get("order").id()).thenReturn(7L);

Order order = mock(Order.class);
when(registry.<Order>get("order")).thenReturn(order);
when(order.id()).thenReturn(7L);

go deeper

for a junior

Recognise that the chain stops at types Mockito cannot mock, such as String and primitives, and that null then causes an NPE.

for a middle

Explain the mockability test, the erasure limit on type variables, and the explicit-stub workaround.

for a senior

Add the version dimension — inline mock maker default since Mockito 5 — and a fast link-by-link diagnosis method.

for a principal

Treat repeated collisions with these limits as evidence the test is reaching too far into a graph, and redirect to an adapter or real value objects.

## How a deep stub decides what to return `RETURNS_DEEP_STUBS` is an `Answer`. On each unstubbed invocation it inspects the *declared* return type of the method and asks whether that type can be mocked. If yes, it creates a child mock (itself deep) and caches it against the invocation. If no, it delegates to the ordinary default answer — the same one an ordinary mock uses — which yields `0`/`false` for primitives, an empty collection for collection types, and `null` for everything else. So every failure of a deep chain comes down to one of two questions: *is this return type mockable?* and *does Mockito know what the return type actually is?* ## Limit 1: types Mockito will not mock Primitives obviously cannot be mocked, so `a.b().count()` returning `int` gives `0` — fine as a terminal, useless as a link. More surprising are the JDK types Mockito explicitly refuses: `String`, `Class`, and the primitive wrapper types. A chain that passes through a `String` gets `null` and NPEs on the next call. That is not a bug to work around; a `String` is a value, and the fix is to stub that link with a real string. `final` classes and `final` methods used to be a hard stop too. Since Mockito 5 the **inline mock maker is the default**, so final classes and final methods *are* mockable and a deep chain can traverse them (`Optional`, for instance, is `final`). On Mockito 4 or with the subclass mock maker configured, the same chain returns `null` at that link. This is a version-sensitive answer worth stating explicitly. ## Limit 2: erasure Mockito carries generic metadata as far as the reflection API allows. If a method is declared `List<Order> findAll()`, the deep stub knows the element type, so `when(repo.findAll().get(0).id()).thenReturn(7L)` can produce an `Order` mock. Likewise a mock created for a field whose declared type is `Repo<Order>` can resolve `T` from the field's generic signature. What it cannot do is invent information that erasure destroyed. A method declared `<T> T get(String key)` has erasure `Object` (or the declared bound). Mockito creates a mock of that bound and hands it back; the compiler has inserted a checkstyle cast at the call site, so you get `ClassCastException: Object$MockitoMock cannot be cast to Order` — typically on the very line where you are writing the stub, which is confusing until you know the cause. Raw types and wildcard-heavy signatures fail the same way. The fix is not to fight it: stub that one link explicitly. ```java Order order = mock(Order.class); when(registry.get("order")).thenReturn(order); when(order.id()).thenReturn(7L); ``` ## Limit 3: what a deep stub does not give you The auto-created children are *empty mocks*: every method on them returns a default until stubbed. So a deep stub gets you past the NPE, but a chain that ends in real data still needs a `thenReturn`. Teams sometimes assume deep stubbing populates the graph with sensible values; it does not, and a test that never stubs the leaf is usually asserting against `null` or `0` by accident. Deep stubs also do not apply to spies, and combining them with strict stubbing makes unused chain stubbings surface as `UnnecessaryStubbingException` — correct behaviour, but noisier than people expect when a chain is only partly exercised. ## Diagnosing quickly When a deep chain fails, walk it link by link and read the *declared* return type of each method. The first one that is a primitive, a `String`, a wrapper, or a bare type variable is your answer. Everything else about deep stubs follows from that single mockability test.

  • Why does the ClassCastException appear on the line that writes the stub rather than inside the production code?
    Because the deep stub is created when the chained call is evaluated, and that evaluation happens while building the `when(...)` argument. The compiler inserted a checked cast to the expected type at that call site, so the mismatched mock of the erased bound fails the cast right there. Reading the stack trace literally sends people hunting in the wrong place.
  • What is the practical fix once you hit an erasure or non-mockable-type limit?
    Stop deep-stubbing that link and wire it explicitly: create a mock (or, better, a real value object) of the concrete type you want and stub the intermediate call to return it. Mixing an explicit `thenReturn` into an otherwise deep chain is perfectly legal, because an explicit stubbing overrides the auto-created child for that invocation.

saying these in an interview costs you the question

  • Believing deep stubs can mock any return type, including String and primitives
  • Thinking Mockito can recover the runtime type of a generic type variable
  • Assuming auto-created child mocks come pre-populated with realistic values
  • Claiming final classes can never appear in a deep chain, ignoring that Mockito 5 defaults to the inline mock maker
  • Blaming an unrelated line because the ClassCastException surfaces inside the when(...) call

context