Every Spring Boot developer knows this moment. You need a simple gauge metric. You open the Micrometer docs. You write ten lines of code. You inject MeterRegistry into another class. Then you repeat it for the next metric. Meanwhile @Timed just works. Why can't everything else? @Timed I built Metrify to fix this. Metrify The Problem Micrometer is great. But the moment you need anything beyond @Timed , you are back to writing boilerplate. @Timed Want a gauge that tracks a method's return value? Here is what you probably write today: @Service public class OrderService { private final AtomicInteger activeOrders = new AtomicInteger(0); public OrderService(MeterRegistry registry) { Gauge.builder("orders.active", activeOrders, AtomicInteger::get) .description("Number of active orders") .register(registry); } public int getActiveOrderCount() { return activeOrders.get(); } } @Service public class OrderService { private final AtomicInteger activeOrders = new AtomicInteger(0); public OrderService(MeterRegistry registry) { Gauge.builder("orders.active", activeOrders, AtomicInteger::get) .description("Number of active orders") .register(registry); } public int getActiveOrderCount() { return activeOrders.get(); } } Here is what you should write instead: @Service public class OrderService { @MetricGauge(name = "orders.active", description = "Number of active orders") public int getActiveOrderCount() { return activeOrders.get(); } } @Service public class OrderService { @MetricGauge(name = "orders.active", description = "Number of active orders") public int getActiveOrderCount() { return activeOrders.get(); } } One annotation. Zero boilerplate. Why Doesn’t Micrometer Have this Functionality Already? The Micrometer team made a conscious decision. In GitHub issue #451, the community asked for Dropwizard-style annotations. The answer was: Micrometer is a low-level facade. Not an annotation framework. Fair. But that left a gap nobody filled for modern Spring Boot 3.x. The old metrics-spring library had @Gauge and more. But it was built on Dropwizard Metrics and has not been updated since 2016. metrics-spring @Gauge Metrify fills that gap. What Metrify Does Add one dependency. All annotations work. No manual bean registration. No TimedAspect setup. No MeterRegistry everywhere. TimedAspect MeterRegistry @MetricGauge @MetricGauge @MetricGauge(name = "queue.depth") public int getQueueDepth() { return processingQueue.size(); } @MetricGauge(name = "queue.depth") public int getQueueDepth() { return processingQueue.size(); } For fields, you can annotate AtomicInteger or AtomicLong directly: AtomicInteger AtomicLong @MetricGauge(name = "connections.active") private final AtomicInteger activeConnections = new AtomicInteger(0); @MetricGauge(name = "connections.active") private final AtomicInteger activeConnections = new AtomicInteger(0); @MetricCounter @MetricCounter @MetricCounter(name = "orders.created", tags = {"channel", "web"}) public Order createOrder(OrderRequest request) { ... } @MetricCounter(name = "orders.created", tags = {"channel", "web"}) public Order createOrder(OrderRequest request) { ... } Dynamic Tags via SpEL Dynamic Tags via SpEL @MetricCounter( name = "orders.processed", dynamicTags = { @MetricTag(key = "region", expression = "#order.region"), @MetricTag(key = "tier", expression = "#order.customer.tier") } ) public void processOrder(Order order) { ... } @MetricCounter( name = "orders.processed", dynamicTags = { @MetricTag(key = "region", expression = "#order.region"), @MetricTag(key = "tier", expression = "#order.customer.tier") } ) public void processOrder(Order order) { ... } @CachedGauge @CachedGauge For expensive calls, you do not want to run on every Prometheus scrape: @CachedGauge(name = "db.connections", ttl = 30, ttlUnit = TimeUnit.SECONDS) public int getDatabaseConnectionCount() { return dataSource.getActiveConnections(); } @CachedGauge(name = "db.connections", ttl = 30, ttlUnit = TimeUnit.SECONDS) public int getDatabaseConnectionCount() { return dataSource.getActiveConnections(); } Startup Tag Validation Startup Tag Validation Micrometer silently fails when you register the same metric name with different tag keys in different places. Metrify catches this at startup: ERROR: Metric 'orders.processed' has inconsistent tag keys. OrderService.processOrder() → [region, tier] LegacyService.handleOrder() → [region] ERROR: Metric 'orders.processed' has inconsistent tag keys. OrderService.processOrder() → [region, tier] LegacyService.handleOrder() → [region] Reactive Support Reactive Support Mono and Flux are handled natively. The counter increments when the stream completes. Not when the method is called. Mono Flux Getting Started <dependency> <groupId>io.github.wtk-ns</groupId> <artifactId>metrify-spring-boot-starter</artifactId> <version>0.1.0</version> </dependency> <dependency> <groupId>io.github.wtk-ns</groupId> <artifactId>metrify-spring-boot-starter</artifactId> <version>0.1.0</version> </dependency> No @Enable annotation. No config class. It just works. @Enable One Honest Caveat Method-level @MetricGauge caches the last returned value. It does not give Prometheus a live view between method calls. @MetricGauge If you need a true real-time gauge, use a field annotation instead: @MetricGauge(name = "connections.active") private final AtomicInteger connectionCount = new AtomicInteger(0); @MetricGauge(name = "connections.active") private final AtomicInteger connectionCount = new AtomicInteger(0); This is a live reference. Always reflects the current value. Try It! GitHub: https://github.com/wtk-ns/metrify-spring-boot-starter Try It! Try It! GitHub: https://github.com/wtk-ns/metrify-spring-boot-starter GitHub: https://github.com/wtk-ns/metrify-spring-boot-starter GitHub If you have ever copy-pasted the same Gauge.builder() block from one service to another. This is for you. Gauge.builder()