Prevent leaking of annotation in a reused response.

Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java
index 52d812f..a1dda0a 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -21,7 +21,6 @@
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -280,13 +279,8 @@
                                 model.getPriority(ContainerResponseFilter.class)));
             }
         }
-
-        _readerInterceptors.addAll(
-                StreamSupport.stream(processingProviders.getGlobalReaderInterceptors().spliterator(), false)
-                             .collect(Collectors.toList()));
-        _writerInterceptors.addAll(
-                StreamSupport.stream(processingProviders.getGlobalWriterInterceptors().spliterator(), false)
-                             .collect(Collectors.toList()));
+        processingProviders.getGlobalReaderInterceptors().forEach(_readerInterceptors::add);
+        processingProviders.getGlobalWriterInterceptors().forEach(_writerInterceptors::add);
 
         if (resourceMethod != null) {
             addNameBoundFiltersAndInterceptors(
@@ -458,9 +452,7 @@
                 if (entityAnn.length == 0) {
                     response.setEntityAnnotations(methodAnnotations);
                 } else {
-                    final Annotation[] mergedAnn = Arrays.copyOf(methodAnnotations,
-                            methodAnnotations.length + entityAnn.length);
-                    System.arraycopy(entityAnn, 0, mergedAnn, methodAnnotations.length, entityAnn.length);
+                    final Annotation[] mergedAnn = mergeDistinctAnnotations(methodAnnotations, entityAnn);
                     response.setEntityAnnotations(mergedAnn);
                 }
             }
@@ -487,6 +479,22 @@
         return jaxrsResponse;
     }
 
+    private static Annotation[] mergeDistinctAnnotations(Annotation[] existing, Annotation[] newOnes) {
+        List<Annotation> merged = new ArrayList<>(existing.length + newOnes.length);
+        Collections.addAll(merged, existing);
+
+        newOnesLoop:
+        for (Annotation n : newOnes) {
+            for (Annotation ex : existing) {
+                if (ex == n) {
+                    continue newOnesLoop;
+                }
+            }
+            merged.add(n);
+        }
+        return merged.toArray(new Annotation[0]);
+    }
+
     private Type unwrapInvocableResponseType(ContainerRequest request, Type entityType) {
         if (isCompletionStageResponseType
                 && request.resolveProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.FALSE)) {
diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/Jersey5939Test.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/Jersey5939Test.java
new file mode 100644
index 0000000..f48dbf9
--- /dev/null
+++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/Jersey5939Test.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.api;
+
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Check the reused response does not leak by additional Annotations (ContainerResponse#setEntityAnnottaions.
+ */
+public class Jersey5939Test extends JerseyTest {
+
+    private final List<ContainerResponse> capturedResponses = new ArrayList<>();
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(Restlet.class).register(new ContainerResponseFilter() {
+            @Override
+            public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
+                    throws IOException {
+                if (responseContext instanceof ContainerResponse) {
+                    capturedResponses.add((ContainerResponse) responseContext);
+                }
+            }
+        });
+    }
+
+    @Test
+    public void testIssue5939() {
+        for (int i = 0; i < 10; i++) {
+            Response response = target("foo/bar").request().get();
+            assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
+            ContainerResponse containerResponse = capturedResponses.get(i);
+            Annotation[] annotations = containerResponse.getEntityAnnotations();
+            // [@javax.ws.rs.GET(), @javax.ws.rs.Path("/bar")]
+            assertEquals(2, annotations.length, "Found " + annotations.length + " annotations, in iteration " + i);
+        }
+    }
+
+    @Path("/foo")
+    public static class Restlet {
+
+        private static final Response RESPONSE_204 = Response.noContent().build();
+
+        @GET
+        @Path("/bar")
+        public Response fooBar() {
+            return RESPONSE_204;
+        }
+    }
+}
\ No newline at end of file