Guava's Stopwatch for Accurate Benchmarking and Timing: A Complete Guide
Guava's Stopwatch provides a lightweight, process-local utility for measuring elapsed time using System.nanoTime or a custom Ticker, offering a readable API for logging and metrics without thread-safety overhead.
If you need to measure code execution time in Java, the Google Guava library offers a purpose-built solution that abstracts away the complexity of raw nanosecond arithmetic. This article explores how Stopwatch in google/guava delivers accurate benchmarking capabilities while remaining simple to test and integrate into production code.
How Stopwatch Works Under the Hood
The implementation in guava/src/com/google/common/base/Stopwatch.java centers on three core fields: isRunning, elapsedNanos, and startTick. This design tracks accumulated time across multiple start/stop cycles while delegating actual time measurement to a pluggable Ticker abstraction.
Time Source Abstraction with Ticker
By default, Stopwatch uses Ticker.systemTicker(), which wraps System.nanoTime(). However, the constructor accepts any Ticker implementation, enabling deterministic testing and platform-specific customization. The read() method on the ticker returns a long value representing ticks, which Stopwatch converts to meaningful durations via elapsed(TimeUnit) or elapsed() (returning java.time.Duration on non-J2KT platforms).
State Management and Lifecycle
The class maintains strict state transitions through four primary operations:
- Construction –
Stopwatch.createUnstarted()orStopwatch.createStarted()optionally accepts a customTicker - Start –
start()records the current tick and setsisRunning = true - Stop –
stop()computestick - startTick, adds the delta toelapsedNanos, and clears the running flag - Reset –
reset()clears accumulated time and forces the stopwatch into a stopped state
Mutating methods are intentionally not idempotent. Calling start() on an already running stopwatch throws IllegalStateException, preventing accidental measurement corruption.
Thread Safety Design
Stopwatch is deliberately not thread-safe. According to the source code, callers must confine instances to a single thread to avoid synchronization overhead. This design choice keeps the implementation lightweight and suitable for high-frequency logging scenarios where lock contention would degrade performance.
Core API Methods for Timing Operations
Beyond basic start/stop functionality, Stopwatch provides several utilities for converting and displaying elapsed time.
Duration Conversion
The elapsed(TimeUnit) method returns a truncated long value in the requested unit, while elapsed() returns a full-precision Duration object. Both methods calculate total elapsed time by summing elapsedNanos with the current delta if the stopwatch is running.
Human-Readable String Output
The toString() method automatically selects an appropriate time unit via the private chooseUnit() method, formats the value using Platform.formatCompact4Digits, and appends a compact abbreviation (ns, μs, ms, s, min, h, d). This produces readable output like "12.3 ms" without manual formatting code.
Practical Examples for Benchmarking
Basic Code Block Timing
For simple timing scenarios, create a started stopwatch, execute your code, and stop:
Stopwatch sw = Stopwatch.createStarted();
doWork(); // Your method to benchmark
sw.stop();
System.out.println("Elapsed: " + sw); // e.g., "45.2 ms"
Reusing a Stopwatch for Multiple Intervals
You can accumulate timing across multiple operations by starting and stopping without resetting:
Stopwatch sw = Stopwatch.createUnstarted();
sw.start();
taskA();
sw.stop();
System.out.println("Task A: " + sw.elapsed(TimeUnit.MILLISECONDS) + " ms");
sw.start(); // Resumes from previous elapsed time
taskB();
sw.stop();
System.out.println("Total (A+B): " + sw.elapsed(TimeUnit.MILLISECONDS) + " ms");
Testing with FakeTicker
The test suite in guava-tests/test/com/google/common/base/StopwatchTest.java demonstrates deterministic testing using FakeTicker from guava-testlib:
Ticker fake = new FakeTicker();
Stopwatch sw = new Stopwatch(fake);
sw.start();
((FakeTicker) fake).advance(500_000_000L); // Advance 0.5 seconds
sw.stop();
assert sw.elapsed(TimeUnit.MILLISECONDS) == 500;
This approach eliminates flakiness caused by system load or clock granularity during test execution.
Android-Specific Timing
On Android devices, the default ticker may pause during sleep. The source documentation recommends using android.os.SystemClock.elapsedRealtimeNanos():
Stopwatch sw = Stopwatch.createStarted(
new Ticker() {
@Override public long read() {
return android.os.SystemClock.elapsedRealtimeNanos();
}
});
This ensures continuous measurement regardless of device sleep state.
Performance Characteristics and Limitations
While System.nanoTime offers sub-nanosecond resolution, the Stopwatch class adds allocation and method call overhead. The StopwatchBenchmark.java file in guava-tests/benchmark/ measures this overhead across creation, starting, stopping, and resetting operations.
For ultra-high-precision profiling (e.g., method-level hot spot analysis), specialized profilers outperform Stopwatch. However, for application-level logging, metrics collection, and macro-benchmarking, the overhead is negligible compared to the readability and maintainability benefits.
Summary
Stopwatchingoogle/guavawrapsSystem.nanoTimevia aTickerabstraction to provide readable, testable timing utilities.- The class is not thread-safe by design, optimizing for single-threaded performance without synchronization overhead.
- State management tracks
isRunning,elapsedNanos, andstartTickto support multiple start/stop cycles. - Testing is simplified through
FakeTicker, allowing deterministic control over time advancement. - Android requires special handling using
SystemClock.elapsedRealtimeNanos()to handle sleep states correctly. - Use
toString()for automatic human-readable formatting orelapsed(TimeUnit)for programmatic duration access.
Frequently Asked Questions
Is Guava's Stopwatch thread-safe?
No, Stopwatch is explicitly not thread-safe. The implementation in guava/src/com/google/common/base/Stopwatch.java omits synchronization to minimize overhead, requiring callers to confine instances to a single thread or provide external synchronization.
How does Stopwatch handle time unit conversion?
The elapsed(TimeUnit) method converts accumulated nanoseconds to your specified unit, truncating fractional values. On platforms supporting J2KT-incompatible APIs, the parameterless elapsed() method returns a java.time.Duration preserving full precision without truncation.
Can I use Stopwatch for micro-benchmarking?
While Stopwatch works for coarse-grained benchmarking, the allocation overhead and method call costs make it unsuitable for micro-benchmarking individual method invocations. For high-precision profiling, use dedicated tools like JMH, reserving Stopwatch for logging, metrics, and timing blocks of code longer than a few microseconds.
How do I create a deterministic Stopwatch for unit tests?
Inject a FakeTicker from Guava's testlib when constructing the Stopwatch. The FakeTicker.advance(long nanos) method allows you to programmatically control time progression, enabling repeatable test assertions without depending on system clock behavior or Thread.sleep().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →