Finding why a test is slow#
Verbatime records each JUnit test method as its own session, including the extensions,
@BeforeEach, and @AfterEach around it. You can find the slowest tests, then see what each one
spent its time on.
Record#
A test JVM exits as soon as the tests are done, so record with the agent alone, from startup until the JVM exits.
-
Add the agent to the test JVM, with
TestMethodTestDescriptor::executeas the root.<plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <forkCount>1</forkCount> <reuseForks>true</reuseForks> <argLine>-javaagent:/path/to/verbatime-agent.jar=record=startup,roots=org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor::execute,out=/path/to/tests.vbtm</argLine> </configuration> </plugin>tasks.test { maxParallelForks = 1 forkEvery = 0 jvmArgs( "-javaagent:/path/to/verbatime-agent.jar=record=startup," + "roots=org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor::execute," + "out=/path/to/tests.vbtm", ) }- Run the tests in one forked JVM. Several JVMs would write the same file over each other.
- Give
out=an absolute path, because the test JVM may run in another directory.
-
Run the tests. When the test JVM exits,
tests.vbtmis complete.
Adding the agent shows where the agent goes for other runners.
Find the slowest tests#
vbtm sessions tests.vbtm --sort dur
file: tests.vbtm status: complete recorded: 2026-09-28T05:53:28.000+00:00
length: 385.8782 ms threads: 1 sessions: 2 calls: 1,601 methods: 4,495 gc: 9 pauses, 11.0000 ms
units: ms, 0.0001 ms = 1 tick of 100 ns
2 sessions, sorted by dur, showing 2
id start dur calls depth throws gc_ms thread root
1 341.9746 22.4053 854 39 1 2.0000 main TestMethodTestDescriptor.execute
2 369.1580 16.7202 747 29 0 0.0000 main TestMethodTestDescriptor.execute
This is a recording of the two tests in the
junit-maven example.
Every session has the same root, so find the test methods by name. Give find part of the test
class or package name, and --all to list every method that matches.
vbtm find tests.vbtm OrderServiceTest --all
pattern: OrderServiceTest -> io.github.yagipass.verbatime.examples.junit.maven.OrderServiceTest.setUp()V, io.github.yagipass.verbatime.examples.junit.maven.OrderServiceTest.placesOrder()V, io.github.yagipass.verbatime.examples.junit.maven.OrderServiceTest.rejectsOutOfStock()V, io.github.yagipass.verbatime.examples.junit.maven.OrderServiceTest.lambda$rejectsOutOfStock$0()V scope: all 2 sessions
matches: 5 calls, 25.0927 ms in total, where a call inside another call of the same method is not added again
sorted by dur, showing 5. start is ms from the session start
id start dur self depth method caller flags
2.643 0.3902 16.2276 0.0180 21 OrderServiceTest.placesOrder ReflectionUtils.invokeMethod
1.768 13.8426 8.1208 3.1366 21 OrderServiceTest.rejectsOutOfStock ReflectionUtils.invokeMethod
1.772 17.1697 4.7902 0.0050 25 OrderServiceTest.lambda$rejectsOutOfStock$0 AssertThrows.assertThrows#4 !OutOfStockException
1.533 8.4614 0.7412 0.7412 25 OrderServiceTest.setUp ReflectionUtils.invokeMethod
2.440 0.2573 0.0031 0.0031 25 OrderServiceTest.setUp ReflectionUtils.invokeMethod
names printed alike:
AssertThrows.assertThrows#4 = org.junit.jupiter.api.AssertThrows.assertThrows(Ljava/lang/Class;Lorg/junit/jupiter/api/function/Executable;Ljava/lang/Object;)Ljava/lang/Throwable;
The number before the dot in an id is the session. Session 1 is rejectsOutOfStock, and session 2
is placesOrder.
The session is longer than the test method, because it also holds JUnit's work around the test.
Session 1 took 22.4 ms, but rejectsOutOfStock itself took 8.1 ms. Here the difference is 14 ms
in the first test and under 1 ms in the second, so a test that runs first can look slower than it
is.
!OutOfStockException marks a call that ended by throwing. rejectsOutOfStock expects that
exception with assertThrows.
See where the time went#
vbtm hot tests.vbtm 2 --limit 5
scope: session 2, 16.7202 ms in root calls, 747 calls, 178 methods
sorted by self, showing 5. units: ms
self_ms self% total_ms calls method
11.6129 69.5% 11.6129 2 Work.io
1.1721 7.0% 1.1721 4 Work.cpu
1.0122 6.1% 14.7718 1 OrderService.placeOrder
0.9607 5.7% 9.6178 1 PaymentGateway.charge
0.7730 4.6% 1.2142 1 Assertions.assertEquals#91
names printed alike:
Assertions.assertEquals#91 = org.junit.jupiter.api.Assertions.assertEquals(Ljava/lang/Object;Ljava/lang/Object;)V
# 173 more methods. next: vbtm hot tests.vbtm 2 --limit 15
placesOrder spends most of its time in Work.io, the example's simulated I/O, called from the
code under test. To see which call made it, print the test method and everything under it.
vbtm tree tests.vbtm --at 2.643
Next#
- Finding why a batch job is slow
junit-mavenandjunit-gradle, with more roots to try