[java-idp-plugin-oidc-op-oidfed] 01/02: Improved the structure of metadata wrappers in the API module

Codeberg noreply at shibboleth.net
Fri Dec 19 12:03:23 UTC 2025


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/db22a7996738d6c53cbf5187e880323d83f543b1

commit db22a7996738d6c53cbf5187e880323d83f543b1
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Dec 19 12:20:57 2025 +0200

    Improved the structure of metadata wrappers in the API module
    
    - The access to specific payloads should now be more convenient
    - Updated IMPL accordingly
---
 ...ntityStatement.java => AbstractJWTWrapper.java} |  45 +------
 .../metadata/BaseExpirableSubjectPayload.java      |  80 +++++++++++
 .../op/oidfed/metadata/BaseMetadataPayload.java    |  46 +++++++
 .../oidc/op/oidfed/metadata/BasePayload.java       | 107 +++++++++++++++
 .../op/oidfed/metadata/EntityConfiguration.java    |   2 +-
 .../oidc/op/oidfed/metadata/EntityStatement.java   |  79 +----------
 .../op/oidfed/metadata/EntityStatementPayload.java | 150 +--------------------
 .../op/oidfed/metadata/ResolveEntityResponse.java  |   2 +-
 .../metadata/ResolveEntityResponsePayload.java     |   2 +-
 .../op/oidfed/metadata/SubordinateStatement.java   |   2 +-
 .../plugin/oidc/op/oidfed/metadata/TrustMark.java  |   2 +-
 .../oidc/op/oidfed/metadata/TrustMarkPayload.java  |   2 +-
 .../oidc/op/oidfed/metadata/TrustMarkStatus.java   |   2 +-
 .../op/oidfed/metadata/TrustMarkStatusPayload.java |   2 +-
 .../context/RelyingPartyTrustChainContext.java     |  24 ++--
 .../ExplicitClientRegistrationRequestDecoder.java  |  28 ++--
 .../impl/ExplicitClientRegistrationRequest.java    |  14 +-
 ...mbinedMetadataFromTrustChainLookupStrategy.java |   4 +-
 ...rustChainSignatureValidationFilterStrategy.java |  18 +--
 ...efaultProvidedTrustChainValidationStrategy.java |  11 +-
 ...DefaultTrustChainTrustMarksParsingStrategy.java |   6 +-
 ...ChainTrustedTrustMarkIssuersLookupStrategy.java |   7 +-
 ...tChainTrustedTrustMarkOwnersLookupStrategy.java |   4 +-
 ...onfigurationMetadataSkeletonLookupStrategy.java |  16 ++-
 ...StatementSignatureValidationFilterStrategy.java |   4 +-
 ...busResponseContainerExpirationTimeStrategy.java |   2 +-
 ...r.java => BaseExpirableStatementContainer.java} |  31 +++--
 ...tEntityStatementContentValidationCondition.java |  17 ++-
 ...faultEntityStatementMetadataFilterStrategy.java |  10 +-
 ...ntityStatementSignatureValidationCondition.java |  17 ++-
 ...atementValidationCriteriaSetLookupFunction.java |  39 +++---
 .../remote/RemoteEntityConfigurationContainer.java |   3 +-
 .../RemoteSubordinateStatementContainer.java       |   3 +-
 .../cache/remote/RemoteTrustChainsContainer.java   |   8 +-
 .../remote/SubjectEntityStatementCriterion.java    |   6 +-
 ...iterion.java => SubjectStatementCriterion.java} |  25 ++--
 ...tityConfigurationCriteriaSetLookupFunction.java |  11 +-
 .../RemoteResolveEntityResponseContainer.java      |   4 +-
 ...efaultSubordinateStatementFetchingStrategy.java |  12 +-
 ...rustChainSignatureValidationFilterStrategy.java |  12 +-
 .../DefaultTrustChainFetchingStrategy.java         |  67 ++++-----
 ...stChainSubjectIdentifierExtractionStrategy.java |   6 +-
 ...ustMarkValidationCriteriaSetLookupFunction.java |  41 +++---
 .../remote/trustmark/RemoteTrustMarkContainer.java |   4 +-
 .../trustmark/RemoteTrustMarkStatusContainer.java  |   4 +-
 .../constraints/FederationPolicyConstraint.java    |   2 +-
 .../FederationPolicyConstraintHelper.java          |   2 +-
 .../impl/AbstractFederationPolicyConstraint.java   |   4 +-
 .../impl/DefaultAllowedEntityTypesConstraint.java  |   4 +-
 .../impl/DefaultMaxPathLengthConstraint.java       |   4 +-
 .../impl/DefaultNamingConstraintsConstraint.java   |   4 +-
 .../metadata/util/EntityStatementHelper.java       |   6 +-
 .../impl/AbstractTrustChainResolutionAction.java   |  16 +--
 .../impl/AddExtensionsToClientInformation.java     |   4 +-
 ...eredTrustChainRequestUriClaimsSetDecorator.java |   2 +-
 .../profile/impl/BuildResolveEntityResponse.java   |   8 +-
 .../oidfed/profile/impl/CallResolveEntityApi.java  |  35 +++--
 ...TrustMarkFromMetadataCacheFetchingFunction.java |   4 +-
 .../op/oidfed/profile/impl/ResolveTrustChains.java |  11 +-
 .../op/oidfed/profile/impl/ResolveTrustMarks.java  |  22 +--
 .../op/oidfed/profile/impl/SelectTrustChain.java   |  11 +-
 .../profile/impl/StoreAutomaticRegistration.java   |   2 +-
 ...eAutomaticRegistrationProfileConfiguration.java |  12 +-
 .../profile/impl/ValidateProvidedTrustChain.java   |  13 +-
 .../profile/impl/ValidateSelectedTrustChain.java   |  12 +-
 .../DefaultMetadataValidationCondition.java        |   1 +
 ...DefaultSelectedTrustChainIDsLookupStrategy.java |   4 +-
 .../DefaultTrustChainIDsLookupStrategy.java        |   4 +-
 .../DefaultTrustChainSelectionStrategy.java        |  16 +--
 ...tRegistrationRequestClientIDLookupFunction.java |   5 +-
 ...entRegistrationRequestJWKSetLookupFunction.java |   5 +-
 ...egistrationRequestTrustChainLookupFunction.java |   4 +-
 ...faultEntityConfigurationCredentialResolver.java |   2 +-
 ...DefaultPayloadJOSEObjectCredentialResolver.java |   1 +
 ...yloadSignatureValidationCredentialResolver.java |   1 +
 ...aultSubordinateStatementCredentialResolver.java |  34 +++--
 .../plugin/oidc/op/oidfed/TrustChainTestUtil.java  |  24 ++--
 .../flow/oidfed/AbstractFederationFlowTest.java    |  20 +--
 .../profile/flow/oidfed/RegistrationFlowTest.java  |   2 +-
 .../oidfed/TokenFlowAutomaticRegistrationTest.java |   1 +
 .../UserInfoFlowAutomaticRegistrationTest.java     |   1 +
 .../EntityConfigurationMetadataCacheTest.java      |   2 +-
 .../SubordinateStatementMetadataCacheTest.java     |   4 +-
 83 files changed, 675 insertions(+), 615 deletions(-)

diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatement.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractJWTWrapper.java
similarity index 58%
copy from idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatement.java
copy to idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractJWTWrapper.java
index 48c2c4f..aea0f17 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatement.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractJWTWrapper.java
@@ -16,9 +16,6 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
 
 import javax.annotation.Nonnull;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.MoreObjects;
 import com.nimbusds.jwt.SignedJWT;
 
@@ -27,15 +24,17 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.ConstraintViolationException;
 
 /**
- * A wrapper class containing the {@link SignedJWT} and parsed claims related to entity statement.
+ * Abstract wrapper class containing {@link SignedJWT} and its parsed payload.
+ *
+ * @param <T> payload format
  */
-public class EntityStatement {
+public abstract class AbstractJWTWrapper<T extends BasePayload> {
 
     /** Entity statement as JWT. */
     @Nonnull private final SignedJWT jwt;
 
     /** Entity statement payload. */
-    @Nonnull private final EntityStatementPayload parsedPayload;
+    @Nonnull private final T parsedPayload;
 
     /**
      * 
@@ -45,12 +44,11 @@ public class EntityStatement {
      * @param payload entity statement payload
      * @throws ConstraintViolationException if the payload syntax/content is not expected
      */
-    protected EntityStatement(@Nonnull final SignedJWT signedJwt, @Nonnull final EntityStatementPayload payload) 
+    protected AbstractJWTWrapper(@Nonnull final SignedJWT signedJwt, @Nonnull final T payload) 
         throws ConstraintViolationException {
         jwt = Constraint.isNotNull(signedJwt, "JWT cannot be null");
         parsedPayload = Constraint.isNotNull(payload, "Entity statement payload cannot be null");
         Constraint.isNotEmpty(parsedPayload.getIssuer(), "Issuer cannot be empty in the payload");
-        Constraint.isNotEmpty(parsedPayload.getSubject(), "Subject cannot be empty in the payload");
     }
 
     /**
@@ -73,43 +71,15 @@ public class EntityStatement {
         return issuer;
     }
 
-    /**
-     * Get the subject claim of the entity statement.
-     * 
-     * @return subject
-     */
-    @Nonnull @NotEmpty public String getSubject() {
-        final String subject = parsedPayload.getSubject();
-        assert subject != null;
-        return subject;
-    }
-
     /**
      * Get the payload of the entity statement.
      * 
      * @return payload
      */
-    @Nonnull public EntityStatementPayload getParsedPayload() {
+    @Nonnull public T getParsedPayload() {
         return parsedPayload;
     }
 
-    /**
-     * Parse an {@link EntityStatement} from the given JWT by using the given object mapper.
-     * 
-     * @param jwt entity statement JWT
-     * @param objectMapper object mapper capable of parsing JWT payload
-     * @return entity statement
-     * @throws JsonProcessingException if the payload could not be parsed
-     */
-    public static EntityStatement parse(@Nonnull final SignedJWT jwt, @Nonnull final ObjectMapper objectMapper)
-            throws JsonProcessingException {
-        final JavaType objectType = objectMapper.constructType(EntityStatementPayload.class);
-        final EntityStatementPayload result =
-                objectMapper.readValue(jwt.getPayload().toString(), objectType);
-        assert result != null;
-        return new EntityStatement(jwt, result);
-    }
-    
     /** {@inheritDoc} */
     @Override public String toString() {
         return MoreObjects.toStringHelper(this)
@@ -117,5 +87,4 @@ public class EntityStatement {
                 .add("parsedPayload", parsedPayload)
                 .toString();
     }
-
 }
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BaseExpirableSubjectPayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BaseExpirableSubjectPayload.java
new file mode 100644
index 0000000..d122489
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BaseExpirableSubjectPayload.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
+
+import java.time.Instant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+/**
+ * Base payload carrying subject and expiration.
+ */
+public class BaseExpirableSubjectPayload extends BasePayload {
+
+    /** The Entity Identifier of the subject. */
+    @JsonProperty("sub") private String subject;
+
+    /** Expiration time after which this statement MUST NOT be accepted for processing. */
+    @JsonProperty("exp") private Instant expiration;
+
+    /**
+     * Get the subject.
+     * 
+     * @return subject
+     */
+    public String getSubject() {
+        return subject;
+    }
+
+    /**
+     * Set the subject.
+     * 
+     * @param sub subject to set.
+     */
+    public void setSubject(final String sub) {
+        subject = sub;
+    }
+
+    /**
+     * Get the expiration time.
+     * 
+     * @return expiration time
+     */
+    public Instant getExpiration() {
+        return expiration;
+    }
+
+    /**
+     * Set the expiration time.
+     * 
+     * @param exp expiration time
+     */
+    public void setExpiration(final Instant exp) {
+        expiration = exp;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("iss", getIssuer())
+                .add("iat", getIssuedAt())
+                .add("sub", subject)
+                .add("exp", expiration).toString();
+    }
+
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BaseMetadataPayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BaseMetadataPayload.java
new file mode 100644
index 0000000..eb50156
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BaseMetadataPayload.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.claim.Metadata;
+
+/**
+ * Base payload carrying metadata claim.
+ */
+public class BaseMetadataPayload extends BaseExpirableSubjectPayload {
+
+    /** A JSON object that declares roles that the Entity plays per its Entity Types. */
+    @JsonProperty("metadata") private Metadata metadata;
+
+    /**
+     * Get the metadata.
+     * 
+     * @return metadata
+     */
+    public Metadata getMetadata() {
+        return metadata;
+    }
+
+    /**
+     * Set the metadata.
+     * 
+     * @param data metadata
+     */
+    public void setMetadata(final Metadata data) {
+        metadata = data;
+    }
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BasePayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BasePayload.java
new file mode 100644
index 0000000..a57b31a
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/BasePayload.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+/**
+ * Base payload for claims used within all payloads.
+ */
+public class BasePayload {
+
+    /** The Entity Identifier of the Entity Statement. */
+    @JsonProperty("iss") private String issuer;
+
+    /** Time when this statement was issued. */
+    @JsonProperty("iat") private Instant issuedAt;
+
+    /** The map of any other claims not directly mapped. */
+    private final Map<String, Object> customClaims = new HashMap<>();
+
+    /**
+     * Get the issuer.
+     * 
+     * @return issuer
+     */
+    public String getIssuer() {
+        return issuer;
+    }
+
+    /**
+     * Set the issuer.
+     * 
+     * @param iss issuer to set
+     */
+    public void setIssuer(final String iss) {
+        issuer = iss;
+    }
+
+    /**
+     * Get the issuance time.
+     * 
+     * @return issuance time
+     */
+    public Instant getIssuedAt() {
+        return issuedAt;
+    }
+
+    /**
+     * Set the issuance time.
+     * 
+     * @param iat issuance time
+     */
+    public void setIssuedAt(final Instant iat) {
+        issuedAt = iat;
+    }
+
+    /**
+     * Get the map of custom claims.
+     * 
+     * @return The map of any other claims not directly mapped.
+     */
+    @JsonAnyGetter
+    public Map<String, Object> getCustomClaims() {
+        return customClaims;
+    }
+
+    /**
+     * Add a custom claim to the map of custom claims.
+     * 
+     * @param name The name of the custom claim.
+     * @param value The value of the custom claim.
+     */
+    @JsonAnySetter
+    public void setCustomClaims(final String name, final Object value) {
+        customClaims.put(name, value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("iss", issuer)
+                .add("iat", issuedAt)
+                .add("customClaims", customClaims).toString();
+    }
+
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityConfiguration.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityConfiguration.java
index 059aa52..9c10854 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityConfiguration.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityConfiguration.java
@@ -26,7 +26,7 @@ import net.shibboleth.shared.logic.ConstraintViolationException;
 /**
  * A wrapper class extending {@link EntityStatement} with parsed claims related to entity configuration.
  */
-public class EntityConfiguration extends EntityStatement {
+public class EntityConfiguration extends EntityStatement<EntityConfigurationPayload> {
 
     /**
      * Constructor.
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatement.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatement.java
index 48c2c4f..e3ad358 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatement.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatement.java
@@ -16,10 +16,6 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
 
 import javax.annotation.Nonnull;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.MoreObjects;
 import com.nimbusds.jwt.SignedJWT;
 
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -28,14 +24,10 @@ import net.shibboleth.shared.logic.ConstraintViolationException;
 
 /**
  * A wrapper class containing the {@link SignedJWT} and parsed claims related to entity statement.
+ *
+ * @param <T> payload format
  */
-public class EntityStatement {
-
-    /** Entity statement as JWT. */
-    @Nonnull private final SignedJWT jwt;
-
-    /** Entity statement payload. */
-    @Nonnull private final EntityStatementPayload parsedPayload;
+public abstract class EntityStatement<T extends EntityStatementPayload> extends AbstractJWTWrapper<T> {
 
     /**
      * 
@@ -45,32 +37,10 @@ public class EntityStatement {
      * @param payload entity statement payload
      * @throws ConstraintViolationException if the payload syntax/content is not expected
      */
-    protected EntityStatement(@Nonnull final SignedJWT signedJwt, @Nonnull final EntityStatementPayload payload) 
+    protected EntityStatement(@Nonnull final SignedJWT signedJwt, @Nonnull final T payload) 
         throws ConstraintViolationException {
-        jwt = Constraint.isNotNull(signedJwt, "JWT cannot be null");
-        parsedPayload = Constraint.isNotNull(payload, "Entity statement payload cannot be null");
-        Constraint.isNotEmpty(parsedPayload.getIssuer(), "Issuer cannot be empty in the payload");
-        Constraint.isNotEmpty(parsedPayload.getSubject(), "Subject cannot be empty in the payload");
-    }
-
-    /**
-     * Get the entity statement as JWT.
-     * 
-     * @return JWT
-     */
-    @Nonnull public SignedJWT getJwt() {
-        return jwt;
-    }
-
-    /**
-     * Get the issuer claim of the entity statement.
-     * 
-     * @return issuer
-     */
-    @Nonnull @NotEmpty public String getIssuer() {
-        final String issuer = parsedPayload.getIssuer();
-        assert issuer != null;
-        return issuer;
+        super(signedJwt, payload);
+        Constraint.isNotEmpty(payload.getSubject(), "Subject cannot be empty in the payload");
     }
 
     /**
@@ -79,43 +49,8 @@ public class EntityStatement {
      * @return subject
      */
     @Nonnull @NotEmpty public String getSubject() {
-        final String subject = parsedPayload.getSubject();
+        final String subject = getParsedPayload().getSubject();
         assert subject != null;
         return subject;
     }
-
-    /**
-     * Get the payload of the entity statement.
-     * 
-     * @return payload
-     */
-    @Nonnull public EntityStatementPayload getParsedPayload() {
-        return parsedPayload;
-    }
-
-    /**
-     * Parse an {@link EntityStatement} from the given JWT by using the given object mapper.
-     * 
-     * @param jwt entity statement JWT
-     * @param objectMapper object mapper capable of parsing JWT payload
-     * @return entity statement
-     * @throws JsonProcessingException if the payload could not be parsed
-     */
-    public static EntityStatement parse(@Nonnull final SignedJWT jwt, @Nonnull final ObjectMapper objectMapper)
-            throws JsonProcessingException {
-        final JavaType objectType = objectMapper.constructType(EntityStatementPayload.class);
-        final EntityStatementPayload result =
-                objectMapper.readValue(jwt.getPayload().toString(), objectType);
-        assert result != null;
-        return new EntityStatement(jwt, result);
-    }
-    
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return MoreObjects.toStringHelper(this)
-                .add("jwt", jwt.serialize())
-                .add("parsedPayload", parsedPayload)
-                .toString();
-    }
-
 }
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementPayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementPayload.java
index 07cb269..0c40482 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementPayload.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementPayload.java
@@ -14,121 +14,24 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
 
-import java.time.Instant;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.MoreObjects;
 import com.nimbusds.jose.jwk.JWKSet;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.claim.Metadata;
-
 /**
  * Entity Statement payload claims as defined by the OpenID Federation 1.0 Section 3.1. These claims MUST or MAY appear
  * in both Entity Configurations and Subordinate Statements.
  */
-public class EntityStatementPayload {
-
-    /** The Entity Identifier of the Entity Statement. */
-    @JsonProperty("iss") private String issuer;
-
-    /** The Entity Identifier of the subject. */
-    @JsonProperty("sub") private String subject;
-
-    /** Time when this statement was issued. */
-    @JsonProperty("iat") private Instant issuedAt;
-
-    /** Expiration time after which this statement MUST NOT be accepted for processing. */
-    @JsonProperty("exp") private Instant expiration;
+public class EntityStatementPayload extends BaseMetadataPayload {
 
     /** A JSON Web Key Set representing the public part of the subject's Federation Entity signing keys. */
     @JsonProperty("jwks") private JWKSet jwkSet;
 
-    /** A JSON object that declares roles that the Entity plays per its Entity Types. */
-    @JsonProperty("metadata") private Metadata metadata;
-
     /** The critical Claims that must be understood and processed. */
     @JsonProperty("crit") private List<String> critical;
 
-    /** The map of any other claims not directly mapped. */
-    private final Map<String, Object> customClaims = new HashMap<>();
-
-    /**
-     * Get the issuer.
-     * 
-     * @return issuer
-     */
-    public String getIssuer() {
-        return issuer;
-    }
-
-    /**
-     * Set the issuer.
-     * 
-     * @param iss issuer to set
-     */
-    public void setIssuer(final String iss) {
-        issuer = iss;
-    }
-
-    /**
-     * Get the subject.
-     * 
-     * @return subject
-     */
-    public String getSubject() {
-        return subject;
-    }
-
-    /**
-     * Set the subject.
-     * 
-     * @param sub subject to set.
-     */
-    public void setSubject(final String sub) {
-        subject = sub;
-    }
-
-    /**
-     * Get the issuance time.
-     * 
-     * @return issuance time
-     */
-    public Instant getIssuedAt() {
-        return issuedAt;
-    }
-
-    /**
-     * Set the issuance time.
-     * 
-     * @param iat issuance time
-     */
-    public void setIssuedAt(final Instant iat) {
-        issuedAt = iat;
-    }
-
-    /**
-     * Get the expiration time.
-     * 
-     * @return expiration time
-     */
-    public Instant getExpiration() {
-        return expiration;
-    }
-
-    /**
-     * Set the expiration time.
-     * 
-     * @param exp expiration time
-     */
-    public void setExpiration(final Instant exp) {
-        expiration = exp;
-    }
-
     /**
      * Get the JWK set.
      * 
@@ -147,24 +50,6 @@ public class EntityStatementPayload {
         jwkSet = jwks;
     }
 
-    /**
-     * Get the metadata.
-     * 
-     * @return metadata
-     */
-    public Metadata getMetadata() {
-        return metadata;
-    }
-
-    /**
-     * Set the metadata.
-     * 
-     * @param data metadata
-     */
-    public void setMetadata(final Metadata data) {
-        metadata = data;
-    }
-
     /**
      * Get the critical claims.
      * 
@@ -183,40 +68,19 @@ public class EntityStatementPayload {
         critical = crit;
     }
 
-    /**
-     * Get the map of custom claims.
-     * 
-     * @return The map of any other claims not directly mapped.
-     */
-    @JsonAnyGetter
-    public Map<String, Object> getCustomClaims() {
-        return customClaims;
-    }
-
-    /**
-     * Add a custom claim to the map of custom claims.
-     * 
-     * @param name The name of the custom claim.
-     * @param value The value of the custom claim.
-     */
-    @JsonAnySetter
-    public void setCustomClaims(final String name, final Object value) {
-        customClaims.put(name, value);
-    }
-
     /**
      * {@inheritDoc}
      */
     @Override public String toString() {
         return MoreObjects.toStringHelper(this)
-                .add("iss", issuer)
-                .add("sub", subject)
-                .add("iat", issuedAt)
-                .add("exp", expiration)
+                .add("iss", getIssuer())
+                .add("sub", getSubject())
+                .add("iat", getIssuedAt())
+                .add("exp", getExpiration())
                 .add("jwks", jwkSet)
-                .add("metadata", metadata)
+                .add("metadata", getMetadata())
                 .add("crit", critical)
-                .add("customClaims", customClaims).toString();
+                .add("customClaims", getCustomClaims()).toString();
     }
 
 }
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponse.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponse.java
index 57d2cec..15fa7a1 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponse.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponse.java
@@ -26,7 +26,7 @@ import net.shibboleth.shared.logic.ConstraintViolationException;
 /**
  * A wrapper class extending {@link EntityStatement} with parsed claims related to resolve entity response.
  */
-public class ResolveEntityResponse extends EntityStatement {
+public class ResolveEntityResponse extends AbstractJWTWrapper<ResolveEntityResponsePayload> {
 
     /**
      * Constructor.
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponsePayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponsePayload.java
index a3f7d5b..dec3bea 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponsePayload.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponsePayload.java
@@ -25,7 +25,7 @@ import com.google.common.base.MoreObjects;
 /**
  * Resolve Entity response payload claims as defined by the OpenID Federation 1.0 Section 8.3.2.
  */
-public class ResolveEntityResponsePayload extends EntityStatementPayload {
+public class ResolveEntityResponsePayload extends BaseMetadataPayload {
 
     /** An array containing the sequence of Entity Statements that compose the Trust Chain. */
     @JsonProperty("trust_chain") private List<String> trustChain;
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/SubordinateStatement.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/SubordinateStatement.java
index f3f5793..1572179 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/SubordinateStatement.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/SubordinateStatement.java
@@ -26,7 +26,7 @@ import net.shibboleth.shared.logic.ConstraintViolationException;
 /**
  * A wrapper class extending {@link EntityStatement} with parsed claims related to subordinate statements.
  */
-public class SubordinateStatement extends EntityStatement {
+public class SubordinateStatement extends EntityStatement<SubordinateStatementPayload> {
 
     /**
      * Constructor.
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMark.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMark.java
index 3a476f5..84f2f47 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMark.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMark.java
@@ -26,7 +26,7 @@ import net.shibboleth.shared.logic.ConstraintViolationException;
 /**
  * A wrapper class extending {@link EntityStatement} with parsed claims related to trust mark.
  */
-public class TrustMark extends EntityStatement {
+public class TrustMark extends AbstractJWTWrapper<TrustMarkPayload> {
 
     /**
      * Constructor.
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkPayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkPayload.java
index 5413bc7..17937b1 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkPayload.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkPayload.java
@@ -22,7 +22,7 @@ import com.google.common.base.MoreObjects;
 /**
  * Trust Mark payload claims as defined by the OpenID Federation 1.0 Section 7.1.
  */
-public class TrustMarkPayload extends EntityStatementPayload {
+public class TrustMarkPayload extends BaseExpirableSubjectPayload {
 
     /** An identifier of the type of the Trust Mark. */
     @JsonProperty("trust_mark_type") private String trustMarkType;
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatus.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatus.java
index 8c566ad..a96af2d 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatus.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatus.java
@@ -26,7 +26,7 @@ import net.shibboleth.shared.logic.ConstraintViolationException;
 /**
  * A wrapper class extending {@link EntityStatement} with parsed claims related to trust mark status.
  */
-public class TrustMarkStatus extends EntityStatement {
+public class TrustMarkStatus extends AbstractJWTWrapper<TrustMarkStatusPayload> {
 
     /**
      * Constructor.
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusPayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusPayload.java
index 8dd182d..ad6bbb5 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusPayload.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusPayload.java
@@ -22,7 +22,7 @@ import com.google.common.base.MoreObjects;
 /**
  * Trust Mark Status payload claims as defined by the OpenID Federation 1.0 Section 8.4.2.
  */
-public class TrustMarkStatusPayload extends EntityStatementPayload {
+public class TrustMarkStatusPayload extends BasePayload {
 
     /** A Trust Mark. */
     @JsonProperty("trust_mark") private String trustMark;
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
index a272a06..0818958 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
@@ -36,13 +36,13 @@ import net.shibboleth.shared.collection.Pair;
 public final class RelyingPartyTrustChainContext extends BaseContext {
 
     /** All resolved trust chains for the relying party. */
-    @Nullable private List<List<EntityStatement>> resolvedTrustChains;
+    @Nullable private List<List<EntityStatement<?>>> resolvedTrustChains;
 
     /** Policy-compliant trust chains for the relying party. */
-    @Nullable private List<Pair<List<EntityStatement>,Metadata>> policyCompliantTrustChains;
+    @Nullable private List<Pair<List<EntityStatement<?>>,Metadata>> policyCompliantTrustChains;
 
     /** Selected trust chain for the relying party. */
-    @Nullable private Pair<List<EntityStatement>,Metadata> selectedTrustChain;
+    @Nullable private Pair<List<EntityStatement<?>>,Metadata> selectedTrustChain;
 
     /** Expiration instant for the selected metadata. */
     @Nullable private Instant selectedMetadataExpiration;
@@ -54,7 +54,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
     @Nullable private Map<String, List<SignedJWT>> verifiedTrustMarks;
 
     /** All previously selected but rejected trust chains. */
-    @Nullable private List<List<EntityStatement>> rejectedTrustChains;
+    @Nullable private List<List<EntityStatement<?>>> rejectedTrustChains;
 
     /** All already attempted trusted remote resolver entities. */
     @Nullable private List<TrustedRemoteResolverEntity> attemptedTrustedRemoteResolverEntities;
@@ -65,7 +65,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * 
      * @return the trust chains
      */
-    @Nullable public List<List<EntityStatement>> getResolvedTrustChains() {
+    @Nullable public List<List<EntityStatement<?>>> getResolvedTrustChains() {
         return resolvedTrustChains;
     }
 
@@ -77,7 +77,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * @return this context
      */
     @Nonnull public RelyingPartyTrustChainContext setResolvedTrustChains(
-            @Nullable final List<List<EntityStatement>> trustChains) {
+            @Nullable final List<List<EntityStatement<?>>> trustChains) {
         resolvedTrustChains = trustChains;
         return this;
     }
@@ -87,7 +87,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * 
      * @return the trust chains
      */
-    @Nullable public List<Pair<List<EntityStatement>,Metadata>> getPolicyCompliantTrustChains() {
+    @Nullable public List<Pair<List<EntityStatement<?>>,Metadata>> getPolicyCompliantTrustChains() {
         return policyCompliantTrustChains;
     }
 
@@ -99,7 +99,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * @return this context
      */
     @Nonnull public RelyingPartyTrustChainContext setPolicyCompliantTrustChains(
-            @Nullable final List<Pair<List<EntityStatement>,Metadata>> chains) {
+            @Nullable final List<Pair<List<EntityStatement<?>>,Metadata>> chains) {
         policyCompliantTrustChains = chains;
         return this;
     }
@@ -109,7 +109,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * 
      * @return the trust chain
      */
-    @Nullable public Pair<List<EntityStatement>,Metadata> getSelectedTrustChain() {
+    @Nullable public Pair<List<EntityStatement<?>>,Metadata> getSelectedTrustChain() {
         return selectedTrustChain;
     }
 
@@ -121,7 +121,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * @return this context
      */
     @Nonnull public RelyingPartyTrustChainContext setSelectedTrustChains(
-            @Nullable final Pair<List<EntityStatement>,Metadata> chain) {
+            @Nullable final Pair<List<EntityStatement<?>>,Metadata> chain) {
         selectedTrustChain = chain;
         return this;
     }
@@ -196,7 +196,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * 
      * @return the trust chains
      */
-    @Nullable public List<List<EntityStatement>> getRejectedTrustChains() {
+    @Nullable public List<List<EntityStatement<?>>> getRejectedTrustChains() {
         return rejectedTrustChains;
     }
 
@@ -208,7 +208,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
      * @return this context
      */
     @Nonnull public RelyingPartyTrustChainContext setRejectedTrustChains(
-            @Nullable final List<List<EntityStatement>> trustChains) {
+            @Nullable final List<List<EntityStatement<?>>> trustChains) {
         rejectedTrustChains = trustChains;
         return this;
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java
index c304b0e..4b6230f 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java
@@ -17,8 +17,8 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.decoding.impl;
 import java.io.IOException;
 import java.net.URI;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -96,21 +96,22 @@ public class ExplicitClientRegistrationRequestDecoder
             }
             final String contentType = request.getContentType();
             if ("application/entity-statement+jwt".equals(contentType)) {
-                final EntityStatement entityConfiguration =
-                        deserializeEntityStatement(httpRequest.getQuery());
-                if (entityConfiguration == null) {
-                    throw new MessageDecodingException("Could not deserialize entity configuration");
+                if (deserializeEntityStatement(httpRequest.getQuery()) instanceof EntityConfiguration configuration) {
+                    log.trace("Entity configuration claims set: {}", configuration.getParsedPayload());
+                    return new ExplicitClientRegistrationRequest(uri, configuration);
                 }
-                log.trace("Entity configuration claims set: {}", entityConfiguration.getParsedPayload());
-                return new ExplicitClientRegistrationRequest(uri, entityConfiguration);
+                throw new MessageDecodingException("Could not deserialize entity configuration");
             } else if ("application/trust-chain+json".equals(contentType)) {
                 final TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {};
                 final List<String> strings = objectMapper.readValue(httpRequest.getQuery(), typeReference);
-                final List<EntityStatement> trustChain = strings.stream()
-                        .map(string -> deserializeEntityStatement(string))
-                        .filter(Objects::nonNull)
-                        .toList();
-                if (trustChain == null || trustChain.isEmpty()) {
+                final List<EntityStatement<?>> trustChain = new ArrayList<>();
+                strings.forEach(item -> {
+                    final EntityStatement<?> deserialized = deserializeEntityStatement(item);
+                    if (deserialized != null) {
+                        trustChain.add(deserialized);
+                    }
+                });
+                if (trustChain.isEmpty()) {
                     throw new MessageDecodingException("Could not deserialize trust chain");
                 }
                 return new ExplicitClientRegistrationRequest(uri, trustChain);
@@ -124,7 +125,7 @@ public class ExplicitClientRegistrationRequestDecoder
         }
     }
 
-    @Nullable private EntityStatement deserializeEntityStatement(@Nullable final String input) {
+    @Nullable private EntityStatement<?> deserializeEntityStatement(@Nullable final String input) {
         try {
             final SignedJWT jwt = SignedJWT.parse(input);
             assert jwt != null;
@@ -142,6 +143,7 @@ public class ExplicitClientRegistrationRequestDecoder
             return null;
         }
     }
+
     /** {@inheritDoc} */
     @Override
     protected String getMessageToLog(@Nullable final ExplicitClientRegistrationRequest message) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ExplicitClientRegistrationRequest.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ExplicitClientRegistrationRequest.java
index f1c093b..288eb30 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ExplicitClientRegistrationRequest.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ExplicitClientRegistrationRequest.java
@@ -23,6 +23,7 @@ import javax.annotation.Nullable;
 import com.nimbusds.oauth2.sdk.Request;
 import com.nimbusds.oauth2.sdk.http.HTTPRequest;
 
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
@@ -36,10 +37,10 @@ public class ExplicitClientRegistrationRequest implements Request {
     @Nonnull private final URI endpointUri;
 
     /** The entity configuration from the request. */
-    @Nullable private final EntityStatement entityConfiguration;
+    @Nullable private final EntityConfiguration entityConfiguration;
 
     /** The trust chain from the request. */
-    @Nullable private final List<EntityStatement> trustChain;
+    @Nullable private final List<EntityStatement<?>> trustChain;
 
     /**
      * Constructor.
@@ -47,7 +48,8 @@ public class ExplicitClientRegistrationRequest implements Request {
      * @param uri endpoint URI
      * @param configuration client configuration
      */
-    public ExplicitClientRegistrationRequest(@Nonnull final URI uri, @Nonnull final EntityStatement configuration) {
+    public ExplicitClientRegistrationRequest(@Nonnull final URI uri,
+            @Nonnull final EntityConfiguration configuration) {
         endpointUri = Constraint.isNotNull(uri, "Endpoint URI cannot be null");
         entityConfiguration = Constraint.isNotNull(configuration, "Entity configuration cannot be null");
         trustChain = null;
@@ -60,7 +62,7 @@ public class ExplicitClientRegistrationRequest implements Request {
      * @param chain trust chain
      */
     public ExplicitClientRegistrationRequest(@Nonnull final URI uri,
-            @Nonnull @NotEmpty final List<EntityStatement> chain) {
+            @Nonnull @NotEmpty final List<EntityStatement<?>> chain) {
         endpointUri = Constraint.isNotNull(uri, "Endpoint URI cannot be null");
         Constraint.isNotEmpty(chain, "Trust chain cannot be empty");
         trustChain = chain;
@@ -85,7 +87,7 @@ public class ExplicitClientRegistrationRequest implements Request {
      * 
      * @return entity configuration
      */
-    @Nullable public EntityStatement getEntityConfiguration() {
+    @Nullable public EntityConfiguration getEntityConfiguration() {
         return entityConfiguration;
     }
 
@@ -94,7 +96,7 @@ public class ExplicitClientRegistrationRequest implements Request {
      * 
      * @return trust chain
      */
-    @Nullable public List<EntityStatement> getTrustChain() {
+    @Nullable public List<EntityStatement<?>> getTrustChain() {
         return trustChain;
     }
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultCombinedMetadataFromTrustChainLookupStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultCombinedMetadataFromTrustChainLookupStrategy.java
index fce60f3..19fc945 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultCombinedMetadataFromTrustChainLookupStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultCombinedMetadataFromTrustChainLookupStrategy.java
@@ -37,14 +37,14 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  */
 @ThreadSafeAfterInit
 public class DefaultCombinedMetadataFromTrustChainLookupStrategy extends AbstractIdentifiableInitializableComponent
-    implements Function<List<EntityStatement>,Map<String,Map<String,Object>>> {
+    implements Function<List<EntityStatement<?>>,Map<String,Map<String,Object>>> {
 
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(DefaultCombinedMetadataFromTrustChainLookupStrategy.class);
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public Map<String,Map<String,Object>> apply(@Nullable final List<EntityStatement> chain) {
+    public Map<String,Map<String,Object>> apply(@Nullable final List<EntityStatement<?>> chain) {
         if (chain == null || chain.size() < 3) {
             log.warn("Unexpected trust chain input: {}", chain == null ? null : "size = " + chain.size());
             return null;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java
index a805265..04b2901 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java
@@ -50,14 +50,14 @@ import net.shibboleth.shared.resolver.CriteriaSet;
 @ThreadSafeAfterInit
 public class DefaultProvidedTrustChainSignatureValidationFilterStrategy
     extends AbstractTrustEngineSignatureValidationComponent
-    implements BiFunction<List<List<EntityStatement>>, MetadataFilterContext, List<List<EntityStatement>>> {
+    implements BiFunction<List<List<EntityStatement<?>>>, MetadataFilterContext, List<List<EntityStatement<?>>>> {
 
     /** Class logger. */
     @Nonnull private Logger log =
             LoggerFactory.getLogger(DefaultProvidedTrustChainSignatureValidationFilterStrategy.class);
 
     /** Signature validation filter strategy for trust anchor entity statements. */
-    @NonnullAfterInit private BiFunction<EntityStatement, MetadataFilterContext, EntityStatement>
+    @NonnullAfterInit private BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>>
         entityStatementSignatureValidationFilterStrategy;
 
     /**
@@ -66,7 +66,7 @@ public class DefaultProvidedTrustChainSignatureValidationFilterStrategy
      * @param strategy signature validation filter strategy
      */
     public void setEntityStatementSignatureValidationFilterStrategy(@Nonnull final
-            BiFunction<EntityStatement, MetadataFilterContext, EntityStatement> strategy) {
+            BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>> strategy) {
         checkSetterPreconditions();
         entityStatementSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
                 "EntityStatementSignatureValidationFilterStrategy cannot be null");
@@ -84,17 +84,17 @@ public class DefaultProvidedTrustChainSignatureValidationFilterStrategy
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public List<List<EntityStatement>> apply(@Nullable final List<List<EntityStatement>> trustChains,
+    public List<List<EntityStatement<?>>> apply(@Nullable final List<List<EntityStatement<?>>> trustChains,
             @Nullable final MetadataFilterContext filterContext) {
         checkComponentActive();
         if (trustChains == null) {
             return null;
         }
-        final List<List<EntityStatement>> result = new ArrayList<>();
+        final List<List<EntityStatement<?>>> result = new ArrayList<>();
         
-        trustChainLoop: for (final List<EntityStatement> trustChain : trustChains) {
+        trustChainLoop: for (final List<EntityStatement<?>> trustChain : trustChains) {
             for (int i = 0; i < trustChain.size() - 1; i++) {
-                final EntityStatement trustChainEntry = trustChain.get(i);
+                final EntityStatement<?> trustChainEntry = trustChain.get(i);
                 final SignedJWT keyContainer = trustChain.get(i + 1).getJwt();
                 final CriteriaSet criteria =
                         new CriteriaSet(new SignatureValidationKeyContainerJwtCriterion(keyContainer));
@@ -108,9 +108,9 @@ public class DefaultProvidedTrustChainSignatureValidationFilterStrategy
                     continue trustChainLoop;
                 }
             }
-            final EntityStatement trustAnchorStatement = trustChain.get(trustChain.size() - 1);
+            final EntityStatement<?> trustAnchorStatement = trustChain.get(trustChain.size() - 1);
             final String entityId = trustAnchorStatement.getSubject();
-            final EntityStatement filteredStatement =
+            final EntityStatement<?> filteredStatement =
                     entityStatementSignatureValidationFilterStrategy.apply(trustAnchorStatement, filterContext);
             if (filteredStatement != null) {
                 log.debug("Successfully validated entity statement for trust anchor {}", entityId);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java
index 14af8dc..29ba25a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java
@@ -42,13 +42,13 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * applying the constraints.
  */
 public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentifiableInitializableComponent 
-    implements BiPredicate<ProfileRequestContext, List<EntityStatement>> {
+    implements BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> {
 
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedTrustChainValidationStrategy.class);
 
     /** Strategy for validating trust anchor's entity configuration signature. */
-    @NonnullAfterInit BiFunction<List<List<EntityStatement>>, MetadataFilterContext, List<List<EntityStatement>>>
+    @NonnullAfterInit BiFunction<List<List<EntityStatement<?>>>, MetadataFilterContext, List<List<EntityStatement<?>>>>
         trustChainSignatureValidationFilterStrategy;
 
     /** Map of supported federation policy constraints. */
@@ -63,7 +63,8 @@ public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentif
      * @param strategy validation strategy
      */
     public void setTrustChainSignatureValidationFilterStrategy(
-            @Nonnull final BiFunction<List<List<EntityStatement>>, MetadataFilterContext, List<List<EntityStatement>>> strategy) {
+            @Nonnull final BiFunction<List<List<EntityStatement<?>>>,
+                MetadataFilterContext, List<List<EntityStatement<?>>>> strategy) {
         checkSetterPreconditions();
         trustChainSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
                 "TrustChainSignatureValidationFilterStrategy cannot be null");
@@ -108,14 +109,14 @@ public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentif
     /** {@inheritDoc} */
     @Override
     public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
-            @Nullable final List<EntityStatement> trustChain) {
+            @Nullable final List<EntityStatement<?>> trustChain) {
         checkComponentActive();
         if (trustChain == null || trustChain.size() < 3 || trustChain.contains(null)) {
             log.error("No satisfactory trust chain provided");
             return false;
         }
 
-        final List<List<EntityStatement>> validatedChains =
+        final List<List<EntityStatement<?>>> validatedChains =
                 trustChainSignatureValidationFilterStrategy.apply(List.of(trustChain), null);
         if (validatedChains == null || validatedChains.size() != 1) {
             log.debug("The trust chain did not pass the signature validation");
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustMarksParsingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustMarksParsingStrategy.java
index bea3845..086017f 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustMarksParsingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustMarksParsingStrategy.java
@@ -42,14 +42,14 @@ import net.shibboleth.shared.primitive.StringSupport;
  */
 @ThreadSafeAfterInit
 public class DefaultTrustChainTrustMarksParsingStrategy extends AbstractIdentifiableInitializableComponent
-        implements Function<List<EntityStatement>,Map<String,List<SignedJWT>>> {
+        implements Function<List<EntityStatement<?>>,Map<String,List<SignedJWT>>> {
 
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainTrustMarksParsingStrategy.class);
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public Map<String, List<SignedJWT>> apply(@Nullable final List<EntityStatement> trustChain) {
+    public Map<String, List<SignedJWT>> apply(@Nullable final List<EntityStatement<?>> trustChain) {
         checkComponentActive();
         if (trustChain == null || trustChain.size() < 3) {
             log.error("Unexpected length in the trust chain: {}", trustChain == null ? "null" : trustChain.size());
@@ -57,7 +57,7 @@ public class DefaultTrustChainTrustMarksParsingStrategy extends AbstractIdentifi
         }
         
         final Map<String, List<SignedJWT>> result = new HashMap<>();
-        for (final EntityStatement statement : trustChain) {
+        for (final EntityStatement<?> statement : trustChain) {
             assert statement != null;
             if (statement.getParsedPayload() instanceof EntityConfigurationPayload ecp) {
                 final SignedJWT statementJwt = statement.getJwt();
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java
index 8d8b1f3..0f08d85 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java
@@ -34,11 +34,11 @@ import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponen
  */
 @ThreadSafeAfterInit
 public class DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy extends AbstractIdentifiableInitializableComponent
-    implements Function<List<EntityStatement>, Map<String, List<String>>> {
+    implements Function<List<EntityStatement<?>>, Map<String, List<String>>> {
 
     /** {@inheritDoc} */
     @Nullable @Override
-    public Map<String, List<String>> apply(@Nullable final List<EntityStatement> trustChain) {
+    public Map<String, List<String>> apply(@Nullable final List<EntityStatement<?>> trustChain) {
         checkComponentActive();
         if (trustChain == null || trustChain.size() < 3) {
             return null;
@@ -56,7 +56,8 @@ public class DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy extends Abst
      * @param trustChain trust chain to be parsed
      * @return map of trusted issuers
      */
-    @Nonnull protected Map<String, List<String>> parseTrustedIssuers(@Nonnull final List<EntityStatement> trustChain) {
+    @Nonnull protected Map<String, List<String>> parseTrustedIssuers(
+            @Nonnull final List<EntityStatement<?>> trustChain) {
         final Map<String, List<String>> map =
                 trustChain.get(trustChain.size() - 1).getParsedPayload() instanceof EntityConfigurationPayload ecp ?
                         ecp.getTrustMarkIssuers() : null;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
index 7fbf9d1..f1f884a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
@@ -35,7 +35,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  */
 @ThreadSafe
 public class DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy
-    implements Function<List<EntityStatement>, Map<String, Map<String, Object>>> {
+    implements Function<List<EntityStatement<?>>, Map<String, Map<String, Object>>> {
 
     /** Class logger. */
     @Nonnull private final Logger log =
@@ -43,7 +43,7 @@ public class DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy
 
     /** {@inheritDoc} */
     @Nullable @Override
-    public Map<String, Map<String, Object>> apply(@Nullable final List<EntityStatement> trustChain) {
+    public Map<String, Map<String, Object>> apply(@Nullable final List<EntityStatement<?>> trustChain) {
         if (trustChain == null || trustChain.size() < 3) {
             return null;
         }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityConfigurationMetadataSkeletonLookupStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityConfigurationMetadataSkeletonLookupStrategy.java
index ca6bd90..c221e5d 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityConfigurationMetadataSkeletonLookupStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityConfigurationMetadataSkeletonLookupStrategy.java
@@ -71,13 +71,15 @@ public class DefaultEntityConfigurationMetadataSkeletonLookupStrategy extends Ab
     @Override @Nullable
     public Map<String,Map<String,Object>> apply(@Nullable final CriteriaSet criteria) {
         checkComponentActive();
-        try {
-            return Optional.of(metadataSkeletonCache.get(criteria))
-                    .filter(result -> result.size() == 1)
-                    .map(result -> result.get(0))
-                    .orElse(CollectionSupport.emptyMap());
-        } catch (final MetadataCacheException e) {
-            log.error("Could not fetch metadata skeleton via metadata cache", e);
+        if (criteria != null) {
+            try {
+                return Optional.of(metadataSkeletonCache.get(criteria))
+                        .filter(result -> result.size() == 1)
+                        .map(result -> result.get(0))
+                        .orElse(CollectionSupport.emptyMap());
+            } catch (final MetadataCacheException e) {
+                log.error("Could not fetch metadata skeleton via metadata cache", e);
+            }
         }
         return CollectionSupport.emptyMap();
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityStatementSignatureValidationFilterStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityStatementSignatureValidationFilterStrategy.java
index 337c2f6..1f04dd7 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityStatementSignatureValidationFilterStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultEntityStatementSignatureValidationFilterStrategy.java
@@ -37,7 +37,7 @@ import net.shibboleth.shared.resolver.CriteriaSet;
 @ThreadSafeAfterInit
 public class DefaultEntityStatementSignatureValidationFilterStrategy
     extends AbstractTrustEngineSignatureValidationComponent
-    implements BiFunction<EntityStatement, MetadataFilterContext, EntityStatement> {
+    implements BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>> {
 
     /** Class logger. */
     @Nonnull private Logger log =
@@ -45,7 +45,7 @@ public class DefaultEntityStatementSignatureValidationFilterStrategy
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public EntityStatement apply(@Nullable final EntityStatement entityStatement,
+    public EntityStatement<?> apply(@Nullable final EntityStatement<?> entityStatement,
             @Nullable final MetadataFilterContext filterContext) {
         checkComponentActive();
         if (entityStatement == null) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultNimbusResponseContainerExpirationTimeStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultNimbusResponseContainerExpirationTimeStrategy.java
index 58d97f0..93ae01c 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultNimbusResponseContainerExpirationTimeStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/DefaultNimbusResponseContainerExpirationTimeStrategy.java
@@ -44,7 +44,7 @@ public class DefaultNimbusResponseContainerExpirationTimeStrategy
         }
         final Instant contextExpiration = context.getNow().plus(context.getMaxCacheDuration());
         final NimbusResponseContainer container = context.getMetadata();
-        if (container == null || container.getExpirationInstant() == null) {
+        if (container == null) {
             return contextExpiration;
         }
         final List<Instant> expirations = new ArrayList<>(List.of(contextExpiration, container.getExpirationInstant()));
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/BaseExpirableEntityStatementContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/BaseExpirableStatementContainer.java
similarity index 69%
rename from idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/BaseExpirableEntityStatementContainer.java
rename to idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/BaseExpirableStatementContainer.java
index e09653c..b046f20 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/BaseExpirableEntityStatementContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/BaseExpirableStatementContainer.java
@@ -20,20 +20,25 @@ import java.time.Instant;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.AbstractJWTWrapper;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.BaseExpirableSubjectPayload;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * A base container class for metadata caches carrying expiration instant.
+ * A base container class for metadata caches carrying expiration instant and statement.
+ * 
+ * @param <T> wrapped statement
  */
-public abstract class BaseExpirableEntityStatementContainer extends BaseExpirableMetadataContainer
+public abstract class BaseExpirableStatementContainer
+    <T extends AbstractJWTWrapper<? extends BaseExpirableSubjectPayload>>
+    extends BaseExpirableMetadataContainer
     implements Serializable {
 
     /** Serial version UID. */
     private static final long serialVersionUID = 656269369356432370L;
 
     /** Entity statement for this container. */
-    @Nullable private final EntityStatement statement;
+    @Nullable private final T statement;
 
     /** Flag to indicate if {@link #statement} is valid. */
     private boolean validStatement;
@@ -48,8 +53,8 @@ public abstract class BaseExpirableEntityStatementContainer extends BaseExpirabl
      * @param entityStatement entity statement
      * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
      */
-    public BaseExpirableEntityStatementContainer(@Nonnull final Instant validExpirationInstant,
-            @Nullable final EntityStatement entityStatement, @Nonnull final Instant invalidExpirationInstant) {
+    public BaseExpirableStatementContainer(@Nonnull final Instant validExpirationInstant,
+            @Nullable final T entityStatement, @Nonnull final Instant invalidExpirationInstant) {
         super(validExpirationInstant);
         statement = entityStatement;
         validStatement = entityStatement != null;
@@ -58,27 +63,27 @@ public abstract class BaseExpirableEntityStatementContainer extends BaseExpirabl
     }
 
     /**
-     * Get the entity statement. If the statement has been invalidated, a null is returned.
+     * Get the statement. If the statement has been invalidated, a null is returned.
      * 
      * @return entity statement
      */
-    @Nullable public EntityStatement getEntityStatement() {
-        return getEntityStatement(false);
+    @Nullable public T getStatement() {
+        return getStatement(false);
     }
 
     /**
-     * Get the entity statement.
+     * Get the statement.
      * 
      * @param includeInvalidStatement a flag to return invalidated entity statement
      * @return entity statement
      */
-    @Nullable public EntityStatement getEntityStatement(final boolean includeInvalidStatement) {
+    @Nullable public T getStatement(final boolean includeInvalidStatement) {
         return includeInvalidStatement ? statement : validStatement ? statement : null;
     }
 
     /** {@inheritDoc} */
     @Nonnull public Instant getExpirationInstant() {
-        return getEntityStatement() != null ? super.getExpirationInstant() : invalidStatementExpiration;
+        return getStatement() != null ? super.getExpirationInstant() : invalidStatementExpiration;
     }
 
     /**
@@ -86,7 +91,7 @@ public abstract class BaseExpirableEntityStatementContainer extends BaseExpirabl
      * 
      * @return this container
      */
-    public BaseExpirableEntityStatementContainer invalidateStatement() {
+    public BaseExpirableStatementContainer<T> invalidateStatement() {
         validStatement = false;
         return this;
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementContentValidationCondition.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementContentValidationCondition.java
index f3eee5b..bdd173a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementContentValidationCondition.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementContentValidationCondition.java
@@ -25,7 +25,6 @@ import org.slf4j.Logger;
 
 import com.nimbusds.jwt.JWTClaimsSet;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
 import net.shibboleth.oidc.jwt.claims.JWTValidationException;
 import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
@@ -44,7 +43,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 @ThreadSafeAfterInit
 public class DefaultEntityStatementContentValidationCondition
     extends AbstractIdentifiableInitializableComponent
-    implements BiPredicate<BaseExpirableEntityStatementContainer, MetadataFilterContext>  {
+    implements BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext>  {
 
     /** Class logger. */
     @Nonnull private Logger log =
@@ -65,24 +64,24 @@ public class DefaultEntityStatementContentValidationCondition
 
     /** {@inheritDoc} */
     @Override
-    public boolean test(@Nullable final BaseExpirableEntityStatementContainer responseContainer,
+    public boolean test(@Nullable final BaseExpirableStatementContainer<?> responseContainer,
             @Nullable final MetadataFilterContext filterContext) {
         checkComponentActive();
 
-        if (responseContainer == null || responseContainer.getEntityStatement() == null) {
+        if (responseContainer == null || responseContainer.getStatement() == null) {
             return false;
         }
 
-        final EntityStatement statement = responseContainer.getEntityStatement();
-        assert statement != null;
-        if (!responseContainer.getEntityId().equals(statement.getParsedPayload().getSubject())) {
+        final var wrapper = responseContainer.getStatement();
+        assert wrapper != null;
+        if (!responseContainer.getEntityId().equals(wrapper.getParsedPayload().getSubject())) {
             log.warn("Entity statement subject {} does not match with the requested entity ID {}",
-                    statement.getParsedPayload().getSubject(), responseContainer.getEntityId());
+                    wrapper.getParsedPayload().getSubject(), responseContainer.getEntityId());
             return false;
         }
 
         try {
-            final JWTClaimsSet claimsSet = statement.getJwt().getJWTClaimsSet();
+            final JWTClaimsSet claimsSet = wrapper.getJwt().getJWTClaimsSet();
             assert claimsSet != null;
             claimsValidator.validate(claimsSet, new ProfileRequestContext());
         } catch (final JWTValidationException | ParseException e) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementMetadataFilterStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementMetadataFilterStrategy.java
index 2773f00..b111c66 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementMetadataFilterStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementMetadataFilterStrategy.java
@@ -35,14 +35,14 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  */
 public class DefaultEntityStatementMetadataFilterStrategy
     extends AbstractIdentifiableInitializableComponent implements
-    BiFunction<BaseExpirableEntityStatementContainer, MetadataFilterContext, BaseExpirableEntityStatementContainer> {
+    BiFunction<BaseExpirableStatementContainer<?>, MetadataFilterContext, BaseExpirableStatementContainer<?>> {
 
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(DefaultEntityStatementMetadataFilterStrategy.class);
 
     /** List of validation conditions. */
     @NonnullAfterInit
-    private List<BiPredicate<BaseExpirableEntityStatementContainer, MetadataFilterContext>> validationConditions;
+    private List<BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext>> validationConditions;
 
     /**
      * Set the list of validation conditions.
@@ -50,7 +50,7 @@ public class DefaultEntityStatementMetadataFilterStrategy
      * @param predicates validation conditions
      */
     public void setValidationConditions(
-            @Nonnull final List<BiPredicate<BaseExpirableEntityStatementContainer, MetadataFilterContext>> predicates) {
+            @Nonnull final List<BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext>> predicates) {
         checkSetterPreconditions();
         validationConditions = Constraint.isNotNull(predicates, "List of validation conditions cannot be null");
     }
@@ -66,13 +66,13 @@ public class DefaultEntityStatementMetadataFilterStrategy
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public BaseExpirableEntityStatementContainer apply(@Nullable final BaseExpirableEntityStatementContainer container,
+    public BaseExpirableStatementContainer<?> apply(@Nullable final BaseExpirableStatementContainer<?> container,
             @Nullable final MetadataFilterContext filterContext) {
         checkComponentActive();
         if (container == null) {
             return null;
         }
-        for (final BiPredicate<BaseExpirableEntityStatementContainer, MetadataFilterContext> condition :
+        for (final BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext> condition :
             validationConditions) {
             if (!condition.test(container, filterContext)) {
                 log.debug("Condition {} failed, invalidating container for {}", condition, container.getEntityId());
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementSignatureValidationCondition.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementSignatureValidationCondition.java
index 0e0e379..975177b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementSignatureValidationCondition.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementSignatureValidationCondition.java
@@ -23,7 +23,6 @@ import javax.annotation.Nullable;
 import org.opensaml.security.trust.TrustEngine;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.AbstractTrustEngineSignatureValidationComponent;
 import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -40,14 +39,14 @@ import net.shibboleth.shared.resolver.CriteriaSet;
 @ThreadSafeAfterInit
 public class DefaultEntityStatementSignatureValidationCondition
     extends AbstractTrustEngineSignatureValidationComponent
-    implements BiPredicate<BaseExpirableEntityStatementContainer, MetadataFilterContext> {
+    implements BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext> {
 
     /** Class logger. */
     @Nonnull private Logger log =
             LoggerFactory.getLogger(DefaultEntityStatementSignatureValidationCondition.class);
 
     /** Lookup strategy for criteria set passed to the trust engine. */
-    @NonnullAfterInit private Function<BaseExpirableEntityStatementContainer, CriteriaSet> criteriaSetLookupStrategy;
+    @NonnullAfterInit private Function<BaseExpirableStatementContainer<?>, CriteriaSet> criteriaSetLookupStrategy;
 
     /**
      * Set the lookup strategy for criteria set passed to the trust engine.
@@ -55,7 +54,7 @@ public class DefaultEntityStatementSignatureValidationCondition
      * @param strategy lookup strategy
      */
     public void setCriteriaSetLookupStrategy(
-            @Nonnull final Function<BaseExpirableEntityStatementContainer, CriteriaSet> strategy) {
+            @Nonnull final Function<BaseExpirableStatementContainer<?>, CriteriaSet> strategy) {
         checkSetterPreconditions();
         criteriaSetLookupStrategy = Constraint.isNotNull(strategy, "CriteriaSet lookup strategy cannot be null");
     }
@@ -71,23 +70,23 @@ public class DefaultEntityStatementSignatureValidationCondition
 
     /** {@inheritDoc} */
     @Override
-    public boolean test(@Nullable final BaseExpirableEntityStatementContainer responseContainer,
+    public boolean test(@Nullable final BaseExpirableStatementContainer<?> responseContainer,
             @Nullable final MetadataFilterContext filterContext) {
         checkComponentActive();
         if (responseContainer == null) {
             return false;
         }
 
-        final EntityStatement entityConfiguration = responseContainer.getEntityStatement();
+        final var statement = responseContainer.getStatement();
         final String entityId = responseContainer.getEntityId();
-        if (entityConfiguration != null ) {
-            log.trace("Starting signature validation of entity configuration for {}", entityId);
+        if (statement != null ) {
+            log.trace("Starting signature validation of statement for {}", entityId);
             final CriteriaSet criteria = criteriaSetLookupStrategy.apply(responseContainer);
             if (criteria == null) {
                 log.error("Could not resolve criteria set for {}", entityId);
                 return false;
             }
-            if (!validateJwt(entityConfiguration.getJwt(), criteria, entityId)) {
+            if (!validateJwt(statement.getJwt(), criteria, entityId)) {
                 return false;
             }
         } else {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementValidationCriteriaSetLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementValidationCriteriaSetLookupFunction.java
index 0f61e76..0e09eff 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementValidationCriteriaSetLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/DefaultEntityStatementValidationCriteriaSetLookupFunction.java
@@ -22,7 +22,8 @@ import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.AbstractJWTWrapper;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCriterion;
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
 import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
@@ -35,12 +36,12 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
- * Default criteria set lookup strategy for {@link BaseExpirableEntityStatementContainer}.
+ * Default criteria set lookup strategy for {@link BaseExpirableStatementContainer}.
  */
 @ThreadSafeAfterInit
 public class DefaultEntityStatementValidationCriteriaSetLookupFunction
     extends AbstractIdentifiableInitializableComponent
-    implements Function<BaseExpirableEntityStatementContainer, CriteriaSet> {
+    implements Function<BaseExpirableStatementContainer<?>, CriteriaSet> {
 
     /** Class logger. */
     @Nonnull
@@ -70,20 +71,22 @@ public class DefaultEntityStatementValidationCriteriaSetLookupFunction
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public CriteriaSet apply(@Nullable final BaseExpirableEntityStatementContainer metadataContainer) {
-        if (metadataContainer == null || metadataContainer.getEntityStatement() == null) {
-            log.debug("Entity statement is not found from the container, returning null");
-            return null;
-        }
-        final EntityStatement statement = metadataContainer.getEntityStatement();
-        assert statement != null;
-        final SubjectEntityStatementCriterion subjectCriterion = new SubjectEntityStatementCriterion(statement);
-        final IssuerEntityStatementCriterion issuerCriterion = getIssuerCriterion(statement);
-        if (issuerCriterion == null) {
-            log.debug("Issuer statement could not be fetched, returning null");
-            return null;
+    public CriteriaSet apply(@Nullable final BaseExpirableStatementContainer<?> metadataContainer) {
+        if (metadataContainer != null && metadataContainer.getStatement() != null) {
+            final AbstractJWTWrapper<?> statement = metadataContainer.getStatement();
+            assert statement != null;
+            final SubjectStatementCriterion subjectCriterion = new SubjectStatementCriterion(statement);
+            final IssuerEntityStatementCriterion issuerCriterion = getIssuerCriterion(statement);
+            if (issuerCriterion == null) {
+                log.debug("Issuer statement could not be fetched, returning null");
+                return null;
+            }
+            return new CriteriaSet(subjectCriterion, issuerCriterion);
         }
-        return new CriteriaSet(subjectCriterion, issuerCriterion);
+
+        log.debug("Entity statement is not found from the container {}, returning null", metadataContainer);
+        return null;
+
     }
 
     /**
@@ -93,13 +96,13 @@ public class DefaultEntityStatementValidationCriteriaSetLookupFunction
      * @return the issuer entity configuration, or null if could not be fetched
      */
     @Nullable protected IssuerEntityStatementCriterion getIssuerCriterion(
-            @Nonnull final EntityStatement statement) {
+            @Nonnull final AbstractJWTWrapper<?> statement) {
         final String issuer = statement.getIssuer();
         final CriteriaSet criteria = new CriteriaSet(new SubjectEntityIDCriterion(issuer));
         try {
             final List<RemoteEntityConfigurationContainer> result = entityConfigurationCache.get(criteria);
             if (!result.isEmpty()) {
-                final EntityStatement issuerStatement = result.get(0).getEntityStatement();
+                final EntityConfiguration issuerStatement = result.get(0).getStatement();
                 if (issuerStatement != null) {
                     return new IssuerEntityStatementCriterion(issuerStatement);
                 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteEntityConfigurationContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteEntityConfigurationContainer.java
index a29acc7..6242db0 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteEntityConfigurationContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteEntityConfigurationContainer.java
@@ -27,7 +27,8 @@ import net.shibboleth.shared.logic.Constraint;
 /**
  * A container class for metadata caches carrying entity ID and statement details related to Entity Configuration.
  */
-public class RemoteEntityConfigurationContainer extends BaseExpirableEntityStatementContainer implements Serializable {
+public class RemoteEntityConfigurationContainer extends BaseExpirableStatementContainer<EntityConfiguration>
+    implements Serializable {
 
     /** Serial version UID. */
     private static final long serialVersionUID = 756269369356865370L;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteSubordinateStatementContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteSubordinateStatementContainer.java
index aac1928..268bef3 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteSubordinateStatementContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteSubordinateStatementContainer.java
@@ -27,7 +27,8 @@ import net.shibboleth.shared.logic.Constraint;
 /**
  * A container class for metadata caches carrying ID and statement details related to Subordinate Statement.
  */
-public class RemoteSubordinateStatementContainer extends BaseExpirableEntityStatementContainer implements Serializable {
+public class RemoteSubordinateStatementContainer extends BaseExpirableStatementContainer<SubordinateStatement>
+    implements Serializable {
 
     /** Serial version UID. */
     private static final long serialVersionUID = 756269369356865370L;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteTrustChainsContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteTrustChainsContainer.java
index ec918f6..a04a091 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteTrustChainsContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/RemoteTrustChainsContainer.java
@@ -40,7 +40,7 @@ public class RemoteTrustChainsContainer extends BaseExpirableMetadataContainer i
     @Nonnull @NotEmpty private final String entityId;
 
     /** Trust chains for this container. */
-    @Nonnull @NonnullElements @Live private final List<List<EntityStatement>> trustChains;
+    @Nonnull @NonnullElements @Live private final List<List<EntityStatement<?>>> trustChains;
 
     /**
      * Constructor.
@@ -62,7 +62,7 @@ public class RemoteTrustChainsContainer extends BaseExpirableMetadataContainer i
      * @param chains initial trust chains value
      */
     public RemoteTrustChainsContainer(@Nonnull final Instant expirationInstant, @Nonnull @NotEmpty final String entity,
-            @Nonnull @NonnullElements final List<List<EntityStatement>> chains) {
+            @Nonnull @NonnullElements final List<List<EntityStatement<?>>> chains) {
         super(expirationInstant);
         entityId = Constraint.isNotNull(entity, "Entity ID cannot be empty");
         trustChains = new ArrayList<>(Constraint.isNotNull(chains, "Trust chains cannot be null"));
@@ -79,7 +79,7 @@ public class RemoteTrustChainsContainer extends BaseExpirableMetadataContainer i
      * 
      * @return list of trust chains
      */
-    @Nonnull @NonnullElements public List<List<EntityStatement>> getTrustChains() {
+    @Nonnull @NonnullElements public List<List<EntityStatement<?>>> getTrustChains() {
         return CollectionSupport.copyToList(trustChains);
     }
 
@@ -90,7 +90,7 @@ public class RemoteTrustChainsContainer extends BaseExpirableMetadataContainer i
      * @return this object
      */
     @Nonnull public RemoteTrustChainsContainer removeTrustChain(
-            @Nonnull @NonnullElements final List<EntityStatement> trustChain) {
+            @Nonnull @NonnullElements final List<EntityStatement<?>> trustChain) {
         trustChains.remove(trustChain);
         return this;
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectEntityStatementCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectEntityStatementCriterion.java
index f33b235..b6b2d22 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectEntityStatementCriterion.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectEntityStatementCriterion.java
@@ -28,14 +28,14 @@ import net.shibboleth.shared.resolver.Criterion;
 public class SubjectEntityStatementCriterion implements Criterion {
 
     /** The entity statement value. */
-    @Nonnull final EntityStatement value;
+    @Nonnull final EntityStatement<?> value;
 
     /**
      * Constructor.
      *
      * @param statement the entity statement value, must not be null
      */
-    public SubjectEntityStatementCriterion(@Nonnull final EntityStatement statement) {
+    public SubjectEntityStatementCriterion(@Nonnull final EntityStatement<?> statement) {
         value = Constraint.isNotNull(statement, "Entity statement cannot be null");
     }
 
@@ -44,7 +44,7 @@ public class SubjectEntityStatementCriterion implements Criterion {
      * 
      * @return the entity statement value
      */
-    @Nonnull public EntityStatement getValue() {
+    @Nonnull public EntityStatement<?> getValue() {
         return value;
     }
 
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectEntityStatementCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectStatementCriterion.java
similarity index 67%
copy from idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectEntityStatementCriterion.java
copy to idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectStatementCriterion.java
index f33b235..fabe730 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectEntityStatementCriterion.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/SubjectStatementCriterion.java
@@ -18,40 +18,41 @@ import java.util.Objects;
 
 import javax.annotation.Nonnull;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.AbstractJWTWrapper;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.resolver.Criterion;
 
 /**
- * A {@link Criterion} representing a subject entity statement.
+ * A {@link Criterion} representing a subject statement.
  */
-public class SubjectEntityStatementCriterion implements Criterion {
+public class SubjectStatementCriterion implements Criterion {
 
-    /** The entity statement value. */
-    @Nonnull final EntityStatement value;
+    /** The statement value. */
+    @Nonnull final AbstractJWTWrapper<?> value;
 
     /**
      * Constructor.
      *
-     * @param statement the entity statement value, must not be null
+     * @param statement the statement value, must not be null
      */
-    public SubjectEntityStatementCriterion(@Nonnull final EntityStatement statement) {
+    public SubjectStatementCriterion(
+            @Nonnull final AbstractJWTWrapper<?> statement) {
         value = Constraint.isNotNull(statement, "Entity statement cannot be null");
     }
 
     /**
-     * Get the entity statement value.
+     * Get the statement value.
      * 
-     * @return the entity statement value
+     * @return the statement value
      */
-    @Nonnull public EntityStatement getValue() {
+    @Nonnull public AbstractJWTWrapper<?> getValue() {
         return value;
     }
 
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "SubjectEntityStatementCriterion [value=" + value.getJwt().serialize() + "]";
+        return "SubjectStatementCriterion [value=" + value.getJwt().serialize() + "]";
     }
 
     /** {@inheritDoc} */
@@ -72,7 +73,7 @@ public class SubjectEntityStatementCriterion implements Criterion {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final SubjectEntityStatementCriterion other = (SubjectEntityStatementCriterion) obj;
+        final SubjectStatementCriterion other = (SubjectStatementCriterion) obj;
         return value.getJwt().serialize().equals(other.value.getJwt().serialize());
     }
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationCriteriaSetLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationCriteriaSetLookupFunction.java
index d541ec0..d778dca 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationCriteriaSetLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationCriteriaSetLookupFunction.java
@@ -20,7 +20,8 @@ import java.util.function.Function;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableEntityStatementContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableStatementContainer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.SubjectEntityStatementCriterion;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
@@ -29,13 +30,15 @@ import net.shibboleth.shared.resolver.CriteriaSet;
  */
 @ThreadSafe
 public class DefaultEntityConfigurationCriteriaSetLookupFunction
-    implements Function<BaseExpirableEntityStatementContainer, CriteriaSet> {
+    implements Function<BaseExpirableStatementContainer<?>, CriteriaSet> {
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public CriteriaSet apply(@Nullable final BaseExpirableEntityStatementContainer metadataContainer) {
+    public CriteriaSet apply(@Nullable final BaseExpirableStatementContainer<?> metadataContainer) {
         return Optional.ofNullable(metadataContainer)
-                .map(container -> container.getEntityStatement())
+                .map(container -> container.getStatement())
+                .filter(EntityConfiguration.class::isInstance)
+                .map(EntityConfiguration.class::cast)
                 .map(statement -> new CriteriaSet(new SubjectEntityStatementCriterion(statement)))
                 .orElse(null);
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/RemoteResolveEntityResponseContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/RemoteResolveEntityResponseContainer.java
index afa88a8..6dcf683 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/RemoteResolveEntityResponseContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/RemoteResolveEntityResponseContainer.java
@@ -21,14 +21,14 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponse;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableEntityStatementContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableStatementContainer;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
  * A container class for metadata caches carrying ID and statement details related to Resolve Entity.
  */
-public class RemoteResolveEntityResponseContainer extends BaseExpirableEntityStatementContainer
+public class RemoteResolveEntityResponseContainer extends BaseExpirableStatementContainer
     implements Serializable {
 
     /** Serial version UID. */
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java
index 8182489..671062a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java
@@ -131,17 +131,15 @@ public class DefaultSubordinateStatementFetchingStrategy
             log.error("Could not resolve entity configuration for issuer {}", issuerEntityId);
             return null;
         }
-        final EntityStatement entityConfiguration = issuerStatements.get(0).getEntityStatement();
-        if (entityConfiguration == null) {
-            log.error("Could not resolve entity configuration for issuer {}", issuerEntityId);
-            return null;
-        }
         final Map<String, Object> federationEntity =
-                Optional.ofNullable(entityConfiguration.getParsedPayload().getMetadata())
+                Optional.ofNullable(issuerStatements.get(0).getStatement())
+                .filter(statement -> statement instanceof EntityStatement<?>)
+                .map(statement -> (EntityStatement<?>) statement)
+                .map(statement -> statement.getParsedPayload().getMetadata())
                 .map(metadata -> metadata.getFederationEntityMetadata())
                 .orElse(null);
         if (federationEntity == null) {
-            log.error("TODO");
+            log.warn("Could not resolve federation entity metadata for issuer {}", issuerEntityId);
             return null;
         }
         if (federationEntity.get("federation_fetch_endpoint") instanceof String endpoint) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultRemoteTrustChainSignatureValidationFilterStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultRemoteTrustChainSignatureValidationFilterStrategy.java
index 9e3c07f..cf2e8b6 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultRemoteTrustChainSignatureValidationFilterStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultRemoteTrustChainSignatureValidationFilterStrategy.java
@@ -59,7 +59,7 @@ public class DefaultRemoteTrustChainSignatureValidationFilterStrategy
             LoggerFactory.getLogger(DefaultRemoteTrustChainSignatureValidationFilterStrategy.class);
 
     /** Signature validation filter strategy for trust anchor entity statements. */
-    @NonnullAfterInit private BiFunction<EntityStatement, MetadataFilterContext, EntityStatement>
+    @NonnullAfterInit private BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>>
         entityStatementSignatureValidationFilterStrategy;
 
     /**
@@ -68,7 +68,7 @@ public class DefaultRemoteTrustChainSignatureValidationFilterStrategy
      * @param strategy signature validation filter strategy
      */
     public void setEntityStatementSignatureValidationFilterStrategy(@Nonnull final
-            BiFunction<EntityStatement, MetadataFilterContext, EntityStatement> strategy) {
+            BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>> strategy) {
         checkSetterPreconditions();
         entityStatementSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
                 "EntityStatementSignatureValidationFilterStrategy cannot be null");
@@ -93,9 +93,9 @@ public class DefaultRemoteTrustChainSignatureValidationFilterStrategy
             return null;
         }
 
-        trustChainLoop: for (final List<EntityStatement> trustChain : container.getTrustChains()) {
+        trustChainLoop: for (final List<EntityStatement<?>> trustChain : container.getTrustChains()) {
             for (int i = 0; i < trustChain.size() - 1; i++) {
-                final EntityStatement trustChainEntry = trustChain.get(i);
+                final EntityStatement<?> trustChainEntry = trustChain.get(i);
                 final SignedJWT keyContainer = trustChain.get(i + 1).getJwt();
                 final CriteriaSet criteria =
                         new CriteriaSet(new SignatureValidationKeyContainerJwtCriterion(keyContainer));
@@ -110,9 +110,9 @@ public class DefaultRemoteTrustChainSignatureValidationFilterStrategy
                     continue trustChainLoop;
                 }
             }
-            final EntityStatement trustAnchorStatement = trustChain.get(trustChain.size() - 1);
+            final EntityStatement<?> trustAnchorStatement = trustChain.get(trustChain.size() - 1);
             final String entityId = trustAnchorStatement.getSubject();
-            final EntityStatement filteredStatement =
+            final EntityStatement<?> filteredStatement =
                     entityStatementSignatureValidationFilterStrategy.apply(trustAnchorStatement, filterContext);
             if (filteredStatement != null) {
                 log.debug("Successfully validated entity statement for trust anchor {}", entityId);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainFetchingStrategy.java
index ef41b86..5fc493c 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainFetchingStrategy.java
@@ -37,7 +37,7 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.IssuerEntityIDCri
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.PreSelectedTrustChainCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.LocalKeyContainer;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableEntityStatementContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableStatementContainer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteEntityConfigurationContainer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteSubordinateStatementContainer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteTrustChainsContainer;
@@ -242,7 +242,7 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
                 if (containers.isEmpty()) {
                     return null;
                 }
-                entityConfiguration = Optional.ofNullable(containers.get(0).getEntityStatement())
+                entityConfiguration = Optional.ofNullable(containers.get(0).getStatement())
                         .filter(EntityConfiguration.class::isInstance)
                         .map(EntityConfiguration.class::cast)
                         .orElse(null);
@@ -259,11 +259,11 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
             return null;
         }
 
-        final List<List<EntityStatement>> rawChains = populateChain(
+        final List<List<EntityStatement<?>>> rawChains = populateChain(
                 CollectionSupport.listOf(CollectionSupport.listOf(entityConfiguration)), preSelectedChain);
-        final List<List<EntityStatement>> trustChains =
+        final List<List<EntityStatement<?>>> trustChains =
                 stripIntermediateConfigurations(entityConfiguration, rawChains);
-        final List<List<EntityStatement>> result = Stream.concat(trustChains.stream(),
+        final List<List<EntityStatement<?>>> result = Stream.concat(trustChains.stream(),
                 resolveLocallyTrustedTrustChains(trustChains).stream()).toList();
         assert result != null; assert invalidExpiration != null; assert validExpiration != null;
         return new RemoteTrustChainsContainer(result.isEmpty() ? invalidExpiration : validExpiration,
@@ -278,10 +278,10 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
      * @return the filtered list of trust chains, intermediate configurations filtered out
      */
     @Nonnull
-    private List<List<EntityStatement>> stripIntermediateConfigurations(@Nonnull final EntityStatement leaf,
-            @Nonnull @NonnullElements final List<List<EntityStatement>> chains) {
-        final List<List<EntityStatement>> result = new ArrayList<>();
-        for (final List<EntityStatement> chain : chains) {
+    private List<List<EntityStatement<?>>> stripIntermediateConfigurations(@Nonnull final EntityStatement<?> leaf,
+            @Nonnull @NonnullElements final List<List<EntityStatement<?>>> chains) {
+        final List<List<EntityStatement<?>>> result = new ArrayList<>();
+        for (final List<EntityStatement<?>> chain : chains) {
             result.add(chain.stream()
                     .filter(es -> (es.equals(leaf)
                         || !es.getSubject().equals(es.getIssuer())
@@ -300,22 +300,22 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
      * @param chains trust chains to be iterated over
      * @return list of trust chains containing locally trusted trust anchors
      */
-    @Nonnull private List<List<EntityStatement>> resolveLocallyTrustedTrustChains(
-            @Nonnull @NonnullElements final List<List<EntityStatement>> chains) {
-        final List<List<EntityStatement>> result = new ArrayList<>();
-        for (final List<EntityStatement> chain : chains) {
+    @Nonnull private List<List<EntityStatement<?>>> resolveLocallyTrustedTrustChains(
+            @Nonnull @NonnullElements final List<List<EntityStatement<?>>> chains) {
+        final List<List<EntityStatement<?>>> result = new ArrayList<>();
+        for (final List<EntityStatement<?>> chain : chains) {
             for (int i = 0; i < chain.size() - 1; i++) {
-                final EntityStatement statement = chain.get(i);
+                final EntityStatement<?> statement = chain.get(i);
                 assert statement != null;
                 final String entityId = chain.get(i).getSubject();
                 assert entityId != null;
                 if (isLocallyTrusted(statement) && !isTrustAnchor(entityId, result)) {
                     try {
                         final CriteriaSet criteria = new CriteriaSet(new SubjectEntityIDCriterion(entityId));
-                        final EntityStatement localAnchorConfiguration =
+                        final EntityStatement<?> localAnchorConfiguration =
                                 getFirstIfFound(entityConfigurationCache.get(criteria));
                         if (localAnchorConfiguration != null) {
-                            final List<EntityStatement> localTrustChain =
+                            final List<EntityStatement<?>> localTrustChain =
                                     new ArrayList<>(chain.subList(0, chain.indexOf(statement)));
                             localTrustChain.add(localAnchorConfiguration);
                             result.add(CollectionSupport.copyToList(localTrustChain));
@@ -336,7 +336,7 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
      * @param statement the entity to be verified
      * @return true if the entity is locally trusted, false otherwise
      */
-    private boolean isLocallyTrusted(@Nonnull final EntityStatement statement) {
+    private boolean isLocallyTrusted(@Nonnull final EntityStatement<?> statement) {
         final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(statement));
         final String entityId = statement.getSubject();
         assert entityId != null;
@@ -362,7 +362,7 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
      * @return true if the entity is already a trust anchor, false otherwise
      */
     private boolean isTrustAnchor(@Nonnull final String entityId,
-            @Nonnull final List<List<EntityStatement>> trustChains) {
+            @Nonnull final List<List<EntityStatement<?>>> trustChains) {
         return trustChains.stream()
                 .filter(chain -> entityId.equals(chain.get(chain.size() - 1).getSubject()))
                 .findAny()
@@ -377,20 +377,20 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
      * @param preSelectedChain the pre-selected trust chain (containing entity IDs as String).
      * @return the list of trust chains being populated
      */
-    @Nonnull @NonnullElements private List<List<EntityStatement>> populateChain(
-            @Nonnull @NonnullElements final List<List<EntityStatement>> entities,
+    @Nonnull @NonnullElements private List<List<EntityStatement<?>>> populateChain(
+            @Nonnull @NonnullElements final List<List<EntityStatement<?>>> entities,
             @Nonnull final List<String> preSelectedChain) {
-        final List<List<EntityStatement>> result = new ArrayList<>();
+        final List<List<EntityStatement<?>>> result = new ArrayList<>();
         boolean hints = false;
-        for (final List<EntityStatement> chain : entities) {
-            final EntityStatement entityStatement = chain.get(chain.size() - 1);
+        for (final List<EntityStatement<?>> chain : entities) {
+            final EntityStatement<?> entityStatement = chain.get(chain.size() - 1);
             final List<String> authorityHints =
                     entityStatement.getParsedPayload() instanceof EntityConfigurationPayload ecp ?
                             ecp.getAuthorityHints() : null;
             if (authorityHints == null || authorityHints.isEmpty()) {
                 result.add(chain);
             } else {
-                final List<Pair<EntityStatement, EntityStatement>> authorities = authorityHints.stream()
+                final List<Pair<EntityStatement<?>, EntityStatement<?>>> authorities = authorityHints.stream()
                         .filter(id -> preSelectedChain.isEmpty() || preSelectedChain.contains(id))
                         .map(id -> fetchAuthority(entityStatement, id))
                         .filter(pair -> pair != null && pair.getFirst() != null && pair.getSecond() != null)
@@ -399,7 +399,7 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
                         .toList();
                 hints = !authorities.isEmpty();
                 authorities.forEach(authority -> {
-                    final ArrayList<EntityStatement> newChain = new ArrayList<>(chain);
+                    final ArrayList<EntityStatement<?>> newChain = new ArrayList<>(chain);
                     newChain.add(authority.getSecond());
                     newChain.add(authority.getFirst());
                     result.add(newChain);
@@ -421,8 +421,8 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
      * @return pair consisting of authority's entity configuration and subordinate statement, or null if they could not
      *      be fetched.
      */
-    @Nullable protected Pair<EntityStatement, EntityStatement> fetchAuthority(@Nonnull final EntityStatement entity,
-            @Nullable final String authorityHint) {
+    @Nullable protected Pair<EntityStatement<?>, EntityStatement<?>> fetchAuthority(
+            @Nonnull final EntityStatement<?> entity, @Nullable final String authorityHint) {
         final String entityId = entity.getSubject();
         if (authorityHint == null) {
             log.error("Entity ID ({}) or authority hint ({}) may not be null", entityId, authorityHint);
@@ -432,9 +432,9 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
                 new IssuerEntityIDCriterion(authorityHint));
 
         try {
-            final EntityStatement authorityConfiguration = getFirstIfFound(
+            final EntityStatement<?> authorityConfiguration = getFirstIfFound(
                     entityConfigurationCache.get(new CriteriaSet(new SubjectEntityIDCriterion(authorityHint))));
-            final EntityStatement metadata = getFirstIfFound(subordinateStatementCache.get(criteria));
+            final EntityStatement<?> metadata = getFirstIfFound(subordinateStatementCache.get(criteria));
             return new Pair<>(authorityConfiguration, metadata);
         } catch (final MetadataCacheException e) {
             log.error("Could not resolve authority hint {} for {}", authorityHint, entityId);
@@ -448,9 +448,12 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
      * @param statements list of entity statements
      * @return first entity statemenet, or null the list was empty.
      */
-    @Nullable private EntityStatement getFirstIfFound(
-            @Nonnull final List<? extends BaseExpirableEntityStatementContainer> statements) {
-        return statements.size() > 0 ? statements.get(0).getEntityStatement() : null;
+    @Nullable private EntityStatement<?> getFirstIfFound(
+            @Nonnull final List<? extends BaseExpirableStatementContainer<?>> statements) {
+        if (statements.size() > 0 && statements.get(0).getStatement() instanceof EntityStatement<?> statement) {
+            return statement;
+        }
+        return null;
     }
 
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainSubjectIdentifierExtractionStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainSubjectIdentifierExtractionStrategy.java
index 9442ba5..15877c8 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainSubjectIdentifierExtractionStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustchain/DefaultTrustChainSubjectIdentifierExtractionStrategy.java
@@ -27,15 +27,15 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
  */
 @ThreadSafe
 public class DefaultTrustChainSubjectIdentifierExtractionStrategy
-        implements Function<List<List<EntityStatement>>, String> {
+        implements Function<List<List<EntityStatement<?>>>, String> {
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public String apply(@Nullable final List<List<EntityStatement>> trustChains) {
+    public String apply(@Nullable final List<List<EntityStatement<?>>> trustChains) {
         if (trustChains == null || trustChains.isEmpty()) {
             return null;
         }
-        final List<EntityStatement> firstChain = trustChains.get(0);
+        final List<EntityStatement<?>> firstChain = trustChains.get(0);
         if (firstChain == null || firstChain.isEmpty() || firstChain.get(0) == null) {
             return null;
         }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkValidationCriteriaSetLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkValidationCriteriaSetLookupFunction.java
index 899b4b7..24fa782 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkValidationCriteriaSetLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkValidationCriteriaSetLookupFunction.java
@@ -22,12 +22,13 @@ import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.AbstractJWTWrapper;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCriterion;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableEntityStatementContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableStatementContainer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.IssuerEntityStatementCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteTrustChainsContainer;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.SubjectEntityStatementCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.SubjectStatementCriterion;
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
 import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -39,13 +40,13 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
- * Default criteria set lookup strategy for {@link BaseExpirableEntityStatementContainer} that fetches the issuer
+ * Default criteria set lookup strategy for {@link BaseExpirableStatementContainer} that fetches the issuer
  * entity statement via trust chain cache.
  */
 @ThreadSafeAfterInit
 public class DefaultTrustMarkValidationCriteriaSetLookupFunction
     extends AbstractIdentifiableInitializableComponent
-    implements Function<BaseExpirableEntityStatementContainer, CriteriaSet> {
+    implements Function<BaseExpirableStatementContainer<?>, CriteriaSet> {
 
     /** Class logger. */
     @Nonnull
@@ -77,20 +78,20 @@ public class DefaultTrustMarkValidationCriteriaSetLookupFunction
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public CriteriaSet apply(@Nullable final BaseExpirableEntityStatementContainer metadataContainer) {
-        if (metadataContainer == null || metadataContainer.getEntityStatement() == null) {
-            log.debug("Entity statement is not found from the container, returning null");
-            return null;
-        }
-        final EntityStatement statement = metadataContainer.getEntityStatement();
-        assert statement != null;
-        final SubjectEntityStatementCriterion subjectCriterion = new SubjectEntityStatementCriterion(statement);
-        final IssuerEntityStatementCriterion issuerCriterion = getIssuerCriterion(statement);
-        if (issuerCriterion == null) {
-            log.debug("Issuer statement could not be fetched, returning null");
-            return null;
+    public CriteriaSet apply(@Nullable final BaseExpirableStatementContainer<?> metadataContainer) {
+        if (metadataContainer != null && metadataContainer.getStatement() != null) {
+            final AbstractJWTWrapper<?> statement = metadataContainer.getStatement();
+            assert statement != null;
+            final SubjectStatementCriterion subjectCriterion = new SubjectStatementCriterion(statement);
+            final IssuerEntityStatementCriterion issuerCriterion = getIssuerCriterion(statement);
+            if (issuerCriterion == null) {
+                log.debug("Issuer statement could not be fetched, returning null");
+                return null;
+            }
+            return new CriteriaSet(subjectCriterion, issuerCriterion);
         }
-        return new CriteriaSet(subjectCriterion, issuerCriterion);
+        log.debug("Entity statement is not found from the container, returning null");
+        return null;
     }
 
     /**
@@ -100,15 +101,15 @@ public class DefaultTrustMarkValidationCriteriaSetLookupFunction
      * @return the issuer entity configuration, or null if could not be fetched
      */
     @Nullable protected IssuerEntityStatementCriterion getIssuerCriterion(
-            @Nonnull final EntityStatement statement) {
+            @Nonnull final AbstractJWTWrapper<?> statement) {
         final String issuer = statement.getIssuer();
         final CriteriaSet criteria = new CriteriaSet(new SubjectEntityIDCriterion(issuer));
         try {
             final List<RemoteTrustChainsContainer> result = trustChainCache.get(criteria);
             if (!result.isEmpty()) {
-                final List<List<EntityStatement>> chains = result.get(0).getTrustChains();
+                final List<List<EntityStatement<?>>> chains = result.get(0).getTrustChains();
                 if (!chains.isEmpty()) {
-                    final EntityStatement issuerStatement = chains.get(0).get(0);
+                    final EntityStatement<?> issuerStatement = chains.get(0).get(0);
                     assert issuerStatement != null;
                     return new IssuerEntityStatementCriterion(issuerStatement);
                 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkContainer.java
index 626130d..eeed549 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkContainer.java
@@ -21,14 +21,14 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TrustMark;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableEntityStatementContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableStatementContainer;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
  * A container class for metadata caches carrying ID and statement details related to Trust Mark.
  */
-public class RemoteTrustMarkContainer extends BaseExpirableEntityStatementContainer implements Serializable {
+public class RemoteTrustMarkContainer extends BaseExpirableStatementContainer implements Serializable {
 
     /** Serial version UID. */
     private static final long serialVersionUID = 756269369356865370L;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkStatusContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkStatusContainer.java
index c042221..33f902d 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkStatusContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/RemoteTrustMarkStatusContainer.java
@@ -21,14 +21,14 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TrustMarkStatus;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableEntityStatementContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.BaseExpirableStatementContainer;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
  * A container class for metadata caches carrying ID and statement details related to Trust Mark status.
  */
-public class RemoteTrustMarkStatusContainer extends BaseExpirableEntityStatementContainer implements Serializable {
+public class RemoteTrustMarkStatusContainer extends BaseExpirableStatementContainer implements Serializable {
 
     /** Serial version UID. */
     private static final long serialVersionUID = 756269369356884270L;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraint.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraint.java
index a7c3bba..5bb1f95 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraint.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraint.java
@@ -33,5 +33,5 @@ public interface FederationPolicyConstraint {
      * @param trustChain trust chain to be evaluated
      * @return true if the trust chain is valid for this constraint, false otherwise.
      */
-    public boolean validate(@Nullable final Object constraint, @Nonnull final List<EntityStatement> trustChain);
+    public boolean validate(@Nullable final Object constraint, @Nonnull final List<EntityStatement<?>> trustChain);
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraintHelper.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraintHelper.java
index 0dfe5f3..65abdc8 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraintHelper.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/FederationPolicyConstraintHelper.java
@@ -47,7 +47,7 @@ public class FederationPolicyConstraintHelper {
      * @return true if trust chain meets the constraints. false otherwise
      */
     public static boolean verifyPolicyConstraints(@Nonnull final ObjectMapper objectMapper,
-            @Nullable final EntityStatement subordinateStatement, @Nonnull final List<EntityStatement> trustChain,
+            @Nullable final EntityStatement<?> subordinateStatement, @Nonnull final List<EntityStatement<?>> trustChain,
             @Nonnull Map<String, FederationPolicyConstraint> federationPolicyConstraints) {
         if (subordinateStatement == null) {
             return true;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
index a495486..78d57b8 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
@@ -41,7 +41,7 @@ public abstract class AbstractFederationPolicyConstraint<T extends Object>
     /** {@inheritDoc} */
     @Override
     public boolean validate(@Nullable final Object constraint,
-            @Nonnull final List<EntityStatement> trustChain) {
+            @Nonnull final List<EntityStatement<?>> trustChain) {
         checkComponentActive();
         try {
             log.trace("Attempting to parse raw constraint value: {}", constraint);
@@ -74,5 +74,5 @@ public abstract class AbstractFederationPolicyConstraint<T extends Object>
      * @return true if the trust chain is valid for this constraint, false otherwise.
      */
     protected abstract boolean doValidate(@Nonnull final T constraintData,
-            @Nonnull final List<EntityStatement> trustChain);
+            @Nonnull final List<EntityStatement<?>> trustChain);
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
index ac20c9d..53fdb66 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
@@ -79,7 +79,7 @@ public class DefaultAllowedEntityTypesConstraint extends AbstractFederationPolic
     /** {@inheritDoc} */
     @Override
     protected boolean doValidate(@Nonnull final List<String> constraintData,
-            @Nonnull final List<EntityStatement> trustChain) {
+            @Nonnull final List<EntityStatement<?>> trustChain) {
         if (constraintData.contains("federation_entity")) {
             log.warn("The value 'federation_entity' is not allowed for allowed_entity_types");
             return false;
@@ -87,7 +87,7 @@ public class DefaultAllowedEntityTypesConstraint extends AbstractFederationPolic
         final List<String> allowedTypes = new ArrayList<>(constraintData);
         allowedTypes.add("federation_entity");
         log.trace("Allowed entity types: {}", allowedTypes);
-        for (final EntityStatement entityStatement : trustChain) {
+        for (final EntityStatement<?> entityStatement : trustChain) {
             assert entityStatement != null;
             final Metadata metadata =
                     Optional.ofNullable(entityStatement.getParsedPayload().getMetadata())
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultMaxPathLengthConstraint.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultMaxPathLengthConstraint.java
index 6284b24..ea6874e 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultMaxPathLengthConstraint.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultMaxPathLengthConstraint.java
@@ -48,8 +48,8 @@ public class DefaultMaxPathLengthConstraint extends AbstractFederationPolicyCons
     /** {@inheritDoc} */
     @Override
     protected boolean doValidate(@Nonnull final Integer constraintData,
-            @Nonnull final List<EntityStatement> trustChain) {
-        final List<EntityStatement> intermediates = trustChain.stream()
+            @Nonnull final List<EntityStatement<?>> trustChain) {
+        final List<EntityStatement<?>> intermediates = trustChain.stream()
                 .filter(es -> !es.getSubject().equals(es.getIssuer()))
                 .toList();
         final int length = intermediates.size();
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultNamingConstraintsConstraint.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultNamingConstraintsConstraint.java
index 6110572..0d67ba7 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultNamingConstraintsConstraint.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultNamingConstraintsConstraint.java
@@ -81,7 +81,7 @@ public class DefaultNamingConstraintsConstraint extends AbstractFederationPolicy
     /** {@inheritDoc} */
     @Override
     protected boolean doValidate(@Nonnull final Map<String, List<String>> constraintData,
-            @Nonnull final List<EntityStatement> trustChain) {
+            @Nonnull final List<EntityStatement<?>> trustChain) {
         final PKIXNameConstraintValidator bcValidator = new PKIXNameConstraintValidator();
 
         Optional.ofNullable(constraintData.get("excluded"))
@@ -94,7 +94,7 @@ public class DefaultNamingConstraintsConstraint extends AbstractFederationPolicy
             .forEach(item -> bcValidator.intersectPermittedSubtree(
                     new GeneralSubtree(new GeneralName(GeneralName.dNSName, item))));
 
-        for (final EntityStatement entityStatement : trustChain) {
+        for (final EntityStatement<?> entityStatement : trustChain) {
             final String host = URI.create(entityStatement.getSubject()).getHost();
             try {
                 bcValidator.checkExcluded(new GeneralName(GeneralName.dNSName, host));
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java
index 475c65c..aac6274 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java
@@ -46,7 +46,7 @@ public class EntityStatementHelper {
      * @return serialized trust chain
      */
     @Nullable
-    public static String getSerializedTrustChain(@Nullable final List<EntityStatement> trustChain) {
+    public static String getSerializedTrustChain(@Nullable final List<EntityStatement<?>> trustChain) {
         return trustChain == null ? null :
             String.join(",", trustChain.stream().map(es -> es.getJwt().serialize()).toList());
     }
@@ -58,7 +58,7 @@ public class EntityStatementHelper {
      * @return serialized entity statement
      */
     @Nullable
-    public static String getSerializedEntityStatement(@Nullable final EntityStatement entityStatement) {
+    public static String getSerializedEntityStatement(@Nullable final EntityStatement<?> entityStatement) {
         return entityStatement == null ? null : entityStatement.getJwt().serialize();
     }
 
@@ -78,7 +78,7 @@ public class EntityStatementHelper {
         try {
             final List<RemoteEntityConfigurationContainer> result = cache.get(criteria);
             if (!result.isEmpty()) {
-                return Optional.ofNullable(result.get(0).getEntityStatement())
+                return Optional.ofNullable(result.get(0).getStatement())
                         .map(statement -> statement.getParsedPayload().getMetadata())
                         .map(metadata -> metadata.getFederationEntityMetadata())
                         .map(map -> map.get(endpointId))
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
index abb3986..148284f 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
@@ -63,10 +63,10 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
     @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextCreationStrategy;
 
     /** Strategy used to get combined entity metadata from trust chain. */
-    @NonnullAfterInit private Function<List<EntityStatement>,Map<String,Map<String,Object>>> metadataLookupStrategy;
+    @NonnullAfterInit private Function<List<EntityStatement<?>>,Map<String,Map<String,Object>>> metadataLookupStrategy;
 
     /** Strategy used to merge metadata policies in trust chain for specific entity type. */
-    @NonnullAfterInit private BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>>
+    @NonnullAfterInit private BiFunction<List<EntityStatement<?>>,String,Map<String, MetadataPolicy>>
         metadataPolicyMergingStrategy;
 
     /** Enforcer function for applying metadata policy for an item. */
@@ -119,7 +119,7 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
      * @param strategy lookup strategy
      */
     public void setMetadataLookupStrategy(
-            @Nonnull final Function<List<EntityStatement>,Map<String,Map<String,Object>>> strategy) {
+            @Nonnull final Function<List<EntityStatement<?>>,Map<String,Map<String,Object>>> strategy) {
         checkSetterPreconditions();
         metadataLookupStrategy =
                 Constraint.isNotNull(strategy, "MetadataLookupStrategy cannot be null");
@@ -131,7 +131,7 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
      * @return lookup strategy
      */
     @NonnullAfterInit
-    public Function<List<EntityStatement>,Map<String,Map<String,Object>>> getMetadataLookupStrategy() {
+    public Function<List<EntityStatement<?>>,Map<String,Map<String,Object>>> getMetadataLookupStrategy() {
         checkComponentActive();
         return metadataLookupStrategy;
     }
@@ -142,7 +142,7 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
      * @param strategy merging strategy
      */
     public void setMetadataPolicyMergingStrategy(@Nonnull final
-            BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>> strategy) {
+            BiFunction<List<EntityStatement<?>>,String,Map<String, MetadataPolicy>> strategy) {
         checkSetterPreconditions();
         metadataPolicyMergingStrategy =
                 Constraint.isNotNull(strategy, "MetadataPolicyMergingStrategy cannot be null");
@@ -154,7 +154,7 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
      * @return merging strategy
      */
     @Nonnull
-    public BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>> getMetadataPolicyMergingStrategy() {
+    public BiFunction<List<EntityStatement<?>>,String,Map<String, MetadataPolicy>> getMetadataPolicyMergingStrategy() {
         checkComponentActive();
         assert metadataPolicyMergingStrategy != null;
         return metadataPolicyMergingStrategy;
@@ -238,8 +238,8 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
      * @param policyCompliantChains the list of policy-compliant trust chains to be populated
      * @return error event ID if metadata policy merging or enforcement failed, null otherwise
      */
-    @Nullable protected String populatePolicyComplaintChains(@Nonnull final List<EntityStatement> chain,
-            @Nonnull List<Pair<List<EntityStatement>, Metadata>> policyCompliantChains) {
+    @Nullable protected String populatePolicyComplaintChains(@Nonnull final List<EntityStatement<?>> chain,
+            @Nonnull List<Pair<List<EntityStatement<?>>, Metadata>> policyCompliantChains) {
         final Map<String,Map<String,Object>> metadata = getMetadataLookupStrategy().apply(chain);
         log.trace("{} Metadata resolved via lookup strategy: {}", getLogPrefix(), metadata);
         if (metadata != null) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
index 55cc8e5..e221e58 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
@@ -64,7 +64,7 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
     @NonnullBeforeExec private  RelyingPartyTrustChainContext trustChainContext;
 
     /** Selected trust chain to operate on. */
-    @NonnullBeforeExec private Pair<List<EntityStatement>, Metadata> selectedTrustChain;
+    @NonnullBeforeExec private Pair<List<EntityStatement<?>>, Metadata> selectedTrustChain;
 
     /** Constructor. */
     public AddExtensionsToClientInformation() {
@@ -147,7 +147,7 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        final List<EntityStatement> trustChain = selectedTrustChain.getFirst();
+        final List<EntityStatement<?>> trustChain = selectedTrustChain.getFirst();
         assert trustChain != null;
 
         clientInformation.getOIDCMetadata().setCustomField(
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java
index 29ef55d..1185961 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java
@@ -65,7 +65,7 @@ public class AutoRegisteredTrustChainRequestUriClaimsSetDecorator extends Abstra
         checkComponentActive();
 
         final List<String> trustChainIds = selectedTrustChainIDsLookupStrategy.apply(profileRequestContext);
-        if (trustChainIds != null && !trustChainIds.isEmpty()) {
+        if (claimsSet != null && trustChainIds != null && !trustChainIds.isEmpty()) {
             claimsSet.put(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChainIds);
         }
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java
index 8986555..8fa53f3 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java
@@ -117,7 +117,7 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
     @Override
     protected boolean populateClaimsSetBuilder(@Nonnull final JWTClaimsSet.Builder builder,
             @Nonnull final ProfileRequestContext profileRequestContext) {
-        final Pair<List<EntityStatement>,Metadata> selectedTrustChain =
+        final Pair<List<EntityStatement<?>>,Metadata> selectedTrustChain =
                 trustChainContext.getSelectedTrustChain();
         if (selectedTrustChain == null) {
             log.debug("{} No selected trust chain found form the context", getLogPrefix());
@@ -148,7 +148,7 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
             builder.claim("metadata", metadata);
         }
 
-        final List<EntityStatement> trustChain = selectedTrustChain.getFirst();
+        final List<EntityStatement<?>> trustChain = selectedTrustChain.getFirst();
         if (trustChain == null || trustChain.isEmpty()) {
             log.debug("{} No selected trust chain set in the trust chain context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.INVALID_METADATA);
@@ -215,9 +215,9 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
      * @param trustChain trust chain
      * @return expiration time
      */
-    @Nullable private Instant resolveTrustChainExpiration(@Nonnull final List<EntityStatement> trustChain) {
+    @Nullable private Instant resolveTrustChainExpiration(@Nonnull final List<EntityStatement<?>> trustChain) {
         Instant metadataExpiration = null;
-        for (final EntityStatement statement : trustChain) {
+        for (final EntityStatement<?> statement : trustChain) {
             final Instant statementExpiration = statement.getParsedPayload().getExpiration();
             metadataExpiration = metadataExpiration == null ? statementExpiration : 
                 statementExpiration.isBefore(metadataExpiration) ? statementExpiration : metadataExpiration;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
index 3cd4786..885f78e 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
@@ -45,9 +45,11 @@ import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponse;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponsePayload;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubordinateStatement;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.ResponseContainerExpirationCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteEntityConfigurationContainer;
@@ -107,10 +109,10 @@ public class CallResolveEntityApi extends AbstractProfileAction {
     @Nonnull private Function<ProfileRequestContext, List<String>> preSelectedTrustChainIdsLookupStrategy;
 
     /** Strategy used to get entity IDs from a trust chain. */
-    @Nonnull private Function<List<EntityStatement>, List<String>> trustChainIDsLookupStrategy;
+    @Nonnull private Function<List<EntityStatement<?>>, List<String>> trustChainIDsLookupStrategy;
 
     /** Strategy used to fetch entity configuration delivered to the trust chain cache. */
-    @Nonnull private Function<ProfileRequestContext, EntityStatement> entityConfigurationLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext, EntityConfiguration> entityConfigurationLookupStrategy;
 
     /** Condition to require entity configuration via {@link #entityConfigurationLookupStrategy}. */
     @Nonnull private Predicate<ProfileRequestContext> requireEntityConfigurationCondition;
@@ -208,7 +210,8 @@ public class CallResolveEntityApi extends AbstractProfileAction {
      * 
      * @param strategy lookup strategy
      */
-    public void setTrustChainIDsLookupStrategy(@Nonnull final Function<List<EntityStatement>, List<String>> strategy) {
+    public void setTrustChainIDsLookupStrategy(@Nonnull final Function<List<EntityStatement<?>>,
+            List<String>> strategy) {
         checkSetterPreconditions();
         trustChainIDsLookupStrategy = Constraint.isNotNull(strategy, "TrustChainIDsLookupStrategy cannot be null");
     }
@@ -219,7 +222,7 @@ public class CallResolveEntityApi extends AbstractProfileAction {
      * @param strategy lookup strategy
      */
     public void setEntityConfigurationLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, EntityStatement> strategy) {
+            @Nonnull final Function<ProfileRequestContext, EntityConfiguration> strategy) {
         checkSetterPreconditions();
         entityConfigurationLookupStrategy = Constraint.isNotNull(strategy,
                 "EntityConfigurationLookupStrategy cannot be null");
@@ -335,7 +338,7 @@ public class CallResolveEntityApi extends AbstractProfileAction {
         log.debug("{} Resolving trust chain via resolve entity API for {}", getLogPrefix(), clientId);
         assert clientId != null;
         final CriteriaSet baseCriteriaSet = new CriteriaSet(new SubjectEntityIDCriterion(clientId));
-        final EntityStatement entityConfiguration = entityConfigurationLookupStrategy.apply(profileRequestContext);
+        final EntityConfiguration entityConfiguration = entityConfigurationLookupStrategy.apply(profileRequestContext);
         if (entityConfiguration != null) {
             log.debug("{} Entity configuration resolved and included to the criteria set", getLogPrefix());
             baseCriteriaSet.add(new SubjectEntityStatementCriterion(entityConfiguration));
@@ -389,7 +392,7 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                 log.warn("{} Could not resolve entity configuration for {}", getLogPrefix(), trustedEntity);
                 continue;
             }
-            final URI uri = Optional.ofNullable(configurationCacheResult.get(0).getEntityStatement())
+            final URI uri = Optional.ofNullable(configurationCacheResult.get(0).getStatement())
                         .map(statement -> statement.getParsedPayload().getMetadata())
                         .map(metadata -> metadata.getFederationEntityMetadata())
                         .map(entityMetadata -> entityMetadata.get("federation_resolve_endpoint"))
@@ -416,8 +419,8 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                 log.debug("{} No data resolved for {} from {}", getLogPrefix(), clientId, trustedEntity);
                 continue;
             }
-            if (cacheResult.get(0).getEntityStatement() instanceof ResolveEntityResponse successResponse
-                    && successResponse.getParsedPayload() instanceof ResolveEntityResponsePayload payload) {
+            if (cacheResult.get(0).getStatement() instanceof ResolveEntityResponse successResponse) {
+                final ResolveEntityResponsePayload payload = successResponse.getParsedPayload();
                 final List<String> rawTrustChain = payload.getTrustChain();
                 final Metadata metadata = payload.getMetadata();
                 final List<Map<String,String>> rawTrustMarks = payload.getTrustMarks();
@@ -426,14 +429,22 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                             trustedEntity);
                     continue;
                 }
-                final List<EntityStatement> chain = rawTrustChain.stream()
+                final List<EntityStatement<?>> chain = rawTrustChain.stream()
                         .filter(Objects::nonNull)
                         .map(entry -> {
                             try {
                                 final SignedJWT jwt = SignedJWT.parse(entry);
                                 assert jwt != null;
                                 assert objectMapper != null;
-                                return EntityStatement.parse(jwt, objectMapper);
+                                final String subject = jwt.getJWTClaimsSet().getSubject();
+                                final String issuer = jwt.getJWTClaimsSet().getIssuer();
+                                if (subject != null && subject.equals(issuer)) {
+                                    assert objectMapper != null;
+                                    return EntityConfiguration.parse(jwt, objectMapper);
+                                } else {
+                                    assert objectMapper != null;
+                                    return SubordinateStatement.parse(jwt, objectMapper);
+                                }
                             } catch (final JsonProcessingException | ParseException e) {
                                 log.warn("Could not parse the trust chain into list of entity statements", e);
                                 return null;
@@ -446,7 +457,7 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                             getLogPrefix());
                     continue;
                 }
-                final List<Pair<List<EntityStatement>, Metadata>> policyCompliantChains =
+                final List<Pair<List<EntityStatement<?>>, Metadata>> policyCompliantChains =
                         new ArrayList<>();
                 policyCompliantChains.add(new Pair<>(chain, metadata));
                 trustChainContext.setPolicyCompliantTrustChains(policyCompliantChains);
@@ -497,7 +508,7 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                 return;
             } else {
                 log.debug("{} The response from {} was not a success response: {}", getLogPrefix(), trustedEntity,
-                        cacheResult.get(0).getEntityStatement());
+                        cacheResult.get(0).getStatement());
                 continue;
             }
         }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/DefaultTrustMarkFromMetadataCacheFetchingFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/DefaultTrustMarkFromMetadataCacheFetchingFunction.java
index ed23c58..b8bedfc 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/DefaultTrustMarkFromMetadataCacheFetchingFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/DefaultTrustMarkFromMetadataCacheFetchingFunction.java
@@ -194,11 +194,11 @@ public class DefaultTrustMarkFromMetadataCacheFetchingFunction extends AbstractI
             log.debug("No data resolved for {} from {}", trustMarkType, trustedEntity);
             return null;
         }
-        if (cacheResult.get(0).getEntityStatement() instanceof TrustMark trustMark) {
+        if (cacheResult.get(0).getStatement() instanceof TrustMark trustMark) {
             return Map.of("trust_mark_type", trustMarkType, "trust_mark", trustMark.getJwt().serialize());
         } else {
             log.debug("The response from {} was not a success response: {}", trustedEntity,
-                    cacheResult.get(0).getEntityStatement());
+                    cacheResult.get(0).getStatement());
         }
         return null;
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
index 6804d2f..8446eec 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
@@ -79,7 +79,7 @@ public class ResolveTrustChains extends AbstractTrustChainResolutionAction {
     @Nonnull private Function<ProfileRequestContext, List<String>> preSelectedTrustChainIdsLookupStrategy;
 
     /** Strategy used to get entity IDs from a trust chain. */
-    @Nonnull private Function<List<EntityStatement>, List<String>> trustChainIDsLookupStrategy;
+    @Nonnull private Function<List<EntityStatement<?>>, List<String>> trustChainIDsLookupStrategy;
 
     /** Strategy used to fetch entity configuration delivered to the trust chain cache. */
     @Nonnull private Function<ProfileRequestContext, EntityConfiguration> entityConfigurationLookupStrategy;
@@ -139,7 +139,8 @@ public class ResolveTrustChains extends AbstractTrustChainResolutionAction {
      * 
      * @param strategy lookup strategy
      */
-    public void setTrustChainIDsLookupStrategy(@Nonnull final Function<List<EntityStatement>, List<String>> strategy) {
+    public void setTrustChainIDsLookupStrategy(@Nonnull final Function<List<EntityStatement<?>>,
+            List<String>> strategy) {
         checkSetterPreconditions();
         trustChainIDsLookupStrategy = Constraint.isNotNull(strategy, "TrustChainIDsLookupStrategy cannot be null");
     }
@@ -208,7 +209,7 @@ public class ResolveTrustChains extends AbstractTrustChainResolutionAction {
         log.debug("{} Resolving trust chain for {}", getLogPrefix(), clientId);
         assert clientId != null;
         final CriteriaSet criteriaSet = new CriteriaSet(new SubjectEntityIDCriterion(clientId));
-        final EntityStatement entityConfiguration = entityConfigurationLookupStrategy.apply(profileRequestContext);
+        final EntityConfiguration entityConfiguration = entityConfigurationLookupStrategy.apply(profileRequestContext);
         if (entityConfiguration != null) {
             log.debug("{} Entity configuration resolved and included to the criteria set", getLogPrefix());
             criteriaSet.add(new SubjectEntityStatementCriterion(entityConfiguration));
@@ -237,11 +238,11 @@ public class ResolveTrustChains extends AbstractTrustChainResolutionAction {
         final RelyingPartyTrustChainContext trustChainContext =
                 getTrustChainContextCreationStrategy().apply(profileRequestContext);
         trustChainContext.setResolvedTrustChains(cacheResult.get(0).getTrustChains());
-        final List<Pair<List<EntityStatement>, Metadata>> policyCompliantChains =
+        final List<Pair<List<EntityStatement<?>>, Metadata>> policyCompliantChains =
                 new ArrayList<>();
 
         String errorEventId = null;
-        for (final List<EntityStatement> chain : cacheResult.get(0).getTrustChains()) {
+        for (final List<EntityStatement<?>> chain : cacheResult.get(0).getTrustChains()) {
             assert chain != null;
             if (!preSelectedChain.isEmpty() && !preSelectedChain.equals(trustChainIDsLookupStrategy.apply(chain))) {
                 log.debug("{} Ignored resolved trust chain that doesn't match with preselected chain", getLogPrefix());
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
index 580a4df..f655fb4 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
@@ -79,15 +79,15 @@ public class ResolveTrustMarks extends AbstractProfileAction {
 
     /** Strategy used to parse trust marks from the selected trust chain. */
     @NonnullAfterInit
-    private Function<List<EntityStatement>,Map<String,List<SignedJWT>>> trustChainTrustMarksParsingStrategy;
+    private Function<List<EntityStatement<?>>,Map<String,List<SignedJWT>>> trustChainTrustMarksParsingStrategy;
 
     /** Strategy used to lookup trusted trust mark issuers for the trust chain. */
     @NonnullAfterInit
-    private Function<List<EntityStatement>, Map<String, List<String>>> trustedTrustMarkIssuersLookupStrategy;
+    private Function<List<EntityStatement<?>>, Map<String, List<String>>> trustedTrustMarkIssuersLookupStrategy;
 
     /** Strategy used to lookup trusted trust mark owners for the trust chain. */
     @NonnullAfterInit
-    private Function<List<EntityStatement>, Map<String, Map<String, Object>>> trustedTrustMarkOwnersLookupStrategy;
+    private Function<List<EntityStatement<?>>, Map<String, Map<String, Object>>> trustedTrustMarkOwnersLookupStrategy;
 
     /** Condition to solely take trusted trust mark issuers into account. */
     @Nonnull private Predicate<ProfileRequestContext> trustedTrustMarkIssuersOnlyCondition;
@@ -112,7 +112,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
     @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
 
     /** The selected trust chain to resolve trust marks from. */
-    @NonnullBeforeExec private List<EntityStatement> selectedTrustChain;
+    @NonnullBeforeExec private List<EntityStatement<?>> selectedTrustChain;
 
     /** Trust mark claims validator. */
     @NonnullBeforeExec private ClaimsValidator trustMarkClaimsValidator;
@@ -150,7 +150,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
      * @param strategy parsing strategy
      */
     public void setTrustChainTrustMarksParsingStrategy(
-            @Nonnull final Function<List<EntityStatement>,Map<String,List<SignedJWT>>> strategy) {
+            @Nonnull final Function<List<EntityStatement<?>>,Map<String,List<SignedJWT>>> strategy) {
         checkSetterPreconditions();
         trustChainTrustMarksParsingStrategy =
                 Constraint.isNotNull(strategy, "TrustChainTrustMarksParsingStrategy cannot be null");
@@ -162,7 +162,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
      * @param strategy lookup strategy
      */
     public void setTrustedTrustMarkIssuersLookupStrategy(
-            @Nonnull final Function<List<EntityStatement>, Map<String, List<String>>> strategy) {
+            @Nonnull final Function<List<EntityStatement<?>>, Map<String, List<String>>> strategy) {
         checkSetterPreconditions();
         trustedTrustMarkIssuersLookupStrategy =
                 Constraint.isNotNull(strategy, "trustedTrustMarkIssuersLookupStrategy cannot be null");
@@ -174,7 +174,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
      * @param strategy lookup strategy
      */
     public void setTrustedTrustMarkOwnersLookupStrategy(
-            @Nonnull final Function<List<EntityStatement>, Map<String, Map<String, Object>>> strategy) {
+            @Nonnull final Function<List<EntityStatement<?>>, Map<String, Map<String, Object>>> strategy) {
         checkSetterPreconditions();
         trustedTrustMarkOwnersLookupStrategy =
                 Constraint.isNotNull(strategy, "trustedTrustMarkOwnersLookupStrategy cannot be null");
@@ -291,7 +291,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
             return false;
         }
 
-        final Pair<List<EntityStatement>, Metadata> selectedChain = trustChainContext.getSelectedTrustChain();
+        final Pair<List<EntityStatement<?>>, Metadata> selectedChain = trustChainContext.getSelectedTrustChain();
 
         if (selectedChain == null || selectedChain.getFirst() == null) {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
@@ -358,7 +358,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
         assert trustedOwners != null;
         
         final Map<String, List<SignedJWT>> verifiedTrustMarks = new HashMap<>();
-        for (final EntityStatement statement : selectedTrustChain) {
+        for (final EntityStatement<?> statement : selectedTrustChain) {
             final List<SignedJWT> trustMarks = chainTrustMarks.get(statement.getSubject());
             if (trustMarks == null || trustMarks.isEmpty()) {
                 break;
@@ -486,8 +486,8 @@ public class ResolveTrustMarks extends AbstractProfileAction {
             log.warn("{} No trust chains resolved for {}", getLogPrefix(), issuer);
             return false;
         }
-        final List<EntityStatement> trustMarkChain = cacheResult.get(0).getTrustChains().get(0);
-        final EntityStatement trustMarkIssuer = trustMarkChain.get(0);
+        final List<EntityStatement<?>> trustMarkChain = cacheResult.get(0).getTrustChains().get(0);
+        final EntityStatement<?> trustMarkIssuer = trustMarkChain.get(0);
         assert trustMarkIssuer != null;
         final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(trustMarkIssuer));
         log.trace("{} Validating entity statement {}", getLogPrefix(), trustMarkIssuer.getJwt().serialize());
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
index 015559e..5b0520b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
@@ -15,7 +15,6 @@
 package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
 import java.util.List;
-import java.util.Map;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -60,7 +59,7 @@ public class SelectTrustChain extends AbstractProfileAction {
 
     /** Strategy used to fetch the selected trust chain and metadata. */
     @NonnullAfterInit
-    private Function<ProfileRequestContext,Pair<List<EntityStatement>, Metadata>> selectedTrustChainLookupStrategy;
+    private Function<ProfileRequestContext,Pair<List<EntityStatement<?>>, Metadata>> selectedTrustChainLookupStrategy;
 
     /** Trust chain context to operate on. */
     @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
@@ -112,7 +111,7 @@ public class SelectTrustChain extends AbstractProfileAction {
      * @param strategy lookup strategy
      */
     public void setSelectedTrustChainLookupStrategy(@Nonnull final
-            Function<ProfileRequestContext,Pair<List<EntityStatement>, Metadata>> strategy) {
+            Function<ProfileRequestContext,Pair<List<EntityStatement<?>>, Metadata>> strategy) {
         checkSetterPreconditions();
         selectedTrustChainLookupStrategy =
                 Constraint.isNotNull(strategy, "SelectedTrustChainLookupStrategy cannot be null");
@@ -146,7 +145,7 @@ public class SelectTrustChain extends AbstractProfileAction {
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final Pair<List<EntityStatement>, Metadata> selectedChain =
+        final Pair<List<EntityStatement<?>>, Metadata> selectedChain =
                 selectedTrustChainLookupStrategy.apply(profileRequestContext);
 
         if (selectedChain == null || selectedChain.getFirst() == null || selectedChain.getSecond() == null) {
@@ -155,14 +154,14 @@ public class SelectTrustChain extends AbstractProfileAction {
             return;
         }
 
-        final List<List<EntityStatement>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+        final List<List<EntityStatement<?>>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
         if (rejectedTrustChains != null && rejectedTrustChains.contains(selectedChain.getFirst())) {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             log.error("{} The selected trust chain has been previously rejected", getLogPrefix());
             return;
         }
         trustChainContext.setSelectedTrustChains(selectedChain);
-        final List<EntityStatement> selectedTrustChain = selectedChain.getFirst();
+        final List<EntityStatement<?>> selectedTrustChain = selectedChain.getFirst();
         assert selectedTrustChain != null;
         final RelyingPartyContext relyingPartyContext =
                 relyingPartyContextCreationStrategy.apply(profileRequestContext);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
index afb65ce..9a35faa 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
@@ -155,7 +155,7 @@ public class StoreAutomaticRegistration extends AbstractProfileAction {
             return false;
         }
 
-        final Pair<List<EntityStatement>,Metadata> selectedTrustChain = trustChainContext.getSelectedTrustChain();
+        final Pair<List<EntityStatement<?>>,Metadata> selectedTrustChain = trustChainContext.getSelectedTrustChain();
         assert selectedTrustChain != null;
 
         clientInformation = new OIDCClientInformation(
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
index 17dfac5..412caf9 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
@@ -90,7 +90,7 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
     @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
 
     /** Selected trust chain to operate on. */
-    @NonnullBeforeExec private Pair<List<EntityStatement>, Metadata> selectedTrustChain;
+    @NonnullBeforeExec private Pair<List<EntityStatement<?>>, Metadata> selectedTrustChain;
 
     /**
      * Constructor.
@@ -231,11 +231,11 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
                     || !verifiedTrustMarks.get(clientId).containsAll(mandatoryTrustMarks)) {
                 log.info("{} Rejecting registration as some of the following mandatory trust marks are missing: {}",
                         getLogPrefix(), mandatoryTrustMarks);
-                final List<List<EntityStatement>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+                final List<List<EntityStatement<?>>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
                 if (rejectedTrustChains == null) {
                     trustChainContext.setRejectedTrustChains(List.of(selectedTrustChain.getFirst()));
                 } else {
-                    final List<List<EntityStatement>> rejectedChains = new ArrayList<>(rejectedTrustChains);
+                    final List<List<EntityStatement<?>>> rejectedChains = new ArrayList<>(rejectedTrustChains);
                     rejectedChains.add(selectedTrustChain.getFirst());
                     trustChainContext.setRejectedTrustChains(CollectionSupport.copyToList(rejectedChains));
                 }
@@ -244,7 +244,7 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
             }
         }
 
-        final List<EntityStatement> trustChain = selectedTrustChain.getFirst();
+        final List<EntityStatement<?>> trustChain = selectedTrustChain.getFirst();
         assert trustChain != null;
         trustChainContext.setSelectedMetadataExpiration(resolveTrustChainExpiration(trustChain));
 
@@ -271,9 +271,9 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
      * @param trustChain trust chain
      * @return expiration time
      */
-    @Nullable private Instant resolveTrustChainExpiration(@Nonnull final List<EntityStatement> trustChain) {
+    @Nullable private Instant resolveTrustChainExpiration(@Nonnull final List<EntityStatement<?>> trustChain) {
         Instant metadataExpiration = null;
-        for (final EntityStatement statement : trustChain) {
+        for (final EntityStatement<?> statement : trustChain) {
             final Instant statementExpiration = statement.getParsedPayload().getExpiration();
             metadataExpiration = metadataExpiration == null ? statementExpiration : 
                 statementExpiration.isBefore(metadataExpiration) ? statementExpiration : metadataExpiration;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
index 39bc225..5b2a8e6 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
@@ -16,7 +16,6 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
 
@@ -53,13 +52,13 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
 
     /** Strategy used to validate provided trust chain. */
     @NonnullAfterInit
-    private BiPredicate<ProfileRequestContext, List<EntityStatement>> providedTrustChainValidationStrategy;
+    private BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> providedTrustChainValidationStrategy;
 
     /** Strategy used to locate the provided trust chain. */
-    @NonnullAfterInit private Function<ProfileRequestContext, List<EntityStatement>> providedTrustChainLookupStrategy;
+    @NonnullAfterInit private Function<ProfileRequestContext, List<EntityStatement<?>>> providedTrustChainLookupStrategy;
 
     /** Trust chain to operate on. */
-    @NonnullBeforeExec private List<EntityStatement> trustChain;
+    @NonnullBeforeExec private List<EntityStatement<?>> trustChain;
 
     /**
      * Set the strategy used to locate the provided trust chain.
@@ -67,7 +66,7 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
      * @param strategy lookup strategy
      */
     public void setProvidedTrustChainLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, List<EntityStatement>> strategy) {
+            @Nonnull final Function<ProfileRequestContext, List<EntityStatement<?>>> strategy) {
         providedTrustChainLookupStrategy =
                 Constraint.isNotNull(strategy, "ProvidedTrustChainLookupStrategy cannot be null");
     }
@@ -78,7 +77,7 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
      * @param strategy validation strategy
      */
     public void setProvidedTrustChainValidationStrategy(
-            @Nonnull final BiPredicate<ProfileRequestContext, List<EntityStatement>> strategy) {
+            @Nonnull final BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> strategy) {
         checkSetterPreconditions();
         providedTrustChainValidationStrategy =
                 Constraint.isNotNull(strategy, "ProvidedTrustChainValidationStrategy cannot be null");
@@ -125,7 +124,7 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
                 getTrustChainContextCreationStrategy().apply(profileRequestContext);
         assert trustChain != null;
         trustChainContext.setResolvedTrustChains(CollectionSupport.listOf(trustChain));
-        final List<Pair<List<EntityStatement>, Metadata>> policyCompliantChains =
+        final List<Pair<List<EntityStatement<?>>, Metadata>> policyCompliantChains =
                 new ArrayList<>();
 
         final String errorEventId = populatePolicyComplaintChains(trustChain, policyCompliantChains);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
index 74ab28e..0460ff0 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
@@ -166,10 +166,10 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         final RelyingPartyTrustChainContext trustChainContext =
                 trustChainContextLookupStrategy.apply(profileRequestContext);
-        final Pair<List<EntityStatement>, Metadata>  selectedTrustChain =
+        final Pair<List<EntityStatement<?>>, Metadata>  selectedTrustChain =
                 trustChainContext != null ? trustChainContext.getSelectedTrustChain() : null;
         if (selectedTrustChain == null || selectedTrustChain.getFirst() == null) {
-            final List<Pair<List<EntityStatement>,Metadata>> allChains =
+            final List<Pair<List<EntityStatement<?>>,Metadata>> allChains =
                     trustChainContext != null ? trustChainContext.getPolicyCompliantTrustChains() : null;
             if (allChains == null || allChains.isEmpty()) {
                 if (isSubjectValid(validatedRequest.getSubject())) {
@@ -189,18 +189,18 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
         }
         
         final List<String> trustAnchors = validatedRequest.getTrustAnchors();
-        final List<EntityStatement> candidateChain = selectedTrustChain.getFirst();
+        final List<EntityStatement<?>> candidateChain = selectedTrustChain.getFirst();
         assert candidateChain != null;
         final String candidateAnchor = candidateChain.get(candidateChain.size() - 1).getSubject();
         if (!trustAnchors.contains(candidateAnchor)) {
             log.debug("{} Selected trust chain candidate has unrequested trust anchor {}", getLogPrefix(),
                     candidateAnchor);
             assert trustChainContext != null;
-            final List<List<EntityStatement>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+            final List<List<EntityStatement<?>>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
             if (rejectedTrustChains == null) {
                 trustChainContext.setRejectedTrustChains(List.of(selectedTrustChain.getFirst()));
             } else {
-                final List<List<EntityStatement>> rejectedChains = new ArrayList<>(rejectedTrustChains);
+                final List<List<EntityStatement<?>>> rejectedChains = new ArrayList<>(rejectedTrustChains);
                 rejectedChains.add(selectedTrustChain.getFirst());
                 trustChainContext.setRejectedTrustChains(CollectionSupport.copyToList(rejectedChains));
             }
@@ -220,7 +220,7 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
         try {
             final List<RemoteEntityConfigurationContainer> result =
                     entityConfigurationCache.get(new CriteriaSet(subjectCriterion));
-            if (result.size() == 1 && result.get(0).getEntityStatement() != null) {
+            if (result.size() == 1 && result.get(0).getStatement() != null) {
                 return true;
             }
         } catch (final MetadataCacheException e) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultMetadataValidationCondition.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultMetadataValidationCondition.java
index 3733037..e762ecd 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultMetadataValidationCondition.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultMetadataValidationCondition.java
@@ -49,6 +49,7 @@ public class DefaultMetadataValidationCondition implements BiPredicate<String, M
             return false;
         }
         try {
+            assert key != null;
             switch (key) {
                 case "federation_entity":
                     log.debug("Ignoring validation of {}", key);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainIDsLookupStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainIDsLookupStrategy.java
index 743904b..9724d24 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainIDsLookupStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainIDsLookupStrategy.java
@@ -32,7 +32,7 @@ import net.shibboleth.shared.logic.Constraint;
 public class DefaultSelectedTrustChainIDsLookupStrategy extends AbstractTrustChainContextLookupFunction<List<String>> {
 
     /** Strategy used to get entity IDs from a trust chain. */
-    @Nonnull private Function<List<EntityStatement>, List<String>> trustChainIDsLookupStrategy;
+    @Nonnull private Function<List<EntityStatement<?>>, List<String>> trustChainIDsLookupStrategy;
 
     /**
      * Constructor.
@@ -48,7 +48,7 @@ public class DefaultSelectedTrustChainIDsLookupStrategy extends AbstractTrustCha
      * @param strategy lookup strategy
      */
     public void setTrustChainIDsLookupStrategy(
-            @Nonnull final Function<List<EntityStatement>, List<String>> strategy) {
+            @Nonnull final Function<List<EntityStatement<?>>, List<String>> strategy) {
         trustChainIDsLookupStrategy = Constraint.isNotNull(strategy, "TrustChainIDsLookupStrategy cannot be null");
     }
 
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainIDsLookupStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainIDsLookupStrategy.java
index 5c6302d..672dbaa 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainIDsLookupStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainIDsLookupStrategy.java
@@ -26,11 +26,11 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 /**
  * Default strategy for looking up the entity IDs of a trust chain.
  */
-public class DefaultTrustChainIDsLookupStrategy implements Function<List<EntityStatement>,List<String>> {
+public class DefaultTrustChainIDsLookupStrategy implements Function<List<EntityStatement<?>>,List<String>> {
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public List<String> apply(@Nullable final List<EntityStatement> input) {
+    public List<String> apply(@Nullable final List<EntityStatement<?>> input) {
         return Optional.ofNullable(input)
                 .filter(chain -> chain != null && !chain.isEmpty())
                 .map(chain -> IntStream.range(0, chain.size())
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
index ab21ccf..e893f8a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
@@ -33,16 +33,16 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * {@link RelyingPartyTrustChainContext#getRejectedTrustChains()}.
  */
 public class DefaultTrustChainSelectionStrategy
-    extends AbstractTrustChainContextLookupFunction<Pair<List<EntityStatement>, Metadata>> {
+    extends AbstractTrustChainContextLookupFunction<Pair<List<EntityStatement<?>>, Metadata>> {
 
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainSelectionStrategy.class);
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public Pair<List<EntityStatement>, Metadata> doApply(
+    public Pair<List<EntityStatement<?>>, Metadata> doApply(
             @Nonnull final RelyingPartyTrustChainContext trustChainContext) {
-        final List<Pair<List<EntityStatement>, Metadata>> policyCompliantChains =
+        final List<Pair<List<EntityStatement<?>>, Metadata>> policyCompliantChains =
                 trustChainContext.getPolicyCompliantTrustChains();
 
         if (policyCompliantChains == null || policyCompliantChains.isEmpty()) {
@@ -54,14 +54,14 @@ public class DefaultTrustChainSelectionStrategy
         if (policyCompliantChains.size() > 1) {
             int shortestIndex = -1;
             for (int i = 0; i < policyCompliantChains.size(); i++) {
-                final List<EntityStatement> candidate = policyCompliantChains.get(i).getFirst();
+                final List<EntityStatement<?>> candidate = policyCompliantChains.get(i).getFirst();
                 if (isTrustChainRejected(trustChainContext, candidate)) {
                     continue;
                 }
                 if (shortestIndex == -1) {
                     shortestIndex = i;
                 } else {
-                    final List<EntityStatement> shortest = policyCompliantChains.get(shortestIndex).getFirst();
+                    final List<EntityStatement<?>> shortest = policyCompliantChains.get(shortestIndex).getFirst();
                     if (candidate != null && shortest != null && candidate.size() < shortest.size()) {
                         shortestIndex = i;
                     }
@@ -70,7 +70,7 @@ public class DefaultTrustChainSelectionStrategy
             log.trace("Shortest non-rejected index {}", shortestIndex);
             return shortestIndex == -1 ? null : policyCompliantChains.get(shortestIndex);
         }
-        final List<EntityStatement> candidate = policyCompliantChains.get(0).getFirst();
+        final List<EntityStatement<?>> candidate = policyCompliantChains.get(0).getFirst();
         return isTrustChainRejected(trustChainContext, candidate) ? null : policyCompliantChains.get(0);
     }
 
@@ -82,11 +82,11 @@ public class DefaultTrustChainSelectionStrategy
      * @return true if trust chain is null or previously rejected, false otherwise
      */
     private boolean isTrustChainRejected(@Nonnull final RelyingPartyTrustChainContext trustChainContext,
-            @Nullable final List<EntityStatement> trustChain) {
+            @Nullable final List<EntityStatement<?>> trustChain) {
         if (trustChain == null) {
             return true;
         }
-        final List<List<EntityStatement>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+        final List<List<EntityStatement<?>>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
         return rejectedTrustChains != null && rejectedTrustChains.contains(trustChain);
     }
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestClientIDLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestClientIDLookupFunction.java
index 4e788c8..29ef8c1 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestClientIDLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestClientIDLookupFunction.java
@@ -26,6 +26,7 @@ import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationRequest;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 
 /**
@@ -56,11 +57,11 @@ public class ExplicitClientRegistrationRequestClientIDLookupFunction
         if (request == null) {
             return null;
         }
-        final EntityStatement entityConfiguration = request.getEntityConfiguration();
+        final EntityConfiguration entityConfiguration = request.getEntityConfiguration();
         if (entityConfiguration != null) {
             return new ClientID(entityConfiguration.getSubject());
         }
-        final List<EntityStatement> trustChain = request.getTrustChain();
+        final List<EntityStatement<?>> trustChain = request.getTrustChain();
         return trustChain != null && !trustChain.isEmpty() ? new ClientID(trustChain.get(0).getSubject()) : null;
     }
 }
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestJWKSetLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestJWKSetLookupFunction.java
index 245b880..384d0f6 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestJWKSetLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestJWKSetLookupFunction.java
@@ -26,6 +26,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import com.nimbusds.jose.jwk.JWKSet;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationRequest;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 
 /**
@@ -57,11 +58,11 @@ public class ExplicitClientRegistrationRequestJWKSetLookupFunction
         if (request == null) {
             return null;
         }
-        final EntityStatement entityConfiguration = request.getEntityConfiguration();
+        final EntityConfiguration entityConfiguration = request.getEntityConfiguration();
         if (entityConfiguration != null) {
             return entityConfiguration.getParsedPayload().getJwks();
         }
-        final List<EntityStatement> trustChain = request.getTrustChain();
+        final List<EntityStatement<?>> trustChain = request.getTrustChain();
         return trustChain != null && !trustChain.isEmpty() ? trustChain.get(0).getParsedPayload().getJwks() : null;
     }
 }
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java
index a5aa0a0..7747d45 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java
@@ -31,11 +31,11 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
  */
 @ThreadSafe
 public class ExplicitClientRegistrationRequestTrustChainLookupFunction
-    implements ContextDataLookupFunction<ProfileRequestContext, List<EntityStatement>> {
+    implements ContextDataLookupFunction<ProfileRequestContext, List<EntityStatement<?>>> {
 
     /** {@inheritDoc} */
     @Nullable
-    public List<EntityStatement> apply(@Nullable final ProfileRequestContext input) {
+    public List<EntityStatement<?>> apply(@Nullable final ProfileRequestContext input) {
         return Optional.ofNullable(input)
                 .map(profileRequestContext -> profileRequestContext.getInboundMessageContext())
                 .map(messageContext -> messageContext.getMessage())
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultEntityConfigurationCredentialResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultEntityConfigurationCredentialResolver.java
index 4dfad32..e29d84b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultEntityConfigurationCredentialResolver.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultEntityConfigurationCredentialResolver.java
@@ -71,7 +71,7 @@ public class DefaultEntityConfigurationCredentialResolver extends BasicJOSEObjec
                     "Credential criteria set did not contain an instance of SubjectEntityStatementCriterion");
         }
         final JWKSet jwks;
-        final EntityStatement subjectStatement = subjectCriterion.getValue();
+        final EntityStatement<?> subjectStatement = subjectCriterion.getValue();
         if (subjectStatement.getSubject().equals(
                 subjectStatement.getIssuer())) {
                     jwks = subjectStatement.getParsedPayload().getJwks();
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadJOSEObjectCredentialResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadJOSEObjectCredentialResolver.java
index 8852db9..2cf1327 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadJOSEObjectCredentialResolver.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadJOSEObjectCredentialResolver.java
@@ -52,6 +52,7 @@ public class DefaultPayloadJOSEObjectCredentialResolver extends BasicJOSEObjectC
         }
 
         final JOSEObjectCriterion joseObjectCriteria = criteriaSet.get(JOSEObjectCriterion.class);
+        assert joseObjectCriteria != null;
         final JOSEObject joseObject = joseObjectCriteria.getJOSEObject();
         if (joseObject == null) {
             throw new ResolverException("JOSEObjectCriterion did not contain an instance of JOSEObject");
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadSignatureValidationCredentialResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadSignatureValidationCredentialResolver.java
index 862106e..310fec9 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadSignatureValidationCredentialResolver.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultPayloadSignatureValidationCredentialResolver.java
@@ -53,6 +53,7 @@ public class DefaultPayloadSignatureValidationCredentialResolver extends BasicJO
 
         final SignatureValidationKeyContainerJwtCriterion keyContainer =
                 criteriaSet.get(SignatureValidationKeyContainerJwtCriterion.class);
+        assert keyContainer != null;
         final SignedJWT jwt = keyContainer.getJwt();
         if (jwt == null) {
             throw new ResolverException(
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultSubordinateStatementCredentialResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultSubordinateStatementCredentialResolver.java
index e8c3923..1fe9096 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultSubordinateStatementCredentialResolver.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultSubordinateStatementCredentialResolver.java
@@ -24,9 +24,11 @@ import org.slf4j.Logger;
 
 import com.nimbusds.jose.jwk.JWKSet;
 
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.BaseExpirableSubjectPayload;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.IssuerEntityStatementCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.SubjectEntityStatementCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.SubjectStatementCriterion;
 import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.resolver.CriteriaSet;
@@ -65,17 +67,16 @@ public class DefaultSubordinateStatementCredentialResolver extends BasicJOSEObje
      * @throws ResolverException if the JWKSet could not be parsed or found
      */
     @Nonnull protected JWKSet parseJwkSet(@Nonnull final CriteriaSet criteriaSet) throws ResolverException {
-        final SubjectEntityStatementCriterion subjectCriterion = criteriaSet.get(SubjectEntityStatementCriterion.class);
-        if (subjectCriterion == null) {
-            log.debug("No mandatory SubjectEntityStatementCriterion criteria supplied, resolver could not process");
+        final BaseExpirableSubjectPayload subjectPayload = getSubjectStatementPayload(criteriaSet);
+        if (subjectPayload == null) {
+            log.debug("No mandatory criteria supplied for resolving subject, resolver could not process");
             throw new ResolverException(
-                    "Credential criteria set did not contain an instance of SubjectEntityStatementCriterion");
+                    "Credential criteria set did not contain criterion to resolve subject");
         }
         final JWKSet jwks;
-        final EntityStatement subjectStatement = subjectCriterion.getValue();
-        if (subjectStatement.getSubject().equals(subjectStatement.getIssuer())) {
+        if (subjectPayload.getSubject().equals(subjectPayload.getIssuer())) {
             throw new ResolverException(
-                    "Unexpected contents in the SubjectEntityStatementCriterion: subject matches issuer");
+                    "Unexpected contents in the subject statement: subject matches issuer");
         } else {
             final IssuerEntityStatementCriterion issuerCriterion =
                     criteriaSet.get(IssuerEntityStatementCriterion.class);
@@ -84,8 +85,8 @@ public class DefaultSubordinateStatementCredentialResolver extends BasicJOSEObje
                 throw new ResolverException(
                         "Credential criteria set did not contain an instance of IssuerEntityStatementCriterion");
             }
-            final EntityStatement issuerStatement = issuerCriterion.getValue();
-            if (!issuerStatement.getSubject().equals(subjectStatement.getIssuer())) {
+            final EntityStatement<?> issuerStatement = issuerCriterion.getValue();
+            if (!issuerStatement.getSubject().equals(subjectPayload.getIssuer())) {
                 throw new ResolverException("Credential criteria do not match for subject and issuer");
             }
             if (!issuerStatement.getSubject().equals(issuerStatement.getIssuer())) {
@@ -99,4 +100,19 @@ public class DefaultSubordinateStatementCredentialResolver extends BasicJOSEObje
         }
         return jwks;
     }
+
+    private BaseExpirableSubjectPayload getSubjectStatementPayload(
+            @Nonnull final CriteriaSet criteriaSet) {
+        final SubjectEntityStatementCriterion entityStatementCriterion =
+                criteriaSet.get(SubjectEntityStatementCriterion.class);
+        if (entityStatementCriterion == null) {
+            final SubjectStatementCriterion subjectCriterion = criteriaSet.get(SubjectStatementCriterion.class);
+            if (subjectCriterion == null) {
+                return null;
+            }
+            return subjectCriterion.getValue().getParsedPayload() instanceof BaseExpirableSubjectPayload payload
+                    ? payload : null;
+        }
+        return entityStatementCriterion.getValue().getParsedPayload();
+    }
 }
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
index 4d62686..0f4e74b 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
@@ -40,7 +40,9 @@ import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubordinateStatement;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.jackson.InstantDeserializer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.jackson.JWKSetDeserializer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.FederationMetadataPolicyDeserializer;
@@ -49,10 +51,9 @@ import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 /**
  * Various utility methods for testing trust chains.
  */
- at SuppressWarnings("null")
 public class TrustChainTestUtil {
 
-    public static List<EntityStatement> chainWithIntermediate(final EntityStatement leaf, final String anchorId,
+    public static List<EntityStatement<?>> chainWithIntermediate(final EntityStatement<?> leaf, final String anchorId,
             final String intermediateId) {
         try {
             final RSAKey anchorKey = new RSAKeyGenerator(2048)
@@ -63,14 +64,14 @@ public class TrustChainTestUtil {
                     .keyID("mockIntermediateKey")
                     .keyUse(KeyUse.SIGNATURE)
                     .generate();
-            final EntityStatement trustAnchor = trustAnchor(JWSAlgorithm.RS256, anchorKey, anchorId);
-            final EntityStatement intermediateStatement = entityStatement(JWSAlgorithm.RS256, anchorKey,
+            final EntityStatement<?> trustAnchor = trustAnchor(JWSAlgorithm.RS256, anchorKey, anchorId);
+            final EntityStatement<?> intermediateStatement = entityStatement(JWSAlgorithm.RS256, anchorKey,
                     new JWTClaimsSet.Builder()
                     .subject(intermediateId)
                     .issueTime(new Date())
                     .issuer(anchorId)
                     .build());
-            final EntityStatement leafSubordinateStatement = entityStatement(JWSAlgorithm.RS256, intermediateKey,
+            final EntityStatement<?> leafSubordinateStatement = entityStatement(JWSAlgorithm.RS256, intermediateKey,
                     new JWTClaimsSet.Builder(leaf.getJwt().getJWTClaimsSet())
                     .subject(leaf.getSubject())
                     .issueTime(new Date())
@@ -83,7 +84,7 @@ public class TrustChainTestUtil {
         return null;
     }
 
-    public static EntityStatement trustAnchor(final JWSAlgorithm algorithm, final JWK jwk, final String entityId) {
+    public static EntityStatement<?> trustAnchor(final JWSAlgorithm algorithm, final JWK jwk, final String entityId) {
         return entityStatement(algorithm, jwk, 
                 new JWTClaimsSet.Builder()
                 .subject(entityId)
@@ -92,15 +93,18 @@ public class TrustChainTestUtil {
                 .build());
     }
 
-    public static EntityStatement entityStatement(final JWSAlgorithm algorithm, final JWK jwk,
+    public static EntityStatement<?> entityStatement(final JWSAlgorithm algorithm, final JWK jwk,
             final JWTClaimsSet claimsSet) {
         return entityStatement(signedJwt(algorithm, jwk, "entity-statement+jwt", claimsSet));
     }
 
-    public static EntityStatement entityStatement(final SignedJWT jwt) {
+    public static EntityStatement<?> entityStatement(final SignedJWT jwt) {
         try {
-            return EntityStatement.parse(jwt, payloadObjectMapper());
-        } catch (JsonProcessingException e) {
+            if (jwt.getJWTClaimsSet().getSubject().equals(jwt.getJWTClaimsSet().getIssuer())) {
+                return EntityConfiguration.parse(jwt, payloadObjectMapper());
+            }
+            return SubordinateStatement.parse(jwt, payloadObjectMapper());
+        } catch (JsonProcessingException | ParseException e) {
             Assert.fail("Could not construct entity configuration", e);
         }
         return null;
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
index e550b84..c9a5ead 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -255,7 +255,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
         if (trustMarks != null) {
             builder.claim("trust_marks", trustMarks);
         }
-        final EntityStatement rpConfiguration =
+        final EntityStatement<?> rpConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, builder.build());
         return rpConfiguration.getJwt().serialize();
     }
@@ -270,7 +270,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                 .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
                         new String[] { anchorId } : authorityHints)
                 .build();
-        final EntityStatement rpConfiguration =
+        final EntityStatement<?> rpConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, anchorKey, claimsSet);
         return rpConfiguration.getJwt().serialize();
     }
@@ -285,7 +285,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                 .claim("metadata", Map.of("federation_entity", Map.of("trust_mark_endpoint",
                         trustMarkEndpoint)))
                 .build();
-        final EntityStatement rpConfiguration =
+        final EntityStatement<?> rpConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustMarkIssuerKey, claimsSet);
         return rpConfiguration.getJwt().serialize();
     }
@@ -305,7 +305,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                 .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
                         new String[] { anchorId } : authorityHints)
                 .build();
-        final EntityStatement rpConfiguration =
+        final EntityStatement<?> rpConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
         return rpConfiguration.getJwt().serialize();
     }
@@ -320,7 +320,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                 .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
                         new String[] { anchorId } : authorityHints)
                 .build();
-        final EntityStatement configuration =
+        final EntityStatement<?> configuration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
         return configuration.getJwt().serialize();
     }
@@ -350,7 +350,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
         if (trustMarkIssuers != null) {
             builder.claim("trust_mark_issuers", trustMarkIssuers);
         }
-        final EntityStatement anchorConfiguration =
+        final EntityStatement<?> anchorConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, builder.build());
         return anchorConfiguration.getJwt().serialize();
     }
@@ -364,7 +364,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                         intermediateId + "/fetch")))
                 .claim("authority_hints", new String[] { anchorId })
                 .build();
-        final EntityStatement anchorConfiguration =
+        final EntityStatement<?> anchorConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, intermediateKey, claimsSet);
         return anchorConfiguration.getJwt().serialize();
     }
@@ -381,7 +381,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                 .claim("jwks", new JWKSet(subjectKey).toJSONObject(true))
                 .claim("metadata", metadata)
                 .build();
-        final EntityStatement rpConfiguration =
+        final EntityStatement<?> rpConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
         return rpConfiguration.getJwt().serialize();
     }
@@ -395,7 +395,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                 .claim("metadata", Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
                 .claim("metadata_policy", rpPolicy)
                 .build();
-        final EntityStatement rpConfiguration =
+        final EntityStatement<?> rpConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, issuerKey, claimsSet);
         return rpConfiguration.getJwt().serialize();
     }
@@ -410,7 +410,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
                 .claim("metadata_policy", rpPolicy)
                 .claim("constraints", constraints)
                 .build();
-        final EntityStatement rpConfiguration =
+        final EntityStatement<?> rpConfiguration =
                 TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, issuerKey, claimsSet);
         return rpConfiguration.getJwt().serialize();
     }
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
index 9504a84..a6e1551 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
@@ -456,7 +456,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
             final String expectedClientId) throws IOException, ParseException, net.minidev.json.parser.ParseException {
         Assert.assertEquals(response.getHeader("Content-Type"),
                 ExplicitClientRegistrationResponse.HTTP_RESPONSE_CONTENT_TYPE.toString());
-        final EntityStatement entityStatement =
+        final EntityStatement<?> entityStatement =
                 TrustChainTestUtil.entityStatement(regResponse.getJWT());
         Assert.assertEquals(entityStatement.getIssuer(), issuer);
         Assert.assertEquals(entityStatement.getParsedPayload().getCustomClaims().get("authority_hints"),
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
index ad89015..4cbd501 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
@@ -43,6 +43,7 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
 import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
 
+ at SuppressWarnings("null")
 public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
 
     @Autowired
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
index de05a17..a041a57 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
@@ -233,6 +233,7 @@ public class UserInfoFlowAutomaticRegistrationTest extends AbstractFederationFlo
         assertErrorDescriptionContains(result, "NoTrustChainsResolved");
     }
 
+    @SuppressWarnings("null")
     protected BearerAccessToken buildToken(final String clientId, final Object trustChain)
             throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
         final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
index 3f1c41b..ba9227e 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
@@ -74,7 +74,7 @@ public class EntityConfigurationMetadataCacheTest extends AbstractFederationFlow
                             new ResponseContainerExpirationCriterion(Instant.now().plusSeconds(300))));
             Assert.assertNotNull(result);
             Assert.assertEquals(result.size(), 1);
-            Assert.assertNotNull(result.get(0).getEntityStatement());
+            Assert.assertNotNull(result.get(0).getStatement());
         } catch (MetadataCacheException e) {
             Assert.fail("Could not resolve entity configuration", e);
         }
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
index 8fa01f6..19b8970 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
@@ -78,7 +78,7 @@ public class SubordinateStatementMetadataCacheTest extends AbstractFederationFlo
                             new IssuerEntityIDCriterion(anchorId)));
             Assert.assertNotNull(result);
             Assert.assertEquals(result.size(), 1);
-            Assert.assertNotNull(result.get(0).getEntityStatement());
+            Assert.assertNotNull(result.get(0).getStatement());
         } catch (MetadataCacheException e) {
             Assert.fail("Could not resolve entity configuration", e);
         }
@@ -237,7 +237,7 @@ public class SubordinateStatementMetadataCacheTest extends AbstractFederationFlo
                             new IssuerEntityIDCriterion(anchorId)));
             Assert.assertNotNull(result);
             Assert.assertEquals(result.size(), 1);
-            Assert.assertNull(result.get(0).getEntityStatement());
+            Assert.assertNull(result.get(0).getStatement());
         } catch (MetadataCacheException e) {
             Assert.fail("Could not resolve entity configuration", e);
         }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list