diff --git a/buffer/src/main/java/io/netty/buffer/AbstractUnpooledSlicedByteBuf.java b/buffer/src/main/java/io/netty/buffer/AbstractUnpooledSlicedByteBuf.java
--- a/buffer/src/main/java/io/netty/buffer/AbstractUnpooledSlicedByteBuf.java
+++ b/buffer/src/main/java/io/netty/buffer/AbstractUnpooledSlicedByteBuf.java
@@ -215,7 +215,12 @@ protected long _getLongLE(int index) {
 
     @Override
     public ByteBuf duplicate() {
-        return unwrap().duplicate().setIndex(idx(readerIndex()), idx(writerIndex()));
+        return slice(0, capacity()).setIndex(readerIndex(), writerIndex());
+    }
+
+    @Override
+    public ByteBuf retainedDuplicate() {
+        return retainedSlice(0, capacity()).setIndex(readerIndex(), writerIndex());
     }
 
     @Override
diff --git a/buffer/src/main/java/io/netty/buffer/PooledSlicedByteBuf.java b/buffer/src/main/java/io/netty/buffer/PooledSlicedByteBuf.java
--- a/buffer/src/main/java/io/netty/buffer/PooledSlicedByteBuf.java
+++ b/buffer/src/main/java/io/netty/buffer/PooledSlicedByteBuf.java
@@ -115,12 +115,12 @@ public ByteBuf retainedSlice(int index, int length) {
 
     @Override
     public ByteBuf duplicate() {
-        return duplicate0().setIndex(idx(readerIndex()), idx(writerIndex()));
+        return slice(0, capacity()).setIndex(readerIndex(), writerIndex());
     }
 
     @Override
     public ByteBuf retainedDuplicate() {
-        return PooledDuplicatedByteBuf.newInstance(unwrap(), this, idx(readerIndex()), idx(writerIndex()));
+        return retainedSlice(0, capacity()).setIndex(readerIndex(), writerIndex());
     }
 
     @Override
diff --git a/buffer/src/main/java/io/netty/buffer/ReadOnlyByteBufferBuf.java b/buffer/src/main/java/io/netty/buffer/ReadOnlyByteBufferBuf.java
--- a/buffer/src/main/java/io/netty/buffer/ReadOnlyByteBufferBuf.java
+++ b/buffer/src/main/java/io/netty/buffer/ReadOnlyByteBufferBuf.java
@@ -595,7 +595,7 @@ public ByteBuf slice(int index, int length) {
 
         @Override
         public ByteBuf duplicate() {
-            return new ReadOnlyDuplicatedByteBuf(this);
+            return slice(0, capacity()).setIndex(readerIndex(), writerIndex());
         }
 
         @Override

diff --git a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java
--- a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java
+++ b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java
@@ -2137,6 +2137,52 @@ public void testRetainedSliceIndex() throws Exception {
         retainedSlice.release();
     }
 
+    @Test
+    public void testDuplicateOfSliceHasTheSameCapacityAsTheSlice() {
+        ByteBuf slice = buffer.slice();
+        ByteBuf duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+
+        slice = buffer.slice(0, buffer.capacity() - 2);
+        duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+
+        slice = buffer.slice();
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        duplicate.release();
+
+        slice = buffer.slice(0, buffer.capacity() - 2);
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        duplicate.release();
+    }
+
+    @Test
+    public void testDuplicateOfRetainedSliceHasTheSameCapacityAsTheSlice() {
+        ByteBuf slice = buffer.retainedSlice();
+        ByteBuf duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+
+        slice = buffer.retainedSlice(0, buffer.capacity() - 2);
+        duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+
+        slice = buffer.retainedSlice();
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+        duplicate.release();
+
+        slice = buffer.retainedSlice(0, buffer.capacity() - 2);
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+        duplicate.release();
+    }
+
     @Test
     @SuppressWarnings("ObjectEqualsNull")
     public void testEquals() {
diff --git a/buffer/src/test/java/io/netty/buffer/ReadOnlyByteBufTest.java b/buffer/src/test/java/io/netty/buffer/ReadOnlyByteBufTest.java
--- a/buffer/src/test/java/io/netty/buffer/ReadOnlyByteBufTest.java
+++ b/buffer/src/test/java/io/netty/buffer/ReadOnlyByteBufTest.java
@@ -297,4 +297,59 @@ public void asReadOnly() {
         assertSame(readOnly, readOnly.asReadOnly());
         readOnly.release();
     }
+
+    @Test
+    public void testDuplicateOfSliceHasTheSameCapacityAsTheSlice() {
+        ByteBuf buffer = buffer(16);
+
+        ByteBuf slice = buffer.slice();
+        ByteBuf duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+
+        slice = buffer.slice(0, buffer.capacity() - 2);
+        duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+
+        slice = buffer.slice();
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        duplicate.release();
+
+        slice = buffer.slice(0, buffer.capacity() - 2);
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        duplicate.release();
+
+        buffer.release();
+    }
+
+    @Test
+    public void testDuplicateOfRetainedSliceHasTheSameCapacityAsTheSlice() {
+        ByteBuf buffer = buffer(16);
+
+        ByteBuf slice = buffer.retainedSlice();
+        ByteBuf duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+
+        slice = buffer.retainedSlice(0, buffer.capacity() - 2);
+        duplicate = slice.duplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+
+        slice = buffer.retainedSlice();
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+        duplicate.release();
+
+        slice = buffer.retainedSlice(0, buffer.capacity() - 2);
+        duplicate = slice.retainedDuplicate();
+        assertEquals(slice.capacity(), duplicate.capacity());
+        slice.release();
+        duplicate.release();
+
+        buffer.release();
+    }
+
 }

ByteBuf slice duplicates are not slices
### Expected behavior

A `ByteBuf` duplicated slice remains a slice.

### Actual behavior

A `ByteBuf` duplicated slice is a duplicate and not a slice anymore.

### Steps to reproduce

Duplicating a sliced `ByteBuf` is enough.

### Minimal yet complete reproducer code (or URL to code)

```java
ByteBuf helloWorld = Unpooled.copiedBuffer("Hello World", StandardCharsets.UTF_8);
ByteBuf hello = helloWorld.slice(0, 5);
try {
  hello.writeByte('_');
  fail();
} catch (Exception ignore) {
}
try {
  hello.duplicate().writeByte('_');
  fail(); // Fails here
} catch (Exception ignore) {
}
```

### Netty version

Most I'd say

### JVM version (e.g. `java -version`)

N/A

### OS version (e.g. `uname -a`)

N/A


I would expect as well that It won't be expanded too

It might be illogical or unexpected, but it is specified to behave this way. The javadocs for `duplicate()` say that it returns…

```
A buffer whose readable content is equivalent to the buffer returned by {@link #slice()}.
However this buffer will share the capacity of the underlying buffer,
and therefore allows access to all of the underlying content if necessary.
```

So `duplicate()` always gives you duplicates, and `slice()` always gives you slices.
@chrisvest I hear that, however it is not possible from the user code perspective to determine whether the ByteBuf is a duplicate or a slice, if it needs to gets a duplicate of a slice with the same semantic, which makes the API not convenient for such use case (and I know that slices are going away in Netty 5 of course)
org.io.netty.buffer.ReadOnlyByteBufTest