Eclipse MicroProfile Interview Questions and Answers
Master Eclipse MicroProfile with production-ready interview questions covering Config, REST Client, Fault Tolerance, Retry, Timeout, Circuit Breaker, Bulkhead, Health, OpenAPI, JWT authentication, telemetry, and cloud-native best practices.
Eclipse MicroProfile Interview Questions and Answers
Introduction
Eclipse MicroProfile provides specifications for developing cloud-native Java microservices.
Jakarta EE provides foundational enterprise capabilities such as:
- Dependency injection
- REST APIs
- Persistence
- Transactions
- Validation
- Security
- Messaging
MicroProfile adds capabilities commonly needed when those applications run as distributed microservices.
These capabilities include:
- Externalized configuration
- Type-safe REST clients
- Retries
- Timeouts
- Circuit breakers
- Bulkheads
- Fallback behavior
- Health checks
- JWT-based security
- OpenAPI documentation
- Telemetry and distributed observability
MicroProfile does not replace Jakarta EE.
It builds on selected Jakarta EE technologies, especially CDI and Jakarta REST, to provide a standardized microservice programming model.
A production-ready MicroProfile service must consider:
- Partial failures
- Network latency
- Configuration management
- Service health
- Authentication and authorization
- Distributed tracing
- Metrics
- Logging
- Retry storms
- Timeout budgets
- Resource isolation
- API documentation
- Graceful degradation
This guide covers the 15 most frequently asked Eclipse MicroProfile interview questions with code examples, Mermaid diagrams, production scenarios, common mistakes, and senior-level recommendations.
Q1. What is Eclipse MicroProfile?
Answer
Eclipse MicroProfile is a collection of specifications for building cloud-native Java microservices.
It standardizes common microservice capabilities so applications can use portable APIs across compatible runtimes.
Core Capabilities
MicroProfile commonly provides specifications for:
- Configuration
- REST clients
- Fault tolerance
- Health checks
- JWT authentication
- OpenAPI documentation
- Telemetry
High-Level Architecture
flowchart TD
A[Client] --> B[MicroProfile Service]
B --> C[Jakarta REST API]
B --> D[MicroProfile Config]
B --> E[MicroProfile Fault Tolerance]
B --> F[MicroProfile Health]
B --> G[MicroProfile JWT]
B --> H[MicroProfile OpenAPI]
B --> I[MicroProfile Telemetry]
B --> J[Database]
B --> K[Remote Service]
B --> L[Message Broker]
Example Service
@Path("/orders")
@ApplicationScoped
@Produces(MediaType.APPLICATION_JSON)
public class OrderResource {
private final OrderService orderService;
@Inject
public OrderResource(
OrderService orderService
) {
this.orderService = orderService;
}
@GET
@Path("/{orderId}")
public OrderResponse findOrder(
@PathParam("orderId")
Long orderId
) {
return orderService.findOrder(
orderId
);
}
}
The REST endpoint uses Jakarta REST.
MicroProfile can then add:
- Configuration for remote URLs
- REST clients for downstream calls
- Retry and timeout behavior
- Health checks
- JWT security
- OpenAPI documentation
- Distributed tracing
Why Interviewers Ask This
Interviewers want to confirm that you understand MicroProfile as a set of standardized microservice specifications rather than one application server or framework.
Production Example
A claims service may use:
- MicroProfile Config for environment-specific URLs
- REST Client for member-service calls
- Fault Tolerance for retries and circuit breaking
- Health for Kubernetes probes
- JWT for API security
- OpenAPI for API documentation
- Telemetry for distributed tracing
Common Mistake
Describing MicroProfile as a replacement for Jakarta EE.
Senior Follow-up
MicroProfile complements Jakarta EE by standardizing cloud-native capabilities that become especially important in distributed systems.
Q2. Why was MicroProfile introduced?
Answer
MicroProfile was introduced to standardize Java APIs for microservice requirements that were not fully covered by the traditional enterprise platform model.
Microservices introduce challenges such as:
- Services failing independently
- Network calls timing out
- Configuration changing by environment
- Services needing readiness checks
- Distributed requests requiring tracing
- APIs needing discoverable documentation
- Tokens carrying caller identity
- Downstream failures propagating through the system
Traditional Monolith
flowchart LR
A[Web Layer] --> B[Business Layer]
B --> C[Persistence Layer]
C --> D[Single Database]
Many calls happen in the same process.
Distributed Microservices
flowchart TD
A[API Gateway] --> B[Order Service]
B --> C[Customer Service]
B --> D[Inventory Service]
B --> E[Payment Service]
C --> F[Customer Database]
D --> G[Inventory Database]
E --> H[Payment Provider]
Every network call can:
- Time out
- Return an error
- Become slow
- Reject traffic
- Return incompatible data
- Fail during deployment
MicroProfile Response
flowchart TD
A[Distributed-System Problem] --> B{Required Capability}
B --> C[External Configuration]
B --> D[REST Client]
B --> E[Retry and Timeout]
B --> F[Circuit Breaker]
B --> G[Health Checks]
B --> H[Telemetry]
B --> I[JWT Security]
Common Mistake
Assuming microservices require only smaller deployment artifacts.
Senior Follow-up
Microservices require operational resilience, independent deployment, observability, secure communication, and failure isolation.
Q3. What is the difference between Jakarta EE and MicroProfile?
Answer
Jakarta EE provides standardized enterprise application APIs.
MicroProfile adds standardized cloud-native microservice capabilities.
Comparison
| Jakarta EE | MicroProfile |
|---|---|
| Enterprise Java platform | Cloud-native Java specifications |
| Jakarta REST | Type-safe REST Client |
| CDI | Config injection |
| Jakarta Security | JWT-based API authentication |
| Jakarta Transactions | Fault-tolerance policies |
| Jakarta Persistence | Health checks |
| Jakarta Messaging | OpenAPI documentation |
| Bean Validation | Telemetry integration |
Combined Architecture
flowchart TD
A[Application] --> B[Jakarta EE Foundation]
A --> C[MicroProfile Capabilities]
B --> D[CDI]
B --> E[REST]
B --> F[Persistence]
B --> G[Transactions]
B --> H[Validation]
C --> I[Config]
C --> J[REST Client]
C --> K[Fault Tolerance]
C --> L[Health]
C --> M[JWT]
C --> N[OpenAPI]
C --> O[Telemetry]
Example
A service may use:
@Inject
private EntityManager entityManager;
from Jakarta Persistence and:
@Inject
@ConfigProperty(
name = "inventory.timeout"
)
private Duration timeout;
from MicroProfile Config.
Common Mistake
Treating Jakarta EE and MicroProfile as competing frameworks.
Senior Follow-up
A MicroProfile runtime commonly provides selected Jakarta EE technologies and MicroProfile specifications as one integrated platform.
Q4. What is MicroProfile Config?
Answer
MicroProfile Config provides a standard way to obtain application configuration from multiple sources.
It presents configuration as one merged logical view.
Common sources include:
- Application properties
- Environment variables
- JVM system properties
- Container configuration
- Kubernetes ConfigMaps
- Kubernetes Secrets
- Custom configuration sources
Configuration Flow
flowchart TD
A[Default Properties] --> E[MicroProfile Config]
B[Environment Variables] --> E
C[System Properties] --> E
D[Custom ConfigSource] --> E
E --> F[Resolved Configuration Value]
F --> G[Application Component]
Property File
inventory.service.url=
https://inventory.example.com
inventory.request.timeout=
2S
order.default.page-size=
20
Injection
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
public class OrderConfiguration {
@Inject
@ConfigProperty(
name = "order.default.page-size",
defaultValue = "20"
)
private int defaultPageSize;
public int defaultPageSize() {
return defaultPageSize;
}
}
Constructor Injection
@ApplicationScoped
public class InventoryConfiguration {
private final URI serviceUrl;
private final Duration timeout;
@Inject
public InventoryConfiguration(
@ConfigProperty(
name = "inventory.service.url"
)
URI serviceUrl,
@ConfigProperty(
name = "inventory.request.timeout",
defaultValue = "2S"
)
Duration timeout
) {
this.serviceUrl = serviceUrl;
this.timeout = timeout;
}
public URI serviceUrl() {
return serviceUrl;
}
public Duration timeout() {
return timeout;
}
}
Programmatic Access
Config config =
ConfigProvider.getConfig();
String serviceUrl =
config.getValue(
"inventory.service.url",
String.class
);
Optional Property
Optional<String> region =
config.getOptionalValue(
"deployment.region",
String.class
);
Common Mistake
Hardcoding remote URLs or environment-specific values in application code.
Senior Follow-up
Configuration should be externalized, validated at startup, protected according to sensitivity, and observable without exposing secrets.
Q5. How does configuration precedence work?
Answer
MicroProfile Config combines values from multiple configuration sources.
When the same property exists in several sources, the value from the source with the higher ordinal wins.
Conceptual Precedence
flowchart TD
A[Configuration Property Requested] --> B[Find All Matching Values]
B --> C[Compare ConfigSource Ordinals]
C --> D[Select Highest-Priority Source]
D --> E[Convert Value to Required Type]
E --> F[Inject or Return Value]
Example
Application default:
payment.timeout=5S
Environment override:
PAYMENT_TIMEOUT=2S
Runtime system property:
-Dpayment.timeout=1S
The effective value depends on source precedence and name mapping supported by the runtime.
Custom Config Source
public class DatabaseConfigSource
implements ConfigSource {
@Override
public Map<String, String>
getProperties() {
return Map.of(
"feature.claim-review.enabled",
"true"
);
}
@Override
public String getValue(
String propertyName
) {
return getProperties()
.get(propertyName);
}
@Override
public String getName() {
return "database-config-source";
}
@Override
public int getOrdinal() {
return 300;
}
}
Best Practices
- Provide safe application defaults.
- Override values externally.
- Document configuration ownership.
- Validate mandatory properties during startup.
- Avoid conflicting configuration sources.
- Never print secret values to logs.
- Keep environment-specific values outside the artifact.
Common Mistake
Assuming every environment-variable name maps identically without testing the runtime's mapping behavior.
Senior Follow-up
Configuration precedence must be deterministic and documented because unexpected overrides can create production incidents.
Q6. What is MicroProfile REST Client?
Answer
MicroProfile REST Client provides a type-safe interface for calling remote REST APIs.
Instead of manually constructing HTTP requests, developers define a Java interface using Jakarta REST annotations.
Client Interface
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/inventory")
@RegisterRestClient(
configKey = "inventory-api"
)
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public interface InventoryClient {
@GET
@Path("/products/{productCode}")
InventoryResponse findInventory(
@PathParam("productCode")
String productCode
);
}
Injection
import org.eclipse.microprofile.rest.client.inject.RestClient;
@ApplicationScoped
public class InventoryService {
private final InventoryClient
inventoryClient;
@Inject
public InventoryService(
@RestClient
InventoryClient inventoryClient
) {
this.inventoryClient =
inventoryClient;
}
public InventoryResponse findInventory(
String productCode
) {
return inventoryClient.findInventory(
productCode
);
}
}
Configuration
inventory-api/mp-rest/url=
https://inventory.example.com
Call Flow
sequenceDiagram
participant OrderService
participant RestClient
participant InventoryAPI
OrderService->>RestClient: findInventory(productCode)
RestClient->>InventoryAPI: GET /inventory/products/{code}
InventoryAPI-->>RestClient: JSON response
RestClient-->>OrderService: InventoryResponse
Benefits
- Type-safe client contract
- Reuse of Jakarta REST annotations
- CDI integration
- Configurable base URL
- Filters and providers
- Fault Tolerance integration
- Easier testing
Common Mistake
Returning internal remote-service entities directly to the caller.
Senior Follow-up
The client interface should use stable integration DTOs and map them into the application's own domain or response models.
Q7. What is MicroProfile Fault Tolerance?
Answer
MicroProfile Fault Tolerance provides annotations for handling failures in distributed calls.
Common strategies include:
- Timeout
- Retry
- Circuit breaker
- Bulkhead
- Fallback
- Asynchronous execution
Example
import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.faulttolerance.Timeout;
@ApplicationScoped
public class CustomerGateway {
@Inject
@RestClient
private CustomerClient customerClient;
@Retry(
maxRetries = 2,
delay = 200,
jitter = 100
)
@Timeout(2000)
public CustomerResponse findCustomer(
Long customerId
) {
return customerClient.findCustomer(
customerId
);
}
}
Fault-Tolerance Pipeline
flowchart TD
A[Service Invocation] --> B[Timeout Policy]
B --> C[Retry Policy]
C --> D[Circuit Breaker]
D --> E[Bulkhead]
E --> F[Remote Service]
F --> G{Success?}
G -->|Yes| H[Return Result]
G -->|No| I[Fallback or Failure]
Important Principle
Fault-tolerance annotations are commonly applied through CDI interception.
The component must therefore be container-managed for the behavior to apply.
Common Mistake
Creating the annotated class manually with new.
CustomerGateway gateway =
new CustomerGateway();
This can bypass CDI interception.
Senior Follow-up
Resilience policies must be designed as a coordinated system. Adding retries, timeouts, and circuit breakers independently can create unexpected interactions.
Q8. What are @Retry and @Timeout?
Answer
@Retry repeats an operation after certain failures.
@Timeout limits how long an invocation may take.
Retry Example
@Retry(
maxRetries = 3,
delay = 250,
maxDuration = 3000,
jitter = 100,
retryOn = {
ServiceUnavailableException.class,
ProcessingException.class
},
abortOn = {
InvalidCustomerException.class
}
)
public CustomerResponse findCustomer(
Long customerId
) {
return customerClient.findCustomer(
customerId
);
}
Timeout Example
@Timeout(1500)
public PaymentResponse authorize(
PaymentRequest request
) {
return paymentClient.authorize(
request
);
}
Retry Flow
flowchart TD
A[Attempt 1] --> B{Success?}
B -->|Yes| C[Return Result]
B -->|No| D{Retriable Failure?}
D -->|No| E[Fail Immediately]
D -->|Yes| F{Retries Remaining?}
F -->|Yes| G[Wait with Delay and Jitter]
G --> H[Next Attempt]
H --> B
F -->|No| E
Timeout Flow
sequenceDiagram
participant Caller
participant TimeoutPolicy
participant RemoteService
Caller->>TimeoutPolicy: Invoke operation
TimeoutPolicy->>RemoteService: Start request
alt Completes within timeout
RemoteService-->>TimeoutPolicy: Result
TimeoutPolicy-->>Caller: Result
else Exceeds timeout
TimeoutPolicy-->>Caller: Timeout failure
end
When Retry Is Appropriate
- Temporary connection error
- Short dependency outage
- Lock timeout
- Service temporarily unavailable
- Rate-limited operation with retry guidance
When Retry Is Dangerous
- Non-idempotent payment creation
- Invalid request data
- Authentication failure
- Permanent business rejection
- Long-running overloaded dependency
- Operation without duplicate protection
Jitter
Jitter adds randomness to retry delay.
Without jitter, many service instances may retry simultaneously.
flowchart TD
A[Dependency Fails] --> B[100 Clients Retry at Same Time]
B --> C[Retry Storm]
C --> D[Dependency Remains Overloaded]
E[Dependency Fails] --> F[Retries Use Jitter]
F --> G[Retry Attempts Spread Over Time]
G --> H[Improved Recovery Chance]
Common Mistake
Retrying every exception.
Senior Follow-up
Retry only transient, idempotent operations and ensure the total retry duration fits within the caller's end-to-end latency budget.
Q9. What is a Circuit Breaker?
Answer
A Circuit Breaker stops calls to a failing dependency when the recent failure rate exceeds a configured threshold.
Its common states are:
- Closed
- Open
- Half-open
States
stateDiagram-v2
[*] --> Closed
Closed --> Open:
Failure threshold reached
Open --> HalfOpen:
Delay expires
HalfOpen --> Closed:
Test calls succeed
HalfOpen --> Open:
Test call fails
Example
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
@CircuitBreaker(
requestVolumeThreshold = 10,
failureRatio = 0.5,
delay = 5000,
successThreshold = 3,
failOn = {
ServiceUnavailableException.class,
ProcessingException.class
},
skipOn = {
InvalidOrderException.class
}
)
public InventoryResponse findInventory(
String productCode
) {
return inventoryClient.findInventory(
productCode
);
}
Closed State
Requests are sent normally.
flowchart LR
A[Caller] --> B[Closed Circuit]
B --> C[Remote Service]
Open State
Calls fail quickly without contacting the dependency.
flowchart LR
A[Caller] --> B[Open Circuit]
B --> C[Fail Fast]
B -. No call .-> D[Failing Dependency]
Half-Open State
A limited number of test calls determine whether the dependency has recovered.
Benefits
- Prevents repeated calls to a failing service
- Reduces resource consumption
- Supports faster failure
- Gives the dependency recovery time
- Prevents cascading failures
Common Mistake
Treating the circuit breaker as a replacement for monitoring or dependency repair.
Senior Follow-up
Circuit-breaker thresholds should be based on traffic volume, dependency behavior, recovery characteristics, and business tolerance.
Q10. What is a Bulkhead?
Answer
A Bulkhead isolates resources so one overloaded dependency or operation cannot consume all application capacity.
The concept comes from ships divided into watertight compartments.
If one compartment fails, the entire ship does not immediately fail.
Without Bulkhead
flowchart TD
A[Shared Thread Pool] --> B[Payment Calls]
A --> C[Inventory Calls]
A --> D[Notification Calls]
B --> E[Payment Service Becomes Slow]
E --> F[All Threads Consumed]
F --> G[Entire Application Becomes Unresponsive]
With Bulkhead
flowchart TD
A[Application] --> B[Payment Bulkhead]
A --> C[Inventory Bulkhead]
A --> D[Notification Bulkhead]
B --> E[Payment Service]
C --> F[Inventory Service]
D --> G[Notification Service]
E --> H[Payment Capacity Exhausted]
H --> I[Other Operations Continue]
Example
import org.eclipse.microprofile.faulttolerance.Bulkhead;
@Bulkhead(
value = 20,
waitingTaskQueue = 50
)
public PaymentResponse authorize(
PaymentRequest request
) {
return paymentClient.authorize(
request
);
}
Meaning
Maximum concurrent executions: 20
Waiting queue capacity: 50
When both capacities are full, additional calls fail quickly.
Benefits
- Failure isolation
- Capacity protection
- Controlled concurrency
- Reduced thread exhaustion
- Better service availability
Common Mistake
Configuring a bulkhead larger than the downstream service or connection pool can support.
Senior Follow-up
Bulkhead capacity must align with HTTP pools, database pools, thread pools, rate limits, CPU, memory, and downstream capacity.
Q11. What is @Fallback?
Answer
@Fallback provides an alternative response when the original operation cannot complete successfully.
Method Fallback
import org.eclipse.microprofile.faulttolerance.Fallback;
@Retry(maxRetries = 2)
@Timeout(1500)
@CircuitBreaker(
requestVolumeThreshold = 10,
failureRatio = 0.5,
delay = 5000
)
@Fallback(
fallbackMethod =
"fallbackInventory"
)
public InventoryResponse findInventory(
String productCode
) {
return inventoryClient.findInventory(
productCode
);
}
public InventoryResponse fallbackInventory(
String productCode
) {
return InventoryResponse
.temporarilyUnavailable(
productCode
);
}
Flow
flowchart TD
A[Invoke Remote Operation] --> B{Successful?}
B -->|Yes| C[Return Actual Result]
B -->|No| D[Retry or Circuit Policy]
D --> E{Recovery Successful?}
E -->|Yes| C
E -->|No| F[Invoke Fallback]
F --> G[Return Degraded Result]
Appropriate Fallbacks
- Return cached reference data
- Return an explicit temporary-unavailable state
- Skip non-critical personalization
- Return default recommendations
- Queue work for later processing
- Provide partial response with clear status
Dangerous Fallbacks
- Return zero balance when account service fails
- Report payment success without confirmation
- Report inventory available without evidence
- Hide permanent data corruption
- Return stale security permissions
Common Mistake
Using a fallback that silently changes business meaning.
Senior Follow-up
Fallbacks must preserve correctness. Graceful degradation is valuable only when the degraded response is safe and clearly understood.
Q12. What is MicroProfile Health?
Answer
MicroProfile Health provides standardized health checks that container platforms can use to evaluate an application.
The primary categories are:
- Liveness
- Readiness
- Startup
MicroProfile Health runtimes commonly expose corresponding endpoints for these categories.
Liveness
Liveness answers:
Is the process alive and capable of continuing?
A failed liveness check may cause the platform to restart the container.
Readiness
Readiness answers:
Is the application ready to receive traffic?
A failed readiness check should remove the instance from service routing without necessarily restarting it.
Startup
Startup answers:
Has the application completed startup successfully?
Health Architecture
flowchart TD
A[Container Platform] --> B[Liveness Check]
A --> C[Readiness Check]
A --> D[Startup Check]
B --> E{Alive?}
E -->|No| F[Restart Container]
E -->|Yes| G[Keep Running]
C --> H{Ready?}
H -->|No| I[Remove From Traffic]
H -->|Yes| J[Route Requests]
D --> K{Started?}
K -->|No| L[Continue Startup Window]
K -->|Yes| C
Liveness Check
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;
@Liveness
@ApplicationScoped
public class ApplicationLivenessCheck
implements HealthCheck {
@Override
public HealthCheckResponse call() {
return HealthCheckResponse
.up(
"application-liveness"
);
}
}
Readiness Check
import org.eclipse.microprofile.health.Readiness;
@Readiness
@ApplicationScoped
public class DatabaseReadinessCheck
implements HealthCheck {
@Inject
private DataSource dataSource;
@Override
public HealthCheckResponse call() {
try (
Connection connection =
dataSource.getConnection()
) {
return HealthCheckResponse
.named(
"database-readiness"
)
.withData(
"database",
connection
.getMetaData()
.getDatabaseProductName()
)
.up()
.build();
} catch (SQLException exception) {
return HealthCheckResponse
.named(
"database-readiness"
)
.withData(
"reason",
"Database unavailable"
)
.down()
.build();
}
}
}
Startup Check
import org.eclipse.microprofile.health.Startup;
@Startup
@ApplicationScoped
public class ApplicationStartupCheck
implements HealthCheck {
private volatile boolean initialized;
@PostConstruct
void initialize() {
initialized = true;
}
@Override
public HealthCheckResponse call() {
return HealthCheckResponse
.named(
"application-startup"
)
.status(initialized)
.build();
}
}
Common Mistake
Calling every remote dependency in the liveness check.
This can create restart loops during a dependency outage.
Senior Follow-up
Liveness should primarily reflect whether the process is irrecoverably unhealthy. Readiness should reflect whether it can safely serve traffic.
Q13. What is MicroProfile OpenAPI?
Answer
MicroProfile OpenAPI provides a standard way to describe and expose REST API documentation using the OpenAPI model.
It supports:
- Annotation-based documentation
- Static OpenAPI documents
- Programmatic model construction
- Runtime-generated API descriptions
Resource Example
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@Path("/orders")
@Tag(
name = "Orders",
description =
"Order management operations"
)
public class OrderResource {
@GET
@Path("/{orderId}")
@Operation(
summary = "Find an order",
description =
"Returns an order using its identifier"
)
@APIResponse(
responseCode = "200",
description = "Order found",
content = @Content(
mediaType =
MediaType.APPLICATION_JSON,
schema = @Schema(
implementation =
OrderResponse.class
)
)
)
@APIResponse(
responseCode = "404",
description = "Order not found"
)
public OrderResponse findOrder(
@PathParam("orderId")
Long orderId
) {
return orderService.findOrder(
orderId
);
}
}
Documentation Flow
flowchart TD
A[REST Resource Annotations] --> B[OpenAPI Model]
C[Static OpenAPI File] --> B
D[Programmatic Model Filter] --> B
B --> E[OpenAPI Document]
E --> F[API Documentation UI]
E --> G[Client Generation]
E --> H[Contract Validation]
Static Document Example
openapi: 3.1.0
info:
title: Order API
version: "1.0"
paths:
/orders/{orderId}:
get:
summary: Find an order
parameters:
- name: orderId
in: path
required: true
schema:
type: integer
format: int64
responses:
"200":
description: Order found
"404":
description: Order not found
Benefits
- Discoverable API contract
- Better consumer integration
- Client-code generation
- API review
- Contract testing
- Documentation consistency
Common Mistake
Treating generated OpenAPI documentation as automatically correct.
Senior Follow-up
The documented API must be tested against runtime behavior so status codes, schemas, validation constraints, and security requirements stay synchronized.
Q14. How do JWT and telemetry fit into MicroProfile?
Answer
MicroProfile JWT Authentication standardizes how REST services consume JWT-based security identities.
MicroProfile Telemetry supports distributed observability through tracing, metrics, and logs using the modern telemetry model.
JWT Security Flow
sequenceDiagram
participant Client
participant IdentityProvider
participant MicroProfileAPI
participant Resource
Client->>IdentityProvider: Authenticate
IdentityProvider-->>Client: Signed access token
Client->>MicroProfileAPI: Bearer token
MicroProfileAPI->>MicroProfileAPI: Validate token claims
MicroProfileAPI->>Resource: Create caller identity
Resource-->>Client: Authorized response
JWT Claim Injection
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/profile")
@Authenticated
public class ProfileResource {
@Inject
private JsonWebToken jsonWebToken;
@GET
public ProfileResponse profile() {
return new ProfileResponse(
jsonWebToken.getSubject(),
jsonWebToken.getGroups()
);
}
}
Role Protection
@GET
@Path("/admin")
@RolesAllowed("ADMIN")
public AdminResponse admin() {
return adminService.loadDashboard();
}
Telemetry Flow
flowchart TD
A[Incoming Request] --> B[Trace Span]
B --> C[Service Method]
C --> D[REST Client Span]
D --> E[Remote Service]
C --> F[Database Span]
B --> G[Trace Exporter]
C --> H[Metrics]
C --> I[Structured Logs]
G --> J[Observability Platform]
H --> J
I --> J
What Telemetry Helps Answer
- Which service caused the delay?
- How many requests failed?
- Which downstream dependency timed out?
- How much time was spent in the database?
- Which trace corresponds to a production error?
- Is latency increasing after deployment?
Correlation Example
traceId=4e91a2c
spanId=8a130d
orderId=ORD-1001
Common Mistake
Logging large JWTs or sensitive claims.
Senior Follow-up
Telemetry must be useful without exposing passwords, access tokens, personal data, or regulated information.
Q15. What are senior-level MicroProfile best practices?
Answer
Senior developers should treat MicroProfile annotations as operational policies rather than decorative configuration.
Best Practices
- Externalize environment-specific configuration.
- Validate required configuration at startup.
- Do not expose secrets through logs or health endpoints.
- Define type-safe REST client contracts.
- Apply network timeouts to every downstream call.
- Retry only transient and idempotent operations.
- Add jitter to reduce retry storms.
- Limit total retry duration.
- Use circuit breakers to stop repeated failure.
- Use bulkheads to isolate resource exhaustion.
- Use fallbacks only when the degraded result is safe.
- Separate liveness from readiness.
- Keep health checks fast.
- Protect health details where required.
- Keep OpenAPI documentation synchronized with runtime behavior.
- Validate JWT signature and claims.
- Use short-lived access tokens.
- Propagate trace context across services.
- Measure latency, error rate, saturation, and traffic.
- Test failure behavior under load.
- Avoid stacking resilience policies without understanding order and timing.
Senior Design Flow
flowchart TD
A[Remote Dependency] --> B[Define Timeout Budget]
B --> C[Classify Failures]
C --> D{Operation Idempotent?}
D -->|Yes| E[Consider Bounded Retry]
D -->|No| F[Do Not Retry Without Protection]
E --> G[Add Jitter]
F --> H[Fail Safely]
G --> I[Configure Circuit Breaker]
H --> I
I --> J[Set Bulkhead Capacity]
J --> K[Define Safe Fallback]
K --> L[Add Metrics and Tracing]
L --> M[Load and Failure Test]
Production Checklist
Externalized Configuration
│
▼
Typed REST Client
│
▼
Timeout Budget
│
▼
Bounded Retry with Jitter
│
▼
Circuit Breaker
│
▼
Bulkhead Isolation
│
▼
Safe Fallback
│
▼
Health and Telemetry
│
▼
Failure Testing
Senior Follow-up
Resilience improves reliability only when policies are coordinated with business semantics, capacity limits, and end-to-end latency objectives.
MicroProfile Platform Architecture
flowchart TD
A[API Gateway] --> B[MicroProfile Order Service]
B --> C[Jakarta REST]
B --> D[MicroProfile Config]
B --> E[MicroProfile JWT]
B --> F[MicroProfile OpenAPI]
B --> G[MicroProfile Health]
B --> H[MicroProfile Telemetry]
B --> I[Fault-Tolerant REST Client]
I --> J[Inventory Service]
I --> K[Payment Service]
B --> L[Jakarta Persistence]
L --> M[Order Database]
End-to-End Request Flow
sequenceDiagram
participant Client
participant Gateway
participant OrderAPI
participant InventoryClient
participant InventoryAPI
participant Telemetry
Client->>Gateway: Bearer token and request
Gateway->>OrderAPI: Forward request
OrderAPI->>OrderAPI: Validate JWT
OrderAPI->>Telemetry: Start trace
OrderAPI->>InventoryClient: Check inventory
InventoryClient->>InventoryAPI: HTTP request with timeout
alt Inventory succeeds
InventoryAPI-->>InventoryClient: Inventory result
InventoryClient-->>OrderAPI: Result
OrderAPI-->>Client: Success
else Inventory fails temporarily
InventoryClient->>InventoryAPI: Bounded retry
InventoryAPI-->>InventoryClient: Failure
InventoryClient-->>OrderAPI: Fallback or failure
OrderAPI-->>Client: Degraded or error response
end
OrderAPI->>Telemetry: Complete trace and metrics
Configuration Example
# Inventory REST Client
inventory-api/mp-rest/url=
https://inventory.example.com
# Business configuration
order.default-page-size=20
order.maximum-page-size=100
# Feature configuration
features.order-recommendations.enabled=true
# Timeout configuration
inventory.timeout=2S
payment.timeout=3S
Typed Configuration Bean
@ApplicationScoped
public class OrderSettings {
private final int defaultPageSize;
private final int maximumPageSize;
private final boolean recommendationsEnabled;
@Inject
public OrderSettings(
@ConfigProperty(
name =
"order.default-page-size",
defaultValue = "20"
)
int defaultPageSize,
@ConfigProperty(
name =
"order.maximum-page-size",
defaultValue = "100"
)
int maximumPageSize,
@ConfigProperty(
name =
"features.order-recommendations.enabled",
defaultValue = "false"
)
boolean recommendationsEnabled
) {
if (
defaultPageSize < 1
|| maximumPageSize
< defaultPageSize
) {
throw new IllegalStateException(
"Invalid order pagination configuration"
);
}
this.defaultPageSize =
defaultPageSize;
this.maximumPageSize =
maximumPageSize;
this.recommendationsEnabled =
recommendationsEnabled;
}
}
Configuration Source Precedence
flowchart TD
A[Application Defaults] --> E[Configuration Resolution]
B[External Properties] --> E
C[Environment Variables] --> E
D[System Properties] --> E
E --> F[Highest-Priority Matching Value]
F --> G[Type Conversion]
G --> H[Injection Point]
Custom Configuration Converter
public final class MoneyConverter
implements Converter<Money> {
@Override
public Money convert(
String value
) {
String[] parts =
value.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException(
"Money must use CURRENCY:AMOUNT"
);
}
return new Money(
Currency.getInstance(
parts[0]
),
new BigDecimal(
parts[1]
)
);
}
}
Property:
order.minimum-total=USD:10.00
Injection:
@Inject
@ConfigProperty(
name = "order.minimum-total"
)
private Money minimumTotal;
REST Client Error Mapping
A response exception mapper converts remote HTTP failures into application-specific exceptions.
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
@Provider
public class InventoryExceptionMapper
implements ResponseExceptionMapper<
RuntimeException
> {
@Override
public RuntimeException toThrowable(
Response response
) {
return switch (
response.getStatus()
) {
case 404 ->
new ProductNotFoundException();
case 429 ->
new InventoryRateLimitedException();
case 503 ->
new InventoryUnavailableException();
default ->
new InventoryClientException(
response.getStatus()
);
};
}
}
Flow
flowchart TD
A[REST Client Response] --> B{HTTP Status}
B -->|2xx| C[Deserialize Success DTO]
B -->|404| D[Product Not Found]
B -->|429| E[Rate Limited]
B -->|503| F[Temporary Unavailable]
B -->|Other Error| G[Generic Client Exception]
REST Client Header Propagation
@RegisterRestClient(
configKey = "inventory-api"
)
@RegisterClientHeaders(
RequestHeaderFactory.class
)
public interface InventoryClient {
}
@ApplicationScoped
public class RequestHeaderFactory
implements ClientHeadersFactory {
@Override
public MultivaluedMap<String, String>
update(
MultivaluedMap<
String,
String
> incomingHeaders,
MultivaluedMap<
String,
String
> outgoingHeaders
) {
MultivaluedMap<String, String>
result =
new MultivaluedHashMap<>();
copyIfPresent(
incomingHeaders,
result,
"Authorization"
);
copyIfPresent(
incomingHeaders,
result,
"X-Correlation-ID"
);
return result;
}
}
Security Warning
Do not automatically propagate every incoming header.
Use an allowlist.
Combined Fault-Tolerance Example
@ApplicationScoped
public class InventoryGateway {
private final InventoryClient client;
@Inject
public InventoryGateway(
@RestClient
InventoryClient client
) {
this.client = client;
}
@Timeout(1500)
@Retry(
maxRetries = 2,
delay = 200,
jitter = 100,
retryOn = {
ProcessingException.class,
InventoryUnavailableException.class
},
abortOn = {
ProductNotFoundException.class
}
)
@CircuitBreaker(
requestVolumeThreshold = 20,
failureRatio = 0.5,
delay = 5000,
successThreshold = 3
)
@Bulkhead(
value = 20,
waitingTaskQueue = 40
)
@Fallback(
fallbackMethod =
"fallbackInventory"
)
public InventoryResponse findInventory(
String productCode
) {
return client.findInventory(
productCode
);
}
InventoryResponse fallbackInventory(
String productCode
) {
return InventoryResponse
.temporarilyUnavailable(
productCode
);
}
}
Resilience Policy Interaction
flowchart TD
A[Caller] --> B[Bulkhead Admission]
B --> C[Circuit Breaker Check]
C --> D[Retry Policy]
D --> E[Timeout Policy]
E --> F[Remote Invocation]
F --> G{Outcome}
G -->|Success| H[Return]
G -->|Transient Failure| D
G -->|Final Failure| I[Fallback]
The exact interceptor order is governed by the specification and runtime behavior. Developers should test the effective behavior rather than assume an intuitive order.
Timeout Budget Design
Assume the API has a total response target of:
3 seconds
The service calls two downstream systems.
Gateway overhead: 200 ms
Database work: 400 ms
Inventory budget: 800 ms
Payment budget: 1,200 ms
Response serialization: 200 ms
Safety margin: 200 ms
Budget Flow
flowchart LR
A[3000 ms Total] --> B[200 Gateway]
B --> C[400 Database]
C --> D[800 Inventory]
D --> E[1200 Payment]
E --> F[200 Serialization]
F --> G[200 Safety Margin]
Common Mistake
Configuring each downstream timeout to the complete endpoint timeout.
That allows the total request to exceed its latency objective.
Retry Amplification
Assume:
- Gateway retries twice.
- Order service retries twice.
- Inventory client retries twice.
Maximum downstream attempts can multiply.
3 × 3 × 3 = 27 attempts
Retry Amplification Diagram
flowchart TD
A[One Client Request] --> B[Gateway Attempts 3 Times]
B --> C[Order Service Attempts 3 Times]
C --> D[Inventory Client Attempts 3 Times]
D --> E[Up to 27 Inventory Calls]
Best Practices
- Define retry ownership.
- Avoid retries at every layer.
- Use retry budgets.
- Add jitter.
- Respect rate-limit guidance.
- Monitor retry count.
- Stop when the request deadline is nearly exhausted.
Circuit Breaker Decision Matrix
| Condition | Suggested Behavior |
|---|---|
| Dependency healthy | Circuit closed |
| Failure ratio exceeded | Open circuit |
| Open delay elapsed | Half-open test |
| Test calls succeed | Close circuit |
| Test call fails | Reopen circuit |
| Invalid client request | Do not count as dependency failure |
| Authentication failure | Usually do not retry |
| Temporary service failure | May count as failure |
Bulkhead Capacity Planning
Application request threads: 200
Inventory bulkhead: 30
Payment bulkhead: 20
Notification bulkhead: 10
Database connection pool: 40
Capacity Rule
Bulkhead capacity should not exceed the actual capacity of:
- Remote connection pool
- Database pool
- Worker executor
- Downstream rate limit
- CPU
- Memory
Safe and Unsafe Fallbacks
| Operation | Safer Fallback | Dangerous Fallback |
|---|---|---|
| Recommendations | Empty recommendation list | Invented products |
| Profile image | Default image | Another user's image |
| Currency reference | Recent cached value with timestamp | Silent fabricated rate |
| Account balance | Explicit unavailable status | Zero balance |
| Payment authorization | Fail closed | Report success |
| Inventory | Unknown availability | Assume available |
| Audit authorization | Deny access | Allow access |
Health Check Design Matrix
| Check | Liveness | Readiness | Startup |
|---|---|---|---|
| Application deadlock indicator | Yes | Possibly | No |
| Required database available | Usually no | Yes | Possibly |
| Optional analytics service | No | Usually no | No |
| Configuration initialized | No | Yes | Yes |
| Required model loaded | No | Yes | Yes |
| Temporary downstream outage | No | Depends | No |
| Internal unrecoverable corruption | Yes | Yes | Possibly |
Health Response Example
{
"status": "UP",
"checks": [
{
"name": "database-readiness",
"status": "UP",
"data": {
"database": "PostgreSQL"
}
},
{
"name": "configuration-readiness",
"status": "UP"
}
]
}
Security Note
Do not expose:
- Credentials
- Connection strings
- Internal hostnames
- Stack traces
- Sensitive dependency details
Kubernetes Probe Mapping
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
startupProbe:
httpGet:
path: /health/started
port: 8080
failureThreshold: 30
periodSeconds: 5
Exact endpoint prefixes can depend on runtime configuration.
OpenAPI Security Scheme Example
@OpenAPIDefinition(
info = @Info(
title = "Claims API",
version = "1.0",
description =
"Claims processing operations"
),
security = {
@SecurityRequirement(
name = "bearerAuth"
)
}
)
@SecurityScheme(
securitySchemeName = "bearerAuth",
type =
SecuritySchemeType.HTTP,
scheme = "bearer",
bearerFormat = "JWT"
)
public class ClaimsApplication
extends Application {
}
Protected operation:
@GET
@Path("/{claimId}")
@SecurityRequirement(
name = "bearerAuth"
)
@RolesAllowed({
"CLAIM_VIEWER",
"CLAIM_ADJUSTER"
})
public ClaimResponse findClaim(
@PathParam("claimId")
Long claimId
) {
return claimService.findClaim(
claimId
);
}
JWT Claims Example
@Path("/identity")
public class IdentityResource {
@Inject
private JsonWebToken token;
@GET
public IdentityResponse identity() {
return new IdentityResponse(
token.getSubject(),
token.getIssuer(),
token.getGroups(),
token.getExpirationTime()
);
}
}
Important Claims
sub: caller identityiss: token issueraud: intended audienceexp: expirationiat: issue timejti: token identifiergroups: role or group information
Distributed Trace Example
sequenceDiagram
participant Gateway
participant OrderService
participant InventoryService
participant PaymentService
participant Database
Gateway->>OrderService: Trace ID abc
OrderService->>Database: Span order-db
Database-->>OrderService: Result
OrderService->>InventoryService: Trace ID abc
InventoryService-->>OrderService: Inventory result
OrderService->>PaymentService: Trace ID abc
PaymentService-->>OrderService: Payment result
OrderService-->>Gateway: Response
All spans can be viewed as one distributed trace.
Observability Signals
Traces
Explain how one request moved through the system.
Metrics
Show aggregated behavior over time.
Examples:
- Request count
- Error rate
- Latency distribution
- Retry count
- Circuit-breaker state
- Bulkhead rejection count
- Queue depth
- JVM memory
- CPU
Logs
Provide event details.
Useful fields:
- Timestamp
- Severity
- Service name
- Trace ID
- Span ID
- Correlation ID
- Error code
- Business identifier where safe
Golden Signals
Monitor:
- Latency
- Traffic
- Errors
- Saturation
Diagram
flowchart TD
A[Service Monitoring] --> B[Latency]
A --> C[Traffic]
A --> D[Errors]
A --> E[Saturation]
B --> F[Response Time]
C --> G[Request Rate]
D --> H[Failure Rate]
E --> I[Threads Pools CPU Memory]
Production Microservice Architecture
flowchart TD
A[Client] --> B[API Gateway]
B --> C[JWT Validation]
C --> D[Order Service]
D --> E[MicroProfile Config]
D --> F[Fault-Tolerant Inventory Client]
D --> G[Fault-Tolerant Payment Client]
D --> H[Order Database]
F --> I[Inventory Service]
G --> J[Payment Service]
D --> K[Health Checks]
D --> L[OpenAPI]
D --> M[Telemetry]
M --> N[Observability Platform]
Graceful Degradation
flowchart TD
A[Order Page Request] --> B[Load Core Order Data]
B --> C{Core Data Available?}
C -->|No| D[Return Service Error]
C -->|Yes| E[Load Recommendations]
E --> F{Recommendation Service Available?}
F -->|Yes| G[Return Order and Recommendations]
F -->|No| H[Return Order Without Recommendations]
Core business data remains required.
Optional personalization can degrade safely.
Common MicroProfile Mistakes
- Treating MicroProfile as a Jakarta EE replacement.
- Hardcoding configuration.
- Logging secrets.
- Assuming all configuration refreshes automatically.
- Calling REST services without timeouts.
- Retrying non-idempotent operations.
- Retrying invalid requests.
- Retrying at every architecture layer.
- Omitting retry jitter.
- Using a fallback that changes business meaning.
- Configuring an oversized bulkhead.
- Counting every client error as a circuit failure.
- Making liveness depend on every downstream system.
- Running slow health checks.
- Exposing internal details through health endpoints.
- Allowing OpenAPI documentation to become stale.
- Trusting JWT claims without validation.
- Logging full access tokens.
- Collecting telemetry without trace propagation.
- Using annotations without load and failure testing.
Senior MicroProfile Checklist
Validate Configuration
│
▼
Define Client Contract
│
▼
Set Connection and Read Timeouts
│
▼
Classify Retriable Failures
│
▼
Use Bounded Retry and Jitter
│
▼
Configure Circuit Breaker
│
▼
Protect Capacity with Bulkhead
│
▼
Define Correct Fallback
│
▼
Add Health and Telemetry
│
▼
Run Failure Tests
Interview Quick Revision
| Concept | Purpose |
|---|---|
| MicroProfile | Cloud-native Java specifications |
| Config | Externalized configuration |
| ConfigSource | Supplies configuration values |
| REST Client | Type-safe remote API client |
| Fault Tolerance | Resilience policies |
@Retry |
Repeat transiently failed operation |
@Timeout |
Limit invocation duration |
@CircuitBreaker |
Stop calls to failing dependency |
@Bulkhead |
Isolate concurrency and capacity |
@Fallback |
Provide safe degraded behavior |
| Health | Runtime health reporting |
| Liveness | Determines whether process should restart |
| Readiness | Determines whether traffic should be routed |
| OpenAPI | API contract documentation |
| Telemetry | Traces, metrics, and logs |
Key Takeaways
- Eclipse MicroProfile standardizes cloud-native capabilities for Java microservices.
- MicroProfile complements Jakarta EE rather than replacing it.
- MicroProfile Config merges values from multiple configuration sources into one logical view.
- Environment-specific configuration should remain outside application artifacts.
- Mandatory configuration should be validated during startup.
- MicroProfile REST Client provides type-safe remote API interfaces using Jakarta REST annotations.
- Remote-service contracts should use dedicated integration DTOs.
- Every remote call should have a bounded timeout.
- Retries should apply only to transient failures and safe operations.
- Retry delay and jitter help prevent retry storms.
- Circuit breakers stop repeated calls to failing dependencies and support faster failure.
- Bulkheads isolate resource exhaustion so one dependency does not consume all capacity.
- Fallbacks must preserve business correctness and communicate degraded behavior clearly.
- Liveness determines whether a process should remain running.
- Readiness determines whether an instance should receive traffic.
- Startup checks protect slow-starting applications from premature liveness failure.
- Health checks should remain fast and should not expose sensitive infrastructure details.
- MicroProfile OpenAPI creates a standardized, discoverable API description.
- API documentation must be verified against actual runtime behavior.
- MicroProfile JWT integrates token-based identities and groups with application security.
- JWT signature, issuer, audience, expiration, and required claims must be validated.
- MicroProfile Telemetry supports cross-service observability through traces, metrics, and logs.
- Retry amplification, timeout budgets, resource pools, and downstream limits must be considered together.
- Senior developers test resilience policies through latency injection, dependency failure, concurrency pressure, and production-scale load.