apereo/cas · error · IllegalArgumentException

subordinate directory

Error message

subordinate directory [%s] is not a directory

What it means

loadSubordinates() checks Files.isDirectory() after confirming the path exists; if the configured subordinate location resolves to a regular file (or other non-directory), it throws this IllegalArgumentException naming the path.

Solutions

  1. Ensure the configured path is a directory and move the subordinate JSON files into it
  2. Update the config to the directory path, not the individual file path
  3. If a single subordinate is intended, wrap it in a directory containing one JSON file

Example fix

// before
subordinate-directory=/etc/cas/oidc/subordinates/acme.json  # a file
// after
subordinate-directory=/etc/cas/oidc/subordinates  # directory containing acme.json
Defensive patterns

Strategy: validation

Validate before calling

if (!Files.isDirectory(Paths.get(subordinateDirectory))) {
    throw new IllegalStateException("Path is not a directory: " + subordinateDirectory);
}

Prevention

When it happens

Trigger: The subordinate-directory config value points at a file (e.g. a single JSON document or an archive) instead of a directory that Files.walk() can traverse.

Common situations: Operator set the property to a JSON file expecting single-entity config; a symlink points at a file; tar extraction replaced the directory with a file.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/d25163e19e4e0c6f. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-federation/src/main/java/org/apereo/cas/oidc/federation/subordinate/OidcFederationSubordinateRepository.java:45

    private static final ObjectMapper MAPPER = JacksonObjectMapperFactory.builder()
        .defaultTypingEnabled(false).build().toObjectMapper();

    @Getter
    private final Map<String, OidcFederationSubordinate> subordinates = new HashMap<>();

    public OidcFederationSubordinateRepository(final OidcProperties oidcProperties) {
        loadSubordinates(oidcProperties.getFederation().getSubordinateDirectory());
    }

    protected void loadSubordinates(final String subordinateDirectory) {
        if (StringUtils.isNotBlank(subordinateDirectory)) {
            LOGGER.debug("Loading subordinates...");
            val dir = Paths.get(subordinateDirectory);
            if (!Files.exists(dir)) {
                throw new IllegalArgumentException("subordinate directory [%s] does not exist".formatted(subordinateDirectory));
            }
            if (!Files.isDirectory(dir)) {
                throw new IllegalArgumentException("subordinate directory [%s] is not a directory".formatted(subordinateDirectory));
            }
            try (val stream = Files.walk(dir).filter(Files::isRegularFile).filter(Files::isReadable)) {
                stream.forEach(path -> FunctionUtils.doUnchecked(_ -> {
                    val file = path.toFile();
                    LOGGER.debug("Parsing [{}]...", file);
                    val subordinate = MAPPER.readValue(file, OidcFederationSubordinate.class);
                    subordinates.put(subordinate.getEntityId(), subordinate);
                }));
            } catch (final IOException e) {
                throw new IllegalArgumentException("Cannot read/load from subordinate directory", e);
            }
            LOGGER.info("Loaded [{}] subordinates", subordinates.size());
        }
    }
}

View on GitHub (pinned to e7288fc434)