Repository: quarkusio/quarkus
Issue:  The behavior of the @RestHeader annotation is different from the @HeaderParam annotation when the parameter is of type List

The @RestHeader javadoc says :

```
/*
* Equivalent of HeaderParam but with optional name.
*/
```

And the javadoc of HeaderParam say that :

```

/**
 * Binds the value(s) of a HTTP header to a resource method parameter,
 * resource class field, or resource class bean property. A default value
 * can be specified using the {@link DefaultValue} annotation.
 *
 * The type {@code T} of the annotated parameter, field or property
 * must either:
 * <ol>
 * <li>Be a primitive type</li>
 * <li>Have a constructor that accepts a single {@code String} argument</li>
 * <li>Have a static method named {@code valueOf} or {@code fromString}
 * that accepts a single
 * {@code String} argument (see, for example, {@link Integer#valueOf(String)})</li>
 * <li>Have a registered implementation of {@link javax.ws.rs.ext.ParamConverterProvider}
 * JAX-RS extension SPI that returns a {@link javax.ws.rs.ext.ParamConverter}
 * instance capable of a "from string" conversion for the type.</li>
 * <li>Be {@code List<T>}, {@code Set<T>} or
 * {@code SortedSet<T>}, where {@code T} satisfies 2, 3 or 4 above.
 * The resulting collection is read-only.</li>
 * </ol>
 *
* /
```

But the behavior of @RestHeader is different when the parameter is a List :

by example a method like that :

Response getSomething(@RestHeader("Prefer") List<String> prefers); 

Throw this kind of exception :
ClassCast class java.util.ImmutableCollections$List12 cannot be cast to class java.lang.String (java.util.ImmutableCollections$List12 and java.lang.String are in module java.base of loader 'bootstrap')

since 2.8 and it can be reproduced in 3.0.0.Alpha5

Changed files:
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java
extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java
independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestResource.java

diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
index 5d9b42b7faa..643efbc6a83 100644
--- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
+++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
@@ -941,7 +941,7 @@ A more full example of generated client (with sub-resource) can is at the bottom
                             MethodDescriptor handleHeaderDescriptor = MethodDescriptor.ofMethod(name,
                                     method.getName() + "$$" + methodIndex + "$$handleHeader$$" + paramIdx,
                                     Invocation.Builder.class,
-                                    Invocation.Builder.class, param.type);
+                                    Invocation.Builder.class, param.declaredType);
                             MethodCreator handleHeaderMethod = classContext.classCreator.getMethodCreator(
                                     handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);
 
@@ -1358,7 +1358,7 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
                                     subMethod.getName() + "$$" + subMethodIndex + "$$handleHeader$$param"
                                             + inheritedParamIndex + "$" + subParamField.paramIndex,
                                     Invocation.Builder.class,
-                                    Invocation.Builder.class, param.type);
+                                    Invocation.Builder.class, param.declaredType);
                             MethodCreator handleHeaderMethod = subContext.classCreator.getMethodCreator(
                                     handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);
 
@@ -1464,7 +1464,7 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
                             MethodDescriptor handleHeaderDescriptor = MethodDescriptor.ofMethod(subName,
                                     subMethod.getName() + "$$" + subMethodIndex + "$$handleHeader$$" + paramIdx,
                                     Invocation.Builder.class,
-                                    Invocation.Builder.class, param.type);
+                                    Invocation.Builder.class, param.declaredType);
                             MethodCreator handleHeaderMethod = subContext.classCreator.getMethodCreator(
                                     handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);
 
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestResource.java
index ec39fe9b197..857e2bb420b 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestResource.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestResource.java
@@ -2,9 +2,13 @@
 
 import java.math.BigDecimal;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
+import java.util.stream.Collectors;
 
 import jakarta.inject.Inject;
 import jakarta.json.JsonArray;
@@ -143,6 +147,42 @@ public Response filters(@Context HttpHeaders headers, @RestHeader("filter-reques
         return Response.ok().header("filter-request", header).build();
     }
 
+    @GET
+    @Path("header-param-list")
+    public Object headerParamWithList(@HeaderParam("header") List list) {
+        return collectionToString(list);
+    }
+
+    @GET
+    @Path("rest-header-list")
+    public Object restHeaderWithList(@RestHeader List header) {
+        return collectionToString(header);
+    }
+
+    @GET
+    @Path("header-param-set")
+    public Object headerParamWithSet(@HeaderParam("header") Set list) {
+        return collectionToString(list);
+    }
+
+    @GET
+    @Path("rest-header-set")
+    public Object restHeaderWithSet(@RestHeader Set header) {
+        return collectionToString(header);
+    }
+
+    @GET
+    @Path("header-param-sorted-set")
+    public Object headerParamWithSortedSet(@HeaderParam("header") SortedSet list) {
+        return collectionToString(list);
+    }
+
+    @GET
+    @Path("rest-header-sorted-set")
+    public String restHeaderWithSortedSet(@RestHeader SortedSet header) {
+        return collectionToString(header);
+    }
+
     @GET
     @Path("feature-filters")
     public Response featureFilters(@Context HttpHeaders headers) {
@@ -380,4 +420,8 @@ public String simplifiedResourceInfo(@Context SimpleResourceInfo simplifiedResou
     public String bigDecimalConverter(BigDecimal val) {
         return val.toString();
     }
+
+    private String collectionToString(Collection list) {
+        return (String) list.stream().map(Object::toString).collect(Collectors.joining(", "));
+    }
 }
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java
index ecadb812402..0281067a255 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java
@@ -17,6 +17,8 @@
 import org.junit.jupiter.api.condition.DisabledOnOs;
 import org.junit.jupiter.api.condition.OS;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import io.quarkus.test.QuarkusUnitTest;
 import io.restassured.RestAssured;
@@ -313,6 +315,34 @@ public void testHeaderParamInCtor() {
                 .then().body(Matchers.is(emptyString()));
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = {
+            "rest-header-list",
+            "rest-header-set",
+            "rest-header-sorted-set"
+    })
+    public void testRestHeaderUsingCollection(String path) {
+        RestAssured.with().header("header", "a", "b")
+                .get("/simple/" + path)
+                .then()
+                .statusCode(HttpStatus.SC_OK)
+                .body(Matchers.equalTo("a, b"));
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {
+            "header-param-list",
+            "header-param-set",
+            "header-param-sorted-set"
+    })
+    public void testHeaderParamUsingCollection(String path) {
+        RestAssured.with().header("header", "a", "b")
+                .get("/simple/" + path)
+                .then()
+                .statusCode(HttpStatus.SC_OK)
+                .body(Matchers.equalTo("a, b"));
+    }
+
     @Test
     public void testFormMap() {
         RestAssured
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java
index 2884a22184d..b13b8e5ca6d 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java
@@ -3,6 +3,13 @@
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.ws.rs.GET;
@@ -40,6 +47,15 @@ void testNullHeaders() {
         assertThat(client.cookieSub("bar", null).send(null, "bar4", "dummy")).isEqualTo("bar:null:null:bar4:X-My-Header/dummy");
     }
 
+    @Test
+    void testHeadersWithCollections() {
+        String expected = "a, b, c, d";
+        Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
+        assertThat(client.headersList(List.of("a", "b"), List.of("c", "d"))).isEqualTo(expected);
+        assertThat(client.headersSet(Set.of("a", "b"), Set.of("c", "d"))).isEqualTo(expected);
+        assertThat(client.headersSet(new TreeSet(List.of("a", "b")), new TreeSet(List.of("c", "d")))).isEqualTo(expected);
+    }
+
     @Path("/")
     @ApplicationScoped
     public static class Resource {
@@ -51,12 +67,50 @@ public String returnHeaders(@HeaderParam("foo") String header, @HeaderParam("foo
             return header + ":" + header2 + ":" + header3 + ":" + header4 + ":" + myHeaderName + "/"
                     + headers.getHeaderString("X-My-Header");
         }
+
+        @GET
+        @Path("/headers-list")
+        public String headersList(@HeaderParam("foo") List foo, @RestHeader List header) {
+            return joiningCollections(foo, header);
+        }
+
+        @GET
+        @Path("/headers-set")
+        public String headersSet(@HeaderParam("foo") Set foo, @RestHeader Set header) {
+            return joiningCollections(foo, header);
+        }
+
+        @GET
+        @Path("/headers-sorted-set")
+        public String headersSortedSet(@HeaderParam("foo") SortedSet foo, @RestHeader SortedSet header) {
+            return joiningCollections(foo, header);
+        }
+
+        private String joiningCollections(Collection... collections) {
+            List<String> allHeaders = new ArrayList<>();
+            for (Collection collection : collections) {
+                collection.forEach(v -> allHeaders.add((String) v));
+            }
+            return allHeaders.stream().collect(Collectors.joining(", "));
+        }
     }
 
     public interface Client {
 
         @Path("/")
         SubClient cookieSub(@HeaderParam("foo") String cookie, @HeaderParam("foo2") String cookie2);
+
+        @GET
+        @Path("/headers-list")
+        String headersList(@HeaderParam("foo") List foo, @RestHeader List header);
+
+        @GET
+        @Path("/headers-set")
+        String headersSet(@HeaderParam("foo") Set foo, @RestHeader Set header);
+
+        @GET
+        @Path("/headers-sorted-set")
+        String headersSortedSet(@HeaderParam("foo") SortedSet foo, @RestHeader SortedSet header);
     }
 
     public interface SubClient {
diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java
index 9f1600b450c..a1c2486c662 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java
@@ -1,6 +1,7 @@
 package io.quarkus.rest.client.reactive.runtime;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -81,11 +82,13 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
         }
     }
 
-    private static List<String> castToListOfStrings(List<Object> values) {
+    private static List<String> castToListOfStrings(Collection<Object> values) {
         List<String> result = new ArrayList<>();
         for (Object value : values) {
             if (value instanceof String) {
                 result.add((String) value);
+            } else if (value instanceof Collection) {
+                result.addAll(castToListOfStrings((Collection<Object>) value));
             } else {
                 result.add(String.valueOf(value));
             }
diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
index 20a679649c4..005dfc9cbcb 100644
--- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
+++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
@@ -1359,13 +1359,28 @@ && isParameterContainerType(paramType.asClassType())) {
             elementType = paramType.name().toString();
             handleTemporalParam(builder, paramType.name(), anns, currentMethodInfo);
             typeHandled = true;
-        } else if (paramType.name().equals(LIST) && (type == ParameterType.QUERY)) { // RESTEasy Classic handles the non-generic List type
+        } else if (paramType.name().equals(LIST) && (type == ParameterType.QUERY
+                || type == ParameterType.HEADER)) { // RESTEasy Classic handles the non-generic List type
             elementType = String.class.getName();
             typeHandled = true;
             builder.setSingle(false);
             if (convertible) {
                 handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
             }
+        } else if (paramType.name().equals(SET) && type == ParameterType.HEADER) { // RESTEasy Classic handles the non-generic Set type
+            elementType = String.class.getName();
+            typeHandled = true;
+            builder.setSingle(false);
+            if (convertible) {
+                handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
+            }
+        } else if (paramType.name().equals(SORTED_SET) && type == ParameterType.HEADER) { // RESTEasy Classic handles the non-generic SortedSet type
+            elementType = String.class.getName();
+            typeHandled = true;
+            builder.setSingle(false);
+            if (convertible) {
+                handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType);
+            }
         } else if (paramType.kind() == Kind.ARRAY) {
             ArrayType at = paramType.asArrayType();
             typeHandled = true;