🕒 Reading time: 7 minutes
TL;DR
System.currentTimeMillis()
— Quick and simple for basic timing (milliseconds)
long startTime = System.currentTimeMillis();
//your code goes here
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: " + (endTime - startTime) + " ms");
System.nanoTime()
— Higher precision, ideal for short-duration tasks (nanoseconds)
long startTime = System.nanoTime();
//your code goes here
long endTime = System.nanoTime();
System.out.println("Elapsed time: " + (endTime - startTime) / 1_000_000 + " ms");
Instant
andDuration
(Java 8+) — Readable and clear timing with object-based API (milliseconds)
Instant start = Instant.now();
//your code goes here
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("Elapsed time: " + duration.toMillis() + " ms");
Sample Code to Measure
We’ll measure the time taken by this sample method, sumArray
, to sum elements in a large array:
public class TimingExample {
public static long sumArray(int[] numbers) {
long sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
}
1. Using System.currentTimeMillis()
This basic method returns the current time in milliseconds since the Unix epoch. Simple but limited.
long startTime = System.currentTimeMillis();
TimingExample.sumArray(new int[1000000]);
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: " + (endTime - startTime) + " ms");
When to Use It
- Quick checks for longer-running processes
- Useful for basic timing in non-critical areas
When Not to Use It
- Avoid for short-duration or high-precision needs
- Susceptible to system clock adjustments
Complexity: Low
2. Using System.nanoTime()
With nanosecond precision, System.nanoTime()
is ideal for timing intervals accurately, unaffected by system time changes.
long startTime = System.nanoTime();
TimingExample.sumArray(new int[1000000]);
long endTime = System.nanoTime();
System.out.println("Elapsed time: " + (endTime - startTime) / 1_000_000 + " ms");
When to Use It
- High-precision timing for short-duration events
- Reliable for benchmarking intervals
When Not to Use It
- Avoid for real-world time (clock time)
- Slight hardware dependence may cause variation
Complexity: Low
3. Instant
and Duration
(Java 8+)
The Instant
and Duration
classes offer readable, object-based timing methods.
Instant start = Instant.now();
TimingExample.sumArray(new int[1000000]);
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("Elapsed time: " + duration.toMillis() + " ms");
When to Use It
- Ideal for Java 8+ projects focusing on readability
- Great for general-purpose timing needs
When Not to Use It
- Avoid if very high precision is critical
- Minor performance overhead due to object creation
Complexity: Medium
4. Timing with Stopwatch
from Guava
Guava’s Stopwatch
provides a structured way to time multiple code blocks.
Dependency:
<!-- Maven -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
<!-- Gradle -->
implementation 'com.google.guava:guava:31.0.1-jre'
import com.google.common.base.Stopwatch;
Stopwatch stopwatch = Stopwatch.createStarted();
TimingExample.sumArray(new int[1000000]);
stopwatch.stop();
System.out.println("Elapsed time: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms");
When to Use It
- Suitable for projects that already use Guava
- Allows multiple timing intervals easily
When Not to Use It
- Avoid if Guava isn’t already in your project
- Adds unnecessary complexity for simple timing needs
Complexity: Medium
5. Precision Benchmarking with JMH
For advanced benchmarking, JMH (Java Microbenchmark Harness) minimizes JVM-related timing distortions.
Dependency:
<!-- Maven -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.36</version>
</dependency>
<!-- Gradle -->
implementation 'org.openjdk.jmh:jmh-core:1.36'
Setting Up the Benchmark
To benchmark sumArray
, set up a JMH test class as follows:
import org.openjdk.jmh.annotations.*;
@State(Scope.Thread)
public class MyBenchmark {
private int[] numbers;
@Setup
public void setUp() {
numbers = new int[1000000];
}
@Benchmark
@Warmup(iterations = 5, time = 1) // 5 warm-up iterations
@Measurement(iterations = 10, time = 1) // 10 measured iterations
@Fork(1) // Run in one JVM fork
@BenchmarkMode(Mode.AverageTime) // Average time per operation
public long benchmarkSumArray() {
return TimingExample.sumArray(numbers);
}
}
Breakdown of Benchmark Setup
- Warm-up: Stabilizes JIT optimizations.
- Measurement: Sets up the actual benchmarking phase.
- Fork: Isolates tests in a separate JVM.
- BenchmarkMode: Measures average execution time per method call.
Example Output
After running the benchmark, the output might look like this:
Benchmark Mode Cnt Score Error Units
MyBenchmark.benchmarkSumArray avgt 10 0.123 ± 0.005 ms/op
Complexity: High
I’ll write a separate article to cover JMH in more detail, including advanced configurations and benchmarking best practices.
Choosing the Right Tool
Use Case | Best Tool | Precision | Complexity |
---|---|---|---|
Quick, informal timing | System.currentTimeMillis() | Milliseconds | Low |
Short-duration events | System.nanoTime() | Nanoseconds | Low |
Readable timing for Java 8+ projects | Instant and Duration | Milliseconds | Medium |
Multiple, structured timing needs | Stopwatch (Guava) | Milliseconds | Medium |
High-precision benchmarking | JMH | Nanoseconds | High |
Final Thoughts
Your choice of timing tool depends on your specific needs. currentTimeMillis()
and nanoTime()
work well for quick checks, while Instant
and Duration
add readability. For advanced benchmarking, JMH is the industry standard.
Timing isn’t just about measuring milliseconds; it’s about creating a responsive and optimized application. Happy coding!