diff --git a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
index e8551bd78c6f..07ca425e23d4 100644
--- a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
+++ b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
@@ -509,8 +509,55 @@ class Direct$Type$Buffer$RW$$BO$
                                             offset, segment);
     }
 
-#end[char]
+    public $Type$Buffer put(int index, CharSequence csq, int start, int end) {
+#if[rw]
+        Objects.checkFromToIndex(start, end, csq.length());
+        Objects.checkIndex(index, limit());
+        if (end - start > limit() - index)
+            throw new BufferOverflowException();
+
+        //
+        // Cannot delegate to put(int,CharBuffer,int,int) until after the check
+        // for BufferOverflowException as that method would instead throw an
+        // IndexOutOfBoundsException for the same condition
+        //
+        if (csq instanceof CharBuffer cb)
+            return put(index, cb, start, end - start);
+
+        if (csq instanceof String ||
+            csq instanceof StringBuilder ||
+            csq instanceof StringBuffer) {
+
+            char[] buf = new char[Math.min(APPEND_BUF_SIZE, end - start)];
+            while (start < end) {
+                int count = end - start;
+                if (count > buf.length)
+                    count = buf.length;
+
+                if (csq instanceof String str) {
+                    str.getChars(start, start + count, buf, 0);
+                } else if (csq instanceof StringBuilder sb) {
+                    sb.getChars(start, start + count, buf, 0);
+                } else if (csq instanceof StringBuffer sb) {
+                    sb.getChars(start, start + count, buf, 0);
+                }
+
+                putArray(index, buf, 0, count);
+
+                start += count;
+                index += count;
+            }
+
+            return this;
+        }
 
+        return super.put(index, csq, start, end);
+#else[rw]
+        throw new ReadOnlyBufferException();
+#end[rw]
+    }
+
+#end[char]
 
 
 #if[!byte]
diff --git a/src/java.base/share/classes/java/nio/Heap-X-Buffer.java.template b/src/java.base/share/classes/java/nio/Heap-X-Buffer.java.template
index d148fe73674a..cf282cbd5710 100644
--- a/src/java.base/share/classes/java/nio/Heap-X-Buffer.java.template
+++ b/src/java.base/share/classes/java/nio/Heap-X-Buffer.java.template
@@ -352,6 +352,38 @@ class Heap$Type$Buffer$RW$
 #end[rw]
     }
 
+    public $Type$Buffer put(int index, CharSequence csq, int start, int end) {
+#if[rw]
+        Objects.checkFromToIndex(start, end, csq.length());
+        Objects.checkIndex(index, limit());
+        if (end - start > limit() - index)
+            throw new BufferOverflowException();
+
+        //
+        // Cannot delegate to put(int,CharBuffer,int,int) until after the check
+        // for BufferOverflowException as that method would instead throw an
+        // IndexOutOfBoundsException for the same condition
+        //
+        if (csq instanceof CharBuffer cb)
+            return put(index, cb, start, end - start);
+
+        if (csq instanceof String str) {
+            str.getChars(start, end, hb, ix(index)); 
+            return this;
+        } else if (csq instanceof StringBuffer strbuf) {
+            strbuf.getChars(start, end, hb, ix(index));
+            return this;
+        } else if (csq instanceof StringBuilder strbld) {
+            strbld.getChars(start, end, hb, ix(index));
+            return this;
+        }
+
+        return super.put(index, csq, start, end);
+#else[rw]
+        throw new ReadOnlyBufferException();
+#end[rw]
+    }
+
 #end[char]
 
     public $Type$Buffer compact() {
diff --git a/src/java.base/share/classes/java/nio/X-Buffer.java.template b/src/java.base/share/classes/java/nio/X-Buffer.java.template
index 2146c86a577e..e9d64b455fa0 100644
--- a/src/java.base/share/classes/java/nio/X-Buffer.java.template
+++ b/src/java.base/share/classes/java/nio/X-Buffer.java.template
@@ -59,8 +59,13 @@ import jdk.internal.util.ArraysSupport;
  *
  *   <li><p> Absolute and relative {@link #put($type$[]) <i>bulk put</i>}
  *   methods that transfer contiguous sequences of $type$s from $a$
- *   $type$ array{#if[char]?, a string,} or some other $type$
- *   buffer into this buffer;</p></li>
+ *   $type$ array{#if[char]?, a string, a <code>CharSequence</code>,}
+ *   or some other $type$ buffer into this buffer{#if[char]?,}{#if[!char]?;}
+#if[char]
+ *   with relative bulk put capability for {@linkplain CharSequence} sources
+ *   provided in the form of the
+ *   {@linkplain #append(CharSequence,int,int) <i>append</i>} methods;
+#end[char]
  *
 #if[byte]
  *
@@ -1467,6 +1472,97 @@ public abstract sealed class $Type$Buffer
         return put(src, 0, src.length());
     }
 
+    /**
+     * Absolute bulk <i>put</i> method&nbsp;&nbsp;<i>(optional operation)</i>.
+     *
+     * <p> This method transfers {@code $type$}s from the given
+     * {@link CharSequence} into this buffer.  If there are more {@code $type$}s
+     * to be copied from the {@code CharSequence} than remain in this buffer,
+     * that is, if
+     * <code>end&nbsp;-&nbsp;start&nbsp;>&nbsp;
+     * limit()&nbsp;-&nbsp;index</code>,
+     * then no {@code $type$}s are transferred and a
+     * {@link BufferOverflowException} is thrown.
+     *
+     * <p> Otherwise, this method copies
+     * <code>n&nbsp;=&nbsp;end&nbsp;-&nbsp;start</code> {@code $type$}s
+     * from the given {@code CharSequence} into this buffer, starting at the
+     * given {@code start} index and at the given {@code index} in this buffer.
+     * The position of this buffer is unchanged.
+     *
+     * <p> In other words, an invocation of this method of the form
+     * <code>dst.put(index,&nbsp;csq,&nbsp;start,&nbsp;end)</code>
+     * has exactly the same effect as the loop
+     *
+     * <pre>{@code
+     *     for (int i = start, j = index; i < end; i++, j++)
+     *         dst.put(j, csq.charAt(i));
+     * }</pre>
+     *
+     * except that it first checks that there is sufficient space in this
+     * buffer and it is potentially much more efficient.
+     *
+     * @apiNote
+     * Unlike the other four-parameter absolute bulk put methods in this class,
+     * this method:
+     * <ul>
+     * <li>defines the range of characters to be read from the source in terms
+     * of the half-open interval <code>[start,&nbsp;end)</code> instead of
+     * an {@code offset} and a {@code length};</li>
+     * <li>throws a {@code BufferOverflowException} instead of an
+     * {@code IndexOutOfBoundsException} for the condition
+     * <code>index&nbsp;+&nbsp;end&nbsp;-&nbsp;start&nbsp;>&nbsp;limit()</code>.
+     * </ul>
+     *
+     * @param  index
+     *         The index in this buffer at which the first char will be written;
+     *         must be non-negative and less than {@code limit()}
+     *
+     * @param  csq
+     *         The {@code CharSequence} from which {@code $type$}s
+     *         are to be read
+     *
+     * @param  start
+     *         The offset within the {@code CharSequence} of the first $type$
+     *         to be read; must be non-negative and no larger than
+     *         {@code csq.length()}
+     *
+     * @param  end
+     *         The offset within the {@code CharSequence} of the last $type$
+     *         to be read, plus one; must be non-negative and no larger than
+     *         {@code csq.length()}
+     *
+     * @return  This buffer
+     *
+     * @throws  BufferOverflowException
+     *          If there is insufficient space in this buffer
+     *
+     * @throws  IndexOutOfBoundsException
+     *          If the preconditions on the {@code index}, {@code start},
+     *          and {@code end} parameters do not hold
+     *
+     * @throws  ReadOnlyBufferException
+     *          If this buffer is read-only
+     *
+     * @see #append(CharSequence, int, int)
+     * @see #put(int, $type$[], int, int)
+     * @see #put(int, $Type$Buffer, int, int)
+     * @since 22
+     */
+    public $Type$Buffer put(int index, CharSequence csq, int start, int end) {
+        Objects.checkFromToIndex(start, end, csq.length());
+        Objects.checkIndex(index, limit());
+        if (end - start > limit() - index)
+            throw new BufferOverflowException();
+        if (isReadOnly())
+            throw new ReadOnlyBufferException();
+
+        for (int i = start; i < end; i++)
+            this.put(index++, csq.charAt(i));
+
+        return this;
+    }
+
 #end[char]
 
 
diff --git a/test/jdk/java/nio/Buffer/Basic-X.java.template b/test/jdk/java/nio/Buffer/Basic-X.java.template
index d26d3e3e6222..4debb1c27e31 100644
--- a/test/jdk/java/nio/Buffer/Basic-X.java.template
+++ b/test/jdk/java/nio/Buffer/Basic-X.java.template
@@ -39,13 +39,11 @@ import java.nio.*;
 import java.nio.channels.FileChannel;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Random;
 #end[byte]
 #if[char]
 import java.util.function.IntFunction;
 #end[char]
 
-
 public class Basic$Type$
     extends Basic
 {
@@ -501,15 +499,11 @@ public class Basic$Type$
         final int maxPow2 = 12;
         ByteBuffer bb = ByteBuffer.allocateDirect(1 << maxPow2); // cap 4096
 
-        Random rnd = new Random();
-        long seed = rnd.nextLong();
-        rnd = new Random(seed);
-
         for (int i = 0; i < 100; i++) {
             // 1 == 2^0 <= unitSize == 2^k <= bb.capacity()/2
-            int unitSize = 1 << rnd.nextInt(maxPow2);
+            int unitSize = 1 << RND.nextInt(maxPow2);
             // 0 <= index < 2*unitSize
-            int index = rnd.nextInt(unitSize << 1);
+            int index = RND.nextInt(unitSize << 1);
             int value = bb.alignmentOffset(index, unitSize);
             try {
                 if (value < 0 || value >= unitSize) {
@@ -630,7 +624,7 @@ public class Basic$Type$
         absBulkGet(b);
 
 #if[char]
-        // 8306623 and 8306959
+        // 8306623, 8306959, and 4860681
         String str = "in violet night walking beneath a reign of uncouth stars";
         char[] chars = str.toCharArray();
         int cslen = chars.length;
@@ -668,10 +662,10 @@ public class Basic$Type$
                 // append() should throw IndexOutOfBoundsException
                 final CharBuffer cbIOOBE = f.apply(cslen + 1);
                 for (int[] bds : bounds)
-                    tryCatch(cbIOOBE, IndexOutOfBoundsException.class, () ->
+                    catchIndexOutOfBounds(cbIOOBE, () ->
                         cbIOOBE.append(csq, bds[0], bds[1]));
 
-                tryCatch(cb, IndexOutOfBoundsException.class, () ->
+                catchIndexOutOfBounds(cb, () ->
                     cb.append(csq.subSequence(0, 8), 4, 12));
 
                 // should append nothing
@@ -681,6 +675,38 @@ public class Basic$Type$
                 // should fill the buffer
                 int start = (csq.length() - rem)/2;
                 ck(cb, cb.append(csq, start, start + rem).remaining(), 0);
+
+                // begin 4860681
+
+                // put() should throw NullPointerException
+                catchNullArgument(cb, () ->
+                    cb.put(7, (CharSequence)null, 0, 42));
+
+                // BufferOverflowException
+                // put() should throw BufferOverflowException
+                tryCatch(cbBOE, BufferOverflowException.class, () ->
+                    cbBOE.put(0, csq, cslen/4, cslen/2));
+
+                // put() should throw IndexOutOfBoundsException
+                for (int[] bds : bounds)
+                    tryCatch(cbIOOBE, IndexOutOfBoundsException.class, () ->
+                        cbIOOBE.put(0, csq, bds[0], bds[1]));
+
+                // Verify correct copying behavior
+                int cap = Math.max(1, RND.nextInt(cslen));
+                CharBuffer cbTarget = f.apply(cap);
+                int index = RND.nextInt(cap);
+                int afterLast = Math.max(1, RND.nextInt(cslen));
+                int first = Math.max(0, afterLast - (cap - index));
+                cbTarget.put(index, csq, first, afterLast);
+                for (int j = index, i = first; i < afterLast; i++, j++) {
+                    if (cbTarget.get(j) != csq.charAt(i)) {
+                        String s = String.format("Unequal values (seed %d)%n");
+                        throw new RuntimeException(s);
+                    }
+                }
+
+                // end 4860681
             }
         }
         // end 8306623 and 8306959
@@ -1060,6 +1086,9 @@ public class Basic$Type$
         catchReadOnlyBuffer(b, () -> rb.put(new String(new char[rb.remaining() + 1])));
         catchReadOnlyBuffer(b, () -> rb.append(new String(new char[rb.remaining() + 1])));
 
+        // 4860681
+        catchReadOnlyBuffer(b, () -> rb.put(0, "123", 1, 2));
+
 #end[char]
 
         if (rb.getClass().getName().startsWith("java.nio.Heap")) {
diff --git a/test/jdk/java/nio/Buffer/Basic.java b/test/jdk/java/nio/Buffer/Basic.java
index 34175f04f196..7689f8b724e5 100644
--- a/test/jdk/java/nio/Buffer/Basic.java
+++ b/test/jdk/java/nio/Buffer/Basic.java
@@ -24,11 +24,16 @@
 /* @test
  * @summary Unit test for buffers
  * @bug 4413135 4414911 4416536 4416562 4418782 4471053 4472779 4490253 4523725
- *      4526177 4463011 4660660 4661219 4663521 4782970 4804304 4938424 5029431
- *      5071718 6231529 6221101 6234263 6535542 6591971 6593946 6795561 7190219
- *      7199551 8065556 8149469 8230665 8237514 8306374 8306623 8306959
+ *      4526177 4463011 4660660 4661219 4663521 4782970 4804304 4860681 4938424
+ *      5029431 5071718 6231529 6221101 6234263 6535542 6591971 6593946 6795561
+ *      7190219 7199551 8065556 8149469 8230665 8237514 8306374 8306623 8306959
+ * @key randomness
+ * @library ..
+ * @library /test/lib
+ * @build jdk.test.lib.RandomFactory
  * @modules java.base/java.nio:open
  *          java.base/jdk.internal.misc
+ * @run main Basic
  * @author Mark Reinhold
  */
 
@@ -39,9 +44,14 @@
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 
+import java.util.Random;
+
+import jdk.test.lib.RandomFactory;
 
 public class Basic {
 
+    private static final Random RND = RandomFactory.getRandom();
+
     static PrintStream out = System.err;
 
     static long ic(int i) {
diff --git a/test/jdk/java/nio/Buffer/BasicByte.java b/test/jdk/java/nio/Buffer/BasicByte.java
index b3db0b0c1053..fa8530f8599d 100644
--- a/test/jdk/java/nio/Buffer/BasicByte.java
+++ b/test/jdk/java/nio/Buffer/BasicByte.java
@@ -698,6 +698,41 @@ public static void test(int level, final ByteBuffer b, boolean direct) {
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 
 
@@ -1062,6 +1097,9 @@ public static void test(int level, final ByteBuffer b, boolean direct) {
 
 
 
+
+
+
         if (rb.getClass().getName().startsWith("java.nio.Heap")) {
             catchReadOnlyBuffer(b, () -> rb.array());
             catchReadOnlyBuffer(b, () -> rb.arrayOffset());
diff --git a/test/jdk/java/nio/Buffer/BasicChar.java b/test/jdk/java/nio/Buffer/BasicChar.java
index c1358847ad25..1e511d0aad41 100644
--- a/test/jdk/java/nio/Buffer/BasicChar.java
+++ b/test/jdk/java/nio/Buffer/BasicChar.java
@@ -42,10 +42,10 @@
 
 
 
+import java.util.Random;
 import java.util.function.IntFunction;
 
 
-
 public class BasicChar
     extends Basic
 {
@@ -630,7 +630,10 @@ public static void test(int level, final CharBuffer b, boolean direct) {
         absBulkGet(b);
 
 
-        // 8306623 and 8306959
+        // 8306623, 8306959, and 4860681
+        long seed = System.nanoTime();
+        Random rnd = new Random(seed);
+
         String str = "in violet night walking beneath a reign of uncouth stars";
         char[] chars = str.toCharArray();
         int cslen = chars.length;
@@ -668,10 +671,10 @@ public static void test(int level, final CharBuffer b, boolean direct) {
                 // append() should throw IndexOutOfBoundsException
                 final CharBuffer cbIOOBE = f.apply(cslen + 1);
                 for (int[] bds : bounds)
-                    tryCatch(cbIOOBE, IndexOutOfBoundsException.class, () ->
+                    catchIndexOutOfBounds(cbIOOBE, () ->
                         cbIOOBE.append(csq, bds[0], bds[1]));
 
-                tryCatch(cb, IndexOutOfBoundsException.class, () ->
+                catchIndexOutOfBounds(cb, () ->
                     cb.append(csq.subSequence(0, 8), 4, 12));
 
                 // should append nothing
@@ -681,6 +684,38 @@ public static void test(int level, final CharBuffer b, boolean direct) {
                 // should fill the buffer
                 int start = (csq.length() - rem)/2;
                 ck(cb, cb.append(csq, start, start + rem).remaining(), 0);
+
+                // begin 4860681
+
+                // put() should throw NullPointerException
+                catchNullArgument(cb, () ->
+                    cb.put(7, (CharSequence)null, 0, 42));
+
+                // BufferOverflowException
+                // put() should throw BufferOverflowException
+                tryCatch(cbBOE, BufferOverflowException.class, () ->
+                    cbBOE.put(0, csq, cslen/4, cslen/2));
+
+                // put() should throw IndexOutOfBoundsException
+                for (int[] bds : bounds)
+                    tryCatch(cbIOOBE, IndexOutOfBoundsException.class, () ->
+                        cbIOOBE.put(0, csq, bds[0], bds[1]));
+
+                // Verify correct copying behavior
+                int cap = Math.max(1, rnd.nextInt(cslen));
+                CharBuffer cbTarget = f.apply(cap);
+                int index = rnd.nextInt(cap);
+                int afterLast = Math.max(1, rnd.nextInt(cslen));
+                int first = Math.max(0, afterLast - (cap - index));
+                cbTarget.put(index, csq, first, afterLast);
+                for (int j = index, i = first; i < afterLast; i++, j++) {
+                    if (cbTarget.get(j) != csq.charAt(i)) {
+                        String s = String.format("Unequal values (seed %d)%n");
+                        throw new RuntimeException(s);
+                    }
+                }
+
+                // end 4860681
             }
         }
         // end 8306623 and 8306959
@@ -1060,6 +1095,9 @@ public static void test(int level, final CharBuffer b, boolean direct) {
         catchReadOnlyBuffer(b, () -> rb.put(new String(new char[rb.remaining() + 1])));
         catchReadOnlyBuffer(b, () -> rb.append(new String(new char[rb.remaining() + 1])));
 
+        // 4860681
+        catchReadOnlyBuffer(b, () -> rb.put(0, "123", 1, 2));
+
 
 
         if (rb.getClass().getName().startsWith("java.nio.Heap")) {
diff --git a/test/jdk/java/nio/Buffer/BasicDouble.java b/test/jdk/java/nio/Buffer/BasicDouble.java
index d74fe3db0b7a..d1562f527fdc 100644
--- a/test/jdk/java/nio/Buffer/BasicDouble.java
+++ b/test/jdk/java/nio/Buffer/BasicDouble.java
@@ -698,6 +698,41 @@ public static void test(int level, final DoubleBuffer b, boolean direct) {
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 
 
@@ -1058,6 +1093,9 @@ public static void test(int level, final DoubleBuffer b, boolean direct) {
 
 
 
+
+
+
 
 
 
diff --git a/test/jdk/java/nio/Buffer/BasicFloat.java b/test/jdk/java/nio/Buffer/BasicFloat.java
index e60fb7b203c5..3681d42de666 100644
--- a/test/jdk/java/nio/Buffer/BasicFloat.java
+++ b/test/jdk/java/nio/Buffer/BasicFloat.java
@@ -698,6 +698,41 @@ public static void test(int level, final FloatBuffer b, boolean direct) {
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 
 
@@ -1058,6 +1093,9 @@ public static void test(int level, final FloatBuffer b, boolean direct) {
 
 
 
+
+
+
 
 
 
diff --git a/test/jdk/java/nio/Buffer/BasicInt.java b/test/jdk/java/nio/Buffer/BasicInt.java
index 2d0f591e892a..f6ebfeaae4f1 100644
--- a/test/jdk/java/nio/Buffer/BasicInt.java
+++ b/test/jdk/java/nio/Buffer/BasicInt.java
@@ -698,6 +698,41 @@ public static void test(int level, final IntBuffer b, boolean direct) {
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 
 
@@ -1058,6 +1093,9 @@ public static void test(int level, final IntBuffer b, boolean direct) {
 
 
 
+
+
+
 
 
 
diff --git a/test/jdk/java/nio/Buffer/BasicLong.java b/test/jdk/java/nio/Buffer/BasicLong.java
index 04807c3461bb..85805d0ec127 100644
--- a/test/jdk/java/nio/Buffer/BasicLong.java
+++ b/test/jdk/java/nio/Buffer/BasicLong.java
@@ -698,6 +698,41 @@ public static void test(int level, final LongBuffer b, boolean direct) {
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 
 
@@ -1058,6 +1093,9 @@ public static void test(int level, final LongBuffer b, boolean direct) {
 
 
 
+
+
+
 
 
 
diff --git a/test/jdk/java/nio/Buffer/BasicShort.java b/test/jdk/java/nio/Buffer/BasicShort.java
index bbf1b7a7fa4e..cbb70e5e4fcd 100644
--- a/test/jdk/java/nio/Buffer/BasicShort.java
+++ b/test/jdk/java/nio/Buffer/BasicShort.java
@@ -698,6 +698,41 @@ public static void test(int level, final ShortBuffer b, boolean direct) {
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 
 
@@ -1058,6 +1093,9 @@ public static void test(int level, final ShortBuffer b, boolean direct) {
 
 
 
+
+
+
 
 
 
diff --git a/test/jdk/java/nio/Buffer/CopyDirectMemory.java b/test/jdk/java/nio/Buffer/CopyDirectMemory.java
index beb0b81865e3..46d42045eb9f 100644
--- a/test/jdk/java/nio/Buffer/CopyDirectMemory.java
+++ b/test/jdk/java/nio/Buffer/CopyDirectMemory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 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
@@ -25,6 +25,8 @@
  * @summary Test view buffer bulk operations for large buffers.
  * @bug 4463011
  *
+ * @library /test/lib
+ * @build jdk.test.lib.RandomFactory
  * @modules java.base/jdk.internal.misc
  * @build Basic
  * @run main CopyDirectMemory
