[java-opensaml] branch master updated: JSPT-79 - Review date and time handling for Java 8

Scott Cantor cantor.2 at osu.edu
Wed Mar 13 16:01:20 EDT 2019


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

scantor pushed a commit to branch master
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=ab872fe16a479067dbfd8bb60490a0d3cee081ab

The following commit(s) were added to refs/heads/master by this push:
       new  ab872fe   JSPT-79 - Review date and time handling for Java 8
ab872fe is described below

commit ab872fe16a479067dbfd8bb60490a0d3cee081ab
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Mar 13 16:01:13 2019 -0400

    JSPT-79 - Review date and time handling for Java 8
    
    https://issues.shibboleth.net/jira/browse/JSPT-79
    
    Converting long to Duration throughout OpenSAML APIs.
---
 .../artifact/ExpiringSAMLArtifactMapEntry.java     |  22 +--
 .../artifact/impl/BasicSAMLArtifactMap.java        |  67 ++++----
 .../impl/StorageServiceSAMLArtifactMap.java        |  47 +++---
 .../impl/MessageLifetimeSecurityHandler.java       |  45 +++---
 .../impl/MessageReplaySecurityHandler.java         |  33 ++--
 .../impl/AddNotOnOrAfterConditionToAssertions.java |  28 ++--
 .../filter/impl/RequiredValidUntilFilter.java      |  49 +++---
 .../impl/AbstractDynamicMetadataResolver.java      | 177 +++++++++++----------
 .../impl/AbstractReloadingMetadataResolver.java    | 116 +++++++-------
 .../impl/FileBackedHTTPMetadataResolver.java       |  36 +++--
 .../impl/OneTimeUseConditionValidator.java         |  45 +++---
 .../artifact/impl/BasicSAMLArtifactMapTest.java    |   6 +-
 .../impl/StorageServiceSAMLArtifactMapTest.java    |   6 +-
 .../impl/MessageLifetimeSecurityHandlerTest.java   |  17 +-
 .../impl/MessageReplaySecurityHandlerTest.java     |   4 +-
 .../AddNotOnOrAfterConditionToAssertionsTest.java  |   6 +-
 .../filter/impl/RequiredValidUntilTest.java        |  27 +---
 .../impl/FileBackedHTTPMetadataResolverTest.java   |  13 +-
 .../impl/LocalDynamicMetadataResolverTest.java     |   7 +-
 .../impl/ResourceBackedMetadataResolverTest.java   |   3 +-
 .../impl/OneTimeUseConditionValidatorTest.java     |   5 +-
 .../opensaml/storage/AbstractStorageService.java   |  36 +++--
 .../java/org/opensaml/storage/ReplayCache.java     |  10 +-
 .../storage/impl/client/ClientStorageService.java  |   4 +-
 .../storage/impl/JPAStorageServiceTest.java        |   3 +-
 .../storage/impl/LDAPStorageServiceTest.java       |   3 +-
 .../storage/impl/MemoryStorageServiceTest.java     |   4 +-
 .../org/opensaml/storage/impl/ReplayCacheTest.java |  10 +-
 28 files changed, 439 insertions(+), 390 deletions(-)

diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/artifact/ExpiringSAMLArtifactMapEntry.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/artifact/ExpiringSAMLArtifactMapEntry.java
index 4370952..a8cd7bc 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/artifact/ExpiringSAMLArtifactMapEntry.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/artifact/ExpiringSAMLArtifactMapEntry.java
@@ -17,7 +17,10 @@
 
 package org.opensaml.saml.common.binding.artifact;
 
+import java.time.Instant;
+
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 
@@ -28,8 +31,8 @@ import org.opensaml.saml.common.SAMLObject;
 /** Extension of {@link BasicSAMLArtifactMapEntry} that tracks expiration. */
 public class ExpiringSAMLArtifactMapEntry extends BasicSAMLArtifactMapEntry {
     
-    /** Expiration in milliseconds since the start of the Unix epoch. */
-    private long expiration;
+    /** Expiration time. */
+    @Nullable private Instant expiration;
 
     /**
      * Constructor.
@@ -49,20 +52,20 @@ public class ExpiringSAMLArtifactMapEntry extends BasicSAMLArtifactMapEntry {
     }
 
     /**
-     * Returns the expiration in milliseconds since the start of the Unix epoch.
+     * Returns the expiration time.
      * 
      * @return  the expiration
      */
-    public long getExpiration() {
+    @Nullable public Instant getExpiration() {
         return expiration;
     }
 
     /**
-     * Sets the expiration in milliseconds since the start of the Unix epoch.
+     * Sets the expiration time.
      * 
      * @param exp the expiration
      */
-    public void setExpiration(final long exp) {
+    public void setExpiration(@Nullable final Instant exp) {
         expiration = exp;
     }
     
@@ -72,7 +75,7 @@ public class ExpiringSAMLArtifactMapEntry extends BasicSAMLArtifactMapEntry {
      * @return true iff the entry is valid as of now
      */
     public boolean isValid() {
-        return System.currentTimeMillis() < expiration;
+        return expiration == null || expiration.isAfter(Instant.now());
     }
 
     /**
@@ -81,7 +84,8 @@ public class ExpiringSAMLArtifactMapEntry extends BasicSAMLArtifactMapEntry {
      * @param effectiveTime the time to evaluate validity against
      * @return true iff the entry is valid as of a specified time
      */
-    public boolean isValid(final long effectiveTime) {
-        return effectiveTime < expiration;
+    public boolean isValid(@Nonnull final Instant effectiveTime) {
+        return expiration == null || expiration.isBefore(effectiveTime);
     }
+    
 }
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMap.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMap.java
index a79ba06..4cf111c 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMap.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMap.java
@@ -18,6 +18,7 @@
 package org.opensaml.saml.common.binding.artifact.impl;
 
 import java.io.IOException;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Iterator;
 import java.util.Map;
@@ -28,13 +29,11 @@ import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.TimerSupport;
 
@@ -54,14 +53,14 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
     /** Artifact mapping storage. */
     @NonnullAfterInit private Map<String,ExpiringSAMLArtifactMapEntry> artifactStore;
 
-    /** Lifetime of an artifact in milliseconds. */
-    @Duration @Positive private long artifactLifetime;
+    /** Lifetime of an artifact. */
+    @Nonnull private Duration artifactLifetime;
 
     /** Factory for SAMLArtifactMapEntry instances. */
     @Nonnull private SAMLArtifactMapEntryFactory entryFactory;
 
-    /** Number of seconds between cleanup checks. Default value: (300) */
-    @Duration @NonNegative private long cleanupInterval;
+    /** Time between cleanup checks. Default value: (5 mins) */
+    @Nonnull private Duration cleanupInterval;
 
     /** Timer used to schedule cleanup tasks. */
     @NonnullAfterInit private Timer cleanupTaskTimer;
@@ -71,8 +70,8 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
 
     /** Constructor. */
     public BasicSAMLArtifactMap() {
-        artifactLifetime = 60000L;
-        cleanupInterval = 300;
+        artifactLifetime = Duration.ofMinutes(1);
+        cleanupInterval = Duration.ofMinutes(5);
         entryFactory = new ExpiringSAMLArtifactMapEntryFactory();
     }
 
@@ -81,10 +80,10 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
         super.doInitialize();
         artifactStore = new ConcurrentHashMap<>();
 
-        if (cleanupInterval > 0) {
+        if (!cleanupInterval.isZero()) {
             cleanupTask = new Cleanup();
             cleanupTaskTimer = new Timer(TimerSupport.getTimerName(this), true);
-            cleanupTaskTimer.schedule(cleanupTask, cleanupInterval * 1000, cleanupInterval * 1000);
+            cleanupTaskTimer.schedule(cleanupTask, cleanupInterval.toMillis(), cleanupInterval.toMillis());
         }
     }
 
@@ -101,11 +100,11 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
     }
 
     /**
-     * Get the artifact entry lifetime in milliseconds.
+     * Get the artifact entry lifetime.
      * 
-     * @return the artifact entry lifetime in milliseconds
+     * @return the artifact entry lifetime
      */
-    @Positive public long getArtifactLifetime() {
+    @Nonnull public Duration getArtifactLifetime() {
         return artifactLifetime;
     }
 
@@ -119,21 +118,29 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
     }
 
     /**
-     * Set the artifact entry lifetime in milliseconds.
+     * Set the artifact entry lifetime.
      * 
-     * @param lifetime artifact entry lifetime in milliseconds
+     * @param lifetime artifact entry lifetime
      */
-    @Duration public void setArtifactLifetime(@Duration @Positive final long lifetime) {
-        artifactLifetime = Constraint.isGreaterThan(0, lifetime, "Artifact lifetime must be greater than zero");
+    public void setArtifactLifetime(@Nonnull final Duration lifetime) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(lifetime, "Lifetime cannot be null");
+        Constraint.isFalse(lifetime.isNegative() || lifetime.isZero(), "Lifetime must be positive");
+        
+        artifactLifetime = lifetime;
     }
 
     /**
-     * Set the cleanup interval in milliseconds, or 0 for none.
+     * Set the cleanup interval, or 0 for none.
      * 
-     * @param interval  cleanup interval in milliseconds
+     * @param interval  cleanup interval
      */
-    @Duration public void setCleanupInterval(@Duration @NonNegative final long interval) {
-        cleanupInterval = Constraint.isGreaterThanOrEqual(0, interval, "Cleanup interval must be non-negative");
+    public void setCleanupInterval(@Nonnull final Duration interval) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(interval, "Interval cannot be null");
+        Constraint.isFalse(interval.isNegative(), "Interval cannot be negative");
+
+        cleanupInterval = interval;
     }
     
     /**
@@ -142,16 +149,18 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
      * @param factory map entry factory
      */
     public void setEntryFactory(@Nonnull final SAMLArtifactMapEntryFactory factory) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
         entryFactory = Constraint.isNotNull(factory, "SAMLArtifactMapEntryFactory cannot be null");
     }
 
     /** {@inheritDoc} */
-    @Override public boolean contains(@Nonnull @NotEmpty final String artifact) throws IOException {
+    public boolean contains(@Nonnull @NotEmpty final String artifact) throws IOException {
         return artifactStore.containsKey(artifact);
     }
 
     /** {@inheritDoc} */
-    @Override @Nullable public SAMLArtifactMapEntry get(@Nonnull @NotEmpty final String artifact) throws IOException {
+    @Nullable public SAMLArtifactMapEntry get(@Nonnull @NotEmpty final String artifact) throws IOException {
         log.debug("Attempting to retrieve entry for artifact: {}", artifact);
         final ExpiringSAMLArtifactMapEntry entry = artifactStore.get(artifact);
 
@@ -171,23 +180,23 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
     }
 
     /** {@inheritDoc} */
-    @Override public void put(@Nonnull @NotEmpty final String artifact, @Nonnull @NotEmpty final String relyingPartyId,
+    public void put(@Nonnull @NotEmpty final String artifact, @Nonnull @NotEmpty final String relyingPartyId,
             @Nonnull @NotEmpty final String issuerId, @Nonnull final SAMLObject samlMessage) throws IOException {
 
         final ExpiringSAMLArtifactMapEntry artifactEntry =
                 (ExpiringSAMLArtifactMapEntry) entryFactory.newEntry(artifact, issuerId, relyingPartyId, samlMessage);
-        artifactEntry.setExpiration(System.currentTimeMillis() + getArtifactLifetime());
+        artifactEntry.setExpiration(Instant.now().plus(getArtifactLifetime()));
 
         if (log.isDebugEnabled()) {
             log.debug("Storing new artifact entry '{}' for relying party '{}', expiring at '{}'", new Object[] {
-                    artifact, relyingPartyId, Instant.ofEpochMilli(artifactEntry.getExpiration()),});
+                    artifact, relyingPartyId, artifactEntry.getExpiration(),});
         }
 
         artifactStore.put(artifact, artifactEntry);
     }
 
     /** {@inheritDoc} */
-    @Override public void remove(@Nonnull @NotEmpty final String artifact) throws IOException {
+    public void remove(@Nonnull @NotEmpty final String artifact) throws IOException {
         log.debug("Removing artifact entry: {}", artifact);
 
         artifactStore.remove(artifact);
@@ -202,7 +211,7 @@ public class BasicSAMLArtifactMap extends AbstractInitializableComponent impleme
         @Override public void run() {
             log.info("Running cleanup task");
 
-            final Long now = System.currentTimeMillis();
+            final Instant now = Instant.now();
 
             final Iterator<Map.Entry<String, ExpiringSAMLArtifactMapEntry>> i = artifactStore.entrySet().iterator();
             while (i.hasNext()) {
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMap.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMap.java
index 95bb732..979f461 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMap.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMap.java
@@ -18,16 +18,16 @@
 package org.opensaml.saml.common.binding.artifact.impl;
 
 import java.io.IOException;
+import java.time.Duration;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 import org.opensaml.saml.common.SAMLObject;
@@ -53,8 +53,8 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
     /** Maximum size of artifacts we can handle. */
     private int artifactStoreKeySize;
 
-    /** Lifetime of an artifact in milliseconds. */
-    @Duration @Positive private long artifactLifetime;
+    /** Lifetime of an artifact. */
+    @Nonnull private Duration artifactLifetime;
 
     /** Factory for SAMLArtifactMapEntry instances. */
     @Nonnull private SAMLArtifactMapEntryFactory entryFactory;
@@ -62,7 +62,7 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
     /** Constructor. */
     public StorageServiceSAMLArtifactMap() {
         entryFactory = new StorageServiceSAMLArtifactMapEntryFactory();
-        artifactLifetime = 60000L;
+        artifactLifetime = Duration.ofMinutes(1);
     }
 
     /** {@inheritDoc} */
@@ -88,11 +88,11 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
     }
 
     /**
-     * Get the artifact entry lifetime in milliseconds.
+     * Get the artifact entry lifetime.
      * 
-     * @return the artifact entry lifetime in milliseconds
+     * @return the artifact entry lifetime
      */
-    @Duration @Positive public long getArtifactLifetime() {
+    @Nonnull public Duration getArtifactLifetime() {
         return artifactLifetime;
     }
 
@@ -111,16 +111,22 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
      * @param store the artifact store
      */
     public void setStorageService(@Nonnull final StorageService store) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
         artifactStore = Constraint.isNotNull(store, "StorageService cannot be null");
     }
 
     /**
-     * Set the artifact entry lifetime in milliseconds.
+     * Set the artifact entry lifetime.
      * 
-     * @param lifetime artifact entry lifetime in milliseconds
+     * @param lifetime artifact entry lifetime
      */
-    @Duration public void setArtifactLifetime(@Duration @Positive final long lifetime) {
-        artifactLifetime = Constraint.isGreaterThan(0, lifetime, "Artifact lifetime must be greater than zero");
+    public void setArtifactLifetime(@Nonnull final Duration lifetime) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(lifetime, "Lifetime cannot be null");
+        Constraint.isFalse(lifetime.isNegative() || lifetime.isZero(), "Lifetime must be positive");
+        
+        artifactLifetime = lifetime;
     }
 
     /**
@@ -135,13 +141,15 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
      * @param factory map entry factory
      */
     public void setEntryFactory(@Nonnull final SAMLArtifactMapEntryFactory factory) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
         Constraint.isTrue(factory != null && factory instanceof StorageSerializer<?>,
                 "SAMLArtifactMapEntryFactory cannot be null and must support the StorageSerializer interface");
         entryFactory = factory;
     }
 
     /** {@inheritDoc} */
-    @Override public boolean contains(@Nonnull @NotEmpty final String artifact) throws IOException {
+    public boolean contains(@Nonnull @NotEmpty final String artifact) throws IOException {
         if (artifact.length() > artifactStoreKeySize) {
             throw new IOException("Length of artifact (" + artifact.length() + ") exceeds storage capabilities");
         }
@@ -149,7 +157,7 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
     }
 
     /** {@inheritDoc} */
-    @Override @Nullable public SAMLArtifactMapEntry get(@Nonnull @NotEmpty final String artifact) throws IOException {
+    @Nullable public SAMLArtifactMapEntry get(@Nonnull @NotEmpty final String artifact) throws IOException {
         log.debug("Attempting to retrieve entry for artifact: {}", artifact);
 
         if (artifact.length() > artifactStoreKeySize) {
@@ -168,7 +176,7 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
     }
 
     /** {@inheritDoc} */
-    @Override public void put(@Nonnull @NotEmpty final String artifact, @Nonnull @NotEmpty final String relyingPartyId,
+    public void put(@Nonnull @NotEmpty final String artifact, @Nonnull @NotEmpty final String relyingPartyId,
             @Nonnull @NotEmpty final String issuerId, @Nonnull final SAMLObject samlMessage) throws IOException {
 
         if (artifact.length() > artifactStoreKeySize) {
@@ -179,20 +187,21 @@ public class StorageServiceSAMLArtifactMap extends AbstractInitializableComponen
                 getEntryFactory().newEntry(artifact, issuerId, relyingPartyId, samlMessage);
 
         if (log.isDebugEnabled()) {
-            log.debug("Storing new artifact entry '{}' for relying party '{}', expiring after {} seconds",
-                    new Object[] {artifact, relyingPartyId, getArtifactLifetime() / 1000});
+            log.debug("Storing new artifact entry '{}' for relying party '{}', expiring after {}",
+                    new Object[] {artifact, relyingPartyId, getArtifactLifetime()});
         }
 
         final boolean success =
                 getStorageService().create(STORAGE_CONTEXT, artifact, artifactEntry,
-                        (StorageSerializer) getEntryFactory(), System.currentTimeMillis() + getArtifactLifetime());
+                        (StorageSerializer) getEntryFactory(),
+                        System.currentTimeMillis() + getArtifactLifetime().toMillis());
         if (!success) {
             throw new IOException("A duplicate artifact was generated");
         }
     }
 
     /** {@inheritDoc} */
-    @Override public void remove(@Nonnull @NotEmpty final String artifact) throws IOException {
+    public void remove(@Nonnull @NotEmpty final String artifact) throws IOException {
         log.debug("Removing artifact entry: {}", artifact);
 
         if (artifact.length() > artifactStoreKeySize) {
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandler.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandler.java
index c7dced3..1dc96f1 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandler.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandler.java
@@ -17,12 +17,11 @@
 
 package org.opensaml.saml.common.binding.security.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 
 import javax.annotation.Nonnull;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
@@ -42,21 +41,20 @@ public class MessageLifetimeSecurityHandler extends AbstractMessageHandler {
     @Nonnull private final Logger log = LoggerFactory.getLogger(MessageLifetimeSecurityHandler.class);
 
     /**
-     * Clock skew - milliseconds before a lower time bound, or after an upper time bound, to consider still
-     * acceptable Default value: 3 minutes.
+     * Clock skew adjustment in both directions to consider still acceptable (Default value: 3 minutes).
      */
-    @Duration @NonNegative private long clockSkew;
+    @Nonnull private Duration clockSkew;
 
-    /** Amount of time in milliseconds for which a message is valid after it is issued. Default value: 3 minutes */
-    @Duration @NonNegative private long messageLifetime;
+    /** Amount of time for which a message is valid after it is issued (Default value: 3 minutes). */
+    @Nonnull private Duration messageLifetime;
     
     /** Whether this rule is required to be met. */
     private boolean requiredRule;
     
     /** Constructor. */
     public MessageLifetimeSecurityHandler() {
-        clockSkew = 60 * 3 * 1000;
-        messageLifetime = 180 * 1000;
+        clockSkew = Duration.ofMinutes(3);
+        messageLifetime = Duration.ofMinutes(3);
         requiredRule = true;
     }
     
@@ -65,7 +63,7 @@ public class MessageLifetimeSecurityHandler extends AbstractMessageHandler {
      * 
      * @return the clock skew
      */
-    @NonNegative @Duration public long getClockSkew() {
+    @Nonnull public Duration getClockSkew() {
         return clockSkew;
     }
 
@@ -74,31 +72,32 @@ public class MessageLifetimeSecurityHandler extends AbstractMessageHandler {
      * 
      * @param skew clock skew to set
      */
-    @Duration public void setClockSkew(@Duration @NonNegative final long skew) {
+    public void setClockSkew(@Nonnull final Duration skew) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        clockSkew = Constraint.isGreaterThanOrEqual(0, skew, "Clock skew must be greater than or equal to 0");
+                
+        clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
     }
 
     /**
-     * Gets the amount of time, in milliseconds, for which a message is valid.
+     * Gets the amount of time for which a message is valid.
      * 
-     * @return amount of time, in milliseconds, for which a message is valid
+     * @return amount of time for which a message is valid
      */
-    @NonNegative @Duration public long getMessageLifetime() {
+    @Nonnull public Duration getMessageLifetime() {
         return messageLifetime;
     }
 
     /**
-     * Sets the amount of time, in milliseconds, for which a message is valid.
+     * Sets the amount of time for which a message is valid.
      * 
-     * @param lifetime amount of time, in milliseconds, for which a message is valid
+     * @param lifetime amount of time for which a message is valid
      */
-    @Duration public synchronized void setMessageLifetime(@Duration @NonNegative final long lifetime) {
+    public synchronized void setMessageLifetime(@Nonnull final Duration lifetime) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(lifetime, "Lifetime cannot be null");
+        Constraint.isFalse(lifetime.isNegative(), "Lifetime cannot be negative");
         
-        messageLifetime = Constraint.isGreaterThanOrEqual(0, lifetime,
-                "Message lifetime must be greater than or equal to 0");
+        messageLifetime = lifetime;
     }
 
     /**
@@ -137,8 +136,8 @@ public class MessageLifetimeSecurityHandler extends AbstractMessageHandler {
 
         final Instant issueInstant = msgInfoContext.getMessageIssueInstant();
         final Instant now = Instant.now();
-        final Instant latestValid = now.plusMillis(getClockSkew());
-        final Instant expiration = issueInstant.plusMillis(getClockSkew() + getMessageLifetime());
+        final Instant latestValid = now.plus(getClockSkew().abs());
+        final Instant expiration = issueInstant.plus(getClockSkew().abs()).plus(getMessageLifetime());
 
         // Check message wasn't issued in the future
         if (issueInstant.isAfter(latestValid)) {
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandler.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandler.java
index eb94faa..5702296 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandler.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandler.java
@@ -17,12 +17,11 @@
 
 package org.opensaml.saml.common.binding.security.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 
 import javax.annotation.Nonnull;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
@@ -52,14 +51,13 @@ public class MessageReplaySecurityHandler extends AbstractMessageHandler {
     /** Whether this rule is required to be met. */
     private boolean requiredRule;
     
-    /** Time in milliseconds to expire cache entries. Default value: (180) */
-    @Duration @NonNegative private long expires;
+    /** Time to expire cache entries. Default value: (3 minutes) */
+    @Nonnull private Duration expires;
 
     /** Constructor. */
     public MessageReplaySecurityHandler() {
-        super();
         requiredRule = true;
-        expires = 180 * 1000;
+        expires = Duration.ofMinutes(3);
     }
 
     /**
@@ -94,23 +92,25 @@ public class MessageReplaySecurityHandler extends AbstractMessageHandler {
     }
 
     /**
-     * Gets the lifetime in milliseconds of replay entries.
+     * Gets the lifetime of replay entries.
      * 
-     * @return lifetime in milliseconds of entries
+     * @return lifetime of entries
      */
-    @Duration @NonNegative public long getExpires() {
+    @Nonnull public Duration getExpires() {
         return expires;
     }
 
     /**
-     * Sets the lifetime in seconds of replay entries.
+     * Sets the lifetime of replay entries.
      * 
-     * @param exp lifetime in seconds of entries
+     * @param exp lifetime of entries
      */
-    @Duration public void setExpires(@Duration @NonNegative final long exp) {
+    public void setExpires(@Nonnull final Duration exp) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(exp, "Lifetime cannot be null");
+        Constraint.isFalse(exp.isNegative(), "Lifetime cannot be negative");
         
-        expires = Constraint.isGreaterThanOrEqual(0, exp, "Expiration must be greater than or equal to 0");
+        expires = exp;
     }
 
     /** {@inheritDoc} */
@@ -118,7 +118,9 @@ public class MessageReplaySecurityHandler extends AbstractMessageHandler {
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
         
-        Constraint.isNotNull(getReplayCache(), "ReplayCache cannot be null");
+        if (getReplayCache() == null) {
+            throw new ComponentInitializationException("ReplayCache cannot be null");
+        }
     }
 
     /** {@inheritDoc} */
@@ -153,12 +155,11 @@ public class MessageReplaySecurityHandler extends AbstractMessageHandler {
         log.debug("{} Evaluating message replay for message ID '{}', issue instant '{}', entityID '{}'", 
                 getLogPrefix(), messageId, issueInstant, entityID);
         
-        if (!getReplayCache().check(getClass().getName(), messageId, issueInstant.toEpochMilli() + expires)) {
+        if (!getReplayCache().check(getClass().getName(), messageId, issueInstant.plus(expires))) {
             log.warn("{} Replay detected of message '{}' from issuer '{}'", getLogPrefix(), messageId, entityID);
             throw new MessageHandlerException("Rejecting replayed message ID '" + messageId + "' from issuer "
                     + entityID);
         }
-
     }
 
 }
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertions.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertions.java
index 5f9160a..345426c 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertions.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertions.java
@@ -17,14 +17,13 @@
 
 package org.opensaml.saml.common.profile.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
@@ -56,10 +55,10 @@ public class AddNotOnOrAfterConditionToAssertions extends AbstractConditionalPro
     @Nonnull private Function<ProfileRequestContext,SAMLObject> responseLookupStrategy;
     
     /** Strategy to obtain assertion lifetime policy. */
-    @Nullable private Function<ProfileRequestContext,Long> assertionLifetimeStrategy;
+    @Nullable private Function<ProfileRequestContext,Duration> assertionLifetimeStrategy;
     
     /** Default lifetime to use to establish timestamp. */
-    @Duration @NonNegative private long defaultAssertionLifetime;
+    @Nonnull private Duration defaultAssertionLifetime;
     
     /** Response to modify. */
     @Nullable private SAMLObject response;
@@ -68,7 +67,7 @@ public class AddNotOnOrAfterConditionToAssertions extends AbstractConditionalPro
     public AddNotOnOrAfterConditionToAssertions() {
         responseLookupStrategy = new MessageLookup<>(SAMLObject.class).compose(new OutboundMessageContextLookup());
         
-        defaultAssertionLifetime = 5 * 60 * 1000;
+        defaultAssertionLifetime = Duration.ofMinutes(5);
     }
 
     /**
@@ -87,7 +86,7 @@ public class AddNotOnOrAfterConditionToAssertions extends AbstractConditionalPro
      * 
      * @param strategy strategy function
      */
-    public void setAssertionLifetimeStrategy(@Nullable final Function<ProfileRequestContext,Long> strategy) {
+    public void setAssertionLifetimeStrategy(@Nullable final Function<ProfileRequestContext,Duration> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         assertionLifetimeStrategy = strategy;
@@ -98,11 +97,12 @@ public class AddNotOnOrAfterConditionToAssertions extends AbstractConditionalPro
      * 
      * @param lifetime  default lifetime in milliseconds
      */
-    @Duration public void setDefaultAssertionLifetime(@Duration @NonNegative final long lifetime) {
+    public void setDefaultAssertionLifetime(@Nonnull final Duration lifetime) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(lifetime, "Lifetime cannot be null");
+        Constraint.isFalse(lifetime.isNegative(), "Lifetime cannot be negative");
         
-        defaultAssertionLifetime = Constraint.isGreaterThanOrEqual(0, lifetime,
-                "Default assertion lifetime must be greater than or equal to 0");
+        defaultAssertionLifetime = lifetime;
     }
     
     /** {@inheritDoc} */
@@ -141,7 +141,7 @@ public class AddNotOnOrAfterConditionToAssertions extends AbstractConditionalPro
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        final Long lifetime = assertionLifetimeStrategy != null ?
+        final Duration lifetime = assertionLifetimeStrategy != null ?
                 assertionLifetimeStrategy.apply(profileRequestContext) : null;
         if (lifetime == null) {
             log.debug("{} No assertion lifetime supplied, using default", getLogPrefix());
@@ -151,8 +151,8 @@ public class AddNotOnOrAfterConditionToAssertions extends AbstractConditionalPro
             for (final org.opensaml.saml.saml1.core.Assertion assertion :
                     ((org.opensaml.saml.saml1.core.Response) response).getAssertions()) {
 
-                final Instant expiration = assertion.getIssueInstant().plusMillis(
-                        lifetime != null ? lifetime : defaultAssertionLifetime);
+                final Instant expiration =
+                        assertion.getIssueInstant().plus(lifetime != null ? lifetime : defaultAssertionLifetime);
                 log.debug("{} Added NotOnOrAfter condition, indicating an expiration of {}, to Assertion {}",
                         new Object[] {getLogPrefix(), expiration, assertion.getID()});
                 SAML1ActionSupport.addConditionsToAssertion(this, assertion).setNotOnOrAfter(expiration);
@@ -161,8 +161,8 @@ public class AddNotOnOrAfterConditionToAssertions extends AbstractConditionalPro
             for (final org.opensaml.saml.saml2.core.Assertion assertion :
                     ((org.opensaml.saml.saml2.core.Response) response).getAssertions()) {
 
-                final Instant expiration = assertion.getIssueInstant().plusMillis(
-                        lifetime != null ? lifetime : defaultAssertionLifetime);
+                final Instant expiration =
+                        assertion.getIssueInstant().plus(lifetime != null ? lifetime : defaultAssertionLifetime);
                 log.debug("{} Added NotOnOrAfter condition, indicating an expiration of {}, to Assertion {}",
                         new Object[] {getLogPrefix(), expiration, assertion.getID()});
                 SAML2ActionSupport.addConditionsToAssertion(this, assertion).setNotOnOrAfter(expiration);
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilFilter.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilFilter.java
index 5a71a85..d038616 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilFilter.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilFilter.java
@@ -17,14 +17,12 @@
 
 package org.opensaml.saml.metadata.resolver.filter.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.xml.DOMTypeSupport;
-
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.saml.metadata.resolver.filter.FilterException;
 import org.opensaml.saml.metadata.resolver.filter.MetadataFilter;
@@ -46,45 +44,39 @@ public class RequiredValidUntilFilter implements MetadataFilter {
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(RequiredValidUntilFilter.class);
 
-    /** The maximum interval, in milliseconds, between now and the <code>validUntil</code> date. */
-    @Duration private long maxValidityInterval;
+    /** The maximum interval between now and the <code>validUntil</code> date. Defaults to 14 days. */
+    @Nullable private Duration maxValidityInterval;
 
     /** Constructor. */
     public RequiredValidUntilFilter() {
-        this(0);
-    }
-
-    /**
-     * Constructor.
-     * 
-     * @param maxValidity maximum interval, in seconds, between now and the <code>validUntil</code> date
-     */
-    public RequiredValidUntilFilter(final long maxValidity) {
-        maxValidityInterval = maxValidity * 1000;
+        maxValidityInterval = Duration.ofDays(14);
     }
 
     /**
-     * Get the maximum interval, in milliseconds, between now and the <code>validUntil</code> date.
-     * A value of less than 1 indicates that there is no restriction.
+     * Get the maximum interval between now and the <code>validUntil</code> date.
+     * A value <=0 indicates that there is no restriction.
      * 
-     * @return maximum interval, in milliseconds, between now and the <code>validUntil</code> date
+     * @return maximum interval between now and the <code>validUntil</code> date
      */
-    @Duration public long getMaxValidityInterval() {
+    @Nullable public Duration getMaxValidityInterval() {
         return maxValidityInterval;
     }
     
     /**
-     * Set the maximum interval, in milliseconds, between now and the <code>validUntil</code> date.
-     * A value of less than 1 indicates that there is no restriction.
+     * Set the maximum interval between now and the <code>validUntil</code> date.
+     * A value <=0 indicates that there is no restriction.
      * 
-     * @param validity time in milliseconds between now and the <code>validUntil</code> date
+     * @param validity time between now and the <code>validUntil</code> date
      */
-    @Duration public void setMaxValidityInterval(@Duration final long validity) {
-        maxValidityInterval = validity;
+    public void setMaxValidityInterval(@Nullable final Duration validity) {
+        if (validity != null && !validity.isNegative() && !validity.isZero()) {
+            maxValidityInterval = validity;
+        } else {
+            maxValidityInterval = null;
+        }
     }
 
     /** {@inheritDoc} */
-    @Override
     @Nullable public XMLObject filter(@Nullable final XMLObject metadata) throws FilterException {
         if (metadata == null) {
             return null;
@@ -97,12 +89,11 @@ public class RequiredValidUntilFilter implements MetadataFilter {
         }
 
         final Instant now = Instant.now();
-        if (maxValidityInterval > 0 && validUntil.isAfter(now)) {
+        if (maxValidityInterval != null && validUntil.isAfter(now)) {
             final long validityInterval = validUntil.toEpochMilli() - now.toEpochMilli();
-            if (validityInterval > maxValidityInterval) {
+            if (Duration.ofMillis(validityInterval).compareTo(maxValidityInterval) > 0) {
                 throw new FilterException(String.format("Metadata's validity interval %s is larger than is allowed %s", 
-                        DOMTypeSupport.longToDuration(validityInterval),
-                        DOMTypeSupport.longToDuration(maxValidityInterval)));
+                        Duration.ofMillis(validityInterval), maxValidityInterval));
             }
         }
         
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractDynamicMetadataResolver.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractDynamicMetadataResolver.java
index 54788aa..f92fcd7 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractDynamicMetadataResolver.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractDynamicMetadataResolver.java
@@ -19,6 +19,7 @@ package org.opensaml.saml.metadata.resolver.impl;
 
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -69,7 +70,6 @@ import com.google.common.base.Predicates;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableSet;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
@@ -131,36 +131,36 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
     @Nullable private Gauge<PersistentCacheInitializationMetrics> gaugePersistentCacheInit;
     
     /** Timer used to schedule background metadata update tasks. */
-    private Timer taskTimer;
+    @Nullable private Timer taskTimer;
     
     /** Whether we created our own task timer during object construction. */
     private boolean createdOwnTaskTimer;
     
     /** Minimum cache duration. */
-    @Duration @Positive private Long minCacheDuration;
+    @Nonnull private Duration minCacheDuration;
     
     /** Maximum cache duration. */
-    @Duration @Positive private Long maxCacheDuration;
+    @Nonnull private Duration maxCacheDuration;
     
     /** Negative lookup cache duration. */
-    @Duration @Positive private Long negativeLookupCacheDuration;
+    @Nonnull private Duration negativeLookupCacheDuration;
     
     /** Factor used to compute when the next refresh interval will occur. Default value: 0.75 */
     @Positive private Float refreshDelayFactor;
     
     /** The maximum idle time in milliseconds for which the resolver will keep data for a given entityID, 
      * before it is removed. */
-    @Duration @Positive private Long maxIdleEntityData;
+    @Nonnull private Duration maxIdleEntityData;
     
     /** Flag indicating whether idle entity data should be removed. */
     private boolean removeIdleEntityData;
     
-    /** Impending expiration warning threshold for metadata refresh, in milliseconds. 
+    /** Impending expiration warning threshold for metadata refresh. 
      * Default value: 0ms (disabled). */
-    @Duration @Positive private Long expirationWarningThreshold;
+    @Nonnull private Duration expirationWarningThreshold;
     
-    /** The interval in milliseconds at which the cleanup task should run. */
-    @Duration @Positive private Long cleanupTaskInterval;
+    /** The interval at which the cleanup task should run. */
+    @Nonnull private Duration cleanupTaskInterval;
     
     /** The backing store cleanup sweeper background task. */
     private BackingStoreCleanupSweeper cleanupTask;
@@ -174,8 +174,8 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
     /** Flag indicating whether should initialize from the persistent cache in the background. */
     private boolean initializeFromPersistentCacheInBackground;
     
-    /** The delay in milliseconds after which to schedule the background initialization from the persistent cache. */
-    @Duration @Positive private Long backgroundInitializationFromCacheDelay;
+    /** The delay after which to schedule the background initialization from the persistent cache. */
+    @Nonnull private Duration backgroundInitializationFromCacheDelay;
     
     /** Predicate which determines whether a given entity should be loaded from the persistent cache
      * at resolver initialization time. */
@@ -207,25 +207,19 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
             taskTimer = backgroundTaskTimer;
         }
         
-        // Default to 0ms
-        expirationWarningThreshold = 0L;
+        expirationWarningThreshold = Duration.ZERO;
         
-        // Default to 10 minutes.
-        minCacheDuration = 10*60*1000L;
+        minCacheDuration = Duration.ofMinutes(10);
         
-        // Default to 8 hours.
-        maxCacheDuration = 8*60*60*1000L;
+        maxCacheDuration = Duration.ofHours(8);
         
         refreshDelayFactor = 0.75f;
         
-        // Default to 10 minutes.
-        negativeLookupCacheDuration = 10*60*1000L;
+        negativeLookupCacheDuration = Duration.ofMinutes(10);
         
-        // Default to 30 minutes.
-        cleanupTaskInterval = 30*60*1000L;
+        cleanupTaskInterval = Duration.ofMinutes(30);
         
-        // Default to 8 hours.
-        maxIdleEntityData = 8*60*60*1000L;
+        maxIdleEntityData = Duration.ofHours(8);
         
         // Default to removing idle metadata
         removeIdleEntityData = true;
@@ -233,8 +227,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
         // Default to initializing from the the persistent cache in the background
         initializeFromPersistentCacheInBackground = true;
         
-        // Default to 2 seconds.
-        backgroundInitializationFromCacheDelay = 2*1000L;
+        backgroundInitializationFromCacheDelay = Duration.ofSeconds(2);
     }
     
     /**
@@ -262,31 +255,36 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
     }
 
     /**
-     * Get the delay in milliseconds after which to schedule the background initialization from the persistent cache.
+     * Get the delay after which to schedule the background initialization from the persistent cache.
      * 
      * <p>Defaults to: 2 seconds.</p>
      * 
-     * @return the delay in milliseconds
+     * @return the delay
      * 
      * @since 3.3.0
      */
-    @Nonnull public Long getBackgroundInitializationFromCacheDelay() {
+    @Nonnull public Duration getBackgroundInitializationFromCacheDelay() {
         return backgroundInitializationFromCacheDelay;
     }
 
     /**
-     * Set the delay in milliseconds after which to schedule the background initialization from the persistent cache.
+     * Set the delay after which to schedule the background initialization from the persistent cache.
      * 
      * <p>Defaults to: 2 seconds.</p>
      * 
-     * @param delay the delay in milliseconds
+     * @param delay the delay
      * 
      * @since 3.3.0
      */
-    public void setBackgroundInitializationFromCacheDelay(@Nonnull final Long delay) {
+    public void setBackgroundInitializationFromCacheDelay(@Nonnull final Duration delay) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        Constraint.isNotNull(delay, "Delay cannot be null");
+        Constraint.isFalse(delay.isNegative(), "Delay cannot be negative");
+        
         backgroundInitializationFromCacheDelay = delay;
+        
     }
 
     /**
@@ -365,9 +363,9 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      *  
      *  <p>Defaults to: 10 minutes.</p>
      *  
-     * @return the minimum cache duration, in milliseconds
+     * @return the minimum cache duration
      */
-    @Nonnull public Long getMinCacheDuration() {
+    @Nonnull public Duration getMinCacheDuration() {
         return minCacheDuration;
     }
 
@@ -376,12 +374,16 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      *  
      *  <p>Defaults to: 10 minutes.</p>
      *  
-     * @param duration the minimum cache duration, in milliseconds
+     * @param duration the minimum cache duration
      */
-    public void setMinCacheDuration(@Nonnull final Long duration) {
+    public void setMinCacheDuration(@Nonnull final Duration duration) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-        minCacheDuration = Constraint.isNotNull(duration, "Minimum cache duration may not be null");
+
+        Constraint.isNotNull(duration, "Duration cannot be null");
+        Constraint.isFalse(duration.isNegative(), "Duration cannot be negative");
+        
+        minCacheDuration = duration;
     }
 
     /**
@@ -389,9 +391,9 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      *  
      *  <p>Defaults to: 8 hours.</p>
      *  
-     * @return the maximum cache duration, in milliseconds
+     * @return the maximum cache duration
      */
-    @Nonnull public Long getMaxCacheDuration() {
+    @Nonnull public Duration getMaxCacheDuration() {
         return maxCacheDuration;
     }
 
@@ -400,12 +402,16 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      *  
      *  <p>Defaults to: 8 hours.</p>
      *  
-     * @param duration the maximum cache duration, in milliseconds
+     * @param duration the maximum cache duration
      */
-    public void setMaxCacheDuration(@Nonnull final Long duration) {
+    public void setMaxCacheDuration(@Nonnull final Duration duration) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-        maxCacheDuration = Constraint.isNotNull(duration, "Maximum cache duration may not be null");
+
+        Constraint.isNotNull(duration, "Duration cannot be null");
+        Constraint.isFalse(duration.isNegative(), "Duration cannot be negative");
+        
+        maxCacheDuration = duration;
     }
     
     /**
@@ -413,9 +419,9 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      *  
      *  <p>Defaults to: 10 minutes.</p>
      *  
-     * @return the negative lookup cache duration, in milliseconds
+     * @return the negative lookup cache duration
      */
-    @Nonnull public Long getNegativeLookupCacheDuration() {
+    @Nonnull public Duration getNegativeLookupCacheDuration() {
         return negativeLookupCacheDuration;
     }
 
@@ -424,12 +430,16 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      *  
      *  <p>Defaults to: 10 minutes.</p>
      *  
-     * @param duration the negative lookup cache duration, in milliseconds
+     * @param duration the negative lookup cache duration
      */
-    public void setNegativeLookupCacheDuration(@Nonnull final Long duration) {
+    public void setNegativeLookupCacheDuration(@Nonnull final Duration duration) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-        negativeLookupCacheDuration = Constraint.isNotNull(duration, "Negative lookup cache duration may not be null");
+
+        Constraint.isNotNull(duration, "Duration cannot be null");
+        Constraint.isFalse(duration.isNegative(), "Duration cannot be negative");
+        
+        negativeLookupCacheDuration = duration;
     }
     
     /**
@@ -439,7 +449,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      * 
      * @return delay factor used to compute the next refresh time
      */
-    public Float getRefreshDelayFactor() {
+    @Nonnull public Float getRefreshDelayFactor() {
         return refreshDelayFactor;
     }
 
@@ -450,7 +460,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      * 
      * @param factor delay factor used to compute the next refresh time
      */
-    public void setRefreshDelayFactor(final Float factor) {
+    public void setRefreshDelayFactor(@Nonnull final Float factor) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
@@ -482,29 +492,33 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
     }
 
     /**
-     * Get the maximum idle time in milliseconds for which the resolver will keep data for a given entityID, 
+     * Get the maximum idle time for which the resolver will keep data for a given entityID, 
      * before it is removed.
      * 
      * <p>Defaults to: 8 hours.</p>
      * 
-     * @return return the maximum idle time in milliseconds
+     * @return return the maximum idle time
      */
-    @Nonnull public Long getMaxIdleEntityData() {
+    @Nonnull public Duration getMaxIdleEntityData() {
         return maxIdleEntityData;
     }
 
     /**
-     * Set the maximum idle time in milliseconds for which the resolver will keep data for a given entityID, 
+     * Set the maximum idle time for which the resolver will keep data for a given entityID, 
      * before it is removed.
      * 
      * <p>Defaults to: 8 hours.</p>
      * 
-     * @param max the maximum entity data idle time, in milliseconds
+     * @param max the maximum entity data idle time
      */
-    public void setMaxIdleEntityData(@Nonnull final Long max) {
+    public void setMaxIdleEntityData(@Nonnull final Duration max) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-        maxIdleEntityData = Constraint.isNotNull(max, "Max idle entity data may not be null");
+
+        Constraint.isNotNull(max, "Max idle time cannot be null");
+        Constraint.isFalse(max.isNegative(), "Max idle time cannot be negative");
+
+        maxIdleEntityData = max;
     }
     
     /**
@@ -512,7 +526,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      * 
      * @return threshold for logging a warning if live metadata will soon expire
      */
-    @Duration @Nonnull public Long getExpirationWarningThreshold() {
+    @Nonnull public Duration getExpirationWarningThreshold() {
         return expirationWarningThreshold;
     }
 
@@ -521,41 +535,45 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
      * 
      * @param threshold the threshold for logging a warning if live metadata will soon expire
      */
-    @Duration public void setExpirationWarningThreshold(@Nullable @Duration @Positive final Long threshold) {
+    public void setExpirationWarningThreshold(@Nullable final Duration threshold) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
         
         if (threshold == null) {
-            expirationWarningThreshold = 0L;
+            expirationWarningThreshold = Duration.ZERO;
         }
-        if (threshold < 0) {
+        if (threshold.isNegative()) {
             throw new IllegalArgumentException("Expiration warning threshold must be greater than or equal to 0");
         }
         expirationWarningThreshold = threshold;
     }
 
     /**
-     * Get the interval in milliseconds at which the cleanup task should run.
+     * Get the interval at which the cleanup task should run.
      * 
      * <p>Defaults to: 30 minutes.</p>
      * 
-     * @return return the interval, in milliseconds
+     * @return return the interval
      */
-    @Nonnull public Long getCleanupTaskInterval() {
+    @Nonnull public Duration getCleanupTaskInterval() {
         return cleanupTaskInterval;
     }
 
     /**
-     * Set the interval in milliseconds at which the cleanup task should run.
+     * Set the interval at which the cleanup task should run.
      * 
      * <p>Defaults to: 30 minutes.</p>
      * 
-     * @param interval the interval to set, in milliseconds
+     * @param interval the interval to set
      */
-    public void setCleanupTaskInterval(@Nonnull final Long interval) {
+    public void setCleanupTaskInterval(@Nonnull final Duration interval) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
-        cleanupTaskInterval = Constraint.isNotNull(interval, "Cleanup task interval may not be null");
+        
+        Constraint.isNotNull(interval, "Cleanup task interval may not be null");
+        Constraint.isFalse(interval.isNegative() || interval.isZero(), "Cleanup task interval must be positive");
+        
+        cleanupTaskInterval = interval;
     }
 
     /**
@@ -1195,8 +1213,8 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
                     getLogPrefix(), descriptor.getEntityID());
         } else {
             if (isRequireValidMetadata() && descriptor.getValidUntil() != null) {
-                if (getExpirationWarningThreshold() > 0 
-                        && descriptor.getValidUntil().isBefore(now.plusMillis(getExpirationWarningThreshold()))) {
+                if (!getExpirationWarningThreshold().isZero() 
+                        && descriptor.getValidUntil().isBefore(now.plus(getExpirationWarningThreshold()))) {
                     log.warn("{} Metadata with ID '{}' currently live will expire "
                             + "within the configured threshhold at '{}'",
                             getLogPrefix(), descriptor.getEntityID(), descriptor.getValidUntil());
@@ -1219,10 +1237,9 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
     @Nonnull protected Instant computeExpirationTime(@Nonnull final EntityDescriptor entityDescriptor,
             @Nonnull final Instant now) {
         
-        final Instant lowerBound = now.plusMillis(getMinCacheDuration());
+        final Instant lowerBound = now.plus(getMinCacheDuration());
         
-        Instant expiration = SAML2Support.getEarliestExpiration(entityDescriptor, 
-                now.plusMillis(getMaxCacheDuration()), now);
+        Instant expiration = SAML2Support.getEarliestExpiration(entityDescriptor, now.plus(getMaxCacheDuration()), now);
         if (expiration.isBefore(lowerBound)) {
             expiration = lowerBound;
         }
@@ -1251,8 +1268,8 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
 
         // if the expiration time was null or the calculated refresh delay was less than the floor
         // use the floor
-        if (refreshDelay < getMinCacheDuration()) {
-            refreshDelay = getMinCacheDuration();
+        if (refreshDelay < getMinCacheDuration().toMillis()) {
+            refreshDelay = getMinCacheDuration().toMillis();
         }
 
         return nowDateTime.plusMillis(refreshDelay);
@@ -1312,7 +1329,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
                             initializeFromPersistentCache();
                         }
                     };
-                    taskTimer.schedule(initTask, getBackgroundInitializationFromCacheDelay());
+                    taskTimer.schedule(initTask, getBackgroundInitializationFromCacheDelay().toMillis());
                 } else {
                     log.debug("{} Initializing from the persistent cache in the foreground", getLogPrefix());
                     initializeFromPersistentCache();
@@ -1321,7 +1338,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
             
             cleanupTask = new BackingStoreCleanupSweeper();
             // Start with a delay of 1 minute, run at the user-specified interval
-            taskTimer.schedule(cleanupTask, 1*60*1000, getCleanupTaskInterval());
+            taskTimer.schedule(cleanupTask, 1*60*1000, getCleanupTaskInterval().toMillis());
 
         } finally {
             initializing = false;
@@ -1673,8 +1690,8 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
         protected EntityManagementData(@Nonnull final String id) {
             entityID = Constraint.isNotNull(id, "Entity ID was null");
             final Instant now = Instant.now();
-            expirationTime = now.plusMillis(getMaxCacheDuration());
-            refreshTriggerTime = now.plusMillis(getMaxCacheDuration());
+            expirationTime = now.plus(getMaxCacheDuration());
+            refreshTriggerTime = now.plus(getMaxCacheDuration());
             lastAccessedTime = now;
             readWriteLock = new ReentrantReadWriteLock(true);
         }
@@ -1773,7 +1790,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
          * @return the time before which no further lookups for the entity will be performed
          */
         public Instant initNegativeLookupCache() {
-            negativeLookupCacheExpiration = Instant.now().plusMillis(getNegativeLookupCacheDuration());
+            negativeLookupCacheExpiration = Instant.now().plus(getNegativeLookupCacheDuration());
             return negativeLookupCacheExpiration;
         }
         
@@ -1824,7 +1841,7 @@ public abstract class AbstractDynamicMetadataResolver extends AbstractMetadataRe
          */
         private void removeExpiredAndIdleMetadata() {
             final Instant now = Instant.now();
-            final Instant earliestValidLastAccessed = now.minusMillis(getMaxIdleEntityData());
+            final Instant earliestValidLastAccessed = now.minus(getMaxIdleEntityData());
             
             final DynamicEntityBackingStore backingStore = getBackingStore();
             final Map<String, List<EntityDescriptor>> indexedDescriptors = backingStore.getIndexedDescriptors();
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractReloadingMetadataResolver.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractReloadingMetadataResolver.java
index 370f172..5db4c5d 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractReloadingMetadataResolver.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/AbstractReloadingMetadataResolver.java
@@ -21,6 +21,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.util.Timer;
@@ -39,10 +40,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.TimerSupport;
 import net.shibboleth.utilities.java.support.resolver.ResolverException;
 
@@ -64,7 +64,7 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
         implements ExtendedRefreshableMetadataResolver {
 
     /** Class logger. */
-    private final Logger log = LoggerFactory.getLogger(AbstractReloadingMetadataResolver.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractReloadingMetadataResolver.class);
 
     /** Timer used to schedule background metadata update tasks. */
     private Timer taskTimer;
@@ -79,35 +79,34 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
     private float refreshDelayFactor = 0.75f;
 
     /**
-     * Refresh interval used when metadata does not contain any validUntil or cacheDuration information. Default value:
-     * 14400000ms (4 hours).
+     * Refresh interval used when metadata does not contain any validUntil or cacheDuration information.
+     * Default value: 4 hours.
      */
-    @Duration @Positive private long maxRefreshDelay = 14400000;
+    @Nonnull private Duration maxRefreshDelay;
 
-    /** Floor, in milliseconds, for the refresh interval. Default value: 300000ms (5 minutes). */
-    @Duration @Positive private long minRefreshDelay = 300000;
+    /** Floor for the refresh interval. Default value: 5 minutes. */
+    @Nonnull private Duration minRefreshDelay;
 
     /** Time when the currently cached metadata file expires. */
-    private Instant expirationTime;
+    @Nullable private Instant expirationTime;
     
-    /** Impending expiration warning threshold for metadata refresh, in milliseconds. 
-     * Default value: 0ms (disabled). */
-    @Duration @Positive private long expirationWarningThreshold;
+    /** Impending expiration warning threshold for metadata refresh. Default value: 0 (disabled). */
+    @Nonnull private Duration expirationWarningThreshold;
 
     /** Last time the metadata was updated. */
-    private Instant lastUpdate;
+    @Nullable private Instant lastUpdate;
 
     /** Last time a refresh cycle occurred. */
-    private Instant lastRefresh;
+    @Nullable private Instant lastRefresh;
 
     /** Next time a refresh cycle will occur. */
-    private Instant nextRefresh;
+    @Nullable private Instant nextRefresh;
     
     /** Last time a successful refresh cycle occurred. */
-    private Instant lastSuccessfulRefresh;
+    @Nullable private Instant lastSuccessfulRefresh;
 
     /** Flag indicating whether last refresh cycle was successful. */
-    private Boolean wasLastRefreshSuccess;
+    @Nullable private Boolean wasLastRefreshSuccess;
     
     /** Internal flag for tracking success during the refresh operation. */
     private boolean trackRefreshSuccess;
@@ -127,6 +126,11 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
     protected AbstractReloadingMetadataResolver(@Nullable final Timer backgroundTaskTimer) {
         setCacheSourceMetadata(true);
         
+        minRefreshDelay = Duration.ofMinutes(5);
+        maxRefreshDelay = Duration.ofHours(4);
+        
+        expirationWarningThreshold = Duration.ZERO;
+        
         if (backgroundTaskTimer == null) {
             taskTimer = new Timer(TimerSupport.getTimerName(this), true);
             createdOwnTaskTimer = true;
@@ -193,7 +197,7 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
      * 
      * @return threshold for logging a warning if live metadata will soon expire
      */
-    @Duration public long getExpirationWarningThreshold() {
+    @Nonnull public Duration getExpirationWarningThreshold() {
         return expirationWarningThreshold;
     }
 
@@ -202,36 +206,37 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
      * 
      * @param threshold the threshold for logging a warning if live metadata will soon expire
      */
-    @Duration public void setExpirationWarningThreshold(@Duration @Positive final long threshold) {
+    public void setExpirationWarningThreshold(@Nonnull final Duration threshold) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
         
-        if (threshold < 0) {
-            throw new IllegalArgumentException("Expiration warning threshold must be greater than or equal to 0");
-        }
+        Constraint.isNotNull(threshold, "Expiration warning threshold cannot be null");
+        Constraint.isFalse(threshold.isNegative(), "Expiration warning threshold cannot be negative");
+        
         expirationWarningThreshold = threshold;
     }
+    
     /**
-     * Gets the maximum amount of time, in milliseconds, between refresh intervals.
+     * Gets the maximum amount of time between refresh intervals.
      * 
      * @return maximum amount of time between refresh intervals
      */
-    @Duration public long getMaxRefreshDelay() {
+    @Nonnull public Duration getMaxRefreshDelay() {
         return maxRefreshDelay;
     }
 
     /**
-     * Sets the maximum amount of time, in milliseconds, between refresh intervals.
+     * Sets the maximum amount of time between refresh intervals.
      * 
-     * @param delay maximum amount of time, in milliseconds, between refresh intervals
+     * @param delay maximum amount of time between refresh intervals
      */
-    @Duration public void setMaxRefreshDelay(@Duration @Positive final long delay) {
+    public void setMaxRefreshDelay(@Nonnull final Duration delay) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
         
-        if (delay < 0) {
-            throw new IllegalArgumentException("Maximum refresh delay must be greater than 0");
-        }
+        Constraint.isNotNull(delay, "Maximum refresh delay cannot be null");
+        Constraint.isFalse(delay.isNegative() || delay.isZero(), "Maximum refresh delay must be greater than 0");
+
         maxRefreshDelay = delay;
     }
 
@@ -261,26 +266,26 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
     }
 
     /**
-     * Gets the minimum amount of time, in milliseconds, between refreshes.
+     * Gets the minimum amount of time between refreshes.
      * 
-     * @return minimum amount of time, in milliseconds, between refreshes
+     * @return minimum amount of time between refreshes
      */
-    @Duration public long getMinRefreshDelay() {
+    @Nonnull public Duration getMinRefreshDelay() {
         return minRefreshDelay;
     }
 
     /**
-     * Sets the minimum amount of time, in milliseconds, between refreshes.
+     * Sets the minimum amount of time between refreshes.
      * 
-     * @param delay minimum amount of time, in milliseconds, between refreshes
+     * @param delay minimum amount of time between refreshes
      */
-    @Duration public void setMinRefreshDelay(@Duration @Positive final long delay) {
+    public void setMinRefreshDelay(@Nonnull final Duration delay) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
-        if (delay < 0) {
-            throw new IllegalArgumentException("Minimum refresh delay must be greater than 0");
-        }
+        Constraint.isNotNull(delay, "Minimum refresh delay cannot be null");
+        Constraint.isFalse(delay.isNegative() || delay.isZero(), "Minimum refresh delay must be greater than 0");
+
         minRefreshDelay = delay;
     }
 
@@ -312,7 +317,7 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
             throw new ComponentInitializationException("Error refreshing metadata during init", e);
         }
         
-        if (minRefreshDelay > maxRefreshDelay) {
+        if (minRefreshDelay.compareTo(maxRefreshDelay) > 0) {
             throw new ComponentInitializationException("Minimum refresh delay " + minRefreshDelay
                     + " is greater than maximum refresh delay " + maxRefreshDelay);
         }
@@ -356,7 +361,7 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
             }
         } catch (final Throwable t) {
             trackRefreshSuccess = false;
-            nextRefresh = Instant.now().plusMillis(computeNextRefreshDelay(null));
+            nextRefresh = Instant.now().plus(computeNextRefreshDelay(null));
             if (t instanceof Exception) {
                 log.error("{} Error occurred while attempting to refresh metadata from '{}'", getLogPrefix(), mdId);
                 throw new ResolverException((Exception) t);
@@ -399,8 +404,8 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
         } else if (cached instanceof TimeBoundSAMLObject) {
             final TimeBoundSAMLObject timebound = (TimeBoundSAMLObject) cached;
             if (isRequireValidMetadata() && timebound.getValidUntil() != null) {
-                if (getExpirationWarningThreshold() > 0 
-                        && timebound.getValidUntil().isBefore(now.plusMillis(getExpirationWarningThreshold()))) {
+                if (!getExpirationWarningThreshold().isZero() 
+                        && timebound.getValidUntil().isBefore(now.plus(getExpirationWarningThreshold()))) {
                     log.warn("{} Metadata root from '{}' currently live (post-refresh) will expire "
                             + "within the configured threshhold at '{}'",
                             getLogPrefix(), mdId, timebound.getValidUntil());
@@ -461,12 +466,11 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
         log.debug("{} Computing new expiration time for cached metadata from '{}'", getLogPrefix(), metadataIdentifier);
         final Instant metadataExpirationTime = 
                 SAML2Support.getEarliestExpiration(getBackingStore().getCachedOriginalMetadata(),
-                refreshStart.plusMillis(getMaxRefreshDelay()), refreshStart);
+                refreshStart.plus(getMaxRefreshDelay()), refreshStart);
 
         trackRefreshSuccess = true;
         expirationTime = metadataExpirationTime;
-        final long nextRefreshDelay = computeNextRefreshDelay(expirationTime);
-        nextRefresh = Instant.now().plusMillis(nextRefreshDelay);
+        nextRefresh = Instant.now().plus(computeNextRefreshDelay(expirationTime));
     }
 
     /**
@@ -506,7 +510,7 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
         log.warn("{} Entire metadata document from '{}' was expired at time of loading, " 
                 + "previous metadata retained, if any", getLogPrefix(), metadataIdentifier);
 
-        nextRefresh = Instant.now().plusMillis(computeNextRefreshDelay(null));
+        nextRefresh = Instant.now().plus(computeNextRefreshDelay(null));
         trackRefreshSuccess = false;
     }
 
@@ -547,7 +551,7 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
         // Note: As noted in its Javadocs, technically this method can sometimes return null, but won't in this case
         // since the candidate time (2nd arg) is not null.
         final Instant metadataExpirationTime = SAML2Support.getEarliestExpiration(
-                newBackingStore.getCachedOriginalMetadata(), refreshStart.plusMillis(getMaxRefreshDelay()),
+                newBackingStore.getCachedOriginalMetadata(), refreshStart.plus(getMaxRefreshDelay()),
                 refreshStart);
         log.debug("{} Expiration of metadata from '{}' will occur at {}", getLogPrefix(), metadataIdentifier, 
                 metadataExpirationTime.toString());
@@ -561,15 +565,15 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
         
         final Instant now = Instant.now();
         
-        final long nextRefreshDelay;
+        final Duration nextRefreshDelay;
         if (metadataExpirationTime.isBefore(now)) {
-            expirationTime = now.plusMillis(getMinRefreshDelay());
+            expirationTime = now.plus(getMinRefreshDelay());
             nextRefreshDelay = getMaxRefreshDelay();
         } else {
             expirationTime = metadataExpirationTime;
             nextRefreshDelay = computeNextRefreshDelay(expirationTime);
         }
-        nextRefresh = now.plusMillis(nextRefreshDelay);
+        nextRefresh = now.plus(nextRefreshDelay);
 
         log.info("{} New metadata successfully loaded for '{}'", getLogPrefix(), getMetadataIdentifier());
     }
@@ -599,9 +603,9 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
      * 
      * @param expectedExpiration the time when the metadata is expected to expire and need refreshing
      * 
-     * @return delay, in milliseconds, until the next refresh time
+     * @return delay until the next refresh time
      */
-    protected long computeNextRefreshDelay(final Instant expectedExpiration) {
+    @Nonnull protected Duration computeNextRefreshDelay(final Instant expectedExpiration) {
         final long now = System.currentTimeMillis();
 
         long expireInstant = 0;
@@ -612,11 +616,11 @@ public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMet
 
         // if the expiration time was null or the calculated refresh delay was less than the floor
         // use the floor
-        if (refreshDelay < getMinRefreshDelay()) {
-            refreshDelay = getMinRefreshDelay();
+        if (refreshDelay < getMinRefreshDelay().toMillis()) {
+            refreshDelay = getMinRefreshDelay().toMillis();
         }
 
-        return refreshDelay;
+        return Duration.ofMillis(refreshDelay);
     }
 
     /**
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolver.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolver.java
index 01877c1..b19e4b2 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolver.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolver.java
@@ -20,16 +20,16 @@ package org.opensaml.saml.metadata.resolver.impl;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Timer;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.resolver.ResolverException;
 
 import org.apache.http.client.HttpClient;
@@ -75,8 +75,8 @@ public class FileBackedHTTPMetadataResolver extends HTTPMetadataResolver {
     /** Flag indicating whether metadata load during init was from backup file. */
     private boolean initializedFromBackupFile;
     
-    /** Duration in milliseconds after which to schedule next refresh, when initialized from backup file. */
-    @Duration @Positive private long backupFileInitNextRefreshDelay = 5000;
+    /** Duration after which to schedule next refresh, when initialized from backup file. */
+    @Nonnull private Duration backupFileInitNextRefreshDelay;
     
     /**
      * Constructor.
@@ -109,6 +109,9 @@ public class FileBackedHTTPMetadataResolver extends HTTPMetadataResolver {
                                           final HttpClient client, final String metadataURL,
             final String backupFilePath) throws ResolverException {
         super(backgroundTaskTimer, client, metadataURL);
+        
+        backupFileInitNextRefreshDelay = Duration.ofSeconds(5);
+        
         setBackupFile(backupFilePath);
     }
     
@@ -149,30 +152,31 @@ public class FileBackedHTTPMetadataResolver extends HTTPMetadataResolver {
     }
 
     /**
-     * Get the duration in milliseconds after which to schedule next refresh, when initialized from backup file.
+     * Get the duration after which to schedule next refresh, when initialized from backup file.
      * 
-     * <p>Defaults to 5000ms.</p>
+     * <p>Defaults to 5s.</p>
      * 
-     * @return the duration in milliseconds
+     * @return the duration
      */
-    public long getBackupFileInitNextRefreshDelay() {
+    @Nonnull public Duration getBackupFileInitNextRefreshDelay() {
         return backupFileInitNextRefreshDelay;
     }
 
     /**
-     * Set the duration in milliseconds after which to schedule next refresh, when initialized from backup file.
+     * Set the duration after which to schedule next refresh, when initialized from backup file.
      * 
-     * <p>Defaults to 5000ms.</p>
+     * <p>Defaults to 5s.</p>
      * 
-     * @param delay the next refresh delay, in milliseconds
+     * @param delay the next refresh delay
      */
-    public void setBackupFileInitNextRefreshDelay(final long delay) {
+    public void setBackupFileInitNextRefreshDelay(@Nonnull final Duration delay) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        Constraint.isNotNull(delay, "Backup file init next refresh delay cannot be null");
+        Constraint.isFalse(delay.isNegative() || delay.isZero(),
+                "Backup file init next refresh delay must be greater than 0");
         
-        if (delay < 0) {
-            throw new IllegalArgumentException("Backup file init next refresh delay must be greater than 0");
-        }
         backupFileInitNextRefreshDelay = delay;
     }
 
@@ -317,7 +321,7 @@ public class FileBackedHTTPMetadataResolver extends HTTPMetadataResolver {
 
     /** {@inheritDoc} */
     @Override
-    protected long computeNextRefreshDelay(final Instant expectedExpiration) {
+    @Nonnull protected Duration computeNextRefreshDelay(@Nullable final Instant expectedExpiration) {
         if (initializing && initializedFromBackupFile) {
             log.debug("{} Detected initialization from backup file, scheduling next refresh from HTTP in {}ms", 
                     getLogPrefix(), getBackupFileInitNextRefreshDelay());
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java
index 6bd362e..e1697fb 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java
@@ -17,6 +17,8 @@
 
 package org.opensaml.saml.saml2.assertion.impl;
 
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Objects;
 
 import javax.annotation.Nonnull;
@@ -24,6 +26,7 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.xml.namespace.QName;
 
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
@@ -64,19 +67,16 @@ import org.slf4j.LoggerFactory;
 public class OneTimeUseConditionValidator implements ConditionValidator {
     
     /** Cache context name. */
-    public static final String CACHE_CONTEXT = OneTimeUseConditionValidator.class.getName();
-    
-    /** Default cache expiration time: 8 hours. */
-    public static final Long DEFAULT_CACHE_EXPIRES = 1000*60*60*8L;
+    @Nonnull @NotEmpty public static final String CACHE_CONTEXT = OneTimeUseConditionValidator.class.getName();
     
     /** Logger. */
-    private Logger log = LoggerFactory.getLogger(OneTimeUseConditionValidator.class);
+    @Nonnull private Logger log = LoggerFactory.getLogger(OneTimeUseConditionValidator.class);
     
     /** Replay cache used to track which assertions have been used. */
-    private ReplayCache replayCache;
+    @Nonnull private final ReplayCache replayCache;
     
-    /** Time (in milliseconds since beginning of epoch) for disposal of value from cache. */
-    private Long replayCacheExpires;
+    /** Time for disposal of value from cache. */
+    @Nonnull private Duration replayCacheExpires;
 
     /**
      * Constructor.
@@ -86,15 +86,16 @@ public class OneTimeUseConditionValidator implements ConditionValidator {
      *         assertion from the replay cache.  May be null, then defaults to 
      *         {@link #DEFAULT_CACHE_EXPIRES}.
      */
-    public OneTimeUseConditionValidator(@Nonnull final ReplayCache replay, @Nullable final Long expires) {
+    public OneTimeUseConditionValidator(@Nonnull final ReplayCache replay, @Nullable final Duration expires) {
         replayCache = Constraint.isNotNull(replay, "Replay cache was null");
         replayCacheExpires = expires;
+        
         if (replayCacheExpires == null) {
-            replayCacheExpires = DEFAULT_CACHE_EXPIRES;
-        } else if (replayCacheExpires < 0) {
+            replayCacheExpires = Duration.ofHours(8);
+        } else if (replayCacheExpires.isNegative()) {
             log.warn("Supplied value for replay cache expires '{}' was negative, using default expiration", 
                     replayCacheExpires);
-            replayCacheExpires = DEFAULT_CACHE_EXPIRES;
+            replayCacheExpires = Duration.ofHours(8);
         }
     }
 
@@ -125,11 +126,11 @@ public class OneTimeUseConditionValidator implements ConditionValidator {
     }
     
     /**
-     * Get the configured validator cache expiration interval, in milliseconds.
+     * Get the configured validator cache expiration interval.
      * 
-     * @return the configured cache expiration interval in milliseconds
+     * @return the configured cache expiration interval
      */
-    @Nonnull protected Long getReplayCacheExpires() {
+    @Nonnull protected Duration getReplayCacheExpires() {
         return replayCacheExpires;
     }
     
@@ -149,7 +150,7 @@ public class OneTimeUseConditionValidator implements ConditionValidator {
      * 
      * @return the effective one-time use expiration for the assertion being evaluated
      */
-    protected long getExpires(final Assertion assertion, final ValidationContext context) {
+    @Nonnull protected Instant getExpires(final Assertion assertion, final ValidationContext context) {
         Long expires = null;
         try {
             expires = (Long) context.getStaticParameters().get(
@@ -159,17 +160,21 @@ public class OneTimeUseConditionValidator implements ConditionValidator {
         }
         log.debug("Saw one-time use cache expires context param: {}", expires);
         
+        Duration suppliedExpiration = null;
+        
         if (expires == null) {
-            expires = getReplayCacheExpires();
+            suppliedExpiration = getReplayCacheExpires();
         } else if (expires < 0) {
             log.warn("Supplied context param for replay cache expires '{}' was negative, using configured expiration", 
                     expires);
-            expires = getReplayCacheExpires();
+            suppliedExpiration = getReplayCacheExpires();
+        } else {
+            suppliedExpiration = Duration.ofMillis(expires);
         }
              
-        log.debug("Effective one-time use cache expires of: {}", expires);
+        log.debug("Effective one-time use cache expires of: {}", suppliedExpiration);
         
-        final long computedExpiration = System.currentTimeMillis() + expires;
+        final Instant computedExpiration = Instant.now().plus(suppliedExpiration);
         log.debug("Computed one-time use cache effective expiration time of: {}", computedExpiration);
         return computedExpiration;
     }
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMapTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMapTest.java
index 0a077b6..178d653 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMapTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/BasicSAMLArtifactMapTest.java
@@ -18,6 +18,7 @@
 package org.opensaml.saml.common.binding.artifact.impl;
 
 import java.io.IOException;
+import java.time.Duration;
 
 import net.shibboleth.utilities.java.support.xml.XMLAssertTestNG;
 
@@ -42,7 +43,6 @@ public class BasicSAMLArtifactMapTest extends XMLObjectBaseTestCase {
     private String artifact = "the-artifact";
     private String issuerId = "urn:test:issuer";
     private String rpId = "urn:test:rp";
-    private long lifetime = 60 * 5 * 1000L;
 
     private SAMLObject samlObject;
     private Document origDocument;
@@ -56,7 +56,7 @@ public class BasicSAMLArtifactMapTest extends XMLObjectBaseTestCase {
         samlObject.releaseDOM();
 
         artifactMap = new BasicSAMLArtifactMap();
-        artifactMap.setArtifactLifetime(lifetime);
+        artifactMap.setArtifactLifetime(Duration.ofMinutes(5));
         artifactMap.initialize();
     }
 
@@ -102,7 +102,7 @@ public class BasicSAMLArtifactMapTest extends XMLObjectBaseTestCase {
     public void testEntryExpiration() throws Exception {
         // lifetime of 1 second should do it
         artifactMap = new BasicSAMLArtifactMap();
-        artifactMap.setArtifactLifetime(1000);
+        artifactMap.setArtifactLifetime(Duration.ofSeconds(1));
         artifactMap.initialize();
 
         Assert.assertFalse(artifactMap.contains(artifact));
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMapTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMapTest.java
index e4c8aa3..38147f2 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMapTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/artifact/impl/StorageServiceSAMLArtifactMapTest.java
@@ -18,6 +18,7 @@
 package org.opensaml.saml.common.binding.artifact.impl;
 
 import java.io.IOException;
+import java.time.Duration;
 
 import net.shibboleth.utilities.java.support.xml.XMLAssertTestNG;
 
@@ -44,7 +45,6 @@ public class StorageServiceSAMLArtifactMapTest extends XMLObjectBaseTestCase {
     private String artifact = "the-artifact";
     private String issuerId = "urn:test:issuer";
     private String rpId = "urn:test:rp";
-    private long lifetime = 60 * 5 * 1000L;
 
     private SAMLObject samlObject;
     private Document origDocument;
@@ -63,7 +63,7 @@ public class StorageServiceSAMLArtifactMapTest extends XMLObjectBaseTestCase {
 
         artifactMap = new StorageServiceSAMLArtifactMap();
         artifactMap.setStorageService(storageService);
-        artifactMap.setArtifactLifetime(lifetime);
+        artifactMap.setArtifactLifetime(Duration.ofMinutes(5));
         artifactMap.initialize();
     }
 
@@ -110,7 +110,7 @@ public class StorageServiceSAMLArtifactMapTest extends XMLObjectBaseTestCase {
         // lifetime of 1 second should do it
         artifactMap = new StorageServiceSAMLArtifactMap();
         artifactMap.setStorageService(storageService);
-        artifactMap.setArtifactLifetime(1000);
+        artifactMap.setArtifactLifetime(Duration.ofSeconds(1));
         artifactMap.initialize();
 
         Assert.assertFalse(artifactMap.contains(artifact));
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandlerTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandlerTest.java
index 96cb395..5598a4d 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandlerTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageLifetimeSecurityHandlerTest.java
@@ -17,6 +17,7 @@
 
 package org.opensaml.saml.common.binding.security.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 
 import org.opensaml.core.xml.XMLObjectBaseTestCase;
@@ -37,16 +38,16 @@ public class MessageLifetimeSecurityHandlerTest extends XMLObjectBaseTestCase {
     
     private MessageLifetimeSecurityHandler handler;
     
-    private long clockSkew;
-    private long messageLifetime;
+    private Duration clockSkew;
+    private Duration messageLifetime;
     
     private Instant now;
 
     @BeforeMethod
     protected void setUp() throws Exception {
         now = Instant.now();
-        clockSkew = 60*5*1000;
-        messageLifetime = 60*10*1000;
+        clockSkew = Duration.ofMinutes(5);
+        messageLifetime = Duration.ofMinutes(10);
         
         messageContext = new MessageContext<>();
         
@@ -73,7 +74,7 @@ public class MessageLifetimeSecurityHandlerTest extends XMLObjectBaseTestCase {
      */
     @Test(expectedExceptions=MessageHandlerException.class)
     public void testInvalidIssuedInFuture() throws MessageHandlerException {
-        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.plusMillis(clockSkew + 5000));
+        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.plus(clockSkew).plusSeconds(5));
         handler.invoke(messageContext);
     }
     
@@ -83,7 +84,7 @@ public class MessageLifetimeSecurityHandlerTest extends XMLObjectBaseTestCase {
      */
     @Test
     public void testValidIssuedInFutureWithinClockSkew() throws MessageHandlerException {
-        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.plusMillis(clockSkew - 5000));
+        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.plus(clockSkew).minusSeconds(5));
         handler.invoke(messageContext);
     }
     
@@ -93,7 +94,7 @@ public class MessageLifetimeSecurityHandlerTest extends XMLObjectBaseTestCase {
      */
     @Test(expectedExceptions=MessageHandlerException.class)
     public void testInvalidExpired() throws MessageHandlerException {
-        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.minusMillis(messageLifetime + (clockSkew + 5000)));
+        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.minus(messageLifetime.plus(clockSkew).plusSeconds(5)));
         handler.invoke(messageContext);
     }
     
@@ -103,7 +104,7 @@ public class MessageLifetimeSecurityHandlerTest extends XMLObjectBaseTestCase {
      */
     @Test
     public void testValidExpiredWithinClockSkew() throws MessageHandlerException {
-        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.minusMillis(messageLifetime + (clockSkew - 5000)));
+        messageContext.getSubcontext(SAMLMessageInfoContext.class, true).setMessageIssueInstant(now.minus(messageLifetime.plus(clockSkew).minusSeconds(5)));
         handler.invoke(messageContext);
     }
  
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandlerTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandlerTest.java
index 42190db..46d67a6 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandlerTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/security/impl/MessageReplaySecurityHandlerTest.java
@@ -19,6 +19,8 @@ package org.opensaml.saml.common.binding.security.impl;
 
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
+import java.time.Duration;
+
 import org.opensaml.core.xml.XMLObjectBaseTestCase;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.handler.MessageHandlerException;
@@ -124,7 +126,7 @@ public class MessageReplaySecurityHandlerTest extends XMLObjectBaseTestCase {
         handler.setReplayCache(replayCache);
         
         // Set rule with 3 second expiration, with no clock skew
-        handler.setExpires(3 * 1000);
+        handler.setExpires(Duration.ofSeconds(3));
         handler.initialize();
         
         handler.invoke(messageContext);
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertionsTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertionsTest.java
index 380236a..1eb6b3a 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertionsTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/profile/impl/AddNotOnOrAfterConditionToAssertionsTest.java
@@ -20,6 +20,8 @@ package org.opensaml.saml.common.profile.impl;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.logic.FunctionSupport;
 
+import java.time.Duration;
+
 import org.opensaml.core.OpenSAMLInitBaseTestCase;
 import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
 import org.opensaml.profile.RequestContextBuilder;
@@ -111,7 +113,7 @@ public class AddNotOnOrAfterConditionToAssertionsTest  extends OpenSAMLInitBaseT
         response.getAssertions().add(assertion);
 
         final AddNotOnOrAfterConditionToAssertions action = new AddNotOnOrAfterConditionToAssertions();
-        action.setDefaultAssertionLifetime(10 * 60 * 1000);
+        action.setDefaultAssertionLifetime(Duration.ofMinutes(10));
         action.initialize();
 
         action.execute(prc);
@@ -134,7 +136,7 @@ public class AddNotOnOrAfterConditionToAssertionsTest  extends OpenSAMLInitBaseT
         response.getAssertions().add(SAML1ActionTestingSupport.buildAssertion());
 
         final AddNotOnOrAfterConditionToAssertions action = new AddNotOnOrAfterConditionToAssertions();
-        action.setAssertionLifetimeStrategy(FunctionSupport.constant(3L * 60 * 1000));
+        action.setAssertionLifetimeStrategy(FunctionSupport.constant(Duration.ofMinutes(3)));
         action.initialize();
 
         action.execute(prc);
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilTest.java
index b736579..3702c4a 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/RequiredValidUntilTest.java
@@ -19,6 +19,7 @@ package org.opensaml.saml.metadata.resolver.filter.impl;
 
 import java.io.File;
 import java.net.URL;
+import java.time.Duration;
 import java.time.Instant;
 
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -48,6 +49,7 @@ public class RequiredValidUntilTest extends XMLObjectBaseTestCase {
     @Test
     public void testRequiredValidUntil() throws Exception {
         RequiredValidUntilFilter filter = new RequiredValidUntilFilter();
+        filter.setMaxValidityInterval(Duration.ZERO);
 
         FilesystemMetadataResolver metadataProvider = new FilesystemMetadataResolver(metadataFile);
         metadataProvider.setParserPool(parserPool);
@@ -62,26 +64,8 @@ public class RequiredValidUntilTest extends XMLObjectBaseTestCase {
 
     @Test
     public void testRequiredValidUntilWithMaxValidity() throws Exception {
-        RequiredValidUntilFilter filter = new RequiredValidUntilFilter(1);
-
-        FilesystemMetadataResolver metadataProvider = new FilesystemMetadataResolver(metadataFile);
-        metadataProvider.setParserPool(parserPool);
-        metadataProvider.setId("test");
-        metadataProvider.setMetadataFilter(filter);
-
-        try {
-            metadataProvider.initialize();
-            Assert.fail("Filter accepted metadata with longer than allowed validity period.");
-        } catch (ComponentInitializationException e) {
-            // we expect this
-            return;
-        }
-    }
-    
-    @Test
-    public void testRequiredValidUntilWithMaxValiditySetter() throws Exception {
         RequiredValidUntilFilter filter = new RequiredValidUntilFilter();
-        filter.setMaxValidityInterval(1);
+        filter.setMaxValidityInterval(Duration.ofSeconds(1));
 
         FilesystemMetadataResolver metadataProvider = new FilesystemMetadataResolver(metadataFile);
         metadataProvider.setParserPool(parserPool);
@@ -96,7 +80,6 @@ public class RequiredValidUntilTest extends XMLObjectBaseTestCase {
             return;
         }
     }
-
     
     @Test
     public void testRequiredValidUntilAlreadyPast() throws Exception {
@@ -105,10 +88,12 @@ public class RequiredValidUntilTest extends XMLObjectBaseTestCase {
         EntitiesDescriptor descriptor = entitiesDescriptorBuilder.buildObject();
         descriptor.setValidUntil(Instant.now().minusMillis(10000));
 
-        RequiredValidUntilFilter filter = new RequiredValidUntilFilter(-1);
+        RequiredValidUntilFilter filter = new RequiredValidUntilFilter();
+        filter.setMaxValidityInterval(Duration.ofSeconds(-1));
         filter.filter(descriptor);
         
         filter = new RequiredValidUntilFilter();
         filter.filter(descriptor);
     }
+    
 }
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java
index b6b25ec..0d3c9cb 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.time.Duration;
 import java.time.Instant;
 
 import org.opensaml.core.criterion.EntityIdCriterion;
@@ -214,7 +215,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
         metadataProvider.setParserPool(parserPool);
         metadataProvider.setFailFastInitialization(true);
         metadataProvider.setId("test");
-        metadataProvider.setBackupFileInitNextRefreshDelay(1000);
+        metadataProvider.setBackupFileInitNextRefreshDelay(Duration.ofSeconds(1));
         metadataProvider.initialize();
         
         Assert.assertTrue(metadataProvider.isInitializedFromBackupFile());
@@ -225,7 +226,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
         Assert.assertNotNull(metadataProvider.resolveSingle(criteriaSet), "Metadata inited from backing file was null");
         
         // Sleep past the artificial next refresh delay on init from backup file.
-        Thread.sleep(metadataProvider.getBackupFileInitNextRefreshDelay() + 5000);
+        Thread.sleep(metadataProvider.getBackupFileInitNextRefreshDelay().toMillis() + 5000);
         
         Assert.assertTrue(initRefresh.isBefore(metadataProvider.getLastRefresh()));
         Assert.assertTrue(initUpdate.isBefore(metadataProvider.getLastUpdate()));
@@ -255,7 +256,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
         metadataProvider.setParserPool(parserPool);
         metadataProvider.setFailFastInitialization(true);
         metadataProvider.setId("test");
-        metadataProvider.setBackupFileInitNextRefreshDelay(1000);
+        metadataProvider.setBackupFileInitNextRefreshDelay(Duration.ofSeconds(1));
         metadataProvider.initialize();
         
         Assert.assertTrue(metadataProvider.isInitializedFromBackupFile());
@@ -269,7 +270,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
         Assert.assertNull(metadataProvider.resolveSingle(criteriaSet), "Metadata inited from backing file was non-null");
         
         // Sleep past the artificial next refresh delay on init from backup file.
-        Thread.sleep(metadataProvider.getBackupFileInitNextRefreshDelay() + 5000);
+        Thread.sleep(metadataProvider.getBackupFileInitNextRefreshDelay().toMillis() + 5000);
         
         Assert.assertTrue(initRefresh.isBefore(metadataProvider.getLastRefresh()));
         Instant refreshUpdate = metadataProvider.getLastUpdate();
@@ -301,7 +302,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
         metadataProvider.setParserPool(parserPool);
         metadataProvider.setFailFastInitialization(false);
         metadataProvider.setId("test");
-        metadataProvider.setBackupFileInitNextRefreshDelay(1000);
+        metadataProvider.setBackupFileInitNextRefreshDelay(Duration.ofSeconds(1));
         metadataProvider.initialize();
         
         Assert.assertTrue(metadataProvider.isInitializedFromBackupFile());
@@ -315,7 +316,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
         Assert.assertNull(metadataProvider.resolveSingle(criteriaSet), "Metadata inited from backing file was non-null");
         
         // Sleep past the artificial next refresh delay on init from backup file.
-        Thread.sleep(metadataProvider.getBackupFileInitNextRefreshDelay() + 5000);
+        Thread.sleep(metadataProvider.getBackupFileInitNextRefreshDelay().toMillis() + 5000);
         
         Assert.assertTrue(initRefresh.isBefore(metadataProvider.getLastRefresh()));
         Instant refreshUpdate = metadataProvider.getLastUpdate();
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/LocalDynamicMetadataResolverTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/LocalDynamicMetadataResolverTest.java
index f4a5458..ef0b50c 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/LocalDynamicMetadataResolverTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/LocalDynamicMetadataResolverTest.java
@@ -19,6 +19,7 @@ package org.opensaml.saml.metadata.resolver.impl;
 
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 
 import org.opensaml.core.criterion.EntityIdCriterion;
@@ -42,7 +43,7 @@ import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 import net.shibboleth.utilities.java.support.resolver.ResolverException;
 
 /**
- *
+ * Unit test for {@link LocalDynamicMetadataResolver}.
  */
 public class LocalDynamicMetadataResolverTest extends XMLObjectBaseTestCase {
     
@@ -74,7 +75,7 @@ public class LocalDynamicMetadataResolverTest extends XMLObjectBaseTestCase {
         resolver.setId("abc123");
         resolver.setParserPool(parserPool);
         // Setting this sort so can wait past it in order to test certain things 
-        resolver.setNegativeLookupCacheDuration(1000L);
+        resolver.setNegativeLookupCacheDuration(Duration.ofSeconds(1));
         resolver.initialize();
     }
     
@@ -104,7 +105,7 @@ public class LocalDynamicMetadataResolverTest extends XMLObjectBaseTestCase {
         sourceManager.save(sha1Digester.apply(entityID2), entity2);
         
         // Wait for the negative lookup cache to expire
-        Uninterruptibles.sleepUninterruptibly(resolver.getNegativeLookupCacheDuration(), TimeUnit.MILLISECONDS);
+        Uninterruptibles.sleepUninterruptibly(resolver.getNegativeLookupCacheDuration().toMillis(), TimeUnit.MILLISECONDS);
         
         // Now should be resolveable
         Assert.assertSame(resolver.resolveSingle(new CriteriaSet(new EntityIdCriterion(entityID2))), entity2);
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/ResourceBackedMetadataResolverTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/ResourceBackedMetadataResolverTest.java
index 8299335..6d01361 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/ResourceBackedMetadataResolverTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/ResourceBackedMetadataResolverTest.java
@@ -19,6 +19,7 @@ package org.opensaml.saml.metadata.resolver.impl;
 
 import java.io.File;
 import java.net.URL;
+import java.time.Duration;
 import java.util.Timer;
 
 import net.shibboleth.ext.spring.resource.ResourceHelper;
@@ -53,7 +54,7 @@ public class ResourceBackedMetadataResolverTest extends XMLObjectBaseTestCase {
 
         metadataProvider = new ResourceBackedMetadataResolver(new Timer(true), mdResource);
         metadataProvider.setParserPool(parserPool);
-        metadataProvider.setMaxRefreshDelay(500000);
+        metadataProvider.setMaxRefreshDelay(Duration.ofSeconds(500));
         metadataProvider.setId("test");
         metadataProvider.initialize();
         
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidatorTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidatorTest.java
index ff7928c..9e57a98 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidatorTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidatorTest.java
@@ -17,6 +17,7 @@
 
 package org.opensaml.saml.saml2.assertion.impl;
 
+import java.time.Duration;
 import java.util.Map;
 
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -39,7 +40,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 /**
- *
+ * Unit test for {@link OneTimeUseConditionValidator}.
  */
 public class OneTimeUseConditionValidatorTest extends BaseAssertionValidationTest {
     
@@ -113,7 +114,7 @@ public class OneTimeUseConditionValidatorTest extends BaseAssertionValidationTes
     @Test
     public void testReplayWithGlobalExpiration() throws AssertionValidationException, InterruptedException {
         // Set validator expiration to 500ms.
-        validator = new OneTimeUseConditionValidator(replayCache, 500L);
+        validator = new OneTimeUseConditionValidator(replayCache, Duration.ofMillis(500));
         
         Assertion assertion = getAssertion();
         Assert.assertNotNull(StringSupport.trimOrNull(assertion.getID()));
diff --git a/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractStorageService.java b/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractStorageService.java
index a76ea97..59c14ed 100644
--- a/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractStorageService.java
+++ b/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractStorageService.java
@@ -18,14 +18,13 @@
 package org.opensaml.storage;
 
 import java.io.IOException;
+import java.time.Duration;
 import java.util.Timer;
 import java.util.TimerTask;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
@@ -46,10 +45,8 @@ import org.opensaml.storage.annotation.AnnotationSupport;
 public abstract class AbstractStorageService extends AbstractIdentifiableInitializableComponent implements
         StorageService, StorageCapabilities {
 
-    /**
-     * Number of seconds between cleanup checks. Default value: (0)
-     */
-    @Duration @NonNegative private long cleanupInterval;
+    /** Time between cleanup checks. Default value: (0) */
+    @Nonnull private Duration cleanupInterval;
 
     /** Timer used to schedule cleanup tasks. */
     private Timer cleanupTaskTimer;
@@ -68,30 +65,37 @@ public abstract class AbstractStorageService extends AbstractIdentifiableInitial
 
     /** Configurable value size limit. */
     @Positive private int valueSize;
+    
+    /** Constructor. */
+    public AbstractStorageService() {
+        cleanupInterval = Duration.ZERO;
+    }
 
     /**
-     * Gets the number of milliseconds between one cleanup and another. A value of 0 indicates that no cleanup will be
+     * Gets the time between one cleanup and another. A value of 0 indicates that no cleanup will be
      * performed.
      * 
-     * @return number of milliseconds between one cleanup and another
+     * @return time between one cleanup and another
      */
-    @NonNegative @Duration public long getCleanupInterval() {
+    @Nonnull public Duration getCleanupInterval() {
         return cleanupInterval;
     }
 
     /**
-     * Sets the number of milliseconds between one cleanup and another. A value of 0 indicates that no cleanup will be
+     * Sets the time between one cleanup and another. A value of 0 indicates that no cleanup will be
      * performed.
      * 
      * This setting cannot be changed after the service has been initialized.
      * 
-     * @param interval number of milliseconds between one cleanup and another
+     * @param interval time between one cleanup and another
      */
-    @Duration public void setCleanupInterval(@Duration @NonNegative final long interval) {
+    public void setCleanupInterval(@Nonnull final Duration interval) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        Constraint.isNotNull(interval, "Interval cannot be null");
+        Constraint.isFalse(interval.isNegative(), "Interval cannot be negative");
 
-        cleanupInterval =
-                Constraint.isGreaterThanOrEqual(0, interval, "Cleanup interval must be greater than or equal to zero");
+        cleanupInterval = interval;
     }
 
     /**
@@ -166,7 +170,7 @@ public abstract class AbstractStorageService extends AbstractIdentifiableInitial
     @Override protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
 
-        if (cleanupInterval > 0) {
+        if (!cleanupInterval.isZero()) {
             cleanupTask = getCleanupTask();
             if (cleanupTask == null) {
                 throw new ComponentInitializationException("Cleanup task cannot be null if cleanupInterval is set.");
@@ -175,7 +179,7 @@ public abstract class AbstractStorageService extends AbstractIdentifiableInitial
             } else {
                 internalTaskTimer = cleanupTaskTimer;
             }
-            internalTaskTimer.schedule(cleanupTask, cleanupInterval, cleanupInterval);
+            internalTaskTimer.schedule(cleanupTask, cleanupInterval.toMillis(), cleanupInterval.toMillis());
         }
     }
 
diff --git a/opensaml-storage-api/src/main/java/org/opensaml/storage/ReplayCache.java b/opensaml-storage-api/src/main/java/org/opensaml/storage/ReplayCache.java
index a44d36d..aefbc5b 100644
--- a/opensaml-storage-api/src/main/java/org/opensaml/storage/ReplayCache.java
+++ b/opensaml-storage-api/src/main/java/org/opensaml/storage/ReplayCache.java
@@ -19,6 +19,7 @@ package org.opensaml.storage;
 
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
 
 import javax.annotation.Nonnull;
 
@@ -120,12 +121,12 @@ public class ReplayCache extends AbstractIdentifiableInitializableComponent {
      * 
      * @param context   a context label to subdivide the cache
      * @param s         value to check
-     * @param expires   time (in milliseconds since beginning of epoch) for disposal of value from cache
+     * @param expires   time for disposal of value from cache
      * 
      * @return true iff the check value is not found in the cache
      */
     public synchronized boolean check(@Nonnull @NotEmpty final String context, @Nonnull @NotEmpty final String s,
-            final long expires) {
+            @Nonnull final Instant expires) {
 
         final String key;
         
@@ -143,10 +144,11 @@ public class ReplayCache extends AbstractIdentifiableInitializableComponent {
             final StorageRecord entry = storage.read(context, key);
             if (entry == null) {
                 log.debug("Value '{}' was not a replay, adding to cache with expiration time {}", s, expires);
-                storage.create(context, key, "x", expires);
+                storage.create(context, key, "x", expires.toEpochMilli());
                 return true;
             } else {
-                log.debug("Replay of value '{}' detected in cache, expires at {}", s, entry.getExpiration());
+                log.debug("Replay of value '{}' detected in cache, expires at {}", s,
+                        Instant.ofEpochMilli(entry.getExpiration()));
                 return false;
             }
         } catch (final IOException e) {
diff --git a/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java
index b2209ce..cef0ccb 100644
--- a/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java
+++ b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java
@@ -130,9 +130,9 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
 
     /** {@inheritDoc} */
     @Override
-    public synchronized void setCleanupInterval(final long interval) {
+    public synchronized void setCleanupInterval(@Nullable final Duration interval) {
         // Don't allow a cleanup task.
-        super.setCleanupInterval(0);
+        super.setCleanupInterval(Duration.ZERO);
     }
     
     /**
diff --git a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/JPAStorageServiceTest.java b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/JPAStorageServiceTest.java
index 0fa502e..5e18a4c 100644
--- a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/JPAStorageServiceTest.java
+++ b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/JPAStorageServiceTest.java
@@ -19,6 +19,7 @@ package org.opensaml.storage.impl;
 
 import java.io.IOException;
 import java.security.SecureRandom;
+import java.time.Duration;
 import java.util.List;
 import java.util.UUID;
 
@@ -66,7 +67,7 @@ public class JPAStorageServiceTest extends StorageServiceTest {
     @BeforeClass public void setUp() throws ComponentInitializationException {
         storageService = new JPAStorageService(createEntityManagerFactory());
         storageService.setId("test");
-        storageService.setCleanupInterval(5000);
+        storageService.setCleanupInterval(Duration.ofSeconds(5));
         storageService.setTransactionRetry(2);
         super.setUp();
     }
diff --git a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/LDAPStorageServiceTest.java b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/LDAPStorageServiceTest.java
index 586d9ca..52f1e46 100644
--- a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/LDAPStorageServiceTest.java
+++ b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/LDAPStorageServiceTest.java
@@ -18,6 +18,7 @@
 package org.opensaml.storage.impl;
 
 import java.io.IOException;
+import java.time.Duration;
 
 import javax.annotation.Nonnull;
 
@@ -183,7 +184,7 @@ public class LDAPStorageServiceTest {
 
     @Test public void invalidConfig() {
         LDAPStorageService ss = new LDAPStorageService(getPooledConnectionFactory());
-        ss.setCleanupInterval(1000);
+        ss.setCleanupInterval(Duration.ofSeconds(1));
 
         try {
             ss.initialize();
diff --git a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java
index 6ab76f7..a5c316d 100644
--- a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java
+++ b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java
@@ -17,6 +17,8 @@
 
 package org.opensaml.storage.impl;
 
+import java.time.Duration;
+
 import javax.annotation.Nonnull;
 
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -36,7 +38,7 @@ public class MemoryStorageServiceTest extends StorageServiceTest {
     @Nonnull protected StorageService getStorageService() {
         MemoryStorageService ss = new MemoryStorageService();
         ss.setId("test");
-        ss.setCleanupInterval(1000);
+        ss.setCleanupInterval(Duration.ofSeconds(1));
         return ss;
     }
         
diff --git a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/ReplayCacheTest.java b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/ReplayCacheTest.java
index bbadb00..15bb441 100644
--- a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/ReplayCacheTest.java
+++ b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/ReplayCacheTest.java
@@ -17,6 +17,8 @@
 
 package org.opensaml.storage.impl;
 
+import java.time.Instant;
+
 import org.opensaml.storage.ReplayCache;
 import org.opensaml.storage.impl.client.ClientStorageService;
 import org.testng.annotations.AfterMethod;
@@ -33,7 +35,7 @@ public class ReplayCacheTest {
     
     private String messageID;
     
-    private long expiration;
+    private Instant expiration;
 
     private MemoryStorageService storageService;
     
@@ -43,7 +45,7 @@ public class ReplayCacheTest {
     protected void setUp() throws Exception {
         context = getClass().getName();
         messageID = "abc123";
-        expiration = System.currentTimeMillis() + 180000;
+        expiration = Instant.now().plusSeconds(180);
 
         storageService = new MemoryStorageService();
         storageService.setId("test");
@@ -121,13 +123,13 @@ public class ReplayCacheTest {
     @Test
     public void testNonReplayValidByMillisecondExpiriation() throws InterruptedException {
 
-        Assert.assertTrue(replayCache.check(context, messageID, System.currentTimeMillis() + 1000),
+        Assert.assertTrue(replayCache.check(context, messageID, Instant.now().plusSeconds(1)),
                 "Message was not replay, insert into empty cache");
         
         // Sleep for 2 seconds to make sure replay cache entry has expired
         Thread.sleep(2000L);
         
-        Assert.assertTrue(replayCache.check(context, messageID, System.currentTimeMillis() + 1000),
+        Assert.assertTrue(replayCache.check(context, messageID, Instant.now().plusSeconds(1)),
                 "Message was not replay, previous cache entry should have expired");
     }
 }
\ No newline at end of file

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


More information about the commits mailing list