Index: src/main/java/com/nimbusds/jose/jwk/source/RemoteJWKSet.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/main/java/com/nimbusds/jose/jwk/source/RemoteJWKSet.java	(revision 671347ce2e0239368a35a81e16f73dbf34d225f0)
+++ src/main/java/com/nimbusds/jose/jwk/source/RemoteJWKSet.java	(date 1583918165000)
@@ -254,8 +254,16 @@
 
 		// Get the JWK set, may necessitate a cache update
 		JWKSet jwkSet = jwkSetCache.get();
-		if (jwkSet == null) {
-			jwkSet = updateJWKSetFromURL();
+		if (jwkSetCache.isExpired() || jwkSet == null) {
+			try {
+				// retrieve jwkSet by calling JWK set URL
+				jwkSet = updateJWKSetFromURL();
+			} catch (Exception ex) {
+				if (jwkSet == null) {
+					// throw the received exception if max lifespan is expired.
+					throw  ex;
+				}
+			}
 		}
 
 		// Run the selector on the JWK set
Index: src/main/java/com/nimbusds/jose/jwk/source/DefaultJWKSetCache.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/main/java/com/nimbusds/jose/jwk/source/DefaultJWKSetCache.java	(revision 671347ce2e0239368a35a81e16f73dbf34d225f0)
+++ src/main/java/com/nimbusds/jose/jwk/source/DefaultJWKSetCache.java	(date 1583917975000)
@@ -37,14 +37,27 @@
 	 * The default lifespan for cached JWK sets (5 minutes).
 	 */
 	public static final long DEFAULT_LIFESPAN_MINUTES = 5;
-	
+
+
+	/**
+	 * The default max lifespan for cached JWK sets (30 minutes).
+	 */
+	public static final long DEFAULT_MAX_LIFESPAN_MINUTES = 30;
+
 	
 	/**
 	 * The lifespan the cached JWK set, in {@link #timeUnit}s, negative
 	 * means no expiration
 	 */
 	private final long lifespan;
-	
+
+
+	/**
+	 * The max lifespan the cached JWK set, in {@link #timeUnit}s, negative
+	 * means no expiration
+	 */
+	private final long maxLifespan;
+
 	
 	/**
 	 * The lifespan time unit, may be {@code null} if no expiration.
@@ -64,7 +77,7 @@
 	 */
 	public DefaultJWKSetCache() {
 		
-		this(DEFAULT_LIFESPAN_MINUTES, TimeUnit.MINUTES);
+		this(DEFAULT_LIFESPAN_MINUTES, DEFAULT_MAX_LIFESPAN_MINUTES, TimeUnit.MINUTES);
 	}
 	
 	
@@ -76,10 +89,11 @@
 	 * @param timeUnit The lifespan time unit, may be {@code null} if no
 	 *                 expiration.
 	 */
-	public DefaultJWKSetCache(final long lifespan, final TimeUnit timeUnit) {
+	public DefaultJWKSetCache(final long lifespan, final long maxLifespan, final TimeUnit timeUnit) {
 		
 		this.lifespan = lifespan;
-		
+		this.maxLifespan = maxLifespan;
+
 		if (lifespan > -1 && timeUnit == null) {
 			throw new IllegalArgumentException("A time unit must be specified for non-negative lifespans");
 		}
@@ -111,13 +125,22 @@
 	@Override
 	public JWKSet get() {
 		
-		if (isExpired()) {
+		if (isMaxLifeExpired()) {
 			jwkSet = null; // clear
 		}
 		
 		return jwkSet;
 	}
-	
+
+
+	@Override
+	public boolean isExpired() {
+
+		return putTimestamp > -1 &&
+				lifespan > -1 &&
+				new Date().getTime() > putTimestamp + TimeUnit.MILLISECONDS.convert(lifespan, timeUnit);
+	}
+
 	
 	/**
 	 * Returns the cache put timestamp.
@@ -128,21 +151,16 @@
 		
 		return putTimestamp;
 	}
-	
-	
-	/**
-	 * Returns {@code true} if the cached JWK set is expired.
-	 *
-	 * @return {@code true} if expired.
-	 */
-	public boolean isExpired() {
-	
+
+
+	public boolean isMaxLifeExpired() {
+
 		return putTimestamp > -1 &&
-			lifespan > -1 &&
-			new Date().getTime() > putTimestamp + TimeUnit.MILLISECONDS.convert(lifespan, timeUnit);
+				maxLifespan > -1 &&
+				new Date().getTime() > putTimestamp + TimeUnit.MILLISECONDS.convert(maxLifespan, timeUnit);
 	}
-	
-	
+
+
 	/**
 	 * Returns the configured lifespan of the cached JWK.
 	 *
@@ -158,4 +176,21 @@
 		
 		return timeUnit.convert(lifespan, timeUnit);
 	}
+
+
+	/**
+	 * Returns the configured maxLifespan of the cached JWK.
+	 *
+	 * @param timeUnit The time unit to use.
+	 *
+	 * @return The configured maxLifespan, negative means no expiration.
+	 */
+	public long getMaxLifespan(final TimeUnit timeUnit) {
+
+		if (maxLifespan < 0) {
+			return maxLifespan;
+		}
+
+		return timeUnit.convert(maxLifespan, timeUnit);
+	}
 }
Index: src/main/java/com/nimbusds/jose/jwk/source/JWKSetCache.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/main/java/com/nimbusds/jose/jwk/source/JWKSetCache.java	(revision 671347ce2e0239368a35a81e16f73dbf34d225f0)
+++ src/main/java/com/nimbusds/jose/jwk/source/JWKSetCache.java	(date 1583916590000)
@@ -44,4 +44,12 @@
 	 * @return The cached JWK set, {@code null} if none or expired.
 	 */
 	JWKSet get();
+
+
+	/**
+	 * Returns {@code true} if the cached JWK set is expired.
+	 *
+	 * @return {@code true} if expired.
+	 */
+	boolean isExpired();
 }
Index: src/test/java/com/nimbusds/jose/jwk/source/DefaultJWKSetCacheTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/test/java/com/nimbusds/jose/jwk/source/DefaultJWKSetCacheTest.java	(revision 671347ce2e0239368a35a81e16f73dbf34d225f0)
+++ src/test/java/com/nimbusds/jose/jwk/source/DefaultJWKSetCacheTest.java	(date 1583917704000)
@@ -62,16 +62,20 @@
 	
 	public void testParamConstructor() throws InterruptedException {
 		
-		DefaultJWKSetCache cache = new DefaultJWKSetCache(1L, TimeUnit.SECONDS);
+		DefaultJWKSetCache cache = new DefaultJWKSetCache(1L, 3L, TimeUnit.SECONDS);
 		
 		assertEquals(1L, cache.getLifespan(TimeUnit.SECONDS));
-		
+
+		assertEquals(3L, cache.getMaxLifespan(TimeUnit.SECONDS));
+
 		assertNull(cache.get());
 		
 		assertEquals(-1L, cache.getPutTimestamp());
 		
 		assertFalse(cache.isExpired());
-		
+
+		assertFalse(cache.isMaxLifeExpired());
+
 		JWKSet jwkSet = new JWKSet();
 		
 		cache.put(jwkSet);
@@ -84,15 +88,25 @@
 		
 		Thread.sleep(2 * 1000L);
 		
+		assertNotNull("Expired", cache.get());
+		
+		assertTrue(cache.isExpired());
+
+		assertFalse(cache.isMaxLifeExpired());
+
+		Thread.sleep(1 * 1000L);
+
 		assertNull("Expired", cache.get());
-		
+
 		assertTrue(cache.isExpired());
+
+		assertTrue(cache.isMaxLifeExpired());
 	}
 	
 	
 	public void testNoExpiration() {
 		
-		DefaultJWKSetCache cache = new DefaultJWKSetCache(-1L, null);
+		DefaultJWKSetCache cache = new DefaultJWKSetCache(-1L, -1L, null);
 		
 		assertEquals(-1L, cache.getLifespan(TimeUnit.HOURS));
 		
@@ -122,6 +136,7 @@
 		assertNull(cache.get());
 		assertEquals(-1L, cache.getPutTimestamp());
 		assertEquals(DefaultJWKSetCache.DEFAULT_LIFESPAN_MINUTES, cache.getLifespan(TimeUnit.MINUTES));
+		assertEquals(DefaultJWKSetCache.DEFAULT_MAX_LIFESPAN_MINUTES, cache.getMaxLifespan(TimeUnit.MINUTES));
 		assertFalse(cache.isExpired());
 		
 		
@@ -131,6 +146,7 @@
 		
 		assertTrue(cache.getPutTimestamp() > 0);
 		assertEquals(DefaultJWKSetCache.DEFAULT_LIFESPAN_MINUTES, cache.getLifespan(TimeUnit.MINUTES));
+		assertEquals(DefaultJWKSetCache.DEFAULT_MAX_LIFESPAN_MINUTES, cache.getMaxLifespan(TimeUnit.MINUTES));
 		assertFalse(cache.isExpired());
 		
 		// clear cache
@@ -139,6 +155,7 @@
 		assertNull(cache.get());
 		assertEquals(-1L, cache.getPutTimestamp());
 		assertEquals(DefaultJWKSetCache.DEFAULT_LIFESPAN_MINUTES, cache.getLifespan(TimeUnit.MINUTES));
+		assertEquals(DefaultJWKSetCache.DEFAULT_MAX_LIFESPAN_MINUTES, cache.getMaxLifespan(TimeUnit.MINUTES));
 		assertFalse(cache.isExpired());
 	}
 }
