Backend Development 14 min read

Design and Implementation of a Lightweight TimeTracker Utility for Java

This article introduces a concise and elegant Java TimeTracker utility class that leverages AutoCloseable, try‑with‑resources, and functional interfaces to provide flexible performance monitoring, automatic duration calculation, and configurable exception handling while reducing repetitive timing code.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Design and Implementation of a Lightweight TimeTracker Utility for Java

Introduction

Performance monitoring and debugging are common challenges during development. Existing tools can be heavyweight, so a lightweight, flexible, and elegant solution is often needed. Manually recording start and end times leads to repetitive code.

This article presents the design and implementation of a simple yet elegant TimeTracker utility class that satisfies basic performance tracking needs and supports functional interfaces, try‑with‑resources, and multiple invocation mechanisms.

Initial Pain Point

Typical timing code looks like this:

long start = System.currentTimeMillis();
try {
    // business logic
} finally {
    // calculate elapsed time
}

Repeating such boilerplate is verbose and error‑prone.

Evolution: Embracing try‑with‑resources

By implementing AutoCloseable , the timing logic can be wrapped in a try‑with‑resources block:

try (TimeTracker ignored = new TimeTracker("Database operation")) {
    // business code, timing handled automatically!
}

This makes the code cleaner and ensures automatic resource management.

The TimeTracker class implements AutoCloseable and records the start time in its constructor; the close() method computes and logs the elapsed time.

Pro: Functional Interface

To reduce boilerplate further, a functional interface can be used:

TimeTracker.track("User query", () -> {
    return userService.findById(123);
});

Even a single‑line lambda works:

TimeTracker.track("Operation", () -> riskyMethod());

For methods with return values:

String result = TimeTracker.track("Simple task", () -> {
    Thread.sleep(1000);
    return "Done";
});

Pro Max: Exception Handling

The initial .track() method swallows exceptions and rethrows them as RuntimeException :

public static <T> T track(String operationName, ThrowableSupplier<T> execution) {
    try {
        return trackThrows(operationName, execution);
    } catch (Exception e) {
        throw new RuntimeException("Execution failed: " + operationName, e);
    }
}

For scenarios requiring explicit exception handling, a trackThrows variant is provided:

try {
    TimeTracker.trackThrows("Operation", () -> {
        return riskyMethod(); // preserve original exception
    });
} catch (SpecificException e) {
    // precise handling
}

Full Source Code

The complete TimeTracker class includes Javadoc, static factory methods, overloads for returning and non‑returning lambdas, and inner functional interfaces ThrowableSupplier and ThrowableRunnable :

/**
 * Performance tracking utility class for measuring code execution time with flexible exception handling.
 *
 *
Key features:
*
*
Accurate timing
*
Support for methods with and without return values
*
Two exception handling modes
*
Automatic resource management
*
*
 * @author [Your Name]
 * @version 1.0
 */
public class TimeTracker implements AutoCloseable {
    private final String operationName;
    private final long startTime;
    private final boolean logEnabled;

    public TimeTracker(String operationName) {
        this(operationName, true);
    }

    private TimeTracker(String operationName, boolean logEnabled) {
        this.operationName = operationName;
        this.startTime = System.nanoTime();
        this.logEnabled = logEnabled;
        if (logEnabled) {
            System.out.printf("Start: %s%n", operationName);
        }
    }

    public static TimeTracker of(String operationName) {
        return new TimeTracker(operationName);
    }

    public static
T track(String operationName, ThrowableSupplier
execution) {
        try {
            return trackThrows(operationName, execution);
        } catch (Exception e) {
            throw new RuntimeException("Execution failed: " + operationName, e);
        }
    }

    public static
T trackThrows(String operationName, ThrowableSupplier
execution) throws Exception {
        try (TimeTracker ignored = new TimeTracker(operationName, true)) {
            return execution.get();
        }
    }

    public static void track(String operationName, ThrowableRunnable execution) {
        try {
            trackThrows(operationName, execution);
        } catch (Exception e) {
            throw new RuntimeException("Execution failed: " + operationName, e);
        }
    }

    public static void trackThrows(String operationName, ThrowableRunnable execution) throws Exception {
        try (TimeTracker ignored = new TimeTracker(operationName, true)) {
            execution.run();
        }
    }

    @Override
    public void close() {
        if (logEnabled) {
            long timeElapsed = (System.nanoTime() - startTime) / 1_000_000;
            System.out.printf("%s completed, elapsed: %d ms%n", operationName, timeElapsed);
        }
    }

    @FunctionalInterface
    public interface ThrowableSupplier
{
        T get() throws Exception;
    }

    @FunctionalInterface
    public interface ThrowableRunnable {
        void run() throws Exception;
    }
}

Demo Usage

A demonstration class shows various usage patterns, including simple tasks, exception‑throwing tasks, nested tracking, and multi‑resource try‑with‑resources:

import java.io.IOException;

public class TimeTrackerDemo {
    public void demonstrateUsage() {
        // 1. Non‑checked‑exception version
        TimeTracker.track("Simple task", () -> {
            Thread.sleep(1000);
            return "Done";
        });

        // 2. Version that may throw checked exception
        try {
            TimeTracker.trackThrows("Potentially failing task", () -> {
                if (Math.random() < 0.5) {
                    throw new IOException("Simulated IO exception");
                }
                return "Success";
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 3. Nested usage example
        try {
            TimeTracker.trackThrows("Complex flow", () -> {
                TimeTracker.track("Subtask 1", () -> {
                    Thread.sleep(500);
                });
                return TimeTracker.trackThrows("Subtask 2", () -> {
                    Thread.sleep(500);
                    return "All done";
                });
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 4. try‑with‑resources example
        try (TimeTracker tracker = TimeTracker.of("Resource demo")) {
            performResourceIntensiveTask();
        }

        // 5. Multiple resources
        try (TimeTracker t1 = TimeTracker.of("Stage 1");
             TimeTracker t2 = TimeTracker.of("Stage 2");
             CustomResource resource = acquireResource()) {
            processResourcesSequentially(resource);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 6. Ignored return value example
        try (TimeTracker ignored = TimeTracker.of("Background task")) {
            performBackgroundTask();
        }
    }

    private void performResourceIntensiveTask() throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("Resource‑intensive task completed");
    }

    private CustomResource acquireResource() {
        return new CustomResource();
    }

    private void processResourcesSequentially(CustomResource resource) {
        resource.process();
    }

    private void performBackgroundTask() {
        System.out.println("Executing background task");
    }

    private static class CustomResource implements AutoCloseable {
        void process() {
            System.out.println("Processing resource");
        }
        @Override
        public void close() {
            System.out.println("Resource closed");
        }
    }
}

Improvement Suggestions

Integrate a logging framework such as SLF4J for flexible output.

Add statistical dimensions (max, min, average) for timing data.

Collect additional performance metrics for monitoring.

Support asynchronous operations.

Conclusion

While the utility class itself is not groundbreaking, it solves a practical problem by reducing repetitive timing code, handling exceptions gracefully, and leveraging modern Java language features to keep code clean and maintainable.

Good tool design balances practicality and ease of use; the TimeTracker exemplifies this principle.

JavaException Handlinglambdaperformance monitoringAutoCloseabletry-with-resourcesUtility Class
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.