apereo/cas · error · SamlException

Unable to determine entity id to fetch metadata via MDQ for

Error message

Unable to determine entity id to fetch metadata via MDQ for 

What it means

Thrown before issuing an MDQ query when no entityID can be determined for the request. The resolver takes the EntityIdCriterion from the criteria set, falling back to the registered service's serviceId; if both are blank there is nothing to query.

Solutions

  1. Set the serviceId (entityID) on the SamlRegisteredService definition.
  2. Include an EntityIdCriterion in the criteria set: new CriteriaSet(new EntityIdCriterion(entityId)).
  3. Fix service registry import/export so the service has a non-blank serviceId.
  4. Log the service definition to confirm which registered service has the blank id.

Example fix

// before
val criteriaSet = new CriteriaSet(new EntityIdCriterion(""));
// after
val criteriaSet = new CriteriaSet(new EntityIdCriterion("https://sp.example.org/metadata"));
Defensive patterns

Strategy: validation

Validate before calling

val entityId = Optional.ofNullable(criteriaSet.get(EntityIdCriterion.class))
    .map(EntityIdCriterion::getEntityId).orElseGet(service::getServiceId);
if (StringUtils.isBlank(entityId)) throw new IllegalArgumentException("Register service with non-blank serviceId before MDQ resolution");

Type guard

boolean hasEntityId(SamlRegisteredService s) { return s != null && StringUtils.isNotBlank(s.getServiceId()); }

Try / catch

try {
    return mdqResolver.resolve(criteriaSet);
} catch (SamlException e) {
    LOGGER.error("No entityID for service [{}]", service.getName(), e);
    return Set.of();
}

Prevention

When it happens

Trigger: Calling getMetadataLocationsForService with a criteria set lacking EntityIdCriterion AND a SamlRegisteredService whose getServiceId() is blank/empty (e.g. service defined with empty serviceId pattern).

Common situations: SAML service registered without a serviceId/entityID; programmatic criteria built without EntityIdCriterion builder; regex-based service whose serviceId does not carry the entityID; import/migration left the field empty.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-saml-idp-metadata/src/main/java/org/apereo/cas/support/saml/services/idp/metadata/cache/resolver/MetadataQueryProtocolMetadataResolver.java:121

            .proxyUrl(service.getMetadataProxyLocation())
            .build();
        val response = HttpUtils.execute(exec);
        if (response == null || HttpStatus.resolve(response.getCode()).is5xxServerError()) {
            LOGGER.error("Unable to fetch metadata from [{}]", metadataLocation);
            throw UnauthorizedServiceException.denied("Rejected: %s".formatted(metadataLocation));
        }
        return response;
    }

    @Override
    protected Set<String> getMetadataLocationsForService(final SamlRegisteredService service, final CriteriaSet criteriaSet) {
        LOGGER.trace("Getting metadata location dynamically for [{}] based on criteria [{}]", service.getName(), criteriaSet);
        val entityIdCriteria = criteriaSet.get(EntityIdCriterion.class);
        val entityId = Optional.ofNullable(entityIdCriteria)
            .map(EntityIdCriterion::getEntityId)
            .orElseGet(service::getServiceId);
        if (StringUtils.isBlank(entityId)) {
            throw new SamlException("Unable to determine entity id to fetch metadata via MDQ for " + service.getName());
        }
        val locations = super.getMetadataLocationsForService(service, criteriaSet);
        return locations
            .stream()
            .map(location -> location.replace("{0}", EncodingUtils.urlEncode(entityId)))
            .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    private static void setFileAttribute(final HttpResponse response, final File backupFile) {
        FunctionUtils.doAndHandle(t -> {
            val path = backupFile.toPath();
            val etag = response.getFirstHeader("ETag").getValue();
            Files.setAttribute(path, "user:ETag", ByteBuffer.wrap(etag.getBytes(StandardCharsets.UTF_8)));
        });
    }
}

View on GitHub (pinned to e7288fc434)