diff --git a/bom/pom.xml b/bom/pom.xml
index cfd9a3835f8..8b52f7c2885 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -898,6 +898,11 @@
helidon-mp-graal-native-image-extension
${helidon.version}
+
+ io.helidon.integrations.crac
+ helidon-integrations-crac
+ ${helidon.version}
+
io.helidon.integrations.common
helidon-integrations-common-rest
diff --git a/dependencies/pom.xml b/dependencies/pom.xml
index 50416562383..c92a14f2f95 100644
--- a/dependencies/pom.xml
+++ b/dependencies/pom.xml
@@ -163,6 +163,7 @@
2.16.4
2.12.5
3.5.7
+ 1.4.0
@@ -1092,6 +1093,11 @@
reactive-streams-tck
${version.lib.reactivestreams}
+
+ org.crac
+ crac
+ ${version.lib.crac}
+
diff --git a/integrations/crac/pom.xml b/integrations/crac/pom.xml
new file mode 100644
index 00000000000..09847a2d89d
--- /dev/null
+++ b/integrations/crac/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+ io.helidon.integrations
+ helidon-integrations-project
+ 4.2.0-SNAPSHOT
+
+ 4.0.0
+
+ io.helidon.integrations.crac
+ helidon-integrations-crac
+ Helidon CRaC support
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ org.crac
+ crac
+
+
+
+
\ No newline at end of file
diff --git a/integrations/crac/src/main/java/io/helidon/integrations/crac/CracImpl.java b/integrations/crac/src/main/java/io/helidon/integrations/crac/CracImpl.java
new file mode 100644
index 00000000000..8380fe66066
--- /dev/null
+++ b/integrations/crac/src/main/java/io/helidon/integrations/crac/CracImpl.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2025 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.integrations.crac;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import io.helidon.webserver.crac.spi.Crac;
+
+import org.crac.management.CRaCMXBean;
+
+/**
+ * Helidon abstraction over CRaC API, replaces no-op implementation when this module is present on classpath.
+ */
+public class CracImpl implements Crac {
+
+ private static final System.Logger LOGGER = System.getLogger(CracImpl.class.getName());
+ private static final Set STRONG_REFERENCES = new HashSet<>();
+
+ @Override
+ public io.helidon.webserver.crac.spi.Crac.Context getGlobalContext() {
+ return new HelidonContext(org.crac.Core.getGlobalContext());
+ }
+
+ @Override
+ public long getUptimeSinceRestore() {
+ return CRaCMXBean.getCRaCMXBean().getUptimeSinceRestore();
+ }
+
+ @Override
+ public long getRestoreTime() {
+ return CRaCMXBean.getCRaCMXBean().getRestoreTime();
+ }
+
+ @Override
+ public void checkpointRestore() throws RestoreException, CheckpointException {
+ try {
+ org.crac.Core.checkpointRestore();
+ } catch (org.crac.RestoreException e) {
+ throw new HelidonRestoreException(e);
+ } catch (org.crac.CheckpointException e) {
+ throw new HelidonCheckpointException(e);
+ }
+ }
+
+ @Override
+ public void checkpointRestoreOnStartup() {
+ if ("onStart".equalsIgnoreCase(System.getProperty("io.helidon.crac.checkpoint"))) {
+ try {
+ org.crac.Core.checkpointRestore();
+ } catch (UnsupportedOperationException e) {
+ LOGGER.log(System.Logger.Level.DEBUG, "CRaC feature is not available", e);
+ } catch (org.crac.RestoreException e) {
+ LOGGER.log(System.Logger.Level.ERROR, "CRaC restore wasn't successful!", e);
+ } catch (org.crac.CheckpointException e) {
+ LOGGER.log(System.Logger.Level.ERROR, "CRaC checkpoint creation wasn't successful!", e);
+ }
+ }
+ }
+
+ private static class HelidonContext extends io.helidon.webserver.crac.spi.Crac.Context {
+
+ private final org.crac.Context delegate;
+
+ HelidonContext(org.crac.Context delegate) {
+ this.delegate = delegate;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void beforeCheckpoint(
+ io.helidon.webserver.crac.spi.Crac.Context extends io.helidon.webserver.crac.spi.Crac.Resource> context)
+ throws io.helidon.webserver.crac.spi.Crac.CheckpointException {
+ try {
+ delegate.beforeCheckpoint(new CracContext((Context) context));
+ } catch (org.crac.CheckpointException e) {
+ throw new HelidonCheckpointException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void afterRestore(Context extends Resource> context) throws io.helidon.webserver.crac.spi.Crac.RestoreException {
+ try {
+ delegate.afterRestore(new CracContext((Context) context));
+ } catch (org.crac.RestoreException e) {
+ throw new HelidonRestoreException(e);
+ }
+ }
+
+ @Override
+ public void register(Resource resource) {
+ CracResource resourceRef = new CracResource(resource);
+ // CRaC API keeps resources as weak refs, we don't want our wrappers being GCed
+ CracImpl.STRONG_REFERENCES.add(resourceRef);
+ delegate.register(resourceRef);
+ }
+
+ }
+
+ private static class HelidonCheckpointException extends io.helidon.webserver.crac.spi.Crac.CheckpointException {
+ private final org.crac.CheckpointException delegate;
+
+ HelidonCheckpointException(org.crac.CheckpointException delegate) {
+ this.delegate = delegate;
+ this.setStackTrace(delegate.getStackTrace());
+ }
+
+ @Override
+ public String getMessage() {
+ return delegate.getMessage();
+ }
+
+ }
+
+ private static class CracCheckpointException extends org.crac.CheckpointException {
+ private final io.helidon.webserver.crac.spi.Crac.CheckpointException delegate;
+
+ CracCheckpointException(io.helidon.webserver.crac.spi.Crac.CheckpointException delegate) {
+ this.delegate = delegate;
+ this.setStackTrace(delegate.getStackTrace());
+ }
+
+ @Override
+ public String getMessage() {
+ return delegate.getMessage();
+ }
+
+ }
+
+ private static class HelidonRestoreException extends io.helidon.webserver.crac.spi.Crac.RestoreException {
+ private final org.crac.RestoreException delegate;
+
+ HelidonRestoreException(org.crac.RestoreException delegate) {
+ this.delegate = delegate;
+ this.setStackTrace(delegate.getStackTrace());
+ }
+
+ @Override
+ public String getMessage() {
+ return delegate.getMessage();
+ }
+ }
+
+ private static class CracRestoreException extends org.crac.RestoreException {
+ private final io.helidon.webserver.crac.spi.Crac.RestoreException delegate;
+
+ CracRestoreException(io.helidon.webserver.crac.spi.Crac.RestoreException delegate) {
+ this.delegate = delegate;
+ this.setStackTrace(delegate.getStackTrace());
+ }
+
+ @Override
+ public String getMessage() {
+ return delegate.getMessage();
+ }
+ }
+
+ private static class CracContext extends org.crac.Context {
+
+ private final io.helidon.webserver.crac.spi.Crac.Context delegate;
+
+ CracContext(io.helidon.webserver.crac.spi.Crac.Context delegate) {
+ this.delegate = delegate;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void beforeCheckpoint(org.crac.Context extends org.crac.Resource> context) throws org.crac.CheckpointException {
+ try {
+ delegate.beforeCheckpoint(new HelidonContext((org.crac.Context) context));
+ } catch (CheckpointException e) {
+ throw new CracCheckpointException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void afterRestore(org.crac.Context extends org.crac.Resource> context) throws org.crac.RestoreException {
+ try {
+ delegate.afterRestore(new HelidonContext((org.crac.Context) context));
+ } catch (RestoreException e) {
+ throw new CracRestoreException(e);
+ }
+ }
+
+ @Override
+ public void register(org.crac.Resource resource) {
+ delegate.register(new HelidonResource(resource));
+ }
+
+ }
+
+ private static class CracResource implements org.crac.Resource {
+
+ private final Resource delegate;
+
+ CracResource(Resource delegate) {
+ this.delegate = delegate;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void beforeCheckpoint(org.crac.Context extends org.crac.Resource> context) throws Exception {
+ delegate.beforeCheckpoint(new HelidonContext((org.crac.Context) context));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void afterRestore(org.crac.Context extends org.crac.Resource> context) throws Exception {
+ delegate.afterRestore(new HelidonContext((org.crac.Context) context));
+ }
+
+ }
+
+ private static class HelidonResource implements io.helidon.webserver.crac.spi.Crac.Resource {
+
+ private final org.crac.Resource delegate;
+
+ HelidonResource(org.crac.Resource delegate) {
+ this.delegate = delegate;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void beforeCheckpoint(
+ io.helidon.webserver.crac.spi.Crac.Context extends io.helidon.webserver.crac.spi.Crac.Resource> context)
+ throws Exception {
+ delegate.beforeCheckpoint(new CracContext((Context) context));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void afterRestore(
+ io.helidon.webserver.crac.spi.Crac.Context extends io.helidon.webserver.crac.spi.Crac.Resource> context)
+ throws Exception {
+ delegate.afterRestore(new CracContext((Context) context));
+ }
+
+ }
+}
diff --git a/integrations/crac/src/main/java/io/helidon/integrations/crac/package-info.java b/integrations/crac/src/main/java/io/helidon/integrations/crac/package-info.java
new file mode 100644
index 00000000000..66233d56f1d
--- /dev/null
+++ b/integrations/crac/src/main/java/io/helidon/integrations/crac/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2025 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Helidon abstraction over CRaC API, replaces no-op implementation when this module is present on classpath.
+ */
+package io.helidon.integrations.crac;
diff --git a/integrations/crac/src/main/java/module-info.java b/integrations/crac/src/main/java/module-info.java
new file mode 100644
index 00000000000..f9ee851120d
--- /dev/null
+++ b/integrations/crac/src/main/java/module-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2025 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module io.helidon.integrations.crac {
+ requires crac;
+ requires io.helidon.webserver;
+ exports io.helidon.integrations.crac to io.helidon.webserver;
+ provides io.helidon.webserver.crac.spi.Crac with io.helidon.integrations.crac.CracImpl;
+}
\ No newline at end of file
diff --git a/integrations/pom.xml b/integrations/pom.xml
index a34e5d32a11..3697f4363ff 100644
--- a/integrations/pom.xml
+++ b/integrations/pom.xml
@@ -42,6 +42,7 @@
common
cdi
+ crac
graal
db
jdbc
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java
index a93ac06e6fb..8e12313bbf6 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java
@@ -205,7 +205,7 @@ static List createTags(String pairs) {
* @return true to include meters related to virtual threads
*/
@Option.Configured("virtual-threads.enabled")
- @Option.DefaultBoolean(true)
+ @Option.DefaultBoolean(false)
boolean virtualThreadsEnabled();
/**
diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java
index 445b3065d1b..6378d705ca8 100644
--- a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java
+++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package io.helidon.microprofile.server;
import java.lang.System.Logger.Level;
-import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Path;
@@ -29,7 +28,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -49,6 +47,7 @@
import io.helidon.webserver.WebServer;
import io.helidon.webserver.WebServerConfig;
import io.helidon.webserver.context.ContextFeature;
+import io.helidon.webserver.crac.spi.Crac;
import io.helidon.webserver.http.HttpRouting;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
@@ -80,10 +79,6 @@
import jakarta.enterprise.inject.spi.ProcessProducerMethod;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.ext.ParamConverterProvider;
-import org.crac.CheckpointException;
-import org.crac.Core;
-import org.crac.Resource;
-import org.crac.RestoreException;
import org.eclipse.microprofile.config.ConfigProvider;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.InjectionManager;
@@ -97,7 +92,7 @@
/**
* Extension to handle web server configuration and lifecycle.
*/
-public class ServerCdiExtension implements Extension, Resource {
+public class ServerCdiExtension implements Extension {
private static final System.Logger LOGGER = System.getLogger(ServerCdiExtension.class.getName());
private static final System.Logger STARTUP_LOGGER = System.getLogger("io.helidon.microprofile.startup.server");
private static final AtomicBoolean IN_PROGRESS_OR_RUNNING = new AtomicBoolean();
@@ -125,10 +120,6 @@ public class ServerCdiExtension implements Extension, Resource {
private Context context;
- private long cracRestoreTime = -1;
- private final CompletableFuture> restored = new CompletableFuture<>();
-
-
/**
* Default constructor required by {@link java.util.ServiceLoader}.
*/
@@ -470,18 +461,6 @@ private void startServer(@Observes @Priority(PLATFORM_AFTER + 100) @Initialized(
}
webserver = serverBuilder.build();
- if ("onStart".equalsIgnoreCase(System.getProperty("io.helidon.crac.checkpoint"))) {
- try {
- Core.checkpointRestore();
- } catch (UnsupportedOperationException e) {
- LOGGER.log(Level.DEBUG, "CRaC feature is not available", e);
- } catch (RestoreException e) {
- LOGGER.log(Level.ERROR, "CRaC restore wasn't successful!", e);
- } catch (CheckpointException e) {
- LOGGER.log(Level.ERROR, "CRaC checkpoint creation wasn't successful!", e);
- }
- }
-
try {
webserver.start();
started = true;
@@ -491,20 +470,16 @@ private void startServer(@Observes @Priority(PLATFORM_AFTER + 100) @Initialized(
this.port = webserver.port();
- long initializationElapsedTime = ManagementFactory.getRuntimeMXBean().getUptime();
+ Crac.get().checkpointRestoreOnStartup();
+ long initializationElapsedTime = Crac.get().getUptime();
String protocol = "http" + (webserver.hasTls() ? "s" : "");
String host = "0.0.0.0".equals(listenHost) ? "localhost" : listenHost;
String note = "0.0.0.0".equals(listenHost) ? " (and all other host addresses)" : "";
-
- String startupTimeReport = cracRestoreTime == -1
- ? " in " + initializationElapsedTime + " milliseconds (since JVM startup). "
- : " in " + (System.currentTimeMillis() - cracRestoreTime) + " milliseconds (since CRaC restore).";
-
LOGGER.log(Level.INFO, () -> "Server started on "
+ protocol + "://" + host + ":" + port
- + note + startupTimeReport);
+ + note + " in " + initializationElapsedTime + " milliseconds (since JVM startup).");
// this is not needed at runtime, collect garbage
serverBuilder = null;
@@ -783,17 +758,4 @@ private HttpRouting.Builder findRouting(String className,
return serverNamedRoutingBuilder(routingName);
}
-
- @Override
- public void beforeCheckpoint(org.crac.Context extends Resource> context) throws Exception {
- LOGGER.log(Level.INFO, "Creating CRaC snapshot after "
- + ManagementFactory.getRuntimeMXBean().getUptime()
- + "ms of runtime.");
- }
-
- @Override
- public void afterRestore(org.crac.Context extends Resource> context) throws Exception {
- cracRestoreTime = System.currentTimeMillis();
- LOGGER.log(Level.INFO, "CRaC snapshot restored!");
- }
}
diff --git a/microprofile/server/src/main/java/module-info.java b/microprofile/server/src/main/java/module-info.java
index 161737fc3db..35f61eae80d 100644
--- a/microprofile/server/src/main/java/module-info.java
+++ b/microprofile/server/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/webserver/webserver/pom.xml b/webserver/webserver/pom.xml
index d20da117294..f7b2e4652d2 100644
--- a/webserver/webserver/pom.xml
+++ b/webserver/webserver/pom.xml
@@ -1,6 +1,6 @@