{"record":{"id":"f572138f7791c7d4","repo":"apereo/cas","slug":"graalvm-native-image-executable-is-unable-to-disco","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/cas-server-core-services-registry/src/main/java/org/apereo/cas/services/resource/AbstractResourceBasedServiceRegistry.java","lineNumber":152,"sourceCode":"            val servicesDirectory = prepareRegisteredServicesDirectory(configDirectory);\n            val file = servicesDirectory.getFile();\n            LOGGER.trace(\"Prepared service registry directory is specified at [{}]\", file);\n\n            initializeRegistry(Paths.get(file.getCanonicalPath()), serializers,\n                registeredServiceReplicationStrategy, resourceNamingStrategy, serviceRegistryConfigWatcher);\n        });\n    }\n\n    private Resource prepareRegisteredServicesDirectory(final Resource configDirectory) throws IOException {\n        val externalForm = configDirectory.getURI().toASCIIString();\n        if (CasRuntimeHintsRegistrar.inNativeImage() && ResourceUtils.isEmbeddedResource(externalForm)) {\n            val servicesDirectory = CasConfigurationPropertiesSourceLocator.DEFAULT_CAS_CONFIG_DIRECTORIES\n                .stream()\n                .map(directory -> new File(directory, \"services\"))\n                .filter(File::exists)\n                .findFirst()\n                .orElse(FALLBACK_REGISTERED_SERVICES_LOCATION);\n            LOGGER.warn(\"\"\"\n                GraalVM native image executable is unable to discover embedded resources at [{}]. The services directory location is changed to use [{}] instead. \\\n                To adjust this behavior, update your CAS settings to use a directory location outside the CAS native executable.\"\"\"\n                .stripIndent(), externalForm, servicesDirectory);\n            return new FileSystemResource(servicesDirectory);\n        }\n        val pattern = String.join(\"|\", getExtensions());\n        return Objects.requireNonNull(ResourceUtils.prepareClasspathResourceIfNeeded(configDirectory, true, pattern),\n            () -> \"Could not determine the services configuration directory from \" + configDirectory);\n    }\n\n    /**\n     * Enable default watcher service.\n     */\n    public void enableDefaultWatcherService() {\n        if (serviceRegistryWatcherService != null) {\n            LOGGER.info(\"Watching service registry directory at [{}]\", serviceRegistryDirectory);\n            serviceRegistryWatcherService.close();\n            val onCreate = new CreateResourceBasedRegisteredServiceWatcher(this);","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/core/cas-server-core-services-registry/src/main/java/org/apereo/cas/services/resource/AbstractResourceBasedServiceRegistry.java#L134-L170","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (application.properties, relying on embedded services)\n# cas.service-registry.json.location=classpath:services\n// after\ncas.service-registry.json.location=file:/etc/cas/config/services","handlingStrategy":"fallback","validationCode":"// Deployment check: ensure services dir exists outside the native image\nFile dir = new File(\"/etc/cas/config/services\");\nif (!dir.exists() || !dir.isDirectory()) throw new IllegalStateException(\"Create /etc/cas/config/services before native-image startup\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["graalvm","native-image","service-registry","filesystem"],"backgroundTag":"unsupported-platform","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}