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

  1. Place service definition JSON files in a real filesystem directory such as /etc/cas/config/services and point cas.service-registry.* settings at it.
  2. Mount an external services directory into the container and update cas.service-registry.json.location (or equivalent) to that path.
  3. 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

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


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)