diff --git a/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc b/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc
index 0968fc2397..f37cd8194c 100644
--- a/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc
+++ b/docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc
@@ -2,5 +2,29 @@
= AOT and Native Image Support
:page-section-summary-toc: 1
-Since `4.0.0`, Spring Cloud Config Server supports Spring AOT transformations. However, for the time being, GraalVM native images are not supported. Implementing native image support is blocked by https://github.com/oracle/graal/issues/5134[graal#5134] and will likely require the completion of the work on https://github.com/graalvm/taming-build-time-initialization[https://github.com/graalvm/taming-build-time-initialization] to be fixed.
+Since `4.0.0`, Spring Cloud Config Server supports Spring AOT transformations. As of `4.1.0` it also supports GraalVM native images, however it requires the user to add some workarounds for known GraalVM issues, as described below.
+
+====
+
+IMPORTANT::
+Due to [a bug](https://github.com/oracle/graal/issues/5134) in Graal's `FileSystemProvider` a configuration workaround needs to be added to allow the Config Server to run as a native image. You will need to add the following options to your GraalVM build plugin setup (please refer to https://www.graalvm.org/[GraalVM] Maven or Gradle plugin documentation for more details):
+
+[source,indent=0]
+----
+-H:-AddAllFileSystemProviders
+--strict-image-heap
+--initialize-at-build-time=org.bouncycastle
+--initialize-at-build-time=net.i2p.crypto.eddsa.EdDSASecurityProvider
+--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$Default
+--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV
+----
+
+NOTE:: Adding the additional build time initializations can affect performance, but it still may offer gains as compared to a regular JVM run. Make sure to measure and compare for your application.
+
+====
+
+TIP::
+If you are connecting with your config data backend over SSH, keep in mind that GraalVM requires https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/JCASecurityServices/#provider-registration[security provider registration using `java.security`]
+
+WARNING: Refresh scope is not supported with native images. If you are going to run your config client application as a native image, make sure to set `spring.cloud.refresh.enabled` property to `false`.
diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml
index e0586f3704..8051f44e57 100644
--- a/spring-cloud-config-client-tls-tests/pom.xml
+++ b/spring-cloud-config-client-tls-tests/pom.xml
@@ -98,13 +98,8 @@
org.bouncycastle
- bcpkix-jdk15on
- test
-
-
- org.bouncycastle
- bcpkix-jdk15on
- 1.67
+ bcpkix-jdk18on
+ 1.74
test
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerRuntimeHints.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerRuntimeHints.java
new file mode 100644
index 0000000000..9017314280
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerRuntimeHints.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2018-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.cloud.config.server.config;
+
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.Signature;
+import java.util.Set;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+
+import org.apache.sshd.common.channel.ChannelListener;
+import org.apache.sshd.common.forward.PortForwardingEventListener;
+import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
+import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
+import org.apache.sshd.common.session.SessionListener;
+import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar;
+import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar;
+import org.eclipse.jgit.api.FetchCommand;
+import org.eclipse.jgit.api.MergeCommand;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+import org.springframework.aot.hint.TypeReference;
+import org.springframework.cloud.config.environment.PropertyValueDescriptor;
+import org.springframework.cloud.config.server.ssh.HostKeyAlgoSupportedValidator;
+import org.springframework.cloud.config.server.ssh.HostKeyAndAlgoBothExistValidator;
+import org.springframework.cloud.config.server.ssh.KnownHostsFileValidator;
+import org.springframework.cloud.config.server.ssh.PrivateKeyValidator;
+import org.springframework.cloud.config.server.ssh.SshPropertyValidator;
+import org.springframework.util.ClassUtils;
+
+/**
+ * A {@link RuntimeHintsRegistrar} implementation that makes types required by Config
+ * Server available in constrained environments.
+ *
+ * @author Olga Maciaszek-Sharma
+ * @since 4.1.0
+ */
+class ConfigServerRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ if (!ClassUtils.isPresent("org.springframework.cloud.config.server.config.ConfigServerConfiguration",
+ classLoader)) {
+ return;
+ }
+ hints.reflection().registerTypes(Set.of(TypeReference.of(HostKeyAndAlgoBothExistValidator.class),
+ TypeReference.of(KnownHostsFileValidator.class), TypeReference.of(HostKeyAlgoSupportedValidator.class),
+ TypeReference.of(PrivateKeyValidator.class), TypeReference.of(SshPropertyValidator.class),
+ TypeReference.of(PropertyValueDescriptor.class)),
+ hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
+ hints.reflection().registerTypes(
+ Set.of(TypeReference.of(PropertyValueDescriptor.class), TypeReference.of(Mac.class),
+ TypeReference.of(KeyAgreement.class), TypeReference.of(KeyPairGenerator.class),
+ TypeReference.of(KeyFactory.class), TypeReference.of(Signature.class),
+ TypeReference.of(MessageDigest.class)),
+ hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
+
+ // TODO: move over to GraalVM reachability metadata
+ if (ClassUtils.isPresent("org.apache.sshd.common.SshConstants", classLoader)) {
+ hints.reflection().registerTypes(Set.of(TypeReference.of(BouncyCastleSecurityProviderRegistrar.class),
+ TypeReference.of(EdDSASecurityProviderRegistrar.class), TypeReference.of(Nio2ServiceFactory.class),
+ TypeReference.of(Nio2ServiceFactoryFactory.class)),
+ hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
+ hints.reflection().registerTypes(Set.of(TypeReference.of(PortForwardingEventListener.class)),
+ hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
+ MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
+ hints.proxies().registerJdkProxy(TypeReference.of(ChannelListener.class),
+ TypeReference.of(PortForwardingEventListener.class), TypeReference.of(SessionListener.class));
+ }
+
+ // TODO: move over to GraalVM reachability metadata
+ if (ClassUtils.isPresent("org.eclipse.jgit.api.Git", classLoader)) {
+ hints.reflection()
+ .registerTypes(Set.of(TypeReference.of(MergeCommand.FastForwardMode.Merge.class),
+ TypeReference.of(MergeCommand.ConflictStyle.class),
+ TypeReference.of(MergeCommand.FastForwardMode.class), TypeReference.of(FetchCommand.class)),
+ hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
+ hints.reflection().registerTypes(Set.of(TypeReference.of(SshdText.class)), hint -> hint
+ .withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS));
+ }
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-config-server/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 0000000000..35c4ba6b0a
--- /dev/null
+++ b/spring-cloud-config-server/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.springframework.cloud.config.server.config.ConfigServerRuntimeHints