ediverse Explore the platform

Spotlight PEPPOL BIS Billing 3.0 The EU e-invoicing mandate is here — France Sept 2026, Belgium Jan 2026, Germany 2025.

Health Check

The endpoint everyone thinks is trivial — and which, badly designed, breaks the auto-healing infrastructure. Liveness, readiness, startup: three different questions.

Problem

An EDI service starts by loading 200 MB of EDIFACT schemas, establishes a Kafka and Redis connection. During this 15s warmup, Kubernetes sends a GET / returning 200: the Pod is marked Ready and starts receiving traffic. The first requests crash with NPE "schemas not loaded". Worse: in production, a corrupted schema crashes the parser on every request, but the process stays alive — Kubernetes never kills the Pod because /health always returns 200. The Pod is a zombie.

Forces

  • Several different questions: is the process running? Can it accept traffic now? Has it finished booting?
  • The orchestrator reacts differently per answer. Liveness fail → restart Pod. Readiness fail → remove from LB. Startup fail → wait longer.
  • Health must be cheap. Polled every 5-10s per Pod. No heavy DB query.
  • Health can lie. If we test "can I ping Kafka", a downstream partner down makes us say ready=false. Distinguish critical vs optional dependency.

Solution

Expose three distinct endpoints: /health/live (the process is alive — minimal test, touches no dependency, just "I can answer HTTP"), /health/ready (ready to receive traffic — check critical dependencies: DB, broker, schemas loaded), /health/startup (boot complete — useful for slow-starting apps). Response format: follow the IETF Health Check Response Format draft or Microsoft Health Endpoint. On Kubernetes: configure livenessProbe, readinessProbe, startupProbe with right parameters (periodSeconds, failureThreshold).

Three distinct endpoints (RFC Health Check Response Format):

  GET /health/live    → liveness
    200 = "process alive, do not kill"
    503 = "process zombie, restart"

  GET /health/ready   → readiness
    200 = "ready to receive traffic"
    503 = "warmup / dependency down / drain"
    → on 503, LB removes the Pod from round-robin

  GET /health/startup → startup
    200 = "initialisation complete"
    503 = "still loading EDIFACT schemas"
    → blocks other probes during boot

  RFC JSON response:
  {
    "status": "pass" | "warn" | "fail",
    "version": "1.4.2",
    "checks": {
      "kafka": { "status": "pass", "time": "2026-05-16T08:30:00Z" },
      "redis": { "status": "pass" },
      "as2.walmart": { "status": "warn", "observedValue": 12s }
    }
  }

EDI implementation

Concrete case: EDIFACT parser service in Spring Boot with Spring Boot Actuator. /actuator/health/liveness returns 200 except if the process is in a non-recoverable state (OutOfMemoryError, detected deadlock). /actuator/health/readiness checks: Kafka producer connection, Redis cache connection, EDIFACT schemas D.96A/D.01B/D.24A loaded, Circuit Breaker state to Walmart (if open for > 30 min → warn, else pass). /actuator/health/startup returns 503 until schemas are loaded (up to 90s). On Kubernetes, startupProbe.failureThreshold=30 × periodSeconds=5s = 2.5 min grace period before abandon. For PEPPOL Access Point, health must also check AS4 certificate rotation — an expired cert makes the AP unusable even if the process runs.

Anti-patterns

  • One single /health. Mixing liveness and readiness means an external dependency down restarts the Pod in a loop (which helps nothing).
  • Health that runs business logic. "I test by sending a real INVOIC to Walmart": pollutes the partner audit log, may breach SLA.
  • Health hidden behind auth. The kubelet cannot easily authenticate. Expose with internal network policy without auth.
  • Health that queries every dependency in cascade. If one dependency check takes 30s, the health times out, the Pod is wrongly restarted.
  • Readiness cascade. Service A ready=false if service B ready=false: a failure propagates backwards. Distinguish critical dependency (fail if missing) vs graceful degradation.

Sources