sodcalculator

08 Everyday Math

How to Calculate Running Time in Java

Guide 08-3213 · Updated · Step by step

This guide shows you how to measure how long a piece of Java code takes to execute, using the built-in System.nanoTime() and System.currentTimeMillis() methods. You will learn the difference between wall-clock time and CPU time, how to write a simple timing block, and how to avoid the classic pitfalls that make benchmarks misleading. All you need is a working Java development environment (JDK 8 or later) and a text editor or IDE.

What You’ll Need

  • Java Development Kit (JDK) installed (version 8 or newer is fine)
  • A text editor or an IDE like IntelliJ IDEA, Eclipse, or VS Code
  • Basic familiarity with writing and running a Java class with a main method

Step-by-Step

  1. Create a new Java class. Open your editor, create a file named TimingDemo.java, and add a main method. This will be the container for your timing code.
  2. Import the time-related package (optional). You do not need an import for System.nanoTime() because it is in java.lang, which is automatically available. If you plan to format the elapsed time using java.time.Duration, add import java.time.Duration; at the top of the file.
  3. Record the start time. Immediately before the code you want to measure, call long start = System.nanoTime();. This captures the current value of the JVM’s high-resolution time source in nanoseconds.
  4. Run the code you want to measure. Place the operation—whether it is a loop, a method call, a database query, or a file read—between the start and end timestamps. Do not include any setup or warm-up work inside this block.
  5. Record the end time. Immediately after the code finishes, call long end = System.nanoTime();.
  6. Compute the elapsed time. Subtract the start from the end: long elapsedNanos = end - start;. This gives you the duration in nanoseconds.
  7. Convert to a readable unit. Divide by 1,000,000 to get milliseconds, or by 1,000,000,000 to get seconds. For example: double elapsedMillis = elapsedNanos / 1_000_000.0;. Use a double to preserve fractional values.
  8. Print the result. Output the elapsed time to the console using something like System.out.println("Elapsed time: " + elapsedMillis + " ms");.
  9. Run the program. Compile with javac TimingDemo.java and run with java TimingDemo in your terminal, or simply press the run button in your IDE. Observe the printed time.
  10. Repeat for stability (advanced but recommended). Run the same measured block several times in a loop, discard the first few results (warm-up), and take the average or median of the remaining runs. This reduces noise from JIT compilation and garbage collection.

Common Mistakes

  • Using currentTimeMillis() for very short operations. This method has millisecond resolution and can return the same value for two consecutive calls if the operation takes less than a millisecond, giving you a result of 0. Use nanoTime() for anything under a few milliseconds.
  • Measuring only one run. A single measurement is heavily influenced by JVM warm-up, background processes, and CPU frequency scaling. One run can be 10x slower or faster than the typical performance.
  • Including input/output or setup in the timed block. If you read a file, open a connection, or allocate large structures inside the timed region, you are measuring that overhead too, not just the algorithm.
  • Assuming nanoTime() is wall-clock time. It is not. It measures a monotonic counter that is only useful for differences. It does not correspond to the time of day and can be negative relative to other timestamps.
  • Printing inside the timed block. Console output is extremely slow (milliseconds per line). If you print inside the loop you are timing, you are measuring the printing, not the loop logic.

Tips

  • Use a warm-up phase. Run the code you are measuring a few hundred times before you start timing. This lets the JIT compiler optimize the hot code paths, giving you numbers that reflect steady-state performance.
  • Take multiple measurements. Run the timed block, say, 10 times and record each result. Then compute the average and the standard deviation. If the standard deviation is large relative to the average, your measurement is noisy.
  • Use java.time.Duration for formatting. You can convert nanoseconds to a human-readable string like Duration.ofNanos(elapsedNanos).toMillis() or toSeconds() to avoid manual division errors.
  • Consider using a microbenchmark library. For serious performance work, tools like JMH (Java Microbenchmark Harness) handle warm-up, fork counts, and statistics automatically. For quick checks, manual timing is fine.
  • Isolate the code. If you are comparing two algorithms, run them in separate JVM invocations or at least in separate warm-up cycles, because the first algorithm can affect the state of the second (caches, GC, etc.).

Troubleshooting

  • The elapsed time is always 0. This happens when you use currentTimeMillis() on a very fast operation. Switch to nanoTime(). If it still shows 0, the operation is genuinely sub-microsecond; consider timing a loop of many repetitions and dividing by the count.
  • The time varies wildly between runs. This is normal due to garbage collection, background threads, and CPU frequency scaling. Run more iterations, take the median, and close other applications. If the variance persists, your code may be allocating a lot of objects; check for accidental new in the timed block.
  • The first run is much slower than subsequent runs. That is the JIT compiler warming up. Discard the first few measurements and only report the later ones, or add an explicit warm-up loop before timing begins.
  • Negative elapsed time. This can occur if you subtract two nanoTime() values from different JVM invocations, or if you accidentally reset the start variable inside a loop. Ensure start is captured once before the block and end once after.
  • The program reports time but you suspect it is wrong. Add a known slow operation, like Thread.sleep(100), inside the timed block. If it reports roughly 100 ms, your timing code is correct. If not, check your unit conversion.

Frequently Asked Questions

Q: What is the difference between nanoTime() and currentTimeMillis()?
A: nanoTime() measures a monotonic counter with nanosecond precision (though actual resolution depends on the system) and is intended for measuring elapsed time. currentTimeMillis() returns wall-clock time in milliseconds and is meant for timestamps, not for precise duration measurement.

Q: Can I measure time in seconds directly?
A: Yes. Capture long start = System.nanoTime();, run your code, then compute double seconds = (System.nanoTime() - start) / 1_000_000_000.0;. Use a double to avoid integer truncation.

Q: Is nanoTime() accurate to the nanosecond?
A: Not necessarily. The precision depends on the operating system and hardware. On many systems the actual resolution is tens or hundreds of nanoseconds. It is still far more precise than currentTimeMillis() for short durations.

Q: Should I always use a benchmarking library like JMH?
A: For quick, informal checks, manual timing is fine. For published results, regression testing, or comparing micro-optimizations, JMH is strongly recommended because it handles JVM warm-up, dead-code elimination, and statistical analysis correctly.

Q: How do I avoid the JIT compiler skewing my results?
A: Run the code you are measuring multiple times before starting the timer. A common pattern is to call the method 10,000 times as warm-up, then time the next 10,000 calls and divide by 10,000 to get per-call time.

Q: Can I measure CPU time instead of wall-clock time?
A: Yes, but not with standard Java SE. You would need to use ThreadMXBean from java.lang.management and call getCurrentThreadCpuTime(), which returns nanoseconds of CPU time consumed by the current thread. This excludes time spent waiting or blocked.

Related

General steps for current mainstream versions; menus can differ by device and version. See the disclaimer or report an error.

More in this bed

The whole Everyday Math bed →