Selected Work / Project

Java 21 Spring Boot Certificate Control Plane API for IoT Fleets

Author: Thomas Bonderup Published: Focus area: Certificate Lifecycle Control Plane

Java 21 Spring Boot API for IoT certificate lifecycle management with tenant-aware REST APIs, Keycloak JWT security, PostgreSQL/Flyway, Kafka outbox events, OpenAPI/Postman docs, and 88 tests.

Why this project matters

Context over abstraction

These projects show the technical environment, delivery pressure, and system constraints surrounding the work.

Decisions should be traceable

The useful part is understanding which architecture or implementation choices mattered and why they were taken.

Proof should be reusable

A strong project write-up leaves behind code, patterns, and lessons that another engineer can evaluate quickly.

Certificate Lifecycle Control Plane Java Spring Boot IoT Security
Java 21 Spring Boot API for IoT certificate lifecycle management with tenant-aware REST APIs, Keycloak JWT security, PostgreSQL/Flyway, Kafka outbox events, OpenAPI/Postman docs, and 88 tests.

Project snapshot

Challenge

Embedded device fleets need a secure, tenant-aware control plane for certificate inventory, asset relationships, expiry risk, renewal workflow state, audit evidence, and operator-facing APIs without mixing lifecycle management into the audit execution engine.

Constraints

  • Keep certificate lifecycle, asset relationships, tenant boundaries, and renewal workflow state in a control plane rather than the audit execution engine.
  • Make security and operations reviewable through JWT scope checks, admin guards, Flyway/PostgreSQL schema ownership, Kafka integration, OpenAPI, Postman, and tests.

Intervention

  • Built Java 21 Spring Boot APIs for certificates, assets, bindings, summary views, expiry risk, attention-needed views, and renewal history.
  • Implemented tenant-aware security, PostgreSQL/Flyway persistence, Kafka evidence and renewal events, outbox-backed renewal publication, OpenAPI/Swagger, Postman workflows, and automated coverage.

Outcomes

  • The project gives the broader audit platform a concrete Java/Spring control-plane slice with clear boundaries around the Scala/ZIO execution engine.
  • The repository now demonstrates secure API design, schema-managed persistence, Kafka/outbox integration, reviewer-friendly docs, and 88 reported tests across web, service, persistence, security, and event behavior.

Context

This project is the Java 21 + Spring Boot control-plane side of the broader IoT audit platform migration described in Migrating an IoT Audit Engine into a Java 21 + Spring Boot Platform.

The public repository is available on GitHub:

ThomasBonderup/certificate-control-plane-api

The problem it solves is operational rather than cryptographic: once an embedded or IoT fleet grows, certificate lifecycle work quickly becomes more than “store a certificate somewhere.” Teams need to know which certificates exist, which tenant owns them, which assets depend on them, which ones are close to expiry, who owns the renewal, what state the renewal is in, and which workflow changes should become durable audit evidence.

The service is intentionally scoped as a control plane, not a certificate parser or audit execution engine. It owns platform state around certificates, assets, bindings, workflow status, tenant boundaries, API documentation, and operational integration. Technical probe execution, TLS validation, finding generation, and report logic remain outside this service.

That separation keeps audit execution and platform workflow concerns in different planes, then makes the boundary clear through APIs, schema migrations, tests, and documentation.

What The API Does

The repository implements a Spring Boot API for certificate lifecycle management in embedded and IoT environments:

  1. Certificate inventory with status, validity window, owner, notes, fingerprint, issuer, serial number, and renewal workflow fields.
  2. Asset inventory with tenant, type, environment, hostname, location, and audit fields.
  3. Certificate-to-asset bindings modeled as first-class records with binding type, endpoint, port, and uniqueness constraints.
  4. Reverse lookup APIs from asset to certificate bindings and bound certificates.
  5. Expiring-soon and attention-needed views for certificates that are expired, blocked, approaching expiry, unowned, or not progressing through renewal.
  6. Summary endpoints for tenant-scoped certificate posture.
  7. Renewal history APIs backed by persisted status-change rows.
  8. Evidence ingestion foundations that accept raw probe evidence and publish envelopes to Kafka.

The controllers are documented with OpenAPI annotations and exposed through local Swagger UI. The committed Postman collection covers token acquisition, authorization failures, certificate/asset/binding flows, and protected actuator endpoints.

Spring Boot Architecture

The implementation uses a conventional but production-minded Spring Boot structure:

  • controller classes own HTTP routes, validation entry points, response codes, Location headers, pagination defaults, and OpenAPI metadata.
  • api records define request and response DTOs with validation annotations and Swagger schema examples.
  • services hold application logic, transaction boundaries, tenant enforcement, renewal workflow rules, outbox writing, and event publishing.
  • repositories use Spring Data JPA derived queries and JPQL for tenant-scoped filters, expiring-soon queries, attention-needed queries, and summary counts.
  • model classes define JPA entities and enums for certificates, assets, bindings, renewal history, evidence envelopes, and outbox events.
  • common and config provide shared API errors, JSON security errors, tenant/user providers, pageable sanitization, OpenAPI setup, scheduling, and Spring Security.

This makes the project easy to review because the core responsibilities are explicit: request shape, security, business rule, persistence model, integration point, and test coverage all have visible homes.

Security, Tenancy, And Authorization

Security is not only described in the README; it is implemented and tested:

  • The service runs as a stateless OAuth2 resource server.
  • Local development uses Keycloak with a rendered realm template and JWTs that include a tenantId claim.
  • Spring Security maps scopes into authorities and extracts Keycloak realm/client roles into ROLE_* authorities.
  • GET /api/** requires controlplane.read; POST, PATCH, and DELETE /api/** require controlplane.write.
  • Actuator health and info are public; metrics and Prometheus require controlplane.read.
  • @EnableMethodSecurity enables method-level authorization.
  • Certificate and asset delete operations are protected with @PreAuthorize("hasRole('ADMIN')").
  • Tenant-aware services use the authenticated tenantId claim as the real boundary.
  • Create requests that carry a tenantId must match the JWT claim.
  • Cross-tenant resource access is intentionally hidden as not found.

The test suite covers the important failure modes: missing token, wrong scope, write token trying to read, read token trying to write, non-admin delete attempts, cross-tenant lookup behavior, and mismatched tenant input.

Persistence And Schema Evolution

The persistence layer is built around PostgreSQL and Flyway rather than ad hoc schema creation:

  • certificates stores certificate metadata, tenant ownership, status, validity timestamps, owner, notes, audit fields, blocked reason, and renewal update timestamp.
  • assets stores tenant-scoped operational assets with type, environment, hostname, location, and audit fields.
  • certificate_bindings models certificate-to-asset relationships with a uniqueness constraint across certificate, asset, binding type, endpoint, and port.
  • certificate_renewal_status_history stores workflow transitions for later inspection.
  • outbox_events stores pending event payloads as PostgreSQL jsonb with aggregate metadata, topic, key, status, and publication timestamps.
  • Hibernate validates the schema at startup with ddl-auto: validate, while Flyway owns schema changes.
  • Indexes support tenant, status, expiry, asset, binding, history, and outbox lookup paths.

That matters because certificate lifecycle platforms live or die on boring correctness: schema ownership, tenant filters, predictable queries, and auditable state transitions.

Kafka, Evidence, And Outbox

The eventing implementation has three useful parts:

  • Raw evidence ingestion accepts probe evidence, maps it into an EvidenceEnvelope, and publishes it to Kafka.
  • Renewal status changes are represented as CertificateRenewalStatusChangedEvent records.
  • Renewal status updates append an outbox event inside the certificate update flow, and an OutboxPublisher publishes pending rows to Kafka and marks them as published.

There is also a Kafka consumer that persists renewal status change events into certificate_renewal_status_history.

The outbox pattern is used here for reliability around renewal workflow events.1 Instead of only updating the certificate row and trying to publish directly to Kafka as a separate best-effort side effect, the service records a pending event in the database as part of the update flow. A publisher can then read pending rows, publish them, and mark them as published. That gives the system a durable handoff point between database state and asynchronous event delivery.

The important nuance: this is an outbox-backed flow for renewal status publication, not a claim that every asynchronous path in the service is outbox-backed. Raw evidence ingestion still publishes directly to Kafka. That distinction keeps the implementation boundary honest while showing where reliability mattered most for lifecycle audit events.

Documentation And Local Developer Experience

The repository is set up to be reviewed, run, and manually exercised:

  • README.md explains the service scope, local setup, Keycloak token flows, tenant enforcement rules, OpenAPI, Swagger UI, Postman usage, actuator endpoints, and security expectations.
  • specs/architecture.md documents the control-plane/execution-plane split, domain model, layered structure, persistence model, and current API surface.
  • docker-compose.yml starts PostgreSQL, Kafka, Keycloak, the API container, and optional Kafka UI with local-only port bindings.
  • .env.example and Postman environment templates are sanitized.
  • Swagger UI is available locally at /swagger-ui.html, backed by /v3/api-docs.
  • The Postman collection includes read/write/admin token flows, no-token demos, read/write failure demos, certificate/asset/binding requests, and protected actuator checks.

That kind of documentation is a delivery signal: the backend is not only implemented; it is packaged so another engineer can evaluate the behavior quickly.

Test Coverage

The automated test suite is one of the strongest parts of the project.

./gradlew test passes, and the generated JUnit XML reports 88 tests across 13 test classes. The coverage is not just happy-path CRUD:

  • MockMvc integration tests for certificates, assets, bindings, audit runs, and authorization behavior.
  • Testcontainers PostgreSQL integration coverage for persistence-backed API behavior.
  • Spring Security test support for JWT scopes, tenant claims, roles, and protected actuator endpoints.
  • Tests for pagination, sorting, malformed Swagger sort fallback, filters, summary counts, expiry windows, attention-needed rules, renewal workflow transitions, and tenant isolation.
  • Service tests for renewal-status outbox writing.
  • Tests for Kafka producer configuration, renewal-status consumer persistence, outbox event writing, and outbox publication ordering.

I describe the test suite by the behavior it proves, not by a headline percentage, because the repository does not currently publish a separate code coverage report.

Outcome

The result is a public backend project that demonstrates:

  • Java 21 and Spring Boot platform delivery.
  • REST API design with validation, response DTOs, pageable/sortable endpoints, and clean HTTP semantics.
  • Spring Security fundamentals with JWT resource server configuration, Keycloak role extraction, scope checks, method-level authorization, and JSON security errors.
  • Tenant-aware service design with explicit claim enforcement and cross-tenant protections.
  • PostgreSQL/Flyway schema ownership with JPA/Hibernate validation.
  • Kafka integration, evidence ingestion foundations, renewal history, and an outbox-backed event flow.
  • OpenAPI, Swagger UI, Postman, Docker Compose, Keycloak local setup, Actuator, Prometheus metrics, and practical developer documentation.
  • A meaningful automated test suite that proves behavior across web, service, persistence, security, and integration boundaries.

For technical context, read the related migration article or inspect the repository directly:

Read the Java migration article or view the public GitHub repository.

References

Footnotes

  1. Chris Richardson, Transactional outbox pattern, Microservices.io.

Thomas Bonderup

Thomas Bonderup

Senior Software Engineer

Specializes in IoT architecture, distributed systems, reliability and observability, edge-to-cloud delivery.

Builder notes and project references

If this portfolio entry feels close to something you're building, let's talk through the implementation details and tradeoffs.

These portfolio entries come from a mix of real delivery work, deep technical explorations, and earlier builder projects. If the constraints, tradeoffs, or implementation choices feel familiar, I can help you compare the next practical move.

Technical scope: IoT architecture, distributed systems, reliability and observability, edge-to-cloud delivery.

Explore related work or start a technical conversation

If this project overlaps with the systems you care about, continue into related work, the CV, or the contact page for hiring and technical follow-up.

Related content

Get in touch about this work

Use this for hiring conversations, collaboration, or technical follow-up.

Prefer direct contact? Call +45 22 39 34 91 or email tb@tbcoding.dk.

Best for hiring conversations, technical discussion, and thoughtful follow-up on published work.

Typical response time: same business day.