/ TIMER, STOPWATCH

JVM timing options

For as long as I have been coding in Java, we have had requirements to measure the execution time of blocks of code. While the current good practice is to use OpenTelemetry’s traces, not every company has reached this stage yet. Plus, some of the alternatives are OpenTelemetry-compatible. Let’s see them in order.

The basic option

The basic option is what we have been doing for ages, and what the other options rely on anyway. It’s based on the following API: System.currentTimeMillis().

Usage is pretty straightforward:

long start = System.currentTimeMillis();
// Execute code
long end =  System.currentTimeMillis();
System.out.println("Code executed in " + (end - start) + " milliseconds");

The Object-Oriented alternative

If you are an OOP developer, then you’d rather encapsulate state. Here’s a draft of such encapsulation:

class Timer {
    private long startTime;

    public void start() {
        startTime = System.currentTimeMillis();
    }

    public long stop() {
        return System.currentTimeMillis() - startTime();
    }
}

You can add a reset() method to reuse the object, a suspend() method to pause the timing, or decide to have a separate get() method on top of stop(). Nothing changes the overall design. Both [Guava](https://guava.dev/releases/19.0/api/docs/com/google/common/base/Stopwatch.html) and Apache Commons Lang provide a Stopwatch class, with minor variations. If either of them is on the classpath, don’t reinvent the wheel and use it.

The functional alternative

A functional approach is also possible if that’s what you prefer. Let’s start by timing a method that accepts a parameter and returns a value, in other words, a java.util.function.Function. We can wrap it in a timing method that accepts the said function, and a Consumer<Long>.

public class TimeUtils {
    public static <I,O> O time(I input, Function<I,O> function, Consumer<Long> time) {
        long start = System.currentTimeMillis();                     (1)
        O result = function.apply(input);                            (2)
        time.accept(System.currentTimeMillis() - start);             (3)
        return result;
    }
}
1 Start the timer
2 Call the method
3 Compute the time and wrap it in a consumer

We can use it like this:

var result = TimeUtils.time(
    0,                                                               (1)
    value -> value + 1,                                              (2)
    time -> System.out.println("Time: " + time  + " ms"));           (3)
System.out.println("value: " + result);
1 Input value
2 Inline function
3 Log the elapsed time

We must add more code to cater to other method signatures.

public class TimeUtils {

    public static <T> T time(Supplier<T> supplier, Consumer<Long> time) {      (1)
        long start = System.currentTimeMillis();
        T result = supplier.get();
        time.accept(System.currentTimeMillis() - start);
        return result;
    }

    public static <T> T time(T t, Consumer<T> consumer, Consumer<Long> time) { (2)
        long start = System.currentTimeMillis();
        consumer.accept(t);
        time.accept(System.currentTimeMillis() - start);
    }
}
1 Wraps a Supplier
2 Wraps a Consumer

You can do the same with Runnable and BiFunction. If your methods require more than two parameters, you’ll need to model more functional interfaces, e.g., Function3, Function4, etc.

Micrometer, a "vendor-neutral observability facade" offers this approach.

The annotations alternative

The last alternative to time code is annotation. I have written an exhaustive post on annotations and annotation processor. In short, you create two components:

  • An annotation, e.g., @Timed
  • An annotation processor, which scans methods and filters those annotated with @Timed at either compile-time or runtime

In the first case, we rely on bytecode manipulation to weave the OOP or functional alternatives above around the original method. In the second one, we would need to intercept the instantiation of classes having such annotated methods, and wrap them in a java.lang.reflect.Proxy. In both cases, you generate additional code to:

  1. Start a timer
  2. Call the annotated method
  3. Stop the timer
  4. Extract the time
  5. Do something with the time, e.g., log execution time

The code is more complex than the two previous alternatives, so I won’t develop it further.

AspectJ, an Aspect-Oriented Programming framework, provides the overall engine. You’ll only need to develop the annotation and the wrapping code.

Alternatively, Micrometer provides a @Timed annotation.

Conclusion

In this post, I described three alternatives to measure the elapsed time of code execution: object-oriented, functional, and annotations. Depending on your context, you may want to use one or the other. In any case, I suggest you don’t roll out your own, but use one of the available libraries/frameworks.

Nicolas Fränkel

Nicolas Fränkel is a technologist focusing on cloud-native technologies, DevOps, CI/CD pipelines, and system observability. His focus revolves around creating technical content, delivering talks, and engaging with developer communities to promote the adoption of modern software practices. With a strong background in software, he has worked extensively with the JVM, applying his expertise across various industries. In addition to his technical work, he is the author of several books and regularly shares insights through his blog and open-source contributions.

Read More
JVM timing options
Share this