diff --git a/src/java.base/share/classes/java/net/InetAddress.java b/src/java.base/share/classes/java/net/InetAddress.java
index 8a4196644eb..fcc2e270026 100644
--- a/src/java.base/share/classes/java/net/InetAddress.java
+++ b/src/java.base/share/classes/java/net/InetAddress.java
@@ -52,6 +52,7 @@
 import java.util.concurrent.ConcurrentSkipListSet;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.Arrays;
+import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Stream;
 
@@ -191,9 +192,9 @@
  * <p> If the default behavior is not desired, then a Java security property
  * can be set to a different Time-to-live (TTL) value for positive
  * caching. Likewise, a system admin can configure a different
- * negative caching TTL value when needed.
+ * negative caching TTL value when needed or extend the usage of the stale data.
  *
- * <p> Two Java security properties control the TTL values used for
+ * <p> Three Java security properties control the TTL values used for
  *  positive and negative host name resolution caching:
  *
  * <dl style="margin-left:2em">
@@ -205,6 +206,25 @@
  * <p>
  * A value of -1 indicates "cache forever".
  * </dd>
+ * <dt><b>networkaddress.cache.stale.ttl</b></dt>
+ * <dd>Indicates the caching policy for stale names. The value is specified as
+ * an integer to indicate the number of seconds that stale names will be kept in
+ * the cache. A name is considered stale if the TTL has expired and an attempt
+ * to lookup the host name again was not successful. This property is useful if
+ * it is preferable to use a stale name rather than fail due to an unsuccessful
+ * lookup. The default setting is to cache for an implementation specific period
+ * of time.
+ * <p>
+ * If the value of this property is larger than "networkaddress.cache.ttl" then
+ * "networkaddress.cache.ttl" will be used as a refresh interval of the name in
+ * the cache. For example, if this property is set to 1 day and
+ * "networkaddress.cache.ttl" is set to 30 seconds, then the positive response
+ * will be cached for 1 day but an attempt to refresh it will be done every
+ * 30 seconds.
+ * <p>
+ * A value of 0 (zero) or if the property is not set means do not use stale
+ * names. Negative values are ignored.
+ * </dd>
  * <dt><b>networkaddress.cache.negative.ttl</b> (default: 10)</dt>
  * <dd>Indicates the caching policy for un-successful name lookups
  * from the name service. The value is specified as an integer to
@@ -933,7 +953,7 @@ public String toString() {
 
     // CachedAddresses that have to expire are kept ordered in this NavigableSet
     // which is scanned on each access
-    private static final NavigableSet<CachedAddresses> expirySet =
+    private static final NavigableSet<CachedLookup> expirySet =
         new ConcurrentSkipListSet<>();
 
     // common interface
@@ -941,15 +961,22 @@ private interface Addresses {
         InetAddress[] get() throws UnknownHostException;
     }
 
-    // a holder for cached addresses with required metadata
-    private static final class CachedAddresses  implements Addresses, Comparable<CachedAddresses> {
+    /**
+     * A cached result of a name service lookup. The result can be either valid
+     * addresses or invalid (ie a failed lookup) containing no addresses.
+     */
+    private static class CachedLookup implements Addresses, Comparable<CachedLookup> {
         private static final AtomicLong seq = new AtomicLong();
         final String host;
-        final InetAddress[] inetAddresses;
-        final long expiryTime; // time of expiry (in terms of System.nanoTime())
+        volatile InetAddress[] inetAddresses;
+        /**
+         * Time of expiry (in terms of System.nanoTime()). Can be modified only
+         * when the record is not added to the "expirySet".
+         */
+        volatile long expiryTime;
         final long id = seq.incrementAndGet(); // each instance is unique
 
-        CachedAddresses(String host, InetAddress[] inetAddresses, long expiryTime) {
+        CachedLookup(String host, InetAddress[] inetAddresses, long expiryTime) {
             this.host = host;
             this.inetAddresses = inetAddresses;
             this.expiryTime = expiryTime;
@@ -964,7 +991,7 @@ public InetAddress[] get() throws UnknownHostException {
         }
 
         @Override
-        public int compareTo(CachedAddresses other) {
+        public int compareTo(CachedLookup other) {
             // natural order is expiry time -
             // compare difference of expiry times rather than
             // expiry times directly, to avoid possible overflow.
@@ -975,6 +1002,106 @@ public int compareTo(CachedAddresses other) {
             // ties are broken using unique id
             return Long.compare(this.id, other.id);
         }
+
+        /**
+         * Checks if the current cache record is expired or not. Expired records
+         * are removed from the expirySet and cache.
+         *
+         * @return {@code true} if the record was removed
+         */
+        public boolean tryRemoveExpiredAddress(long now) {
+            // compare difference of time instants rather than
+            // time instants directly, to avoid possible overflow.
+            // (see System.nanoTime() recommendations...)
+            if ((expiryTime - now) < 0L) {
+                // ConcurrentSkipListSet uses weakly consistent iterator,
+                // so removing while iterating is OK...
+                if (expirySet.remove(this)) {
+                    // ... remove from cache
+                    cache.remove(host, this);
+                }
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * A cached valid lookup containing addresses whose validity may be
+     * temporarily extended by an additional stale period pending the mapping
+     * being refreshed or updated.
+     */
+    private static final class ValidCachedLookup extends CachedLookup {
+        /**
+         * Time to refresh (in terms of System.nanoTime()).
+         */
+        private volatile long refreshTime;
+        /**
+         * For how long the stale data should be used after TTL expiration.
+         * Initially equal to the expiryTime, but increased over time after each
+         * successful lookup.
+         */
+        private volatile long staleTime;
+
+        /**
+         * only one thread is doing lookup to name service
+         * for particular host at any time.
+         */
+        private final Lock lookupLock = new ReentrantLock();
+
+        ValidCachedLookup(String host, InetAddress[] inetAddresses,
+                          long staleTime, long refreshTime)
+        {
+            super(host, inetAddresses, staleTime);
+            this.refreshTime = refreshTime;
+            this.staleTime = staleTime;
+        }
+
+        @Override
+        public InetAddress[] get() {
+            long now = System.nanoTime();
+            if ((refreshTime - now) < 0L && lookupLock.tryLock()) {
+                try {
+                    // cachePolicy is in [s] - we need [ns]
+                    refreshTime = now + InetAddressCachePolicy.get() * 1000_000_000L;
+                    // getAddressesFromNameService returns non-empty/non-null value
+                    inetAddresses = getAddressesFromNameService(host);
+                    // don't update the "expirySet", will do that later
+                    staleTime = refreshTime + InetAddressCachePolicy.getStale() * 1000_000_000L;
+                } catch (UnknownHostException ignore) {
+                } finally {
+                    lookupLock.unlock();
+                }
+            }
+            return inetAddresses;
+        }
+
+        /**
+         * Overrides the parent method to skip deleting the record from the
+         * cache if the stale data can still be used. Note to update the
+         * "expiryTime" field we have to remove the record from the expirySet
+         * and add it back. It is not necessary to remove/add it here, we can do
+         * that in the "get()" method above, but extracting it minimizes
+         * contention on "expirySet".
+         */
+        @Override
+        public boolean tryRemoveExpiredAddress(long now) {
+            // compare difference of time instants rather than
+            // time instants directly, to avoid possible overflow.
+            // (see System.nanoTime() recommendations...)
+            if ((expiryTime - now) < 0L) {
+                if ((staleTime - now) < 0L) {
+                    return super.tryRemoveExpiredAddress(now);
+                }
+                // ConcurrentSkipListSet uses weakly consistent iterator,
+                // so removing while iterating is OK...
+                if (expirySet.remove(this)) {
+                    expiryTime = staleTime;
+                    expirySet.add(this);
+                }
+            }
+            return false;
+        }
     }
 
     // a name service lookup based Addresses implementation which replaces itself
@@ -1021,18 +1148,33 @@ public InetAddress[] get() throws UnknownHostException {
                     if (cachePolicy == InetAddressCachePolicy.NEVER) {
                         cache.remove(host, this);
                     } else {
-                        CachedAddresses cachedAddresses = new CachedAddresses(
-                            host,
-                            inetAddresses,
-                            cachePolicy == InetAddressCachePolicy.FOREVER
-                            ? 0L
-                            // cachePolicy is in [s] - we need [ns]
-                            : System.nanoTime() + 1000_000_000L * cachePolicy
-                        );
-                        if (cache.replace(host, this, cachedAddresses) &&
+                        long now = System.nanoTime();
+                        long expiryTime =
+                                cachePolicy == InetAddressCachePolicy.FOREVER ?
+                                0L
+                                // cachePolicy is in [s] - we need [ns]
+                                : now + 1000_000_000L * cachePolicy;
+                        CachedLookup cachedLookup;
+                        if (InetAddressCachePolicy.getStale() > 0 &&
+                                ex == null && expiryTime > 0)
+                        {
+                            long refreshTime = expiryTime;
+                            //  staleCachePolicy is in [s] - we need [ns]
+                            expiryTime = refreshTime + 1000_000_000L *
+                                    InetAddressCachePolicy.getStale();
+                            cachedLookup = new ValidCachedLookup(host,
+                                                                 inetAddresses,
+                                                                 expiryTime,
+                                                                 refreshTime);
+                        } else {
+                            cachedLookup = new CachedLookup(host,
+                                                            inetAddresses,
+                                                            expiryTime);
+                        }
+                        if (cache.replace(host, this, cachedLookup) &&
                             cachePolicy != InetAddressCachePolicy.FOREVER) {
                             // schedule expiry
-                            expirySet.add(cachedAddresses);
+                            expirySet.add(cachedLookup);
                         }
                     }
                     if (inetAddresses == null || inetAddresses.length == 0) {
@@ -1638,18 +1780,8 @@ private static InetAddress[] getAllByName0(String host,
         // remove expired addresses from cache - expirySet keeps them ordered
         // by expiry time so we only need to iterate the prefix of the NavigableSet...
         long now = System.nanoTime();
-        for (CachedAddresses caddrs : expirySet) {
-            // compare difference of time instants rather than
-            // time instants directly, to avoid possible overflow.
-            // (see System.nanoTime() recommendations...)
-            if ((caddrs.expiryTime - now) < 0L) {
-                // ConcurrentSkipListSet uses weakly consistent iterator,
-                // so removing while iterating is OK...
-                if (expirySet.remove(caddrs)) {
-                    // ... remove from cache
-                    cache.remove(caddrs.host, caddrs);
-                }
-            } else {
+        for (CachedLookup caddrs : expirySet) {
+            if (!caddrs.tryRemoveExpiredAddress(now)) {
                 // we encountered 1st element that expires in future
                 break;
             }
@@ -1662,7 +1794,7 @@ private static InetAddress[] getAllByName0(String host,
         } else {
             addrs = cache.remove(host);
             if (addrs != null) {
-                if (addrs instanceof CachedAddresses) {
+                if (addrs instanceof CachedLookup) {
                     // try removing from expirySet too if CachedAddresses
                     expirySet.remove(addrs);
                 }
diff --git a/src/java.base/share/classes/java/net/doc-files/net-properties.html b/src/java.base/share/classes/java/net/doc-files/net-properties.html
index ffc65492b51..85af2487bf1 100644
--- a/src/java.base/share/classes/java/net/doc-files/net-properties.html
+++ b/src/java.base/share/classes/java/net/doc-files/net-properties.html
@@ -1,6 +1,6 @@
 <!DOCTYPE HTML>
 <!--
- Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 
  This code is free software; you can redistribute it and/or modify it
@@ -267,13 +267,22 @@ <H2>Address Cache</H2>
 	policy, while a value of 0 (zero) means no caching. The default value
 	is -1 (forever) if a security manager is installed, and implementation-specific
 	when no security manager is installed.</P>
+	<LI><P><B>{@systemProperty networkaddress.cache.stale.ttl}</B> (default: see below)<BR>
+	Value is an integer corresponding to the number of seconds that stale names
+	will be kept in the cache. A name is considered stale if the TTL has expired
+	and an attempt to lookup the host name again was not successful. This
+	property is useful if it is preferable to use a stale name rather than
+	fail due to an unsuccessful lookup.
+	A value of 0 (zero) or if the property is not set means do not use stale
+	names. Negative values are ignored.
+	The default value is implementation-specific.</P>
 	<LI><P><B>{@systemProperty networkaddress.cache.negative.ttl}</B> (default: {@code 10})<BR>
 	Value is an integer corresponding to the number of seconds an
 	unsuccessful name lookup will be kept in the cache. A value of -1,
 	or any negative value, means &ldquo;cache forever&rdquo;, while a
 	value of 0 (zero) means no caching.</P>
 </UL>
-<P>Since these 2 properties are part of the security policy, they are
+<P>Since these 3 properties are part of the security policy, they are
 not set by either the -D option or the {@code System.setProperty()} API,
 instead they are set as security properties.</P>
 <a id="Unixdomain"></a>
diff --git a/src/java.base/share/classes/sun/net/InetAddressCachePolicy.java b/src/java.base/share/classes/sun/net/InetAddressCachePolicy.java
index 8b4673bdd6a..5c127025682 100644
--- a/src/java.base/share/classes/sun/net/InetAddressCachePolicy.java
+++ b/src/java.base/share/classes/sun/net/InetAddressCachePolicy.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 import java.security.PrivilegedAction;
 import java.security.Security;
+import java.util.concurrent.TimeUnit;
 
 @SuppressWarnings("removal")
 public final class InetAddressCachePolicy {
@@ -36,6 +37,12 @@ public final class InetAddressCachePolicy {
     private static final String cachePolicyPropFallback =
         "sun.net.inetaddr.ttl";
 
+    // Controls the cache stale policy for successful lookups only
+    private static final String cacheStalePolicyProp =
+        "networkaddress.cache.stale.ttl";
+    private static final String cacheStalePolicyPropFallback =
+        "sun.net.inetaddr.stale.ttl";
+
     // Controls the cache policy for negative lookups only
     private static final String negativeCachePolicyProp =
         "networkaddress.cache.negative.ttl";
@@ -59,6 +66,15 @@ public final class InetAddressCachePolicy {
      */
     private static volatile int cachePolicy = FOREVER;
 
+    /* The Java-level namelookup cache stale policy:
+     *
+     * any positive value: the number of seconds to use the stale names
+     * zero: do not use stale names
+     *
+     * default value is never (NEVER).
+     */
+    private static volatile int staleCachePolicy = NEVER;
+
     /* The Java-level namelookup cache policy for negative lookups:
      *
      * -1: caching forever
@@ -85,31 +101,7 @@ public final class InetAddressCachePolicy {
      * Initialize
      */
     static {
-
-        Integer tmp = java.security.AccessController.doPrivileged(
-          new PrivilegedAction<Integer>() {
-            public Integer run() {
-                try {
-                    String tmpString = Security.getProperty(cachePolicyProp);
-                    if (tmpString != null) {
-                        return Integer.valueOf(tmpString);
-                    }
-                } catch (NumberFormatException ignored) {
-                    // Ignore
-                }
-
-                try {
-                    String tmpString = System.getProperty(cachePolicyPropFallback);
-                    if (tmpString != null) {
-                        return Integer.decode(tmpString);
-                    }
-                } catch (NumberFormatException ignored) {
-                    // Ignore
-                }
-                return null;
-            }
-          });
-
+        Integer tmp = getProperty(cachePolicyProp, cachePolicyPropFallback);
         if (tmp != null) {
             cachePolicy = tmp < 0 ? FOREVER : tmp;
             propertySet = true;
@@ -121,40 +113,60 @@ public Integer run() {
                 cachePolicy = DEFAULT_POSITIVE;
             }
         }
-        tmp = java.security.AccessController.doPrivileged (
-          new PrivilegedAction<Integer>() {
-            public Integer run() {
-                try {
-                    String tmpString = Security.getProperty(negativeCachePolicyProp);
-                    if (tmpString != null) {
-                        return Integer.valueOf(tmpString);
-                    }
-                } catch (NumberFormatException ignored) {
-                    // Ignore
-                }
-
-                try {
-                    String tmpString = System.getProperty(negativeCachePolicyPropFallback);
-                    if (tmpString != null) {
-                        return Integer.decode(tmpString);
-                    }
-                } catch (NumberFormatException ignored) {
-                    // Ignore
-                }
-                return null;
-            }
-          });
+        tmp = getProperty(negativeCachePolicyProp,
+                          negativeCachePolicyPropFallback);
 
         if (tmp != null) {
             negativeCachePolicy = tmp < 0 ? FOREVER : tmp;
             propertyNegativeSet = true;
         }
+        if (cachePolicy > 0) {
+            tmp = getProperty(cacheStalePolicyProp,
+                              cacheStalePolicyPropFallback);
+            if (tmp != null) {
+                staleCachePolicy = tmp;
+            }
+        }
+    }
+
+    private static Integer getProperty(String cachePolicyProp,
+                                       String cachePolicyPropFallback)
+    {
+        return java.security.AccessController.doPrivileged(
+                new PrivilegedAction<Integer>() {
+                    public Integer run() {
+                        try {
+                            String tmpString = Security.getProperty(
+                                    cachePolicyProp);
+                            if (tmpString != null) {
+                                return Integer.valueOf(tmpString);
+                            }
+                        } catch (NumberFormatException ignored) {
+                            // Ignore
+                        }
+
+                        try {
+                            String tmpString = System.getProperty(
+                                    cachePolicyPropFallback);
+                            if (tmpString != null) {
+                                return Integer.decode(tmpString);
+                            }
+                        } catch (NumberFormatException ignored) {
+                            // Ignore
+                        }
+                        return null;
+                    }
+                });
     }
 
     public static int get() {
         return cachePolicy;
     }
 
+    public static int getStale() {
+        return staleCachePolicy;
+    }
+
     public static int getNegative() {
         return negativeCachePolicy;
     }
diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security
index 92bde0749ce..d40710995f0 100644
--- a/src/java.base/share/conf/security/java.security
+++ b/src/java.base/share/conf/security/java.security
@@ -357,6 +357,17 @@ ssl.TrustManagerFactory.algorithm=PKIX
 #
 #networkaddress.cache.ttl=-1
 
+#
+# The Java-level namelookup cache stale policy:
+#
+# any positive value: the number of seconds to use the stale names
+# zero: do not use stale names
+# negative values are ignored
+#
+# default value is 0 (NEVER).
+#
+#networkaddress.cache.stale.ttl=0
+
 # The Java-level namelookup cache policy for failed lookups:
 #
 # any negative value: cache forever
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/AddressesCachingTest.java b/test/jdk/java/net/spi/InetAddressResolverProvider/AddressesCachingTest.java
index 4de9e74936e..26dabc00063 100644
--- a/test/jdk/java/net/spi/InetAddressResolverProvider/AddressesCachingTest.java
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/AddressesCachingTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -37,9 +37,27 @@
  * @library lib providers/simple
  * @build test.library/testlib.ResolutionRegistry
  *  simple.provider/impl.SimpleResolverProviderImpl AddressesCachingTest
- * @run testng/othervm -Djava.security.properties=${test.src}/NeverCache.props
+ * @run testng/othervm -Djava.security.properties=${test.src}/props/NeverCache.props
  *  -Dtest.cachingDisabled=true AddressesCachingTest
- * @run testng/othervm -Djava.security.properties=${test.src}/ForeverCache.props
+ * @run testng/othervm -Djava.security.properties=${test.src}/props/ForeverCache.props
+ *  -Dtest.cachingDisabled=false AddressesCachingTest
+ * @run testng/othervm
+ *  -Djava.security.properties=${test.src}/props/NeverCacheIgnoreMinusStale.props
+ *  -Dtest.cachingDisabled=true AddressesCachingTest
+ * @run testng/othervm
+ *  -Djava.security.properties=${test.src}/props/NeverCacheIgnorePositiveStale.props
+ *  -Dtest.cachingDisabled=true AddressesCachingTest
+ * @run testng/othervm
+ *  -Djava.security.properties=${test.src}/props/NeverCacheIgnoreZeroStale.props
+ *  -Dtest.cachingDisabled=true AddressesCachingTest
+ * @run testng/othervm
+ *  -Djava.security.properties=${test.src}/props/ForeverCacheIgnoreMinusStale.props
+ *  -Dtest.cachingDisabled=false AddressesCachingTest
+ * @run testng/othervm
+ *  -Djava.security.properties=${test.src}/props/ForeverCacheIgnorePositiveStale.props
+ *  -Dtest.cachingDisabled=false AddressesCachingTest
+ * @run testng/othervm
+ *  -Djava.security.properties=${test.src}/props/ForeverCacheIgnoreZeroStale.props
  *  -Dtest.cachingDisabled=false AddressesCachingTest
  */
 public class AddressesCachingTest {
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/AddressesStaleCachingTest.java b/test/jdk/java/net/spi/InetAddressResolverProvider/AddressesStaleCachingTest.java
new file mode 100644
index 00000000000..a8393221a72
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/AddressesStaleCachingTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import impl.SimpleResolverProviderImpl;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+/*
+ * @test
+ * @summary Test that stale InetAddress caching security properties work as
+ *          expected when a custom resolver is installed.
+ * @library lib providers/simple
+ * @build test.library/testlib.ResolutionRegistry
+ *  simple.provider/impl.SimpleResolverProviderImpl AddressesStaleCachingTest
+ * @run testng/othervm -Djava.security.properties=${test.src}/props/CacheStale.props AddressesStaleCachingTest
+ */
+public class AddressesStaleCachingTest {
+
+    private static class Lookup {
+        private final byte[] address;
+        private final long timestamp;
+
+        private Lookup(byte[] address, long timestamp) {
+            this.address = address;
+            this.timestamp = timestamp;
+        }
+    }
+
+    /**
+     * Validates successful and unsuccessful lookups when the stale cache is
+     * enabled.
+     */
+    @Test
+    public void testRefresh() throws Exception{
+        // The first request is to save the data into the cache
+        Lookup first = doLookup(false, 0);
+
+        Thread.sleep(10000); // intentionally big delay > x2 stale property
+        // The refreshTime is expired, we will do the successful lookup.
+        Lookup second = doLookup(false, 0);
+        Assert.assertNotEquals(first.timestamp, second.timestamp,
+                               "Two lookups are expected");
+
+        Thread.sleep(10000); // intentionally big delay > x2 stale property
+        // The refreshTime is expired again, we will do the failed lookup.
+        Lookup third = doLookup(true, 0);
+        Assert.assertNotEquals(second.timestamp, third.timestamp,
+                               "Two lookups are expected");
+
+        // The stale cache is enabled, so we should get valid/same data for
+        // all requests(even for the failed request).
+        Assert.assertEquals(first.address, second.address,
+                            "Same address is expected");
+        Assert.assertEquals(second.address, third.address,
+                            "Same address is expected");
+    }
+
+    /**
+     * Validates that only one thread is blocked during "refresh", all others
+     * will continue to use the "stale" data.
+     */
+    @Test
+    public void testOnlyOneThreadIsBlockedDuringRefresh() throws Exception {
+        long timeout = System.nanoTime() + TimeUnit.SECONDS.toNanos(12);
+        doLookup(false, timeout);
+        Thread.sleep(9000);
+
+        CountDownLatch blockServer = new CountDownLatch(1);
+        SimpleResolverProviderImpl.setBlocker(blockServer);
+
+        Thread ts[] = new Thread[10];
+        CountDownLatch wait9 = new CountDownLatch(ts.length - 1);
+        CountDownLatch wait10 = new CountDownLatch(ts.length);
+        CountDownLatch start = new CountDownLatch(ts.length);
+        for (int i = 0; i < ts.length; i++) {
+            ts[i] = new Thread(() -> {
+                start.countDown();
+                try {
+                    start.await();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                doLookup(true, timeout);
+                wait9.countDown();
+                wait10.countDown();
+            });
+        }
+        for (Thread t : ts) {
+            t.start();
+        }
+        if (!wait9.await(10, TimeUnit.SECONDS)) {
+            blockServer.countDown();
+            throw new RuntimeException("Some threads hang");
+        }
+        blockServer.countDown();
+        if (!wait10.await(10, TimeUnit.SECONDS)) {
+            throw new RuntimeException("The last thread hangs");
+        }
+    }
+
+    private static Lookup doLookup(boolean error, long timeout) {
+        SimpleResolverProviderImpl.setUnreachableServer(error);
+        try {
+            byte[] firstAddress = InetAddress.getByName("javaTest.org").getAddress();
+            long firstTimestamp = SimpleResolverProviderImpl.getLastLookupTimestamp();
+
+            byte[] secondAddress = InetAddress.getByName("javaTest.org").getAddress();
+            long secondTimestamp = SimpleResolverProviderImpl.getLastLookupTimestamp();
+
+            Assert.assertEquals(firstAddress, secondAddress,
+                                "Same address is expected");
+            if (timeout == 0 || timeout - System.nanoTime() > 0) {
+                Assert.assertEquals(firstTimestamp, secondTimestamp,
+                        "Only one positive lookup is expected with caching enabled");
+            }
+            return new Lookup(firstAddress, firstTimestamp);
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/props/CacheStale.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/CacheStale.props
new file mode 100644
index 00000000000..6e5aa6f0376
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/props/CacheStale.props
@@ -0,0 +1,3 @@
+networkaddress.cache.ttl=7
+networkaddress.cache.negative.ttl=3
+networkaddress.cache.stale.ttl=30
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/ForeverCache.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCache.props
similarity index 100%
rename from test/jdk/java/net/spi/InetAddressResolverProvider/ForeverCache.props
rename to test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCache.props
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnoreMinusStale.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnoreMinusStale.props
new file mode 100644
index 00000000000..041fedbb4c7
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnoreMinusStale.props
@@ -0,0 +1,3 @@
+networkaddress.cache.ttl=-1
+networkaddress.cache.negative.ttl=-1
+networkaddress.cache.stale.ttl=-1
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnorePositiveStale.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnorePositiveStale.props
new file mode 100644
index 00000000000..6ceb3100aa3
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnorePositiveStale.props
@@ -0,0 +1,3 @@
+networkaddress.cache.ttl=-1
+networkaddress.cache.negative.ttl=-1
+networkaddress.cache.stale.ttl=10000
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnoreZeroStale.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnoreZeroStale.props
new file mode 100644
index 00000000000..52febf14a94
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/props/ForeverCacheIgnoreZeroStale.props
@@ -0,0 +1,3 @@
+networkaddress.cache.ttl=-1
+networkaddress.cache.negative.ttl=-1
+networkaddress.cache.stale.ttl=0
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/NeverCache.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCache.props
similarity index 100%
rename from test/jdk/java/net/spi/InetAddressResolverProvider/NeverCache.props
rename to test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCache.props
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnoreMinusStale.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnoreMinusStale.props
new file mode 100644
index 00000000000..e643ed849d9
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnoreMinusStale.props
@@ -0,0 +1,3 @@
+networkaddress.cache.ttl=0
+networkaddress.cache.negative.ttl=0
+networkaddress.cache.stale.ttl=-1
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnorePositiveStale.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnorePositiveStale.props
new file mode 100644
index 00000000000..ec1a501ba95
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnorePositiveStale.props
@@ -0,0 +1,3 @@
+networkaddress.cache.ttl=0
+networkaddress.cache.negative.ttl=0
+networkaddress.cache.stale.ttl=10000
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnoreZeroStale.props b/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnoreZeroStale.props
new file mode 100644
index 00000000000..8ead383a681
--- /dev/null
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/props/NeverCacheIgnoreZeroStale.props
@@ -0,0 +1,3 @@
+networkaddress.cache.ttl=0
+networkaddress.cache.negative.ttl=0
+networkaddress.cache.stale.ttl=0
diff --git a/test/jdk/java/net/spi/InetAddressResolverProvider/providers/simple/simple.provider/impl/SimpleResolverProviderImpl.java b/test/jdk/java/net/spi/InetAddressResolverProvider/providers/simple/simple.provider/impl/SimpleResolverProviderImpl.java
index 7fde1755084..7fc10423c2a 100644
--- a/test/jdk/java/net/spi/InetAddressResolverProvider/providers/simple/simple.provider/impl/SimpleResolverProviderImpl.java
+++ b/test/jdk/java/net/spi/InetAddressResolverProvider/providers/simple/simple.provider/impl/SimpleResolverProviderImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -31,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.logging.Logger;
 import java.util.stream.Stream;
 
@@ -41,6 +42,8 @@ public class SimpleResolverProviderImpl extends InetAddressResolverProvider {
     public static ResolutionRegistry registry = new ResolutionRegistry();
     private static List<LookupPolicy> LOOKUP_HISTORY = Collections.synchronizedList(new ArrayList<>());
     private static volatile long LAST_LOOKUP_TIMESTAMP;
+    private static volatile boolean unreachableServer;
+    private static volatile CountDownLatch blocker;
     private static Logger LOGGER = Logger.getLogger(SimpleResolverProviderImpl.class.getName());
 
     @Override
@@ -51,14 +54,27 @@ public InetAddressResolver get(Configuration configuration) {
             public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException {
                 LOGGER.info("Looking-up addresses for '" + host + "'. Lookup characteristics:" +
                         Integer.toString(lookupPolicy.characteristics(), 2));
+                if (blocker != null) {
+                    try {
+                        blocker.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
                 LOOKUP_HISTORY.add(lookupPolicy);
                 LAST_LOOKUP_TIMESTAMP = System.nanoTime();
+                if (unreachableServer) {
+                    throw new UnknownHostException("unreachableServer");
+                }
                 return registry.lookupHost(host, lookupPolicy);
             }
 
             @Override
             public String lookupByAddress(byte[] addr) throws UnknownHostException {
                 LOGGER.info("Looking host name for the following address:" + ResolutionRegistry.addressBytesToString(addr));
+                if (unreachableServer) {
+                    throw new UnknownHostException("unreachableServer");
+                }
                 return registry.lookupAddress(addr);
             }
         };
@@ -73,6 +89,14 @@ public static long getLastLookupTimestamp() {
         return LAST_LOOKUP_TIMESTAMP;
     }
 
+    public static void setUnreachableServer(boolean unreachableServer) {
+        SimpleResolverProviderImpl.unreachableServer = unreachableServer;
+    }
+
+    public static void setBlocker(CountDownLatch blocker) {
+        SimpleResolverProviderImpl.blocker = blocker;
+    }
+
     public static LookupPolicy lookupPolicyHistory(int position) {
         if (LOOKUP_HISTORY.isEmpty()) {
             throw new RuntimeException("No registered lookup policies");