executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
microprofileMicroProfile specification reference covering Config, OpenAPI, Fault Tolerance, Metrics, Health, JWT Security, Open Telemetry, and Server Sent Events for cloud-native Java microservices.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: microprofile description: MicroProfile specification reference covering Config, OpenAPI, Fault Tolerance, Metrics, Health, JWT Security, Open Telemetry, and Server Sent Events for cloud-native Java microservices. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: cncf triggers: microprofile, fault tolerance, service mesh, openapi spec, health checks, metrics endpoint, config source, jwt security metrics endpoint archetypes:
Implements and configures MicroProfile specifications to add cloud-native operational capabilities — externalized configuration, service mesh compatibility, observability (metrics, tracing, health), fault tolerance patterns, and JWT-based security — on top of Jakarta EE application servers or as part of native compilation with Quarkus.
MicroProfile is a specification overlay on Jakarta EE focused exclusively on cloud-native microservice concerns. While Jakarta EE provides the enterprise platform (servlets, JPA, CDI, transactions), MicroProfile adds operational capabilities required for containerized, Kubernetes-deployed services.
Use MicroProfile when:
Do not use MicroProfile when:
Use this skill when:
Avoid this skill for:
Identify Required Specifications — List the MicroProfile specs needed for your deployment scenario:
Add Dependencies and Activate Features — For each selected spec, add the corresponding Maven artifact. On OpenLiberty, also add features to server.xml. On WildFly, use feature-manager CLI commands. Quarkus auto-activates features from dependencies.
Checkpoint: Verify the server logs show all enabled MicroProfile features during startup — missing activations cause silent runtime failures.
Implement Health Checks — Create at least two health check classes: one annotated with @Liveness (process-alive indicator) and one with @Readiness (traffic-ready indicator). Aggregate dependency states (database, cache, external services) into composite checks.
Checkpoint: Test both endpoints against localhost before deploying to Kubernetes — missing health probes cause pods to remain in Pending state.
Configure Externalized Settings — Use @ConfigProperty annotations with explicit default values for all configuration. Define a priority-ordered config source chain: environment variables (highest) > system properties > application.properties > defaults. Prefix all property names with the service name.
Checkpoint: Verify that required configurations are set in each deployment environment before enabling traffic to the pod.
Deploy and Verify — Package as a WAR or JAR, deploy to the target container. Verify: health endpoints return JSON, metrics expose at /metrics, OpenAPI docs render at /openapi. Run integration tests against all MicroProfile endpoints.
Checkpoint: Confirm Prometheus can scrape the metrics endpoint and that liveness/readiness probes trigger correct Kubernetes actions in a test namespace.
MicroProfile sits on top of Jakarta EE as a modular specification overlay. Each MicroProfile specification is independently implementable — an application only needs to enable the features it actually uses. The runtime (typically SmallRye) provides the implementation for each spec.
┌─────────────────────────────────────────────────────────┐
│ Kubernetes / Cloud │
│ ┌─────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ curl │ │Prometheus│ │ Jaeger / Zipkin │ │
│ │ /health │ │ scrape │ │ tracing ingestion │ │
│ └────┬────┘ └────┬─────┘ └──────────┬───────────┘ │
│ │ │ │ │
└─────────┼─────────────┼───────────────────┼──────────────┘
│ HTTP │ HTTP │ gRPC/HTTP
┌─────────▼─────────────▼───────────────────▼──────────────┐
│ MicroProfile Layer (Spec Overlay) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Config │ │ Health │ │ Metrics │ │ JWT │ │
│ │ (MP3.1) │ │ (MP4.0) │ │ (MP3.2) │ │ Security │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Fault │ │ OpenAPI │ │ SSE │ │ Open │ │
│ │ Tolerance│ │(MP3.1) │ │ (MP2.0) │ │Telemetry │ │
│ │(MP3.1) │ └──────────┘ └──────────┘ │ (MP2.0) │ │
│ └────┬─────┘ └────────────┘ │
└─────────┼─────────────────────────────────────────────────┘
│ CDI / Jakarta EE APIs
┌─────────▼─────────────────────────────────────────────────┐
│ Jakarta EE Container │
│ (WildFly, Payara, OpenLiberty, Quarkus) │
│ CDI · Servlet · JAX-RS · JPA · EJB · Interceptors │
└───────────────────────────────────────────────────────────┘
Each MicroProfile feature is activated independently by adding the appropriate Maven dependency. This means minimal overhead — you only pay for what you use:
<!-- Minimal MicroProfile setup — enable only what you need -->
<dependency>
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.health</groupId>
<artifactId>microprofile-health-api</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.fault-tolerance</groupId>
<artifactId>microprofile-fault-tolerance-api</artifactId>
<version>3.1</version>
</dependency>
| Specification | Current Version | Maven Artifact | Description |
|---|---|---|---|
| Config | MP Config 3.1 | microprofile-config-api | Externalized configuration with layered sources (env vars, system properties, config.properties files), typed values, and validators |
| OpenAPI | MP OpenAPI 3.1 | microprofile-openapi-api | Automatic OpenAPI 3.0 specification generation from JAX-RS annotations, providing interactive API documentation |
| Fault Tolerance | MP Fault Tolerance 3.1 | microprofile-fault-tolerance-api | Resilience patterns: circuit breaker, retry with exponential backoff, timeout enforcement, bulkhead isolation |
| Metrics | MP Metrics 3.2 | microprofile-metrics-api | Prometheus-compatible metrics export with counter, gauge, histogram, timer, and meter types |
| Health | MP Health 3.1 | microprofile-health-api | Liveness and readiness probes for Kubernetes pod lifecycle management and service mesh routing |
| JWT Security | MP JWT 2.0 | microprofile-jwt-auth-api | JSON Web Token authentication and authorization, container-managed JWT extraction from HTTP headers |
| OpenTelemetry | MP OpenTelemetry 2.0 | microprofile-opentelemetry-api | Distributed tracing with W3C Trace Context propagation across service boundaries |
| Server Sent Events | MP SSE 2.0 | microprofile-sse-api | Server-side streaming responses using the Server-Sent Events protocol |
| Rest Client | MP Rest Client 3.1 | microprofile-rest-client-api | Type-safe declarative REST client interfaces with automatic JSON serialization and fault tolerance integration |
| Messaging | MP Messaging 3.0 | microprofile-messaging-api | Asynchronous messaging using the MicroProfile Messaging pattern (Kafka, RabbitMQ adapters) |
| ConductR | Retired | — | Service discovery for Lightbend ConductR — retired, replaced by Kubernetes-native approaches |
Combines @CircuitBreaker, @Retry, and @Timeout on a single service method. This pattern is the most common production configuration for inter-service communication:
package com.example.service;
import org.eclipse.microprofile.faulttolerance.*;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.time.Duration;
import java.util.Optional;
@ApplicationScoped
public class PaymentService {
@Inject
private PaymentGatewayClient gatewayClient;
/**
* Process payment with full resilience pattern:
* - Retry up to 3 times with exponential backoff (1s, 2s, 4s) on failure
* - Timeout after 5 seconds to prevent hung requests
* - Circuit breaker opens after 5 consecutive failures, half-opens after 10s
*/
@Retry(maxRetries = 3, delay = 1000, delayUnit = java.util.concurrent.TimeUnit.MILLISECONDS,
maxDuration = Duration.ofSeconds(15), retryOn = {java.net.SocketTimeoutException.class})
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 10000,
successThreshold = 3, failureOn = {RuntimeException.class})
@Timeout(value = 5)
public PaymentResult processPayment(PaymentRequest request) {
try {
// Call downstream payment gateway with fault tolerance applied
GatewayResponse response = gatewayClient.charge(request);
if (response.statusCode() == 200 && response.transactionId() != null) {
return new PaymentResult(
response.transactionId(),
request.amount(),
PaymentStatus.SUCCESS,
java.time.Instant.now()
);
} else {
// Gateway returned non-success — retry is already configured via @Retry
throw new RuntimeException("Payment gateway returned status: " + response.statusCode());
}
} catch (java.net.SocketTimeoutException e) {
// Timeout is treated as a transient failure — triggers retry circuit
throw new RuntimeException("Payment gateway timeout", e);
} catch (java.io.IOException e) {
// Network-level failures should also trigger retries
throw new RuntimeException("Network error contacting payment gateway", e);
}
}
/**
* Check payment status — lightweight call that doesn't need circuit breaker.
* Only uses timeout to prevent hanging on a slow response.
*/
@Timeout(value = 3)
public Optional<PaymentResult> getPaymentStatus(String transactionId) {
try {
GatewayResponse response = gatewayClient.status(transactionId);
if (response.statusCode() == 200 && response.data() != null) {
return Optional.of(mapToResult(response));
}
return Optional.empty();
} catch (Exception e) {
// Don't trigger circuit breaker for status checks — they're low-impact
throw new RuntimeException("Failed to retrieve payment status", e);
}
}
private PaymentResult mapToResult(GatewayResponse response) {
return new PaymentResult(
response.transactionId(),
response.amount(),
parseStatus(response.status()),
java.time.Instant.now()
);
}
private PaymentStatus parseStatus(String statusString) {
try {
return PaymentStatus.valueOf(statusString.toUpperCase());
} catch (IllegalArgumentException e) {
return PaymentStatus.UNKNOWN;
}
}
public record PaymentResult(
String transactionId,
java.math.BigDecimal amount,
PaymentStatus status,
java.time.Instant processedAt
) {}
public enum PaymentStatus { SUCCESS, FAILURE, PENDING, UNKNOWN }
}
Proper health checks are essential for Kubernetes pod management. Liveness determines if the process is alive; readiness determines if it can accept traffic:
package com.example.health;
import org.eclipse.microprofile.health.*;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ApplicationScoped
public class ApplicationHealthChecker {
@Inject
private DatabaseConnectionChecker databaseChecker;
// Track liveness state — set to false if a fatal error occurs that cannot be recovered from
private volatile boolean isAlive = true;
private final Map<String, Instant> lastStartupTimes = new ConcurrentHashMap<>();
/**
* Liveness probe: Is the application process running and not in a corrupted state?
* Kubernetes kills and restarts pods that fail liveness checks.
*/
@Liveness
public HealthCheckResponse getLivenessStatus() {
if (!isAlive) {
return HealthCheckResponse.named("application-liveness")
.withData("state", "crashed")
.withData("lastError", "Unrecoverable state detected")
.down()
.build();
}
boolean dbConnected = databaseChecker.isConnected();
if (!dbConnected) {
return HealthCheckResponse.named("database-liveness")
.withData("connectionStatus", "lost")
.down()
.build();
}
return HealthCheckResponse.named("application-liveness")
.withData("state", "running")
.withData("uptime", Duration.between(lastStartupTimes.getOrDefault("start", Instant.now()), Instant.now()).toSeconds())
.withData("database", dbConnected ? "connected" : "disconnected")
.up()
.build();
}
/**
* Readiness probe: Is the application ready to accept traffic?
* Kubernetes removes pods from service load balancing when readiness fails.
*/
@Readiness
public HealthCheckResponse getReadinessStatus() {
boolean dbConnected = databaseChecker.isConnected();
boolean cacheWarm = checkCacheWarmth();
if (!dbConnected) {
return HealthCheckResponse.named("service-readiness")
.withData("database", "disconnected")
.notReady()
.build();
}
if (!cacheWarm) {
// Not ready until cache is warmed — important for cold starts after scaling events
return HealthCheckResponse.named("cache-readiness")
.withData("cacheStatus", "warming")
.notReady()
.build();
}
return HealthCheckResponse.named("service-readiness")
.withData("database", "connected")
.withData("cache", "warm")
.withData("readyToServeTraffic", true)
.up()
.build();
}
private boolean checkCacheWarmth() {
// In production, this would check a proper cache initialization state
return lastStartupTimes.values().stream()
.anyMatch(t -> Duration.between(t, Instant.now()).toSeconds() > 30);
}
/**
* Mark the application as alive — call during startup completion.
* This is a deliberate liveness signal that survives container restarts.
*/
public void markAlive() {
this.isAlive = true;
this.lastStartupTimes.put("start", Instant.now());
}
/**
* Mark the application as crashed — call from error handlers for unrecoverable errors.
* This triggers Kubernetes to restart the pod automatically.
*/
public void markCrashed(String reason) {
this.isAlive = false;
// Log the crash reason for debugging
System.err.println("CRITICAL: Application marked as crashed: " + reason);
}
}
Auto-generates OpenAPI 3.0 specification from JAX-RS annotations — no manual API docs required:
package com.example.resource;
import org.eclipse.microprofile.openapi.annotations.*;
import org.eclipse.microprofile.openapi.annotations.enums.*;
import org.eclipse.microprofile.openapi.annotations.media.*;
import org.eclipse.microprofile.openapi.annotations.parameters.*;
import org.eclipse.microprofile.openapi.annotations.responses.*;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@OpenAPIDefinition(
info = @Info(
title = "User Management API",
version = "1.0.0",
description = "RESTful API for managing user accounts and profiles in the platform.",
contact = @Contact(name = "API Support", email = "api-support@example.com"),
license = @License(name = "MIT", url = "https://opensource.org/licenses/MIT")
),
tags = {
@Tag(name = "users", description = "User account operations"),
@Tag(name = "admin", description = "Administrative user operations")
}
)
@Path("api/v1/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
/**
* Retrieve all users with pagination support.
*/
@GET
@Operation(
summary = "List all users",
description = "Returns a paginated list of users ordered by creation date.",
tags = {"users"},
responses = {
@ApiResponse(
responseCode = "200",
description = "Successfully returned user list",
content = @Content(
array = @ArraySchema(
schema = @Schema(implementation = UserDTO.class)
),
examples = @ExampleObject(
name = "Sample users",
value = "[{\"id\":1,\"username\":\"alice\",\"email\":\"alice@example.com\"}]"
)
)
),
@ApiResponse(responseCode = "500", description = "Internal server error")
}
)
@Parameter(name = "page", description = "Page number (1-indexed)", in = ParameterIn.QUERY, schema = @Schema(defaultValue = "1"))
@Parameter(name = "limit", description = "Items per page (max 100)", in = ParameterIn.QUERY, schema = @Schema(defaultValue = "20", maximum = "100"))
public Response listUsers(
@QueryParam("page") @DefaultValue("1") int page,
@QueryParam("limit") @DefaultValue("20") int limit
) {
// Implementation delegated to service layer
return Response.ok(java.util.Collections.emptyList()).build();
}
/**
* Create a new user account.
*/
@POST
@Operation(
summary = "Create a new user",
description = "Creates a new user account and returns the created resource with its assigned ID.",
tags = {"users"},
requestBody = @RequestBody(
required = true,
content = @Content(
schema = @Schema(implementation = UserCreateRequest.class),
examples = {
@ExampleObject(
name = "Standard creation",
value = "{\"username\":\"bob\",\"email\":\"bob@example.com\"}"
),
@ExampleObject(
name = "With role assignment",
value = "{\"username\":\"admin-user\",\"email\":\"admin@example.com\",\"role\":\"ADMIN\"}"
)
}
)
),
responses = {
@ApiResponse(
responseCode = "201",
description = "User created successfully",
content = @Content(schema = @Schema(implementation = UserDTO.class))
),
@ApiResponse(
responseCode = "409",
description = "Username already exists",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
}
)
public Response createUser(UserCreateRequest request) {
return Response.status(Response.Status.CREATED).build();
}
public record UserDTO(Long id, String username, String email, String role) {}
public record UserCreateRequest(String username, String email, String role) {}
public record ErrorResponse(String message) {}
}
Inject configuration from multiple sources with typed values and validation:
package com.example.config;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.time.Duration;
import java.util.Optional;
@ApplicationScoped
public class ServiceConfiguration {
/**
* Inject configuration from layered sources.
* Priority: System properties > Environment variables > config.properties > default value
*
* Example env var: SERVICE_RETRY_DELAY=5000
*/
@ConfigProperty(name = "payment.retry.delay", defaultValue = "1000")
int retryDelayMs;
/**
* Typed configuration with explicit type coercion.
* Can be set via: PAYMENT_TIMEOUT_SECONDS=30
*/
@ConfigProperty(name = "payment.timeout.seconds", defaultValue = "10")
Duration paymentTimeout;
/**
* Optional configuration — no error if the property is not set anywhere.
* Falls back to null, never throws.
*/
@ConfigProperty(name = "payment.gateway.url")
Optional<String> gatewayUrl;
/**
* Boolean configuration — auto-coerced from strings ("true", "yes", "1" all work).
*/
@ConfigProperty(name = "payment.enabled", defaultValue = "true")
boolean paymentEnabled;
/**
* Custom config source can override default values at runtime.
* For example, a custom ConfigSource could read from Vault or Consul.
*/
public int getRetryDelayMs() {
return retryDelayMs;
}
public Duration getPaymentTimeout() {
return paymentTimeout;
}
public String getGatewayUrlOrDefault(String fallback) {
return gatewayUrl.orElse(fallback);
}
public boolean isPaymentEnabled() {
return paymentEnabled;
}
}
SmallRye is the reference implementation for most MicroProfile specifications. It integrates with each major Jakarta EE server:
| Server | SmallRye Integration | Activation Method |
|---|---|---|
| OpenLiberty | Built-in — no additional dependencies needed | Add features to server.xml: <feature>microprofile-faultTolerance-3.1</feature> |
| WildFly | Optional feature via WildFly Swarm / overlay | Install the MicroProfile subsystem: jboss-cli.sh --command="feature-manager add microprofile-fault-tolerance-3.1" |
| Payara Server | Included by default in Payara Platform 6+ | No configuration needed — all MicroProfile features auto-activate when APIs are on classpath |
| Quarkus | Native MicroProfile implementation optimized for GraalVM | Enable via application.properties: quarkus.smallrye-fault-tolerance.enabled=true |
When compiling Jakarta EE + MicroProfile applications to native images with GraalVM, additional configuration is needed:
# application.properties — Quarkus native compilation config
quarkus.native.additional-build-args=\
-H:ReflectionConfigurationFiles=reflection-config.json,\
-H:ResourceConfigurationFiles=resource-config.json
# Register beans for reflection (required for CDI proxy generation)
# src/main/resources/reflection-config.json
[
{
"name": "com.example.service.PaymentService",
"allPublicMethods": true,
"allPublicFields": true
}
]
# MicroProfile specific native config
quarkus.smallrye-openapi.store-schema-directory=target/openapi
quarkus.smallrye-metrics.enabled=true
| Pitfall | Cause | Prevention |
|---|---|---|
| Fault tolerance transaction boundary conflicts | @CircuitBreaker and @Retry on a method with @TransactionAttribute.REQUIRED causes the transaction to participate in the retry loop | Keep fault tolerance annotations on methods that do NOT begin new JTA transactions; put them on the service facade and let the EJB/CDI bean manage transactions separately |
| Health check false positives causing pod restarts | Readiness checks passing even when critical dependencies are degraded | Implement layered health checks — separate liveness (process-alive) from readiness (traffic-ready); use composite health checks that aggregate multiple dependency states |
| Metrics naming collisions across services | Multiple services exporting metrics with identical names to the same Prometheus instance | Use service-specific metric prefixes: my_service_http_requests_total instead of just http_requests_total. Configure via MicroProfile Metrics custom naming. |
| Config source priority confusion | Environment variables silently override application-default config.properties values, causing unexpected behavior in different deployment environments | Document the full config source priority order; use unique property names with service prefixes (e.g., payment.retry.delay instead of retry.delay) |
| OpenAPI duplication | Multiple JAX-RS resource classes define overlapping @OpenAPIDefinition annotations that merge incorrectly | Define OpenAPI metadata at a single entry point or use package-level @OpenAPIDefinition; avoid annotating individual methods with tag definitions |
/openapi before each release — it is the source of truth for API consumerstimeout vs payment.timeout) — use a consistent naming convention throughoutWhen implementing or reviewing a MicroProfile-enabled Jakarta EE application, produce:
| Skill | Purpose |
|---|---|
jakarta-ee | Base platform reference for Jakarta EE specifications that MicroProfile extends with cloud-native capabilities |
jakarta-migration | Migration guide for moving existing Java EE 8 applications to Jakarta EE before adding MicroProfile features |
Authoritative documentation for MicroProfile specifications and implementations.