Skip to content
Eclipse MicroProfile — Cloud-Native Java Explained

Eclipse MicroProfile — Cloud-Native Java Explained

DodaTech Updated Jun 15, 2026 6 min read

Eclipse MicroProfile is a set of Jakarta EE specifications optimized for microservices architectures, providing standardized APIs for configuration, fault tolerance, health checks, OpenAPI, and REST client communication in cloud-native Java applications.

Why MicroProfile Matters

MicroProfile fills the gap between Jakarta EE (enterprise monoliths) and Spring Cloud (framework-specific). It’s a vendor-neutral standard for microservices — your services run on any compliant runtime (Open Liberty, Quarkus, Payara Micro, WildFly). DodaTech uses MicroProfile on Quarkus for services that need fast startup times and low memory footprint, critical for containerized deployments in Kubernetes.

MicroProfile Architecture

    graph TD
    subgraph ServiceA[<b>MicroProfile Service</b>]
        Config[Config<br/>Externalized configuration]
        FT[Fault Tolerance<br/>Retry, CircuitBreaker, Fallback]
        Health[Health<br/>Liveness & Readiness]
        OpenAPI[OpenAPI<br/>API documentation]
        RC[Rest Client<br/>Type-safe HTTP calls]
        Metrics[Metrics<br/>Prometheus integration]
        JWT[JWT Propagation<br/>Security]
    end
    ServiceA --> ConfigServer[Config Server]
    ServiceA --> ServiceB[Other MicroProfile Service]
    ServiceA --> K8s[Kubernetes<br/>Probes]
    ServiceA --> Prometheus[Prometheus / Grafana]
    K8s --> Health
    style MicroProfile fill:#3b82f6,color:#fff
  

Config — Externalized Configuration

MicroProfile Config externalizes configuration so the same artifact can run in dev, staging, and production without rebuilding.

<!-- Maven dependency -->
<dependency>
    <groupId>org.eclipse.microprofile.config</groupId>
    <artifactId>microprofile-config-api</artifactId>
    <version>3.0</version>
</dependency>
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.inject.Inject;

@ApplicationScoped
public class DatabaseConfig {

    @Inject
    @ConfigProperty(name = "db.url")
    private String dbUrl;

    @Inject
    @ConfigProperty(name = "db.username")
    private String username;

    @Inject
    @ConfigProperty(name = "db.password")
    private String password;

    @Inject
    @ConfigProperty(name = "db.pool.size", defaultValue = "10")
    private int poolSize;

    public void printConfig() {
        System.out.println("Database URL: " + dbUrl);
        System.out.println("Username: " + username);
        System.out.println("Pool size: " + poolSize);
    }
}
# src/main/resources/META-INF/microprofile-config.properties
db.url=jdbc:postgresql://localhost:5432/dodatech
db.username=app_user
db.password=${DB_PASSWORD}
db.pool.size=20
server.port=8080
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

@Path("/config")
@Produces(MediaType.APPLICATION_JSON)
public class ConfigResource {

    @Inject
    private DatabaseConfig dbConfig;

    @GET
    @Path("/db")
    public Response showDbConfig() {
        dbConfig.printConfig();
        return Response.ok("Config logged").build();
    }

    @GET
    @Path("/all")
    public Response getAllConfig() {
        Config config = ConfigProvider.getConfig();
        return Response.ok(config.getConfigValue("server.port").getValue()).build();
    }
}

Expected output when hitting GET /config/db:

Database URL: jdbc:postgresql://localhost:5432/dodatech
Username: app_user
Pool size: 20

Fault Tolerance — Resilient Microservices

MicroProfile Fault Tolerance provides annotations for retries, circuit breakers, fallbacks, bulkheads, and timeouts.

import jakarta.ws.rs.*;
import org.eclipse.microprofile.faulttolerance.*;
import java.util.concurrent.*;
import java.util.*;

@Path("/api/devices")
@ApplicationScoped
public class DeviceResource {

    private int failureCount = 0;

    @GET
    @Retry(maxRetries = 3, delay = 500, delayUnit = ChronoUnit.MILLIS)
    @CircuitBreaker(
        requestVolumeThreshold = 5,
        failureRatio = 0.5,
        delay = 10000, delayUnit = ChronoUnit.MILLIS,
        successThreshold = 3
    )
    @Fallback(fallbackMethod = "getDefaultDevices")
    @Bulkhead(value = 5, waitingTaskQueue = 10)
    public Response getDevices() {
        failureCount++;
        if (failureCount % 3 != 0) {
            throw new RuntimeException("Simulated DB failure #" + failureCount);
        }
        return Response.ok(Arrays.asList(
            new DeviceDTO("Sensor-A1", "temperature"),
            new DeviceDTO("Sensor-B2", "humidity")
        )).build();
    }

    public Response getDefaultDevices() {
        return Response.ok(Arrays.asList(
            new DeviceDTO("Fallback-Device", "offline")
        )).build();
    }

    @GET
    @Path("/timeout")
    @Timeout(value = 2, unit = ChronoUnit.SECONDS)
    public Response slowOperation() throws Exception {
        Thread.sleep(5000); // Simulates a timeout
        return Response.ok("Completed").build();
    }
}

Expected output on first two requests (failures):

500 Internal Server Error — Simulated DB failure #1

Expected output on third request (success):

[{"name":"Sensor-A1","type":"temperature"},{"name":"Sensor-B2","type":"humidity"}]

Expected output when circuit breaker is open:

[{"name":"Fallback-Device","type":"offline"}]

Health — Kubernetes Readiness and Liveness

MicroProfile Health provides endpoints that Kubernetes uses for pod lifecycle management.

import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.*;

@Health
@ApplicationScoped
public class DatabaseHealthCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        try {
            boolean dbConnected = testDatabaseConnection();
            if (dbConnected) {
                return HealthCheckResponse.named("database")
                    .up()
                    .withData("type", "PostgreSQL")
                    .build();
            }
            return HealthCheckResponse.named("database")
                .down()
                .withData("error", "Cannot connect to database")
                .build();
        } catch (Exception e) {
            return HealthCheckResponse.named("database")
                .down()
                .withData("error", e.getMessage())
                .build();
        }
    }

    private boolean testDatabaseConnection() {
        // In real code, attempt a JDBC connection
        return Math.random() > 0.3; // 70% success rate for demo
    }
}
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.*;

@Readiness
@ApplicationScoped
public class ReadyHealthCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse.named("ready")
            .up()
            .withData("status", "Service ready to accept traffic")
            .build();
    }
}

Expected output for GET /health/ready:

{
  "status": "UP",
  "checks": [
    {
      "name": "ready",
      "status": "UP",
      "data": {
        "status": "Service ready to accept traffic"
      }
    }
  ]
}

OpenAPI — API Documentation

MicroProfile OpenAPI generates OpenAPI 3.0 documentation from annotations, viewable in Swagger UI.

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import org.eclipse.microprofile.openapi.annotations.*;
import org.eclipse.microprofile.openapi.annotations.info.*;
import org.eclipse.microprofile.openapi.annotations.media.*;
import org.eclipse.microprofile.openapi.annotations.responses.*;

@Path("/api/v1/devices")
@Tag(name = "Devices", description = "Device management operations")
public class DeviceApi {

    @GET
    @Operation(summary = "List all devices",
               description = "Returns a paginated list of registered devices")
    @APIResponse(responseCode = "200",
                 description = "Successful retrieval",
                 content = @Content(mediaType = "application/json",
                     schema = @Schema(implementation = DeviceDTO.class)))
    public Response listDevices(
            @Parameter(description = "Page number (0-based)")
            @QueryParam("page") @DefaultValue("0") int page,
            @Parameter(description = "Page size")
            @QueryParam("size") @DefaultValue("20") int size) {
        return Response.ok(new ArrayList<>()).build();
    }

    @POST
    @Operation(summary = "Register a new device")
    @APIResponse(responseCode = "201", description = "Device created")
    @APIResponse(responseCode = "400", description = "Invalid input")
    public Response createDevice(DeviceDTO device) {
        return Response.status(Response.Status.CREATED).entity(device).build();
    }
}

Generated OpenAPI document available at GET /openapi.

Rest Client — Type-safe HTTP Calls

MicroProfile Rest Client lets you call other services with interfaces (like Feign in Spring Cloud).

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import java.util.*;

@RegisterRestClient(configKey = "device-service-api")
@Path("/api/v1/devices")
public interface DeviceServiceClient {

    @GET
    List<DeviceDTO> getAllDevices(
        @QueryParam("page") @DefaultValue("0") int page,
        @QueryParam("size") @DefaultValue("20") int size
    );

    @GET
    @Path("/{id}")
    DeviceDTO getDevice(@PathParam("id") Long id);

    @POST
    Response createDevice(DeviceDTO device);
}
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.rest.client.inject.RestClient;

@ApplicationScoped
public class DeviceAggregator {

    @Inject
    @RestClient
    private DeviceServiceClient deviceClient;

    public List<DeviceDTO> getActiveDevices() {
        return deviceClient.getAllDevices(0, 100);
    }

    public DeviceDTO getDeviceDetails(Long id) {
        return deviceClient.getDevice(id);
    }
}
# microprofile-config.properties
device-service-api/mp-rest/url=http://device-service:8080
device-service-api/mp-rest/connectTimeout=5000
device-service-api/mp-rest/readTimeout=10000

Running MicroProfile on Quarkus

<!-- pom.xml dependency -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-micrometer</artifactId>
</dependency>
# Create a MicroProfile app with Quarkus
mvn io.quarkus.platform:quarkus-maven-plugin:3.2.0:create \
    -DprojectGroupId=com.dodatech \
    -DprojectArtifactId=device-service \
    -Dextensions="resteasy-reactive,smallrye-health,smallrye-fault-tolerance,smallrye-openapi"

cd device-service

# Dev mode with live reload
./mvnw quarkus:dev

# Build native executable (fast startup, low memory)
./mvnw package -Pnative

# Run
./target/device-service-1.0.0-runner

Common Mistakes

  1. Not externalizing configuration: Hardcoded values in code mean rebuilding for each environment. Always use @ConfigProperty with microprofile-config.properties.
  2. Ignoring fallback methods: @Retry without @Fallback means failures propagate after exhausting retries. Always provide a graceful fallback.
  3. Missing health checks in Kubernetes: Without liveness and readiness probes, Kubernetes can’t restart unhealthy pods or route traffic correctly.
  4. Forgetting Rest Client config keys: The configKey in @RegisterRestClient must match the properties prefix in microprofile-config.properties.
  5. Not setting timeouts on Rest Clients: Default timeouts can be minutes. Always set connectTimeout and readTimeout to fail fast.

Practice Questions

  1. How does MicroProfile Config externalize configuration?
  2. What does @CircuitBreaker protect against?
  3. What is the difference between @Health and @Readiness?
  4. How does MicroProfile Rest Client compare to JAX-RS?
  5. What is the advantage of native compilation with Quarkus?

Answers:

  1. Configuration sources are layered: system properties, environment variables, microprofile-config.properties, and custom ConfigSource implementations. The first match wins.
  2. @CircuitBreaker prevents cascading failures. When failures exceed a threshold, the circuit opens and subsequent calls fail fast (or use fallback). After a delay, it tries again.
  3. @Health (liveness) tells Kubernetes the pod is alive and should be restarted if failing. @Readiness tells Kubernetes the pod can accept traffic.
  4. Rest Client uses JAX-RS annotations on an interface to generate HTTP calls automatically. You call methods like local code instead of manually building HTTP requests.
  5. Native compilation (GraalVM) produces a binary that starts in milliseconds and uses minimal memory — ideal for serverless and auto-scaling containers.

Mini Project: MicroProfile Device Service

Build a cloud-native device service:

  1. Use MicroProfile Config for database and service URLs
  2. Implement health checks (liveness and readiness) for database connectivity
  3. Add fault tolerance with @Retry, @CircuitBreaker, and @Fallback
  4. Generate OpenAPI documentation with MicroProfile OpenAPI
  5. Create a Rest Client to call an external inventory service
  6. Run on Quarkus in dev mode, then build a native executable
  7. Test that Kubernetes health probes work correctly

This pattern is used in DodaTech’s containerized microservices architecture running on Kubernetes.

Related topics: Java, Spring, Jakarta EE, API, CI/CD

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro