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:

  1. ConstructionStopwatch.createUnstarted() or Stopwatch.createStarted() optionally accepts a custom Ticker
  2. Startstart() records the current tick and sets isRunning = true
  3. Stopstop() computes tick - startTick, adds the delta to elapsedNanos, and clears the running flag
  4. Resetreset() 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

  • Stopwatch in google/guava wraps System.nanoTime via a Ticker abstraction 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, and startTick to 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 or elapsed(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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →