Knock rests on a simple principle: implement MicroProfile Health 4.0 with the JDK and Jakarta specs alone. No third-party library, no reflection-heavy machinery, no platform-thread pool. This page documents the implementation choices that uphold that discipline.
Module architecture
| Module | Responsibility |
|---|---|
|
Vidocq fork of the MicroProfile Health API, repackaged with a |
|
Knock SPI: |
|
Standalone runtime: registry, aggregator, JSON-P serialiser, response builder, |
|
CDI Build Compatible Extension + auto-registration on Vauban. |
|
Jakarta REST resource exposing |
|
Official MicroProfile Health 4.0 TCK runner. |
Runtime flow
-
HealthCheckRegistry.getChecks(ProbeType)returns the checks for the requested probe. -
Each
HealthCheck.call()runs on its own virtual thread. -
KnockAggregatorreduces the responses to aHealthSnapshot(spec §3.2 rules). -
KnockJsonSerializerrenders the snapshot through Jakarta JSON-P. -
KnockHealthServicewraps the result in aHealthReport(HTTP status + payload).
Core classes
| Class | Role |
|---|---|
|
|
|
Applies the §3.2 aggregation rule and folds exceptions into |
|
Builds the response with Jakarta JSON-P ( |
|
|
|
|
|
Immutable aggregation result: probe type, overall status, checks list. |
|
Public facade tying registry + aggregator + serialiser together. |
|
HTTP response wrapper: status code + serialised JSON. |
CDI discovery
knock-cdi-vauban ships two internal pieces:
-
HealthCheckCdiExtension— a Build Compatible Extension validating the single-probe-qualifier rule at build time (spec §4.2); -
HealthCheckRegistrar— a CDI bean that, on@Initialized(ApplicationScoped.class), registers each discoveredHealthCheckinto the registry (spec §4.1).
Discovery uses Vauban’s BCE pipeline — no runtime classpath scanning, no setAccessible(true).
JAX-RS resource
KnockHealthResource is annotated @Path("/health") and exposes the four endpoints. Each method maps its path to a ProbeType, calls KnockHealthService.report(…), and returns a Response with the snapshot’s HTTP status. The resource imports only jakarta.ws.rs — never a Cassini-internal package.
Threading model
All check execution goes through Executors.newVirtualThreadPerTaskExecutor() (JEP 444). Consequences:
-
a slow check never blocks the rest of a probe group;
-
no platform-thread pool, no bounded queue, no
ThreadLocal; -
shared state (the registry) uses
ConcurrentHashMapexclusively.
JPMS workaround (ADR-001)
The upstream MicroProfile Health API jar ships without a module-info.class, which breaks strict JPMS and jlink. Knock forks it as knock-mp-health-api and rebuilds it with a proper module descriptor. To keep the descriptor from interfering with annotation processing, the module-info.java lives in src/main/module-info/ and is compiled in a dedicated phase at prepare-package. The SPI is registered twice — through JPMS provides and META-INF/services — so the implementation resolves on both the module path and the classpath. Full rationale: docs/adr/ADR-001-jpms-workaround-microprofile-health.md.
AOT compatibility
No dynamic proxy generation, no setAccessible(true), no hot reflection in the request path. Knock is native-image friendly and works inside a minimal jlink image.
Sources
-
knock-core — registry, aggregator, serialiser, facade.
-
knock-cdi-vauban — CDI discovery.
-
knock-jaxrs — REST endpoints.
-
ADR-001 — JPMS workaround.