How to Build an Elegant Java TimeTracker with AutoCloseable and Lambdas
This article explains how to design a lightweight, flexible Java TimeTracker utility that leverages AutoCloseable, try‑with‑resources, functional interfaces and lambda expressions to simplify performance monitoring, support automatic exception handling, and provide extensible features for real‑world backend development.
Introduction
During development, performance monitoring and debugging are frequent challenges. While many mature tools exist, sometimes a lightweight, flexible, and elegant solution is needed.
Manually recording start and end times with startTime creates repetitive code. This article introduces a concise TimeTracker utility that satisfies basic performance tracking needs and supports functional interfaces and try‑with‑resources usage.
Main Content
Initial Pain Point
Typical time‑tracking code repeats the same pattern:
long start = System.currentTimeMillis();
try {
// business logic
} finally {
// calculate elapsed time
}Repeating this boilerplate leads to verbose and error‑prone code.
Evolving with try‑with‑resources
By implementing AutoCloseable, the utility can be used in a try block, automatically handling start and end timestamps:
try (TimeTracker ignored = new TimeTracker("Database operation")) {
// business code, timing handled automatically!
}This makes the code much cleaner.
Pro: Functional Interface
Using a functional interface allows one‑line tracking:
TimeTracker.track("User query", () -> {
return userService.findById(123);
});Even without a try block, the execution time is recorded.
For methods that return a value:
String result = TimeTracker.track("Simple task", () -> {
Thread.sleep(1000);
return "Done";
});Pro Max: Exception Handling
The basic .track() method wraps any exception in a RuntimeException. For scenarios requiring explicit exception handling, trackThrows propagates the original exception:
public static <T> T trackThrows(String operationName, ThrowableSupplier<T> execution) throws Exception {
try (TimeTracker ignored = new TimeTracker(operationName, true)) {
return execution.get();
}
}Example usage:
try {
TimeTracker.trackThrows("Complex query", () -> complexQuery());
} catch (SQLException e) {
// precise handling
}Full Implementation
The complete TimeTracker class includes Javadoc, support for both return‑value and void operations, two exception handling modes, and automatic resource management:
/**
* Performance tracking utility that measures execution time and provides flexible exception handling.
*
* Features:
* • Precise time measurement
* • Support for methods with and without return values
* • Two exception handling strategies
* • Automatic resource management via AutoCloseable
*/
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> T track(String operationName, ThrowableSupplier<T> execution) {
try {
return trackThrows(operationName, execution);
} catch (Exception e) {
throw new RuntimeException("Execution failed: " + operationName, e);
}
}
public static <T> T trackThrows(String operationName, ThrowableSupplier<T> 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 elapsedMs = (System.nanoTime() - startTime) / 1_000_000;
System.out.printf("%s completed, elapsed: %d ms%n", operationName, elapsedMs);
}
}
@FunctionalInterface
public interface ThrowableSupplier<T> {
T get() throws Exception;
}
@FunctionalInterface
public interface ThrowableRunnable {
void run() throws Exception;
}
}Demo Usage
A supplementary demo class shows various usage patterns, including simple tasks, exception‑throwing calls, nested tracking, and multiple resource management:
public class TimeTrackerDemo {
public void demonstrateUsage() {
// 1. Simple task without checked exceptions
TimeTracker.track("Simple task", () -> {
Thread.sleep(1000);
return "Done";
});
// 2. Task that may throw a checked exception
try {
TimeTracker.trackThrows("Potentially failing task", () -> {
if (Math.random() < 0.5) {
throw new IOException("Simulated IO error");
}
return "Success";
});
} catch (Exception e) {
e.printStackTrace();
}
// 3. Nested tracking example
try {
TimeTracker.trackThrows("Complex flow", () -> {
TimeTracker.track("Subtask 1", () -> {
Thread.sleep(500);
return null;
});
return TimeTracker.trackThrows("Subtask 2", () -> {
Thread.sleep(500);
return "All done";
});
});
} catch (Exception e) {
e.printStackTrace();
}
// 4. try‑with‑resources demonstration
try (TimeTracker tracker = TimeTracker.of("Resource management demo")) {
performResourceIntensiveTask();
} catch (Exception e) {
e.printStackTrace();
}
}
private void performResourceIntensiveTask() throws InterruptedException {
Thread.sleep(1000);
System.out.println("Resource‑intensive task completed");
}
}Improvement Suggestions
Integrate a logging framework such as SLF4J for flexible output.
Add additional statistics (max, min, average).
Collect performance metrics for monitoring dashboards.
Support asynchronous operations.
Conclusion
Designing utility classes requires balancing practicality and ease of use. The TimeTracker demonstrates how Java features like AutoCloseable and lambda expressions can reduce boilerplate, while thoughtful exception handling ensures robustness in real‑world applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
