apereo/cas · warning
GraalVM native image executable is unable to discover…
Error message
GraalVM native image executable is unable to discover embedded resources at [{}]. The services directory location is changed to use [{}] instead. To adjust this behavior, update your CAS settings to use a directory location outside the CAS native executable. What it means
When CAS runs as a GraalVM native image, embedded classpath resources (the bundled services directory) cannot be discovered at runtime. AbstractResourceBasedServiceRegistry.prepareRegisteredServicesDirectory falls back to a filesystem directory under the default CAS config directories (e.g. /etc/cas/config/services) or FALLBACK_REGISTERED_SERVICES_LOCATION, and logs this warning to explain the change.
Solutions
- Place service definition JSON files in a real filesystem directory such as /etc/cas/config/services and point cas.service-registry.* settings at it.
- Mount an external services directory into the container and update cas.service-registry.json.location (or equivalent) to that path.
- If not running native image, verify this branch is only hit by the native-image check; on JVM deployments the embedded resource should be found normally.
Example fix
// before (application.properties, relying on embedded services) # cas.service-registry.json.location=classpath:services // after cas.service-registry.json.location=file:/etc/cas/config/services
Defensive patterns
Strategy: fallback
Validate before calling
// Deployment check: ensure services dir exists outside the native image
File dir = new File("/etc/cas/config/services");
if (!dir.exists() || !dir.isDirectory()) throw new IllegalStateException("Create /etc/cas/config/services before native-image startup"); Prevention
- For native-image deployments, always mount service definitions as filesystem resources, not classpath resources.
- Set cas.service-registry.*.location explicitly to a file: URL in native deployments.
- Test native builds with the external services directory before shipping.
When it happens
Trigger: Running a CAS native-image build where the services directory resource lookup fails; prepareRegisteredServicesDirectory is invoked from servicesDirectory() when the embedded resource cannot be resolved inside the native executable.
Common situations: Deploying CAS as a GraalVM/native-image container where service JSON files were baked into the classpath; Docker native-image deployments that expect embedded default services.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
- The service definition file could not be saved at
- [ ] is not readable. Check file permissions
- [ ] is not found at the path specified
- subordinate directory
- subordinate directory
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/f572138f7791c7d4.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-services-registry/src/main/java/org/apereo/cas/services/resource/AbstractResourceBasedServiceRegistry.java:152
val servicesDirectory = prepareRegisteredServicesDirectory(configDirectory);
val file = servicesDirectory.getFile();
LOGGER.trace("Prepared service registry directory is specified at [{}]", file);
initializeRegistry(Paths.get(file.getCanonicalPath()), serializers,
registeredServiceReplicationStrategy, resourceNamingStrategy, serviceRegistryConfigWatcher);
});
}
private Resource prepareRegisteredServicesDirectory(final Resource configDirectory) throws IOException {
val externalForm = configDirectory.getURI().toASCIIString();
if (CasRuntimeHintsRegistrar.inNativeImage() && ResourceUtils.isEmbeddedResource(externalForm)) {
val servicesDirectory = CasConfigurationPropertiesSourceLocator.DEFAULT_CAS_CONFIG_DIRECTORIES
.stream()
.map(directory -> new File(directory, "services"))
.filter(File::exists)
.findFirst()
.orElse(FALLBACK_REGISTERED_SERVICES_LOCATION);
LOGGER.warn("""
GraalVM native image executable is unable to discover embedded resources at [{}]. The services directory location is changed to use [{}] instead. \
To adjust this behavior, update your CAS settings to use a directory location outside the CAS native executable."""
.stripIndent(), externalForm, servicesDirectory);
return new FileSystemResource(servicesDirectory);
}
val pattern = String.join("|", getExtensions());
return Objects.requireNonNull(ResourceUtils.prepareClasspathResourceIfNeeded(configDirectory, true, pattern),
() -> "Could not determine the services configuration directory from " + configDirectory);
}
/**
* Enable default watcher service.
*/
public void enableDefaultWatcherService() {
if (serviceRegistryWatcherService != null) {
LOGGER.info("Watching service registry directory at [{}]", serviceRegistryDirectory);
serviceRegistryWatcherService.close();
val onCreate = new CreateResourceBasedRegisteredServiceWatcher(this);View on GitHub (pinned to e7288fc434)