CRaC readiness checks
The CRaC panel reviews the host application's Coordinated Restore at Checkpoint readiness. It combines a live runtime-status view with a heuristic readiness advisor. This page lists every check that ships with BootUI today, what it inspects, when it fires, and what to do about it.
Each check is a small class registered in CracCheckRegistry and implemented in CracChecks.java. Both live in the framework-neutral bootui-engine module — CRaC readiness is a Spring-only panel today (see QUARKUS-SUPPORT.md), but its rule engine has no Spring dependency of its own. The list intentionally stays compact and reviewable; adding a new check means adding one focused class plus a registry entry.
What BootUI does
The runtime-status section (always read-only) reports:
- whether the application-facing
org.craccompatibility API is on the classpath (org.crac.Coremarker), - whether the running JVM is a CRaC-capable JDK (such as Azul Zulu CRaC or BellSoft Liberica), detected via the real CRaC implementation (
jdk.crac.Coreorjavax.crac.Core) rather than the no-op shim that ships with stock JDKs, - whether automatic checkpoint-on-refresh is actually active. Spring Framework's
DefaultLifecycleProcessoronly honorsspring.context.checkpoint=onRefreshthroughorg.springframework.core.SpringProperties— a JVM system property (-Dspring.context.checkpoint=onRefresh) or a classpathspring.propertiesfile — never the Spring BootEnvironment(so setting it inapplication.yml/application.properties/an OS environment variable has no effect). BootUI reads the property the same way Spring Framework does, and separately checks whether the BootEnvironmentalso claims the property is set; if the Environment says one thing and the real property says another, a restore caveat flags the mismatch, - whether
spring.context.exit=onRefreshis active. This runs the same lifecycle stop phase and exits without creating a checkpoint, so it is a safe dry run on a regular JDK before using CRIU, - any
-XX:CRaCCheckpointTo/-XX:CRaCRestoreFromJVM arguments, read from theRuntimeMXBeaninput arguments (the same source the JVM Tuning panel uses), - and any checkpoint & restore caveats worth reviewing before a checkpoint: a reminder that CRaC freezes configuration (environment variables, system properties, the active Spring profile) into the image at checkpoint time when checkpoint-on-refresh is actually active, a warning when the Boot
Environmentclaims checkpoint-on-refresh is set but the real,SpringProperties-backed property is not (so no automatic checkpoint will actually be taken), and a note when live connection pools are detected so the backing service is kept reachable at both checkpoint and restore (seeCRAC-POOL-001).
The readiness advisor detects the host application's base package(s) from the @SpringBootApplication configuration via AutoConfigurationPackages, imports the compiled .class files from those packages with ArchUnit's ClassFileImporter, and evaluates every registered check against the imported classes. Importing is bounded to the application's own base package(s) — never the entire classpath — and runs only on demand when the scan action is invoked, recomputing the report (with fresh runtime status) on each request.
When BootUI is installed through bootui-spring-boot-starter, ArchUnit is included transitively so the panel works without an extra application dependency. The advisor is available only when ArchUnit is on the classpath and a base package is resolvable from the running application. If no classes can be imported, the panel degrades to a stable, empty report with an explanatory reason rather than failing.
Four checks also consume live runtime context: CRAC-POOL-001, CRAC-CACHE-001, CRAC-SCHED-001, and CRAC-LIFECYCLE-002. Connection pools, cache managers, and classpath dependency presence are either contributed by Spring Boot auto-configuration or are a runtime/dependency signal rather than something visible in the application's own base package, so those checks read a small read-only runtime inventory — pooled clients (JDBC DataSources, plus R2DBC/Redis/RabbitMQ/Kafka/ Mongo/Cassandra/JMS connection factories and similar), Spring CacheManagers backed by local/in-heap storage, and whether org.crac:crac is on the classpath, plus whether checkpoint-on-refresh is active — instead of imported bytecode. They still trigger only on demand and never open, close, or checkpoint anything. Every other check, including CRAC-POOL-002 (unmanaged HTTP/RPC clients), inspects imported application bytecode like the rest of the catalogue.
What BootUI does not do
- BootUI does not trigger a checkpoint or restore. Taking a checkpoint is a process-level JVM operation; the panel only inspects the running process and the application's bytecode.
- It is not a replacement for an actual checkpoint/restore run on a CRaC-enabled JDK. Static analysis cannot see resources acquired through runtime data, so the checks are heuristic review prompts.
- It does not analyze third-party dependency bytecode; it inspects only the application's own base-package classes.
- It does not modify, compile, or instrument application code; it reads already-compiled bytecode.
Severity scale
Severity reflects the worst plausible impact if the finding is real, not the likelihood:
- CRITICAL — a construct with the most severe checkpoint/restore impact if the finding is real. No active CRaC check currently emits this severity.
- HIGH — a construct that typically blocks a clean checkpoint or leaks/duplicates state across restore (open OS resources, direct network sockets, unmanaged HTTP/RPC clients, connection pools with an open connection, predictable random state, captured secrets).
- MEDIUM — a construct that often misbehaves across restore unless handled (unmanaged threads/pools, fixed-rate scheduled tasks that may burst after restore, captured wall-clock time, captured environment/system configuration, a missing
org.crac:cracdependency). - LOW — a construct that usually needs review but rarely breaks restore on its own.
- INFO — an informational prompt about checkpoint/restore lifecycle participation.
The scan evaluates every registered check, but the panel only lists checks that found something to review. Findings are ordered by importance (CRITICAL, HIGH, MEDIUM, LOW, INFO), then by the number of occurrences, and include up to a handful of sample detail lines.
Network
CRAC-NET-001 — Direct network socket creation must be released at checkpoint
- Severity: HIGH
- Inspects: code that opens network sockets directly — constructors (
new Socket/ServerSocket/DatagramSocket/MulticastSocket) and the NIO static factory methods that are the conventional way to obtain a channel (SocketChannel.open(...),ServerSocketChannel.open(),DatagramChannel.open(...),AsynchronousSocketChannel.open(...),AsynchronousServerSocketChannel.open(...)). - Fires when: an application class constructs or opens one of those sockets/channels outside a managed CRaC lifecycle. Open sockets hold OS file descriptors that CRaC refuses to checkpoint unless they are closed first.
- Exempt when: the call site is inside
beforeCheckpoint/afterRestore/start/stopon a class that already implementsorg.crac.Resource,org.springframework.context.Lifecycle, ororg.springframework.context.SmartLifecycle— i.e. exactly the pattern this check's own recommendation asks for. Without this exemption, following the recommendation (re-opening the socket insideafterRestore()) would keep re-triggering the same finding forever. A socket opened elsewhere in an otherwise-managed class (outside those callback methods) is still flagged. - Recommendation: close the socket in an
org.crac.Resource.beforeCheckpoint()callback and re-open it inafterRestore(), or let a managed component (SpringLifecyclebean, server connector) own the socket so the framework closes it around the checkpoint.
Connection pools
CRAC-POOL-001 — Connection pools must hold no open connection at checkpoint
- Severity: HIGH
- Inspects: live connection pools and pooled clients in the running context — JDBC
DataSources, plus R2DBC/Redis/ RabbitMQ/Kafka/MongoDB/Cassandra/JMS connection factories and similar — detected from the application's beans rather than its bytecode. - Fires when: at least one such pool bean is present. This is the most common real-world cause of a failed checkpoint: a pooled connection that is still open when
spring.context.checkpoint=onRefreshfires holds an OS socket that CRaC refuses to snapshot, so the checkpoint aborts withCheckpointOpenSocketException. The backing service must also be reachable both when the checkpoint is taken and when it is restored. - Recommendation: ensure no pooled connection is open at checkpoint time and keep the database/cache reachable at checkpoint and restore. HikariCP itself does not implement
org.crac.Resource, but Spring Boot 3.2+ suppliesHikariCheckpointRestoreLifecyclewhenorg.crac:cracis present. Setspring.datasource.hikari.allow-pool-suspension=true: Boot then suspends new borrows, evicts connections, waits for closure, and resumes after restore. For other pools/clients, verify the exact version's native support or register anorg.crac.Resourcewrapper.
CRAC-POOL-002 — HTTP/RPC clients hold sockets and threads that must be released at checkpoint
- Severity: HIGH
- Inspects: fields that hold a long-lived HTTP/RPC client with its own connection pool or event-loop threads — the JDK's
java.net.http.HttpClient, Apache HttpClient'sCloseableHttpClient(4.x and 5.x), OkHttp'sOkHttpClient, Reactor Netty'sHttpClient/ConnectionProvider, or gRPC'sManagedChannel. - Fires when: an application class holds one of those client types in a field outside a managed CRaC lifecycle. These clients hold open OS file descriptors and background threads exactly like the raw socket/pool types
CRAC-RES-001already covers, but are easy to miss because the client is typically built once via a builder rather than constructed directly. - Exempt when: the field is declared on a class that already implements
org.crac.Resource,org.springframework.context.Lifecycle, ororg.springframework.context.SmartLifecycle— the same whole-class exemptionCRAC-RES-001uses, since the managed class is expected to close and rebuild the client itself around the checkpoint. - Recommendation: close the client (and its underlying connection pool/event-loop) in an
org.crac.Resource.beforeCheckpoint()callback and rebuild it inafterRestore(), or hold it in a SpringLifecycle/SmartLifecyclebean so the framework stops it before the checkpoint.
CRAC-POOL-003 — Spring HTTP client fields require a restore-aware transport
- Severity: MEDIUM
- Inspects: fields of type Spring
RestClientorWebClient. - Fires when: an unmanaged application class holds one of these facades. The facade itself may share framework-managed resources, so this is deliberately not a HIGH-severity assertion that every field owns a socket.
- Recommendation: verify the underlying
ClientHttpRequestFactory, Reactor NettyConnectionProvider, and event-loop lifecycle. Prefer a Spring-managedReactorResourceFactorywith non-global resources or another lifecycle-managed connector; otherwise close/recreate the transport owner around checkpoint/restore.
Caches
CRAC-CACHE-001 — In-memory caches may hold stale entries after restore
- Severity: LOW
- Inspects: live Spring
CacheManagerbeans backed by local, in-heap storage (for exampleConcurrentMapCacheManageror Caffeine), detected from the application's beans rather than its bytecode. A no-op cache manager holds no entries and is ignored, and well-known remote/external-store-backed managers — currently Spring Data Redis'sRedisCacheManager— are excluded too, because their entries live outside the JVM heap in an external store and are not frozen into the checkpoint image the way a local manager's entries are. - Fires when: at least one non-no-op, local/in-heap
CacheManagerbean is present. Cache entries populated before the checkpoint are frozen into the image and survive into every restored process, where they may be stale (for example expired tokens or other time-sensitive data). Cache managers backed by a remote store are a lower concern and are not reported here. - Recommendation: clear or refresh time-sensitive local caches in an
org.crac.Resource.afterRestore()callback, or use restore-aware expiry, so a restored process does not serve data captured at checkpoint time.
Hazelcast is intentionally not excluded by type: BootUI cannot reliably distinguish an embedded member from a remote client from the CacheManager bean type. Hazelcast has no currently verified CRaC lifecycle hooks in its public core source. Client mode is safer because it can reconnect to an existing cluster; an embedded member must explicitly quiesce/leave and rejoin, and restoring multiple copies of one member checkpoint risks duplicate identity/topology state.
Resources
CRAC-RES-001 — Open resources held in fields must be released at checkpoint
- Severity: HIGH
- Inspects: fields whose type holds an OS resource (sockets, file streams,
FileReader/FileWriter,RandomAccessFile, zip/jar files, NIO channels and selectors,FileLock,WatchService,Process, JDBCConnection) on classes that do not implementorg.crac.Resourceor a SpringLifecycle. - Fires when: such a field exists on an unmanaged class. CRaC cannot snapshot live file descriptors. Auto-configured pools (a HikariCP
DataSource, a Redis client) are the common case and are covered separately byCRAC-POOL-001; long-lived HTTP/RPC clients (HttpClient,OkHttpClient, gRPCManagedChanneland similar) are covered separately byCRAC-POOL-002. - Exempt when: the field is declared on a class that already implements
org.crac.Resource,org.springframework.context.Lifecycle, ororg.springframework.context.SmartLifecycle— the managed class is expected to close and reopen the resource itself around the checkpoint. - Recommendation: implement
org.crac.Resourceand close the resource inbeforeCheckpoint(), re-opening it inafterRestore(); or hold the resource in a SpringLifecycle/SmartLifecyclebean so the framework stops it before the checkpoint. For auto-configured connection pools, seeCRAC-POOL-001.
CRAC-FILE-001 — Direct file handles must be released at checkpoint
- Severity: HIGH
- Inspects: code that opens file handles directly (
new FileInputStream/FileOutputStream/RandomAccessFile/FileReader/FileWriter/ZipFile/JarFile, or theFiles.newInputStream/newOutputStream/newByteChannel/newBufferedReader/newBufferedWriterandFileChannel/AsynchronousFileChannel.openfactory methods). - Fires when: an application class opens one of those file handles outside a managed CRaC lifecycle. An open file holds an OS file descriptor that CRaC refuses to checkpoint, so the checkpoint aborts with
CheckpointOpenFileException. - Exempt when: the call site is inside
beforeCheckpoint/afterRestore/start/stopon a class that already implementsorg.crac.Resource,org.springframework.context.Lifecycle, ororg.springframework.context.SmartLifecycle— reopening the file fromafterRestore()/start()on a managed class is the recommended pattern and is not flagged. A file opened elsewhere in an otherwise-managed class is still flagged. - Recommendation: close the file before the checkpoint (try-with-resources for short-lived handles, or release it in an
org.crac.Resource.beforeCheckpoint()callback and re-open it inafterRestore()), or let a managed component own the file so the framework closes it around the checkpoint.
Threads
CRAC-THREAD-001 — Threads or executor pools created outside the Spring lifecycle
- Severity: MEDIUM
- Inspects: threads, timers, and executor pools created directly (
new Thread/Timer/ThreadPoolExecutor,Executorsfactories,Thread.startVirtualThread(), andThread.ofVirtual()/Thread.ofPlatform()builders when they callstart(...)or escape as aThreadFactory). - Fires when: an application class creates one of those directly outside a managed CRaC lifecycle. CRIU (which CRaC is built on) freezes every OS thread in the process to take a checkpoint — no thread "keeps running through" it, and none is skipped. The actual risk is in what happens just before that freeze: Spring stops
SmartLifecyclebeans gracefully before the checkpoint is even requested, so managed background work reaches a quiescent, consistent state first. A raw thread or executor has no such hook, so CRIU captures it abruptly mid-execution — in whatever state it happens to be in at that instant — which risks deadlocks, stale locks, or inconsistent state on restore. - Does not fire when: a builder only creates an
unstarted(...)thread object. That is ordinary heap state, not a running unmanaged thread. Virtual threads executing native or pinned work still need to quiesce; JDK 24's JEP 491 reducessynchronizedpinning but does not make arbitrary running work lifecycle-managed. - Exempt when: the call site is inside
beforeCheckpoint/afterRestore/start/stopon a class that already implementsorg.crac.Resource,org.springframework.context.Lifecycle, ororg.springframework.context.SmartLifecycle— restarting the pool fromafterRestore()/start()on a managed class is the recommended pattern and is not flagged. A thread/pool created elsewhere in an otherwise-managed class is still flagged. - Recommendation: drive background work through Spring (
@Async,TaskExecutor/TaskSchedulerbeans,@Scheduled) so the lifecycle stops it gracefully before checkpoint, or register anorg.crac.Resourcethat quiesces the pool inbeforeCheckpoint()and restarts it inafterRestore().
CRAC-SCHED-001 — Fixed-rate scheduled tasks may run a catch-up burst after restore
- Severity: MEDIUM
- Inspects:
@Scheduledmethods that explicitly declarefixedRateorfixedRateString. - Fires when: a method is annotated this way and the running application is not configured for
spring.context.checkpoint=onRefresh. Fixed-rate scheduling computes each execution from a fixed wall-clock point rather than the end of the previous run, so on-demand checkpoint/restore of an already-running application can leave a long idle gap between the checkpoint and a later restore; every execution missed during that gap fires back-to-back immediately after restore. This risk is specific to on-demand checkpoint/restore of a running application. Automatic checkpoint/restore at startup takes the checkpoint beforeLifecycle.start()and beforeContextRefreshedEvent, so the scheduler has not started and this check is suppressed. - Recommendation: if a catch-up burst after restore is not the behavior you want, switch to
fixedDelay(or a cron expression), which Spring schedules relative to the end of the previous execution rather than a fixed wall-clock point, so a checkpoint/restore gap does not queue up missed runs.
Time
CRAC-TIME-001 — Static initializer captures wall-clock time
- Severity: MEDIUM
- Inspects: static initializers that read the current time (
System.currentTimeMillis/nanoTime,java.timenow(),new Date(),Instant/Clock). - Fires when: a static initializer caches a current-time value. The value is frozen into the checkpoint image and becomes stale after every restore.
- Recommendation: read the time when it is needed at runtime instead of caching it in a static field, or refresh the cached value in an
org.crac.Resource.afterRestore()callback.
Configuration
CRAC-CONFIG-001 — Static initializer captures environment or system configuration
- Severity: MEDIUM
- Inspects: static initializers that read environment variables or system properties (
System.getenv,System.getProperty,System.getProperties). - Fires when: a static initializer caches an environment- or property-derived value. With
spring.context.checkpoint=onRefreshthe value is read once when the original JVM starts and frozen into the checkpoint image, so changing it for a restore-only start has no effect until a new checkpoint is taken. - Recommendation: read environment- or property-derived configuration at runtime (or refresh it in an
org.crac.Resource.afterRestore()callback) instead of caching it in a static field, so configuration changes take effect for a restored process.
Randomness
CRAC-RANDOM-001 — Predictable random state is frozen into the checkpoint
- Severity: HIGH
- Inspects: fields of type
java.util.Random(including subclasses), plusSecureRandom(byte[])construction andSecureRandom.setSeed(...)calls. - Fires when: predictable generator state or explicit seeding would be copied into every process restored from one checkpoint.
- Recommendation: recreate/reseed deterministic generators with process-specific state in
afterRestore(). For security-sensitive values, use an unseeded standardSecureRandom.
CRAC-RANDOM-002 — SecureRandom restore-time reseeding depends on the target CRaC JDK
- Severity: INFO
- Inspects: fields of type
java.security.SecureRandom. - Fires when: a cached
SecureRandomexists. Supported Azul/BellSoft CRaC JDKs automatically reseed standard no-arg, never-explicitly-seeded instances after restore, so this is a target-runtime verification prompt rather than an unjustified HIGH finding. Custom security providers may not participate in those JDK hooks. - Recommendation: use a current supported CRaC JDK, avoid explicit seeds, and verify the exact JDK/provider with a real checkpoint/restore test. Explicit seeding is still reported by
CRAC-RANDOM-001.
Secrets
CRAC-SECRET-001 — Secrets captured in fields are frozen into the checkpoint
- Severity: HIGH
- Inspects: fields, static or instance, that capture a secret — a field whose name looks like a secret (
token,password,secret,api key,credential,private key) holding aString,char[], orbyte[], or a field holding cryptographic key material (javax.crypto.SecretKey,java.security.PrivateKey,KeyStore,KeyPair,SSLContext,KeyManager, orTrustManager) regardless of name. - Fires when: such a field exists. A secret loaded at startup — whether into a static field or a singleton bean's instance field, such as an
@Value-injected credential — is baked into the checkpoint image and shipped with every restored process, so rotation no longer takes effect and the snapshot leaks the value. - Recommendation: load secrets lazily, or wipe and rebuild key/trust managers and initialized
SSLContexts in anorg.crac.Resource.afterRestore()callback. Treat checkpoint files as sensitive artifacts: anyone who can pull an image containing them may be able to recover private keys or TLS session material.
Lifecycle
CRAC-LIFECYCLE-001 — No CRaC Resource implementations were found
- Severity: INFO
- Inspects: whether the application implements current
org.crac.Resourceor legacyjavax.crac.Resource. - Fires when: no application class implements either Resource API. These callbacks let components release and re-acquire stateful resources around a checkpoint; an application with none usually relies entirely on Spring lifecycle handling.
- Recommendation: if the application owns resources that Spring does not manage (custom sockets, native handles, caches), implement
org.crac.Resourceand register it withCore.getGlobalContext().register(...)so it participates in checkpoint/restore.
CRAC-LIFECYCLE-002 — The org.crac:crac API is not on the classpath
- Severity: MEDIUM
- Inspects: whether the
org.crac:cracAPI (org.crac.Core/org.crac.Resource) is present on the application's classpath, read from the live runtime inventory rather than imported bytecode — the same mechanismCRAC-POOL-001andCRAC-CACHE-001use, since classpath presence is a runtime/dependency signal, not something visible in any one class's bytecode. - Fires when: the dependency is absent. Spring Boot's checkpoint/restore lifecycle auto-configuration is conditional on
org.crac.Resource, even when the CRaC-enabled JVM exposes its vendor implementation asjavax.cracorjdk.crac. - Recommendation: add the
org.crac:cracdependency (its version is managed by the Spring Boot BOM) so application classes and Spring Boot integrations can registerorg.crac.Resourcecallbacks. BootUI probesjavax.crac.Core(BellSoft) andjdk.crac.Core(Azul) separately as JVM capability markers; applications should depend on the vendor-neutralorg.craccompatibility API.
Generated container assets
The generated Dockerfile-crac uses --cap-add=CHECKPOINT_RESTORE --cap-add=SYS_PTRACE --cap-add=SYS_ADMIN --cap-add=NET_ADMIN. CHECKPOINT_RESTORE requires Linux kernel 5.9 or newer, Docker's default /proc restrictions require SYS_ADMIN so CRIU can restore the checkpointed PID, and NET_ADMIN lets CRIU recreate the container's network interfaces. Because SYS_ADMIN grants broad host access, run the generated image only for local development on an isolated machine, never production or a shared host.
CRIU/kernel compatibility must be validated on the same host family used for deployment. macOS and Windows cannot create the deployable CRIU image; use -Dspring.context.exit=onRefresh there to exercise Spring's lifecycle stop phase, then run the real checkpoint/restore test on supported Linux. JVM/GC/heap/CPU feature choices and already-read startup configuration are frozen when the checkpoint is created. The generated entrypoint therefore applies JAVA_OPTS only to -XX:CRaCCheckpointTo; change those flags by deleting and regenerating the checkpoint, not by appending flags to -XX:CRaCRestoreFrom.