skip to content

How do you write a custom Hamcrest matcher, and when is it worth doing over a plain assertion or allOf?

level: seniorimportance: nice to knowfreq 33%

answer

  1. Extend TypeSafeMatcher<T>
  2. matchesSafely = check; describeTo = expected; describeMismatchSafely = actual
  3. Expose a static factory returning Matcher<T>
  4. Worth it for reuse + domain message (and Mockito argThat)
  5. FeatureMatcher for extract-property-then-match

basics

~10 s

Extend TypeSafeMatcher<T>, implement matchesSafely (the check) and describeTo (the expected message), and expose a static factory method returning Matcher<T>. Write one when a check is reused a lot or needs a domain-specific failure message.

solid answer

~40 s

To create a custom matcher, extend TypeSafeMatcher<YourType>: implement matchesSafely(item) for the boolean check, describeTo(description) for the 'Expected:' text, and optionally describeMismatchSafely(item, description) for a tailored 'but:' line. Then expose a static factory method (e.g. public static Matcher<Order> isPaid()) so tests read fluently: assertThat(order, isPaid()). It is worth writing when (a) the same non-trivial condition is asserted in many tests — DRY; (b) you want a domain-vocabulary failure message ('Expected: a paid order but: was status PENDING') instead of a low-level one; or (c) the matcher must be reused as an argument matcher in Mockito. For a one-off combination of existing checks, allOf or a couple of assertThat lines are simpler — don't build a class. The payoff of a custom matcher is reuse plus diagnostic, intention-revealing messages.

go deeper

for a junior

Can use an existing custom matcher; may not yet write one.

for a middle

Writes a basic TypeSafeMatcher with matchesSafely + describeTo and a factory.

for a senior

Adds tailored mismatch messages, parameterised/composable matchers, and judges when a matcher beats allOf/AssertJ.

for a principal

Designs a reusable domain-matcher library with consistent vocabulary and diagnostic messages, and sets the team policy on Hamcrest vs AssertJ.

## Why a custom matcher Matchers are reusable, self-describing condition objects. Writing your own pays off when a check is (1) **repeated** across many tests, (2) **domain-specific** so a tailored message reads better than a generic one, or (3) needed as a **Mockito argument matcher** (`argThat`) as well as in assertions — the same object serves both. If you just need 'x > 0 and x < 100 once', use `allOf(...)` or two `assertThat` lines. A class is overhead; reach for it when reuse or message quality justifies it. ## The recipe (TypeSafeMatcher) Extend `TypeSafeMatcher<T>` (it handles null/type checks and casting for you), implement two methods, optionally a third, and add a **static factory** so call sites read fluently. ```java import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; public final class IsPaidOrder extends TypeSafeMatcher<Order> { @Override protected boolean matchesSafely(Order order) { // the check return order.getStatus() == Status.PAID; } @Override public void describeTo(Description description) { // the 'Expected:' line description.appendText("a paid order"); } @Override protected void describeMismatchSafely(Order order, Description mismatch) { mismatch.appendText("was an order with status ").appendValue(order.getStatus()); } public static Matcher<Order> isPaid() { // the factory return new IsPaidOrder(); } } ``` Usage: ```java import static com.example.IsPaidOrder.isPaid; assertThat(order, isPaid()); // failure: Expected: a paid order but: was an order with status <PENDING> ``` ## The three methods - **`matchesSafely(T item)`** — return the boolean result. TypeSafeMatcher already returned `false` for null/wrong type before calling this, so you can assume the item is a non-null `Order`. - **`describeTo(Description)`** — append the *expectation* in domain words. Keep it noun-phrase-like ('a paid order') so it reads after 'Expected:'. - **`describeMismatchSafely(T, Description)`** *(optional)* — append what was actually wrong. If you skip it you get the default 'was <item>'; a tailored message is usually the whole reason to write the matcher. ## Parameterised matchers Factories can take arguments to build a configurable matcher: ```java public static Matcher<Order> hasTotal(Matcher<BigDecimal> amountMatcher) { ... } // assertThat(order, hasTotal(greaterThan(BigDecimal.TEN))); ``` Accepting a *nested matcher* (rather than a raw value) keeps your matcher composable, the Hamcrest way. ## Generics & the factory return type Return `Matcher<T>` from the factory (not the concrete class) so callers depend on the interface. For matchers usable across types you sometimes see `Matcher<? super T>`; for most domain matchers a plain `Matcher<Order>` is fine. ## The shorthand: FeatureMatcher For 'extract a property, then apply a sub-matcher' cases, `FeatureMatcher<T,U>` removes boilerplate: you implement `featureValueOf(T)` to pull the property and pass descriptions to the constructor, then it delegates matching to a supplied sub-matcher — great for `hasTotal`-style accessors. ## When NOT to - One-off checks → `allOf`/separate assertions. - When AssertJ's fluent extracting/`satisfies` already reads well in your codebase. - When the 'matcher' would just wrap `equalTo` on a field with no better message. ## Terms - *Static factory method*: a static method returning a new object, used so call sites read fluently. - *Generics (`<T>`)*: type parameters letting the matcher be type-checked for a specific type. - *DRY*: 'Don't Repeat Yourself' — extract repeated logic. - *Argument matcher (Mockito)*: a matcher used to specify expected arguments in stubbing/verification.

  • What does FeatureMatcher simplify?
    The common 'extract a feature/property from the object, then apply a sub-matcher to it' pattern — you implement featureValueOf to pull the value and it delegates matching/description to a supplied matcher.
  • Why expose a static factory method for a custom matcher?
    So tests read fluently (assertThat(order, isPaid())) and depend on the Matcher<T> interface rather than the concrete class; it also mirrors Hamcrest's own API style.

saying these in an interview costs you the question

  • Building a custom matcher class for a one-off check (use allOf instead)
  • Forgetting describeTo, so the failure message is empty/unhelpful
  • Implementing Matcher directly and writing null/cast boilerplate by hand
  • Returning the concrete class instead of Matcher<T> from the factory
  • Hardcoding a raw value where accepting a nested matcher would be more composable

context