Mocking Static Methods in Java with PowerMock (and Mockito 3.4+)
Static methods are the hardest thing to mock in Java. They're called directly on the class, not on an injected instance, so standard mocking frameworks — which work by creating proxy subclasses — can't intercept them. That's why two separate solutions exist: PowerMock's mockStatic(), which predates the problem being solvable any other way, and Mockito's MockedStatic API introduced in version 3.4.
This post covers both approaches in full: setup, syntax, verification, argument matching, and the real-world gotchas that will cost you hours if you don't know about them.
The Problem with Static Methods
Consider this code:
public class OrderService {
public String createOrder(String userId, double amount) {
String orderId = UUID.randomUUID().toString();
AuditLog.record(orderId, userId, amount);
return orderId;
}
}UUID.randomUUID() is static — you can't inject it. AuditLog.record() is static — same problem. Testing createOrder in isolation means either:
- Accepting non-deterministic behavior (real UUID, real audit log)
- Wrapping both in injectable adapters (correct design, real refactoring cost)
- Mocking the static calls (PowerMock or Mockito inline)
Option 3 is what this post covers.
PowerMock Approach
Setup
PowerMock requires specific dependencies and a custom JUnit runner. As of this writing, PowerMock 2.0.9 is the latest stable release and supports Mockito 3.x with JUnit 4.
Maven:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>Gradle:
testImplementation 'org.powermock:powermock-module-junit4:2.0.9'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.9'@PrepareForTest
@PrepareForTest tells PowerMock which classes to rewrite before the test runs. This is mandatory — without it, mockStatic() will fail silently or throw an error.
The annotation accepts the class containing the static method you want to mock, not the class under test:
@RunWith(PowerMockRunner.class)
@PrepareForTest({UUID.class, AuditLog.class})
public class OrderServiceTest {
// ...
}You can prepare multiple classes. List every class whose static methods you intend to mock.
mockStatic()
@RunWith(PowerMockRunner.class)
@PrepareForTest({UUID.class, AuditLog.class})
public class OrderServiceTest {
@Test
public void shouldReturnMockedOrderId() {
UUID mockUUID = UUID.fromString("00000000-0000-0000-0000-000000000001");
PowerMockito.mockStatic(UUID.class);
when(UUID.randomUUID()).thenReturn(mockUUID);
PowerMockito.mockStatic(AuditLog.class);
// void static — no return value to stub, just mock it
OrderService service = new OrderService();
String result = service.createOrder("user-42", 99.99);
assertEquals("00000000-0000-0000-0000-000000000001", result);
}
}Key points:
PowerMockito.mockStatic(Clazz.class)activates mocking for that class's static methods within the current test- After
mockStatic,when(StaticClass.method())syntax works exactly like Mockito instance mocking - Void static methods are mocked automatically once
mockStaticis called — no stub needed unless you want to verify or throw
verifyStatic()
Verifying static calls requires a two-step pattern that's different from Mockito's verify():
@Test
public void shouldRecordAuditLog() {
PowerMockito.mockStatic(AuditLog.class);
OrderService service = new OrderService();
service.createOrder("user-42", 99.99);
// Step 1: Tell PowerMock which class to verify
PowerMockito.verifyStatic(AuditLog.class);
// Step 2: Call the method you're verifying (this isn't a real call — it's captured)
AuditLog.record(anyString(), eq("user-42"), eq(99.99));
}This two-step pattern trips people up. The call on line "Step 2" looks like a real method invocation but isn't — it's captured by PowerMock for verification. If you forget step 1 and just call AuditLog.record(...), the verification won't happen.
Verifying call count:
PowerMockito.verifyStatic(AuditLog.class, times(2));
AuditLog.record(anyString(), anyString(), anyDouble());PowerMockito.verifyStatic(AuditLog.class, never());
AuditLog.record(anyString(), anyString(), anyDouble());Argument Matchers
Mockito argument matchers work with PowerMock static mocking:
PowerMockito.mockStatic(CacheManager.class);
when(CacheManager.get(eq("user:42"))).thenReturn(cachedUser);
when(CacheManager.get(startsWith("session:"))).thenReturn(null);
when(CacheManager.get(anyString())).thenReturn(null); // fallbackOne rule: if you use a matcher for any argument, you must use matchers for all arguments. Mixing literal values and matchers in the same call will throw InvalidUseOfMatchersException:
// WRONG
when(CacheManager.get(eq("user:42"), 30)).thenReturn(cachedUser);
// RIGHT
when(CacheManager.get(eq("user:42"), eq(30))).thenReturn(cachedUser);Stubbing Exceptions
PowerMockito.mockStatic(ExternalAPI.class);
when(ExternalAPI.fetch(anyString())).thenThrow(new NetworkException("timeout"));
assertThrows(NetworkException.class, () -> service.loadData("resource-1"));Mockito 3.4+ Approach: MockedStatic
Starting with Mockito 3.4, you can mock static methods without PowerMock using MockedStatic. This requires the mockito-inline artifact (or Mockito 5.x, where it's built in).
Maven:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>Gradle:
testImplementation 'org.mockito:mockito-inline:4.11.0'MockedStatic Syntax
@Test
void shouldReturnMockedOrderId() {
UUID fixedUuid = UUID.fromString("00000000-0000-0000-0000-000000000001");
try (MockedStatic<UUID> mockedUUID = mockStatic(UUID.class)) {
mockedUUID.when(UUID::randomUUID).thenReturn(fixedUuid);
OrderService service = new OrderService();
String result = service.createOrder("user-42", 99.99);
assertEquals("00000000-0000-0000-0000-000000000001", result);
}
// Static mock is automatically cleaned up after try block
}The try-with-resources pattern is important — MockedStatic implements AutoCloseable, and the static mock is only active within the try block. Outside it, the original static implementation is restored. Forgetting to close it leaks the mock into other tests.
Verification with MockedStatic
try (MockedStatic<AuditLog> mockedLog = mockStatic(AuditLog.class)) {
service.createOrder("user-42", 99.99);
mockedLog.verify(() -> AuditLog.record(anyString(), eq("user-42"), eq(99.99)));
mockedLog.verify(() -> AuditLog.record(anyString(), anyString(), anyDouble()), times(1));
}Much cleaner than PowerMock's two-step verifyStatic pattern. The lambda captures the call, matchers work as usual.
Verify never called:
mockedLog.verify(() -> AuditLog.record(anyString(), anyString(), anyDouble()), never());Comparison Table
| Feature | PowerMock | Mockito MockedStatic |
|---|---|---|
| JUnit version | 4 (limited 5 support) | 4 and 5 |
| Mockito version | Up to 3.x | 3.4+ |
| Java version | 8–11 (16+ needs flags) | 8+ |
| Setup complexity | High (@RunWith, @PrepareForTest) |
Low (try-with-resources) |
| Verification syntax | Two-step verifyStatic |
Lambda in verify() |
| Argument matchers | Yes | Yes |
| Scope control | Per-test-method (auto via runner) | Explicit try block |
| Parallel test safety | Poor (classloader isolation) | Better |
| Active maintenance | Dormant since ~2021 | Actively maintained |
| Mockito 4/5 support | No | Yes |
If you're starting a project today, use MockedStatic. If you're maintaining a JUnit 4 + Mockito 2/3 project, PowerMock may be what you already have.
Gotchas
ClassLoader Isolation in PowerMock
PowerMock rewrites bytecode for classes listed in @PrepareForTest. This happens in a separate classloader context. The consequence: if you're testing a class that has static initializers or class-level constants, those initializers re-run in the PowerMock classloader. In some cases this causes:
NullPointerExceptionin static initializer blocks that assumed a real environmentClassCastExceptionwhen objects cross classloader boundaries (same class name, different loaders)- Spring beans or JPA entities failing to initialize
The fix for static initializer issues is to suppress them:
@SuppressStaticInitializationFor("com.example.ProblematicClass")Use this sparingly — suppressing initialization can hide bugs.
@RunWith Conflicts
Only one @RunWith annotation is allowed per class. This creates conflicts when you need both PowerMock and another runner (e.g., Spring's SpringRunner, Parameterized, etc.):
// This doesn't work — only one @RunWith allowed
@RunWith(PowerMockRunner.class)
@RunWith(SpringRunner.class)
public class MyTest { ... }Workaround using PowerMockRule (JUnit 4 only):
@RunWith(SpringRunner.class)
@PrepareForTest({StaticUtility.class})
public class MyTest {
@Rule
public PowerMockRule rule = new PowerMockRule();
// Now you can use both Spring context and PowerMock
}Requires the powermock-module-junit4-rule dependency:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>MockedStatic Scope Leaks
If you use mockStatic() without try-with-resources and forget to close it, the mock leaks to subsequent tests:
// WRONG — mock leaks
MockedStatic<UUID> leak = mockStatic(UUID.class);
leak.when(UUID::randomUUID).thenReturn(fixedUuid);
// test code...
// forgot: leak.close();// RIGHT
try (MockedStatic<UUID> mockedUUID = mockStatic(UUID.class)) {
mockedUUID.when(UUID::randomUUID).thenReturn(fixedUuid);
// test code...
} // closed automaticallyLeaked mocks cause org.mockito.exceptions.base.MockitoException: For com.example.Foo, static mocking is already registered in the current thread in subsequent tests.
Java Module System (Java 16+)
Both PowerMock and Mockito inline use reflection to instrument classes. Java 16+ enforces strong encapsulation by default, which can break both tools for JDK classes.
For Mockito inline on Java 16+:
-XX:+EnableDynamicAgentLoadingOr add to surefire plugin config in Maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
</argLine>
</configuration>
</plugin>For Gradle, add to jvmArgs in the test block:
test {
jvmArgs '--add-opens', 'java.base/java.lang=ALL-UNNAMED',
'--add-opens', 'java.base/java.util=ALL-UNNAMED'
}When Static Mocking Is a Design Smell
Needing to mock a static method is often a signal — not always, but often enough to ask the question.
You own the code and it's static: Consider whether the method needs to be static at all. A static UserValidator.validate(user) can become an injectable UserValidator service. One refactor, and you never need to mock it.
You call a static method in many places: The coupling is wide. A thin wrapper/adapter class (UUIDGenerator, Clock, SystemProperties) injected via constructor gives you mockability without framework magic.
You reach for static mocking in every test class: This is the strongest signal. If your test suite regularly needs mockStatic, the production code has a structural problem.
Static mocking of third-party code you don't own — UUID.randomUUID(), System.currentTimeMillis(), file system operations — is more defensible. Wrapping every JDK static call is excessive. Use MockedStatic for these cases and move on.
Unit tests with MockedStatic or PowerMock verify your logic in isolation. For validating the actual behavior users see — form submissions, API responses, multi-step flows — HelpMeTest lets you describe and run end-to-end tests in plain English, without writing a single line of test framework code.