skip to content

A Mockito verification written as verify(service).setRetryCount(argThat(n -> n > 3)) blows up with a NullPointerException before any assertion is reported, on a method whose parameter is a primitive int. What is happening, and which Mockito API fixes it?

level: middleimportance: should knowfreq 30%

answer

  1. matchers return placeholders, not values
  2. argThat returns null -> unboxing NPE on primitives
  3. intThat / longThat / doubleThat / booleanThat
  4. same reason anyInt() exists next to any()
  5. orphaned matcher -> InvalidUseOfMatchersException later

basics

~20 s

argThat is generic and returns null. Java must unbox that null into the primitive int parameter, which throws NullPointerException before Mockito compares anything. Use intThat(n -> n > 3) - and the siblings longThat, doubleThat, booleanThat and so on - which return a primitive default.

solid answer

~50 s

Mockito matchers do not return the value being matched; they register a matcher on an internal stack and return a dummy. For the generic argThat that dummy is (T) null, and when the mocked method's parameter is a primitive the compiler inserts Integer.intValue() on it, so the NPE is thrown by the unboxing, in the test, before Mockito ever runs the predicate. That is why the stack trace looks unrelated to matching. Mockito ships primitive-typed variants for this: intThat, longThat, shortThat, byteThat, charThat, floatThat, doubleThat, booleanThat. Each takes an ArgumentMatcher of the boxed type and returns 0 or false, so the call is safe. The same reasoning explains why the built-in family has anyInt() and anyLong() alongside any(). If the parameter is a boxed Integer rather than an int there is no unboxing, and plain argThat works.

code

java · 8 lines
java
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.verify;

verify(service).setRetryCount(argThat(n -> n > 3));

verify(service).setRetryCount(intThat(n -> n > 3));
verify(meter).record(longThat(v -> v > 0));
verify(flag).set(booleanThat(b -> b));

go deeper

for a junior

Name the cause - the matcher returns null and it gets unboxed - and the fix, intThat.

for a middle

Explain the placeholder-and-stack mechanism and generalise to the whole primitive matcher family, including anyInt().

for a senior

Add the orphaned-matcher follow-on failure and how you recognise the pattern from the stack trace alone.

for a principal

Use it as an example of a leaky DSL built on static state, and the review guidance that follows: matcher calls belong only as direct arguments of the mocked call.

## What the matcher call really does A Mockito matcher is not a value. argThat(p) pushes a matcher object onto a thread-local ArgumentMatcherStorage and returns a placeholder so that the surrounding method call compiles and executes. Mockito later pops the stack and uses the registered matchers instead of the placeholder arguments it recorded. The placeholder for a generic matcher is (T) null. ## Why a primitive parameter explodes If the mocked method is setRetryCount(int), the expression argThat(n -> n > 3) has static type Integer (inferred), and the compiler must convert it to int. That conversion is a call to Integer.intValue() on a null reference, so the JVM throws NullPointerException at that exact point. Mockito is not involved at all - it has registered the matcher but the method was never invoked on the mock. Two secondary symptoms follow: the stack trace points at the test line with no Mockito frames worth reading, and the orphaned matcher stays on the stack, so the next stubbing or verification in the same test may fail with InvalidUseOfMatchersException, blaming an innocent line. ## The fix org.mockito.ArgumentMatchers provides typed wrappers whose only job is to return a primitive default: - intThat(ArgumentMatcher<Integer>) returns 0 - longThat, shortThat, byteThat, charThat, floatThat, doubleThat return the matching zero value - booleanThat returns false So verify(service).setRetryCount(intThat(n -> n > 3)) works. The predicate itself is unchanged; only the returned placeholder differs. The same design explains the built-in primitive family - anyInt(), anyLong(), anyBoolean() exist for identical reasons, not because they match a narrower set of values. ## When you do not need them If the parameter type is a boxed Integer, Long or Boolean, no unboxing conversion happens and argThat is fine, though the primitive variant still compiles. Java's autoboxing rules are what decide this, so the rule of thumb is simple: primitive parameter, primitive matcher. ## Kotlin and other languages The same class of problem appears in Kotlin, where matchers returning null collide with non-nullable parameter types even for reference types. Teams either wrap matchers in helpers that cast away nullability or adopt a Kotlin-oriented mocking library. The underlying cause is identical - matchers return placeholders, not values. ## Diagnosing it quickly The tell is an NPE whose top stack frame is your own test line containing a matcher, with no obvious null in sight, on a call whose parameter is primitive. Once you have seen it once it is instantly recognisable, and the fix is a one-word change from argThat to intThat or the appropriate sibling. If a later test in the same class then fails with InvalidUseOfMatchersException, that is the orphaned matcher from the first failure, not a second independent bug.

  • After that NullPointerException, the next verification in the same test fails with InvalidUseOfMatchersException. Why?
    The matcher was already pushed onto Mockito's thread-local stack when the unboxing NPE aborted the call, so it was never consumed. The leftover matcher corrupts the next stubbing or verification, and Mockito reports the mismatch there. Fixing the original line removes both failures; the second is a symptom, not a separate defect.

saying these in an interview costs you the question

  • Blaming the mock or the predicate rather than unboxing of the matcher's return value
  • Thinking intThat matches a narrower set of values than argThat
  • Wrapping the parameter type in Integer purely to make the matcher compile
  • Treating the follow-on InvalidUseOfMatchersException as an unrelated bug
  • Believing Mockito evaluates the predicate at the point argThat is written

context