Modern, JVM-based framework for building modular, easily testable microservice and serverless applications with compile-time DI and fast startup.
—
Micronaut provides built-in management endpoints for monitoring, health checks, metrics, and application information, following Spring Boot Actuator patterns.
Built-in and custom health indicators for monitoring application health.
/**
* Custom health indicator
*/
@Singleton
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Publisher<HealthResult> getResult() {
return Single.fromCallable(() -> {
try (Connection connection = dataSource.getConnection()) {
return HealthResult.builder("database")
.status(HealthStatus.UP)
.details("connection", "available")
.build();
} catch (SQLException e) {
return HealthResult.builder("database")
.status(HealthStatus.DOWN)
.exception(e)
.build();
}
});
}
}Create custom endpoints for application management and monitoring.
/**
* Custom management endpoint
*/
@Endpoint(id = "custom", defaultEnabled = true)
public class CustomEndpoint {
@Read
public Map<String, Object> status() {
return Map.of(
"status", "healthy",
"timestamp", Instant.now(),
"version", "1.0.0"
);
}
@Write
public String refresh(@Selector String component) {
// Perform refresh operation
return "Refreshed component: " + component;
}
@Delete
public String reset(@Selector String component) {
// Perform reset operation
return "Reset component: " + component;
}
}Collect and expose application metrics using Micrometer.
/**
* Custom metrics
*/
@Singleton
public class OrderMetrics {
private final Counter orderCounter;
private final Timer orderProcessingTimer;
private final Gauge activeOrdersGauge;
public OrderMetrics(MeterRegistry meterRegistry) {
this.orderCounter = Counter.builder("orders.created")
.description("Total orders created")
.register(meterRegistry);
this.orderProcessingTimer = Timer.builder("orders.processing.time")
.description("Order processing time")
.register(meterRegistry);
this.activeOrdersGauge = Gauge.builder("orders.active")
.description("Active orders count")
.register(meterRegistry, this, OrderMetrics::getActiveOrdersCount);
}
public void recordOrderCreated() {
orderCounter.increment();
}
public void recordProcessingTime(Duration duration) {
orderProcessingTimer.record(duration);
}
private double getActiveOrdersCount() {
// Return current active orders count
return activeOrders.size();
}
}Provide application information through the info endpoint.
/**
* Custom application info contributor
*/
@Singleton
public class CustomInfoContributor implements InfoContributor {
@Override
public Publisher<Map<String, Object>> getInfo() {
return Single.fromCallable(() -> Map.of(
"app", Map.of(
"name", "My Application",
"description", "Sample Micronaut application",
"version", "1.0.0"
),
"build", Map.of(
"timestamp", "2023-01-01T00:00:00Z",
"version", "1.0.0-SNAPSHOT"
)
));
}
}Access environment and configuration information.
/**
* Environment endpoint for configuration details
*/
@Endpoint(id = "env", defaultEnabled = false)
public class EnvironmentEndpoint {
private final Environment environment;
public EnvironmentEndpoint(Environment environment) {
this.environment = environment;
}
@Read
public Map<String, Object> getEnvironment() {
return Map.of(
"activeNames", environment.getActiveNames(),
"packages", environment.getPackages(),
"properties", getProperties()
);
}
@Read
public Map<String, Object> getProperty(@Selector String name) {
return environment.getProperty(name, Object.class)
.map(value -> Map.of("property", name, "value", value))
.orElse(Map.of("property", name, "value", null));
}
private Map<String, Object> getProperties() {
// Return filtered properties (exclude sensitive data)
return environment.getPropertySources().stream()
.flatMap(ps -> ps.asMap().entrySet().stream())
.filter(entry -> !isSensitive(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}Monitor application threads and their states.
/**
* Thread dump endpoint
*/
@Endpoint(id = "threaddump", defaultEnabled = true)
public class ThreadDumpEndpoint {
@Read
public ThreadDumpInfo getThreadDump() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true);
return ThreadDumpInfo.builder()
.threads(Arrays.stream(threadInfos)
.map(this::mapThreadInfo)
.collect(Collectors.toList()))
.build();
}
private ThreadDetail mapThreadInfo(ThreadInfo threadInfo) {
return ThreadDetail.builder()
.threadName(threadInfo.getThreadName())
.threadId(threadInfo.getThreadId())
.threadState(threadInfo.getThreadState().name())
.blockedTime(threadInfo.getBlockedTime())
.waitedTime(threadInfo.getWaitedTime())
.stackTrace(Arrays.stream(threadInfo.getStackTrace())
.map(StackTraceElement::toString)
.collect(Collectors.toList()))
.build();
}
}// Management annotations
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Endpoint {
String id();
boolean defaultEnabled() default true;
boolean defaultSensitive() default true;
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Read {
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Write {
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Delete {
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Selector {
}
// Health check interfaces
public interface HealthIndicator {
Publisher<HealthResult> getResult();
}
public final class HealthResult {
public static Builder builder(String name);
public String getName();
public HealthStatus getStatus();
public Map<String, Object> getDetails();
public static class Builder {
public Builder status(HealthStatus status);
public Builder details(String key, Object value);
public Builder details(Map<String, Object> details);
public Builder exception(Throwable exception);
public HealthResult build();
}
}
public enum HealthStatus {
UP, DOWN, OUT_OF_SERVICE, UNKNOWN
}
// Info contributor interface
public interface InfoContributor {
Publisher<Map<String, Object>> getInfo();
}
// Metrics interfaces
public interface MeterRegistry {
Counter counter(String name);
Timer timer(String name);
Gauge gauge(String name, Object obj, ToDoubleFunction<Object> valueFunction);
}
public interface Counter extends Meter {
void increment();
void increment(double amount);
double count();
}
public interface Timer extends Meter {
void record(long amount, TimeUnit unit);
void record(Duration duration);
long count();
double totalTime(TimeUnit unit);
}Install with Tessl CLI
npx tessl i tessl/maven-micronaut