skip to content

How does Mockito.mock create a mock at the bytecode level, and what limits exist for final classes, final methods, and static methods?

level: seniorimportance: should knowfreq 40%

answer

  1. runtime subclass via ByteBuddy + MockMethodInterceptor
  2. MockMaker is a pluggable SPI; Objenesis skips constructor
  3. subclass maker can't do final/static/private
  4. inline maker (default in Mockito 5) rewrites bytecode -> final/static OK
  5. mockStatic/mockConstruction are scoped, must be closed

basics

~20 s

Mockito generates the mock at runtime by subclassing the type (using ByteBuddy) and overriding its methods. The default subclass approach can't override final classes/methods or static methods. The newer inline mock maker (default in Mockito 5) can mock final and static via mockStatic.

solid answer

~50 s

Mockito creates a mock by dynamically generating a subclass of the target type at runtime and routing every method through its own interceptor that records the call and returns the stubbed-or-default value. The engine doing the bytecode generation is ByteBuddy, behind a pluggable component called the MockMaker. The classic subclass-based mock maker can mock interfaces and non-final classes by extending them, but it fundamentally cannot override what the JVM won't let you override: final classes, final methods, and static methods, plus constructors and private methods. The inline mock maker (mockito-inline, and the default since Mockito 5.0 on supported JVMs) uses the JVM's instrumentation API to redefine classes in place, which lets it mock final classes/methods, and provides mockStatic(...)/mockConstruction(...) for statics and constructors. Even so, mocking statics/finals is a smell often signalling a hard-to-test design; wrapping the dependency behind an interface you own is usually cleaner. Each mock also carries per-instance state (recorded invocations, stubs), which is why cleanup matters with the inline maker.

go deeper

for a junior

Aware that Mockito generates the mock at runtime and that some things (statics) can't be mocked with plain mock().

for a middle

Knows the subclass approach can't mock final/static and that mockStatic exists for statics.

for a senior

Explains ByteBuddy subclassing vs the inline mock maker's class redefinition, the final/static/constructor matrix, and scope/cleanup of MockedStatic.

for a principal

Treats static/final mocking as a design signal, drives DI-based seams over instrumentation, and weighs MockMaker choice, agent/instrumentation constraints, and suite-wide leak/cleanup implications.

## What 'create a mock' actually does When you call `Mockito.mock(OrderRepository.class)`, Mockito must hand you back an object that (a) **is** an `OrderRepository` (assignable to that type) and (b) intercepts every method call so it can record it and return a stubbed or default value. Java has no built-in 'fake any type' facility, so Mockito *generates code at runtime*. ## ByteBuddy and the MockMaker The component responsible is the **`MockMaker`** — a pluggable SPI. The default implementations use **ByteBuddy**, a runtime bytecode-generation library, to synthesize a new class. Two strategies exist: ### 1. Subclass mock maker (the classic default) ByteBuddy generates a **subclass** of the target (or, for an interface, a class implementing it) and overrides every method to delegate to a `MockMethodInterceptor`. The interceptor consults the mock's stubbing rules and default `Answer`. Because it relies on **overriding**, it inherits the JVM's overriding rules: - **Interfaces** — fine (implement them). - **Non-final concrete/abstract classes** — fine (subclass them); Mockito uses Objenesis to instantiate without calling a constructor. - **`final` classes** — *cannot* be subclassed → not mockable. - **`final` methods** — *cannot* be overridden → calls hit the real implementation, not the mock. - **`static` methods** — not associated with an instance, so a subclass override is impossible. - **`private` methods / constructors** — not overridable. ### 2. Inline mock maker (default since Mockito 5.0) The **inline mock maker** (formerly the separate `mockito-inline` artifact, now the default on supported JDKs in Mockito 5) takes a different route: it uses the JVM **instrumentation / class-redefinition** API (a Java agent) to **rewrite the bytecode of the real class in place**, inserting interception hooks. Because it modifies the class itself rather than subclassing, it can intercept **`final` classes and `final` methods**, and it enables: - **`mockStatic(SomeClass.class)`** → returns a `MockedStatic` (scoped, usually in try-with-resources) that lets you stub static methods *for the current thread only*. - **`mockConstruction(SomeClass.class)`** → intercepts `new SomeClass(...)` within a scope. ```java try (MockedStatic<UUID> uuids = mockStatic(UUID.class)) { uuids.when(UUID::randomUUID).thenReturn(fixedId); // code under test sees fixedId } // static stubbing removed here ``` These scoped mocks must be **closed** (try-with-resources) so the static/constructor stubbing doesn't leak into other tests on the same thread. ## Per-instance state and cleanup Each mock holds its own recorded invocations and stubbings. With the inline mock maker, mocks are tracked via strong references during their scope; failing to close `@Mock` resources (`openMocks(...).close()`) or `MockedStatic` scopes can leak memory across a large suite — which is why `openMocks` returns an `AutoCloseable` and why static/construction mocks are scoped. ## Why limits matter — and the design signal Before the inline maker, 'can't mock final/static' was a hard wall that pushed teams toward testable design: depend on **interfaces you own**, wrap third-party static/final APIs behind a thin seam, and inject `Clock`/`Supplier<UUID>` instead of calling `Instant.now()`/`UUID.randomUUID()` directly. Even now that the inline maker *can* mock these, doing so is frequently a **code smell**: reaching for `mockStatic` often means a dependency wasn't injected. The mature stance is: it's available, use it for legacy/third-party code you can't change, but prefer dependency injection and small seams so plain `mock(Class)` suffices. ## Summary table | Target | Subclass maker | Inline maker | |---|---|---| | interface | yes | yes | | non-final class | yes | yes | | final class | no | yes | | final method | no (runs real) | yes | | static method | no | yes (mockStatic) | | constructor | no | yes (mockConstruction) | | private method | no | no |

  • Why does mocking a static method require mockStatic rather than mock(Class)?
    Static methods aren't tied to an instance, so a generated subclass can't override them. The inline mock maker rewrites the class itself and exposes a scoped MockedStatic, which intercepts static calls on the current thread until the scope closes.
  • You need to control UUID.randomUUID() in a test. What's the cleaner alternative to mockStatic?
    Inject a seam — e.g. a Supplier<UUID> or a custom IdGenerator interface — and mock that with plain mock(Class). It avoids static mocking entirely and makes the dependency explicit.

saying these in an interview costs you the question

  • Saying plain mock(Class) can mock static methods (needs mockStatic / inline maker)
  • Claiming final classes were never mockable (the inline maker handles them)
  • Forgetting to close MockedStatic/MockedConstruction scopes, leaking stubs across tests
  • Thinking the mock runs real code by default — interception returns stubs/defaults, not real logic

context