CVE-2022-21700: Uncontrolled Resource Consumption (CWE-400)
value: Micronaut is a JVM-based, full stack Java framework designed for building JVM web applications with support for Java, Kotlin and the Groovy language. In affected versions sending an invalid Content Type header leads to memory leak in DefaultArgumentConversionContext as this type is erroneously used in static state. ### Impact Sending an invalid Content Type header leads to memory leak in `DefaultArgumentConversionContext` as this type is erroneously used in static state. ### Patches The problem is patched in Micronaut 3.2.7 and above. ### Workarounds The default content type binder can be replaced in an existing Micronaut application to mitigate the issue: ```java package example; import java.util.List; import io.micronaut.context.annotation.Replaces; import io.micronaut.core.convert.ConversionService; import io.micronaut.http.MediaType; import io.micronaut.http.bind.DefaultRequestBinderRegistry; import io.micronaut.http.bind.binders.RequestArgumentBinder; import jakarta.inject.Singleton; @Singleton @Replaces(DefaultRequestBinderRegistry.class) class FixedRequestBinderRegistry extends DefaultRequestBinderRegistry { public FixedRequestBinderRegistry(ConversionService conversionService, List<RequestArgumentBinder> binders) { super(conversionService, binders); } @Override protected void registerDefaultConverters(ConversionService<?> conversionService) { super.registerDefaultConverters(conversionService); conversionService.addConverter(CharSequence.class, MediaType.class, charSequence -> { try { return MediaType.of(charSequence); } catch (IllegalArgumentException e) { return null; } }); } } ``` ### References Commit that introduced the vulnerability https://github.com/micronaut-projects/micronaut-core/commit/b8ec32c311689667c69ae7d9f9c3b3a8abc96fe3 ### For more information If you have any questions or comments about this advisory: * Open an issue in [Micronaut Core](https://github.com/micronaut-projects/micronaut-core/issues) * Email us at [info@micronaut.io](mailto:info@micronaut.io)
CWE description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
CVSS v3 base score: 5.3
Published: 2022-01-18T23:15Z
Repository: https://github.com/micronaut-projects/micronaut-core
Language: Java
Files changed: core/src/main/java/io/micronaut/core/convert/ConversionContext.java, core/src/main/java/io/micronaut/core/convert/DefaultArgumentConversionContext.java, core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValues.java, core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java, core/src/main/java/io/micronaut/core/type/Argument.java, core/src/main/java/io/micronaut/core/type/DefaultArgument.java, http-client/src/main/java/io/micronaut/http/client/exceptions/HttpClientErrorDecoder.java, http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java, http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/CompletableFutureBodyBinder.java, http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/PublisherBodyBinder.java, http-server-netty/src/test/groovy/io/micronaut/http/server/netty/cors/CorsFilterSpec.groovy, http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java, http-server/src/main/java/io/micronaut/http/server/cors/CorsOriginConverter.java, http/src/main/java/io/micronaut/http/HttpHeaders.java, http/src/main/java/io/micronaut/http/MediaType.java, runtime/src/main/java/io/micronaut/cache/interceptor/CacheInterceptor.java, runtime/src/main/java/io/micronaut/scheduling/io/watch/FileWatchCondition.java, session/src/main/java/io/micronaut/session/binder/SessionArgumentBinder.java
Commit date: 2019-11-18 08:23:08 +0100
Commit message: Use ConversionContext constants where possible instead of class (#2356)

Changes
-------
 * Added ArgumentConversionContext constants in ConversionContext
 * Replaced Argument.of and use of argument classes with
ConversionContext constants where possible
 * Added getFirst method in ConvertibleMultiValues that accepts
ArgumentConversionContent parameter

Partially addresses issue #2355
Diff: diff --git a/core/src/main/java/io/micronaut/core/convert/ConversionContext.java b/core/src/main/java/io/micronaut/core/convert/ConversionContext.java
index 5af5fab1ec..a8d422124c 100644
--- a/core/src/main/java/io/micronaut/core/convert/ConversionContext.java
+++ b/core/src/main/java/io/micronaut/core/convert/ConversionContext.java
@@ -39,10 +39,35 @@ public interface ConversionContext extends AnnotationMetadataProvider, TypeVaria
      * The default conversion context.
      */
     ConversionContext DEFAULT = new ConversionContext() {
     };
 
+    /**
+     * Constant for Boolean argument.
+     */
+    ArgumentConversionContext<Boolean> BOOLEAN = ConversionContext.of(Argument.BOOLEAN);
+
+    /**
+     * Constant for Integer argument.
+     */
+    ArgumentConversionContext<Integer> INT = ConversionContext.of(Argument.INT);
+
+    /**
+     * Constant for Long argument.
+     */
+    ArgumentConversionContext<Long> LONG = ConversionContext.of(Argument.LONG);
+
+    /**
+     * Constant for String argument.
+     */
+    ArgumentConversionContext<String> STRING = ConversionContext.of(Argument.STRING);
+
+    /**
+     * Constant for List<String> argument.
+     */
+    ArgumentConversionContext<List<String>> LIST_OF_STRING = ConversionContext.of(Argument.LIST_OF_STRING);
+
     /**
      * In the case where the type to be converted contains generic type arguments this map will return
      * the concrete types of those arguments. For example for the {@link Map} type two keys will be present
      * called 'K' and 'V' with the actual types of the key and value.
      *
diff --git a/core/src/main/java/io/micronaut/core/convert/DefaultArgumentConversionContext.java b/core/src/main/java/io/micronaut/core/convert/DefaultArgumentConversionContext.java
index aa2873dfb1..0ed8027d5c 100644
--- a/core/src/main/java/io/micronaut/core/convert/DefaultArgumentConversionContext.java
+++ b/core/src/main/java/io/micronaut/core/convert/DefaultArgumentConversionContext.java
@@ -17,17 +17,11 @@ package io.micronaut.core.convert;
 
 import io.micronaut.core.annotation.Internal;
 import io.micronaut.core.type.Argument;
 
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 
 /**
  * Default implementation of the {@link ConversionContext} interface.
  *
  * @param <T> type Generic
@@ -112,10 +106,29 @@ class DefaultArgumentConversionContext<T> implements ArgumentConversionContext<T
     @Override
     public Argument<T> getArgument() {
         return argument;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        DefaultArgumentConversionContext<?> that = (DefaultArgumentConversionContext<?>) o;
+        return Objects.equals(getArgument(), that.getArgument()) &&
+            Objects.equals(finalLocale, that.finalLocale) &&
+            Objects.equals(finalCharset, that.finalCharset);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(argument, finalLocale, finalCharset);
+    }
+
     @Override
     public String toString() {
         return argument.toString();
     }
 }
diff --git a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValues.java b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValues.java
index 0e152102c6..03550b2dec 100644
--- a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValues.java
+++ b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValues.java
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 package io.micronaut.core.convert.value;
 
+import io.micronaut.core.convert.ArgumentConversionContext;
 import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.ConversionService;
 import io.micronaut.core.reflect.GenericTypeUtils;
 import io.micronaut.core.type.Argument;
 
@@ -168,10 +169,26 @@ public interface ConvertibleMultiValues<V> extends ConvertibleValues<List<V>> {
             return ConversionService.SHARED.convert(v, ConversionContext.of(requiredType));
         }
         return Optional.empty();
     }
 
+    /**
+     * Find a header and convert it to the given type.
+     *
+     * @param name              The name of the header
+     * @param conversionContext The conversion context
+     * @param <T>               The generic type
+     * @return If the header is presented and can be converted an optional of the value otherwise {@link Optional#empty()}
+     */
+    default <T> Optional<T> getFirst(CharSequence name, ArgumentConversionContext<T> conversionContext) {
+        V v = get(name);
+        if (v != null) {
+            return ConversionService.SHARED.convert(v, conversionContext);
+        }
+        return Optional.empty();
+    }
+
     /**
      * Find a header and convert it to the given type.
      *
      * @param name         The name of the header
      * @param requiredType The required type
diff --git a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java
index 8f3e9d4fba..4da655ba3d 100644
--- a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java
+++ b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java
@@ -68,11 +68,11 @@ public interface ConvertibleValues<V> extends ValueResolver<CharSequence>, Itera
      *
      * @param name The key name
      * @return True if it is
      */
     default boolean contains(String name) {
-        return get(name, Object.class).isPresent();
+        return get(name, Argument.OBJECT_ARGUMENT).isPresent();
     }
 
     /**
      * Performs the given action for each value. Note that in the case
      * where multiple values exist for the same header then the consumer will be invoked
diff --git a/core/src/main/java/io/micronaut/core/type/Argument.java b/core/src/main/java/io/micronaut/core/type/Argument.java
index ccbf7cb905..fe4401fe8f 100644
--- a/core/src/main/java/io/micronaut/core/type/Argument.java
+++ b/core/src/main/java/io/micronaut/core/type/Argument.java
@@ -38,63 +38,68 @@ import java.util.stream.Collectors;
  * @author Graeme Rocher
  * @since 1.0
  */
 public interface Argument<T> extends TypeVariableResolver, AnnotatedElement, Type {
 
+    /**
+     * Constant for string argument.
+     */
+    Argument<String> STRING = Argument.of(String.class);
+
     /**
      * Constant for int argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument INT = Argument.of(int.class);
+    Argument<Integer> INT = Argument.of(int.class);
 
     /**
      * Constant for long argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument LONG = Argument.of(long.class);
+    Argument<Long> LONG = Argument.of(long.class);
 
     /**
      * Constant for float argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument FLOAT = Argument.of(float.class);
+    Argument<Float> FLOAT = Argument.of(float.class);
 
     /**
      * Constant for double argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument DOUBLE = Argument.of(double.class);
+    Argument<Double> DOUBLE = Argument.of(double.class);
 
     /**
      * Constant for void argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument VOID = Argument.of(void.class);
+    Argument<Void> VOID = Argument.of(void.class);
 
     /**
      * Constant for byte argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument BYTE = Argument.of(byte.class);
+    Argument<Byte> BYTE = Argument.of(byte.class);
 
     /**
      * Constant for boolean argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument BOOLEAN = Argument.of(boolean.class);
+    Argument<Boolean> BOOLEAN = Argument.of(boolean.class);
 
     /**
      * Constant char argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument CHAR = Argument.of(char.class);
+    Argument<Character> CHAR = Argument.of(char.class);
 
     /**
      * Constant short argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
-    Argument SHORT = Argument.of(short.class);
+    Argument<Short> SHORT = Argument.of(short.class);
 
     /**
      * Constant representing zero arguments. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
@@ -105,10 +110,15 @@ public interface Argument<T> extends TypeVariableResolver, AnnotatedElement, Typ
      * Default Object argument. Used by generated code, do not remove.
      */
     @SuppressWarnings("unused")
     Argument<Object> OBJECT_ARGUMENT = of(Object.class);
 
+    /**
+     * Constant for List<String> argument.
+     */
+    Argument<List<String>> LIST_OF_STRING = Argument.listOf(String.class);
+
     /**
      * @return The name of the argument
      */
     @Nonnull String getName();
 
diff --git a/core/src/main/java/io/micronaut/core/type/DefaultArgument.java b/core/src/main/java/io/micronaut/core/type/DefaultArgument.java
index 45c9262224..b91d708f3d 100644
--- a/core/src/main/java/io/micronaut/core/type/DefaultArgument.java
+++ b/core/src/main/java/io/micronaut/core/type/DefaultArgument.java
@@ -45,15 +45,11 @@ public class DefaultArgument<T> implements Argument<T> {
      * @param name               The name
      * @param annotationMetadata The annotation metadata
      * @param genericTypes       The generic types
      */
     public DefaultArgument(Class<T> type, String name, AnnotationMetadata annotationMetadata, Argument... genericTypes) {
-        this.type = type;
-        this.name = name;
-        this.annotationMetadata = annotationMetadata != null ? annotationMetadata : AnnotationMetadata.EMPTY_METADATA;
-        this.typeParameters = initializeTypeParameters(genericTypes);
-        this.typeParameterArray = genericTypes;
+        this(type, name, annotationMetadata, initializeTypeParameters(genericTypes), genericTypes);
     }
 
     /**
      * @param type               The type
      * @param name               The name
@@ -182,11 +178,11 @@ public class DefaultArgument<T> implements Argument<T> {
     @Override
     public int hashCode() {
         return Objects.hash(type, name, typeParameters);
     }
 
-    private Map<String, Argument<?>> initializeTypeParameters(Argument[] genericTypes) {
+    private static Map<String, Argument<?>> initializeTypeParameters(Argument[] genericTypes) {
         Map<String, Argument<?>> typeParameters;
         if (genericTypes != null && genericTypes.length > 0) {
             typeParameters = new LinkedHashMap<>(genericTypes.length);
             for (Argument genericType : genericTypes) {
                 typeParameters.put(genericType.getName(), genericType);
diff --git a/http-client/src/main/java/io/micronaut/http/client/exceptions/HttpClientErrorDecoder.java b/http-client/src/main/java/io/micronaut/http/client/exceptions/HttpClientErrorDecoder.java
index 83b372346f..53aec8d40c 100644
--- a/http-client/src/main/java/io/micronaut/http/client/exceptions/HttpClientErrorDecoder.java
+++ b/http-client/src/main/java/io/micronaut/http/client/exceptions/HttpClientErrorDecoder.java
@@ -69,9 +69,9 @@ public interface HttpClientErrorDecoder {
         if (mediaType.equals(MediaType.APPLICATION_JSON_TYPE)) {
             return Argument.of(JsonError.class);
         } else if (mediaType.equals(MediaType.APPLICATION_VND_ERROR_TYPE)) {
             return Argument.of(VndError.class);
         } else {
-            return Argument.of(String.class);
+            return Argument.STRING;
         }
     }
 }
diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java
index 7735fc3649..5f903b403f 100644
--- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java
+++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java
@@ -139,10 +139,11 @@ import java.util.stream.Collectors;
 class RoutingInBoundHandler extends SimpleChannelInboundHandler<io.micronaut.http.HttpRequest<?>> {
 
     private static final Logger LOG = LoggerFactory.getLogger(RoutingInBoundHandler.class);
     private static final Pattern IGNORABLE_ERROR_MESSAGE = Pattern.compile(
             "^.*(?:connection.*(?:reset|closed|abort|broken)|broken.*pipe).*$", Pattern.CASE_INSENSITIVE);
+    private static final Argument ARGUMENT_PART_DATA = Argument.of(PartData.class);
 
     private final Router router;
     private final ExecutorSelector executorSelector;
     private final StaticResourceResolver staticResourceResolver;
     private final ExecutorService ioExecutor;
@@ -767,11 +768,11 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler<io.micronaut.htt
                                     return new HttpDataReference(data);
                                 });
                                 Argument typeVariable;
 
                                 if (StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
-                                    typeVariable = Argument.of(PartData.class);
+                                    typeVariable = ARGUMENT_PART_DATA;
                                 } else {
                                     typeVariable = argument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                                 }
                                 Class typeVariableType = typeVariable.getType();
 
@@ -782,11 +783,11 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler<io.micronaut.htt
                                         ClassUtils.isJavaLangType(typeVariableType);
 
                                 if (Publishers.isConvertibleToPublisher(typeVariableType)) {
                                     boolean streamingFileUpload = StreamingFileUpload.class.isAssignableFrom(typeVariableType);
                                     if (streamingFileUpload) {
-                                        typeVariable = Argument.of(PartData.class);
+                                        typeVariable = ARGUMENT_PART_DATA;
                                     } else {
                                         typeVariable = typeVariable.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                                     }
                                     dataReference.subject.getAndUpdate(subject -> {
                                         if (subject == null) {
diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/CompletableFutureBodyBinder.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/CompletableFutureBodyBinder.java
index abafcd15c3..4e9abb1496 100644
--- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/CompletableFutureBodyBinder.java
+++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/CompletableFutureBodyBinder.java
@@ -47,10 +47,12 @@ import java.util.concurrent.CompletableFuture;
 @Singleton
 @Internal
 public class CompletableFutureBodyBinder extends DefaultBodyAnnotationBinder<CompletableFuture>
     implements NonBlockingBodyArgumentBinder<CompletableFuture> {
 
+    private static final Argument<CompletableFuture> TYPE = Argument.of(CompletableFuture.class);
+
     private final BeanLocator beanLocator;
     private final HttpServerConfiguration httpServerConfiguration;
 
     /**
      * @param beanLocator             The bean locator
@@ -63,11 +65,11 @@ public class CompletableFutureBodyBinder extends DefaultBodyAnnotationBinder<Com
         this.httpServerConfiguration = httpServerConfiguration;
     }
 
     @Override
     public Argument<CompletableFuture> argumentType() {
-        return Argument.of(CompletableFuture.class);
+        return TYPE;
     }
 
     @Override
     public BindingResult<CompletableFuture> bind(ArgumentConversionContext<CompletableFuture> context, HttpRequest<?> source) {
         if (source instanceof NettyHttpRequest) {
diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/PublisherBodyBinder.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/PublisherBodyBinder.java
index 540af07df0..7d055c5f7d 100644
--- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/PublisherBodyBinder.java
+++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/PublisherBodyBinder.java
@@ -52,10 +52,11 @@ import java.util.Optional;
 @Singleton
 @Internal
 public class PublisherBodyBinder extends DefaultBodyAnnotationBinder<Publisher> implements NonBlockingBodyArgumentBinder<Publisher> {
 
     private static final Logger LOG = LoggerFactory.getLogger(NettyHttpServer.class);
+    private static final Argument<Publisher> TYPE = Argument.of(Publisher.class);
 
     private final BeanLocator beanLocator;
     private final HttpServerConfiguration httpServerConfiguration;
 
     /**
@@ -69,11 +70,11 @@ public class PublisherBodyBinder extends DefaultBodyAnnotationBinder<Publisher>
         this.httpServerConfiguration = httpServerConfiguration;
     }
 
     @Override
     public Argument<Publisher> argumentType() {
-        return Argument.of(Publisher.class);
+        return TYPE;
     }
 
     @Override
     public BindingResult<Publisher> bind(ArgumentConversionContext<Publisher> context, HttpRequest<?> source) {
         if (source instanceof NettyHttpRequest) {
diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/cors/CorsFilterSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/cors/CorsFilterSpec.groovy
index bb58e5fcb3..98400f8e10 100644
--- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/cors/CorsFilterSpec.groovy
+++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/cors/CorsFilterSpec.groovy
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 package io.micronaut.http.server.netty.cors
 
+import io.micronaut.core.convert.ConversionContext
 import io.micronaut.core.type.Argument
 import io.micronaut.http.HttpHeaders
 import io.micronaut.http.HttpMethod
 import io.micronaut.http.HttpRequest
 import io.micronaut.http.HttpResponse
@@ -144,11 +145,11 @@ class CorsFilterSpec extends Specification {
 
         then: "the request is passed through because allowed headers are only checked for preflight requests"
         2 * headers.getOrigin() >> Optional.of('http://www.foo.com')
         1 * request.getMethod() >> HttpMethod.GET
         !result.isPresent()
-        0 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String))
+        0 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String)))
     }
 
     void "test preflight handleRequest with disallowed header"() {
         given:
         HttpRequest request = Mock(HttpRequest)
@@ -168,12 +169,12 @@ class CorsFilterSpec extends Specification {
         headers.contains(ACCESS_CONTROL_REQUEST_METHOD) >> true
         def result = corsHandler.handleRequest(request)
 
         then: "the request is rejected because bar is not allowed"
         2 * headers.getOrigin() >> Optional.of('http://www.foo.com')
-        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
-        1 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> ['foo', 'bar']
+        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
+        1 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> ['foo', 'bar']
         result.get().status == HttpStatus.FORBIDDEN
     }
 
     void "test preflight handleRequest with allowed header"() {
         given:
@@ -194,12 +195,12 @@ class CorsFilterSpec extends Specification {
         headers.contains(ACCESS_CONTROL_REQUEST_METHOD) >> true
         def result = corsHandler.handleRequest(request)
 
         then: "the request is successful"
         4 * headers.getOrigin() >> Optional.of('http://www.foo.com')
-        2 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
-        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['foo'])
+        2 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
+        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['foo'])
         result.get().status == HttpStatus.OK
     }
 
     void "test handleResponse when configuration not present"() {
         given:
@@ -272,12 +273,12 @@ class CorsFilterSpec extends Specification {
         when:
         headers.contains(ACCESS_CONTROL_REQUEST_METHOD) >> true
         HttpResponse response = corsHandler.handleRequest(request).get()
 
         then: "the response is not modified"
-        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['X-Header', 'Y-Header'])
-        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
+        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['X-Header', 'Y-Header'])
+        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_METHODS) == 'GET'
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_ORIGIN) == 'http://www.foo.com' // The origin is echo'd
         response.getHeaders().get(VARY) == 'Origin' // The vary header is set
         response.getHeaders().getAll(ACCESS_CONTROL_EXPOSE_HEADERS) == ['Foo-Header', 'Bar-Header'] // Expose headers are set from config
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_CREDENTIALS) == 'true' // Allow credentials header is set
@@ -303,12 +304,12 @@ class CorsFilterSpec extends Specification {
         when:
         headers.contains(ACCESS_CONTROL_REQUEST_METHOD) >> true
         HttpResponse response = corsHandler.handleRequest(request).get()
 
         then: "the response is not modified"
-        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['X-Header', 'Y-Header'])
-        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
+        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['X-Header', 'Y-Header'])
+        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_METHODS) == 'GET'
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_ORIGIN) == 'http://www.foo.com' // The origin is echo'd
         response.getHeaders().get(VARY) == 'Origin' // The vary header is set
         response.getHeaders().get(ACCESS_CONTROL_EXPOSE_HEADERS) == 'Foo-Header,Bar-Header' // Expose headers are set from config
         response.getHeaders().get(ACCESS_CONTROL_ALLOW_CREDENTIALS) == 'true' // Allow credentials header is set
diff --git a/http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java b/http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java
index 102b2e7f66..5741e0f356 100644
--- a/http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java
+++ b/http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java
@@ -25,12 +25,13 @@ import static io.micronaut.http.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS;
 import static io.micronaut.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD;
 import static io.micronaut.http.HttpHeaders.ORIGIN;
 import static io.micronaut.http.HttpHeaders.VARY;
 
 import io.micronaut.core.async.publisher.Publishers;
+import io.micronaut.core.convert.ArgumentConversionContext;
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.order.Ordered;
-import io.micronaut.core.type.Argument;
 import io.micronaut.core.util.StringUtils;
 import io.micronaut.http.HttpHeaders;
 import io.micronaut.http.HttpMethod;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
@@ -57,10 +58,12 @@ import java.util.stream.Collectors;
  * @since 1.0
  */
 @Filter("/**")
 public class CorsFilter implements HttpServerFilter {
 
+    private static final ArgumentConversionContext<HttpMethod> CONVERSION_CONTEXT_HTTP_METHOD = ConversionContext.of(HttpMethod.class);
+
     protected final HttpServerConfiguration.CorsConfiguration corsConfiguration;
 
     /**
      * @param corsConfiguration The {@link CorsOriginConfiguration} instance
      */
@@ -106,13 +109,13 @@ public class CorsFilter implements HttpServerFilter {
 
             if (optionalConfig.isPresent()) {
                 CorsOriginConfiguration config = optionalConfig.get();
 
                 if (CorsUtil.isPreflightRequest(request)) {
-                    Optional<HttpMethod> result = headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class);
+                    Optional<HttpMethod> result = headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, CONVERSION_CONTEXT_HTTP_METHOD);
                     setAllowMethods(result.get(), response);
-                    Optional<List> allowedHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List.class, String.class));
+                    Optional<List<String>> allowedHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.LIST_OF_STRING);
                     allowedHeaders.ifPresent(val ->
                         setAllowHeaders(val, response)
                     );
 
                     setMaxAge(config.getMaxAge(), response);
@@ -148,18 +151,18 @@ public class CorsFilter implements HttpServerFilter {
                 HttpMethod requestMethod = request.getMethod();
 
                 List<HttpMethod> allowedMethods = config.getAllowedMethods();
 
                 if (!isAnyMethod(allowedMethods)) {
-                    HttpMethod methodToMatch = preflight ? headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class).orElse(requestMethod) : requestMethod;
+                    HttpMethod methodToMatch = preflight ? headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, CONVERSION_CONTEXT_HTTP_METHOD).orElse(requestMethod) : requestMethod;
                     if (allowedMethods.stream().noneMatch(method -> method.equals(methodToMatch))) {
                         return Optional.of(HttpResponse.status(HttpStatus.FORBIDDEN));
                     }
                 }
 
                 if (preflight) {
-                    Optional<List<String>> accessControlHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.listOf(String.class));
+                    Optional<List<String>> accessControlHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.LIST_OF_STRING);
 
                     List<String> allowedHeaders = config.getAllowedHeaders();
 
                     if (!isAny(allowedHeaders) && accessControlHeaders.isPresent()) {
                         if (!accessControlHeaders.get().stream()
diff --git a/http-server/src/main/java/io/micronaut/http/server/cors/CorsOriginConverter.java b/http-server/src/main/java/io/micronaut/http/server/cors/CorsOriginConverter.java
index b673d919d3..9f4d1ab76b 100644
--- a/http-server/src/main/java/io/micronaut/http/server/cors/CorsOriginConverter.java
+++ b/http-server/src/main/java/io/micronaut/http/server/cors/CorsOriginConverter.java
@@ -13,18 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 package io.micronaut.http.server.cors;
 
+import io.micronaut.core.convert.ArgumentConversionContext;
 import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.TypeConverter;
 import io.micronaut.core.convert.value.ConvertibleValues;
 import io.micronaut.core.convert.value.ConvertibleValuesMap;
 import io.micronaut.core.type.Argument;
 import io.micronaut.http.HttpMethod;
 
 import javax.inject.Singleton;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
 /**
  * Responsible for converting a map of configuration to an instance of {@link CorsOriginConfiguration}.
@@ -41,39 +43,41 @@ public class CorsOriginConverter implements TypeConverter<Object, CorsOriginConf
     private static final String ALLOWED_HEADERS = "allowedHeaders";
     private static final String EXPOSED_HEADERS = "exposedHeaders";
     private static final String ALLOW_CREDENTIALS = "allowCredentials";
     private static final String MAX_AGE = "maxAge";
 
+    private static final ArgumentConversionContext<List<HttpMethod>> CONVERSION_CONTEXT_LIST_OF_HTTP_METHOD = ConversionContext.of(Argument.listOf(HttpMethod.class));
+
     @Override
     public Optional<CorsOriginConfiguration> convert(Object object, Class<CorsOriginConfiguration> targetType, ConversionContext context) {
         CorsOriginConfiguration configuration = new CorsOriginConfiguration();
         if (object instanceof Map) {
             Map mapConfig = (Map) object;
             ConvertibleValues<Object> convertibleValues = new ConvertibleValuesMap<>(mapConfig);
 
             convertibleValues
-                .get(ALLOWED_ORIGINS, Argument.listOf(String.class))
+                .get(ALLOWED_ORIGINS, ConversionContext.LIST_OF_STRING)
                 .ifPresent(configuration::setAllowedOrigins);
 
             convertibleValues
-                .get(ALLOWED_METHODS, Argument.listOf(HttpMethod.class))
+                .get(ALLOWED_METHODS, CONVERSION_CONTEXT_LIST_OF_HTTP_METHOD)
                 .ifPresent(configuration::setAllowedMethods);
 
             convertibleValues
-                .get(ALLOWED_HEADERS, Argument.listOf(String.class))
+                .get(ALLOWED_HEADERS, ConversionContext.LIST_OF_STRING)
                 .ifPresent(configuration::setAllowedHeaders);
 
             convertibleValues
-                .get(EXPOSED_HEADERS, Argument.listOf(String.class))
+                .get(EXPOSED_HEADERS, ConversionContext.LIST_OF_STRING)
                 .ifPresent(configuration::setExposedHeaders);
 
             convertibleValues
-                .get(ALLOW_CREDENTIALS, Boolean.class)
+                .get(ALLOW_CREDENTIALS, ConversionContext.BOOLEAN)
                 .ifPresent(configuration::setAllowCredentials);
 
             convertibleValues
-                .get(MAX_AGE, Long.class)
+                .get(MAX_AGE, ConversionContext.LONG)
                 .ifPresent(configuration::setMaxAge);
         }
         return Optional.of(configuration);
     }
 }
diff --git a/http/src/main/java/io/micronaut/http/HttpHeaders.java b/http/src/main/java/io/micronaut/http/HttpHeaders.java
index 38528de17f..ec6fd1a011 100644
--- a/http/src/main/java/io/micronaut/http/HttpHeaders.java
+++ b/http/src/main/java/io/micronaut/http/HttpHeaders.java
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 package io.micronaut.http;
 
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.ConversionService;
 import io.micronaut.core.type.Headers;
 
 import java.time.LocalDateTime;
 import java.time.ZoneId;
@@ -468,39 +469,39 @@ public interface HttpHeaders extends Headers {
      *
      * @param name The name of the header
      * @return An {@link Optional} of {@link Integer}
      */
     default Optional<Integer> findInt(CharSequence name) {
-        return get(name, Integer.class);
+        return get(name, ConversionContext.INT);
     }
 
     /**
      * Get the first value of the given header.
      *
      * @param name The header name
      * @return The first value or null if it is present
      */
     default Optional<String> findFirst(CharSequence name) {
-        return getFirst(name, String.class);
+        return getFirst(name, ConversionContext.STRING);
     }
 
     /**
      * The request or response content type.
      *
      * @return The content type
      */
     default Optional<MediaType> contentType() {
-        return getFirst(HttpHeaders.CONTENT_TYPE, MediaType.class);
+        return getFirst(HttpHeaders.CONTENT_TYPE, MediaType.CONVERSION_CONTEXT);
     }
 
     /**
      * The request or response content type.
      *
      * @return The content type
      */
     default OptionalLong contentLength() {
-        Optional<Long> optional = getFirst(HttpHeaders.CONTENT_LENGTH, Long.class);
+        Optional<Long> optional = getFirst(HttpHeaders.CONTENT_LENGTH, ConversionContext.LONG);
         return optional.map(OptionalLong::of).orElseGet(OptionalLong::empty);
     }
 
     /**
      * A list of accepted {@link MediaType} instances.
@@ -509,20 +510,20 @@ public interface HttpHeaders extends Headers {
      */
     default List<MediaType> accept() {
         return getAll(HttpHeaders.ACCEPT)
             .stream()
             .flatMap(x -> Arrays.stream(x.split(",")))
-            .flatMap(s -> ConversionService.SHARED.convert(s, MediaType.class).map(Stream::of).orElse(Stream.empty()))
+            .flatMap(s -> ConversionService.SHARED.convert(s, MediaType.CONVERSION_CONTEXT).map(Stream::of).orElse(Stream.empty()))
             .distinct()
             .collect(Collectors.toList());
     }
 
     /**
      * @return Whether the {@link HttpHeaders#CONNECTION} header is set to Keep-Alive
      */
     default boolean isKeepAlive() {
-        return getFirst(CONNECTION, String.class).map(val -> val.equalsIgnoreCase("keep-alive")).orElse(false);
+        return getFirst(CONNECTION, ConversionContext.STRING).map(val -> val.equalsIgnoreCase("keep-alive")).orElse(false);
     }
 
     /**
      * @return The {@link #ORIGIN} header
      */
diff --git a/http/src/main/java/io/micronaut/http/MediaType.java b/http/src/main/java/io/micronaut/http/MediaType.java
index e7bb5c3f23..087cad80fa 100644
--- a/http/src/main/java/io/micronaut/http/MediaType.java
+++ b/http/src/main/java/io/micronaut/http/MediaType.java
@@ -13,13 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 package io.micronaut.http;
 
+import io.micronaut.core.annotation.Internal;
 import io.micronaut.core.annotation.TypeHint;
+import io.micronaut.core.convert.ArgumentConversionContext;
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.convert.ConversionService;
 import io.micronaut.core.naming.NameUtils;
+import io.micronaut.core.type.Argument;
 import io.micronaut.core.util.StringUtils;
 import io.micronaut.core.value.OptionalValues;
 import io.micronaut.http.annotation.Produces;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -316,10 +320,16 @@ public class MediaType implements CharSequence {
     /**
      * Parameter {@code "v"}.
      */
     public static final String V_PARAMETER = "v";
 
+    @Internal
+    static final Argument<MediaType> ARGUMENT = Argument.of(MediaType.class);
+
+    @Internal
+    static final ArgumentConversionContext<MediaType> CONVERSION_CONTEXT = ConversionContext.of(ARGUMENT);
+
     private static final BigDecimal QUALITY_RATING_NUMBER = new BigDecimal("1.0");
     private static final String QUALITY_RATING = "1.0";
     private static final String SEMICOLON = ";";
 
     @SuppressWarnings("ConstantName")
diff --git a/runtime/src/main/java/io/micronaut/cache/interceptor/CacheInterceptor.java b/runtime/src/main/java/io/micronaut/cache/interceptor/CacheInterceptor.java
index 0520a10aae..c38b4293ea 100644
--- a/runtime/src/main/java/io/micronaut/cache/interceptor/CacheInterceptor.java
+++ b/runtime/src/main/java/io/micronaut/cache/interceptor/CacheInterceptor.java
@@ -257,11 +257,11 @@ public class CacheInterceptor implements MethodInterceptor<Object, Object> {
             AsyncCache<?> asyncCache = cacheManager.getCache(cacheOperation.cacheableCacheName).async();
             CacheKeyGenerator keyGenerator = resolveKeyGenerator(cacheOperation.defaultKeyGenerator, cacheable);
             Object[] params = resolveParams(context, cacheable.get(MEMBER_PARAMETERS, String[].class, StringUtils.EMPTY_STRING_ARRAY));
             Object key = keyGenerator.generateKey(context, params);
             CompletableFuture<Object> thisFuture = new CompletableFuture<>();
-            Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.of(Object.class));
+            Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
             asyncCache.get(key, firstTypeVariable).whenComplete((BiConsumer<Optional<?>, Throwable>) (o, throwable) -> {
                 if (throwable == null && o.isPresent()) {
                     // cache hit, return result
                     if (LOG.isDebugEnabled()) {
                         LOG.debug("Value found in cache [" + asyncCache.getName() + "] for invocation: " + context);
@@ -501,11 +501,11 @@ public class CacheInterceptor implements MethodInterceptor<Object, Object> {
             AnnotationValue<Cacheable> cacheable) {
         AsyncCache<?> asyncCache = cacheManager.getCache(cacheOperation.cacheableCacheName).async();
         CacheKeyGenerator keyGenerator = resolveKeyGenerator(cacheOperation.defaultKeyGenerator, cacheable);
         Object[] params = resolveParams(context, cacheable.get(MEMBER_PARAMETERS, String[].class, StringUtils.EMPTY_STRING_ARRAY));
         Object key = keyGenerator.generateKey(context, params);
-        Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.of(Object.class));
+        Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
 
         Maybe<Object> maybe = Maybe.create(emitter -> {
             asyncCache.get(key, firstTypeVariable).whenComplete((opt, throwable) -> {
                if (throwable != null) {
                    if (errorHandler.handleLoadError(asyncCache, key, asRuntimeException(throwable))) {
diff --git a/runtime/src/main/java/io/micronaut/scheduling/io/watch/FileWatchCondition.java b/runtime/src/main/java/io/micronaut/scheduling/io/watch/FileWatchCondition.java
index eeede8273e..6d1e13497a 100644
--- a/runtime/src/main/java/io/micronaut/scheduling/io/watch/FileWatchCondition.java
+++ b/runtime/src/main/java/io/micronaut/scheduling/io/watch/FileWatchCondition.java
@@ -18,11 +18,11 @@ package io.micronaut.scheduling.io.watch;
 import io.micronaut.context.ApplicationContext;
 import io.micronaut.context.BeanContext;
 import io.micronaut.context.condition.Condition;
 import io.micronaut.context.condition.ConditionContext;
 import io.micronaut.core.annotation.Introspected;
-import io.micronaut.core.type.Argument;
+import io.micronaut.core.convert.ConversionContext;
 import io.micronaut.core.util.CollectionUtils;
 
 import java.io.File;
 import java.util.List;
 
@@ -38,11 +38,11 @@ public class FileWatchCondition implements Condition {
     public boolean matches(ConditionContext context) {
         BeanContext beanContext = context.getBeanContext();
         if (beanContext instanceof ApplicationContext) {
             List<String> paths = ((ApplicationContext) beanContext)
                     .getEnvironment()
-                    .getProperty(FileWatchConfiguration.PATHS, Argument.listOf(String.class))
+                    .getProperty(FileWatchConfiguration.PATHS, ConversionContext.LIST_OF_STRING)
                     .orElse(null);
 
             if (CollectionUtils.isNotEmpty(paths)) {
 
 
diff --git a/session/src/main/java/io/micronaut/session/binder/SessionArgumentBinder.java b/session/src/main/java/io/micronaut/session/binder/SessionArgumentBinder.java
index c1fc81743f..957286a128 100644
--- a/session/src/main/java/io/micronaut/session/binder/SessionArgumentBinder.java
+++ b/session/src/main/java/io/micronaut/session/binder/SessionArgumentBinder.java
@@ -40,10 +40,12 @@ import java.util.Optional;
 @SuppressWarnings("unused")
 @Singleton
 @Requires(classes = HttpServerConfiguration.class)
 public class SessionArgumentBinder implements TypedRequestArgumentBinder<Session> {
 
+    private static final Argument<Session> TYPE = Argument.of(Session.class);
+
     private final SessionStore<Session> sessionStore;
 
     /**
      * Constructor.
      *
@@ -53,11 +55,11 @@ public class SessionArgumentBinder implements TypedRequestArgumentBinder<Session
         this.sessionStore = sessionStore;
     }
 
     @Override
     public Argument<Session> argumentType() {
-        return Argument.of(Session.class);
+        return TYPE;
     }
 
     @Override
     public ArgumentBinder.BindingResult<Session> bind(ArgumentConversionContext<Session> context, HttpRequest<?> source) {
         if (!source.getAttributes().contains(OncePerRequestHttpServerFilter.getKey(HttpSessionFilter.class))) {

Vulnerable code: import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
        return get(name, Object.class).isPresent();
    Argument INT = Argument.of(int.class);
    Argument LONG = Argument.of(long.class);
    Argument FLOAT = Argument.of(float.class);
    Argument DOUBLE = Argument.of(double.class);
    Argument VOID = Argument.of(void.class);
    Argument BYTE = Argument.of(byte.class);
    Argument BOOLEAN = Argument.of(boolean.class);
    Argument CHAR = Argument.of(char.class);
    Argument SHORT = Argument.of(short.class);
        this.type = type;
        this.name = name;
        this.annotationMetadata = annotationMetadata != null ? annotationMetadata : AnnotationMetadata.EMPTY_METADATA;
        this.typeParameters = initializeTypeParameters(genericTypes);
        this.typeParameterArray = genericTypes;
    private Map<String, Argument<?>> initializeTypeParameters(Argument[] genericTypes) {
            return Argument.of(String.class);
                                    typeVariable = Argument.of(PartData.class);
                                        typeVariable = Argument.of(PartData.class);
        return Argument.of(CompletableFuture.class);
        return Argument.of(Publisher.class);
        0 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String))
        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
        1 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> ['foo', 'bar']
        2 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['foo'])
        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['X-Header', 'Y-Header'])
        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List,String)) >> Optional.of(['X-Header', 'Y-Header'])
        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class) >> Optional.of(HttpMethod.GET)
import io.micronaut.core.type.Argument;
                    Optional<HttpMethod> result = headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class);
                    Optional<List> allowedHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.of(List.class, String.class));
                    HttpMethod methodToMatch = preflight ? headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.class).orElse(requestMethod) : requestMethod;
                    Optional<List<String>> accessControlHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, Argument.listOf(String.class));
                .get(ALLOWED_ORIGINS, Argument.listOf(String.class))
                .get(ALLOWED_METHODS, Argument.listOf(HttpMethod.class))
                .get(ALLOWED_HEADERS, Argument.listOf(String.class))
                .get(EXPOSED_HEADERS, Argument.listOf(String.class))
                .get(ALLOW_CREDENTIALS, Boolean.class)
                .get(MAX_AGE, Long.class)
        return get(name, Integer.class);
        return getFirst(name, String.class);
        return getFirst(HttpHeaders.CONTENT_TYPE, MediaType.class);
        Optional<Long> optional = getFirst(HttpHeaders.CONTENT_LENGTH, Long.class);
            .flatMap(s -> ConversionService.SHARED.convert(s, MediaType.class).map(Stream::of).orElse(Stream.empty()))
        return getFirst(CONNECTION, String.class).map(val -> val.equalsIgnoreCase("keep-alive")).orElse(false);
            Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.of(Object.class));
        Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.of(Object.class));
import io.micronaut.core.type.Argument;
                    .getProperty(FileWatchConfiguration.PATHS, Argument.listOf(String.class))
        return Argument.of(Session.class);
Fixed code:     /**
     * Constant for Boolean argument.
     */
    ArgumentConversionContext<Boolean> BOOLEAN = ConversionContext.of(Argument.BOOLEAN);

    /**
     * Constant for Integer argument.
     */
    ArgumentConversionContext<Integer> INT = ConversionContext.of(Argument.INT);

    /**
     * Constant for Long argument.
     */
    ArgumentConversionContext<Long> LONG = ConversionContext.of(Argument.LONG);

    /**
     * Constant for String argument.
     */
    ArgumentConversionContext<String> STRING = ConversionContext.of(Argument.STRING);

    /**
     * Constant for List<String> argument.
     */
    ArgumentConversionContext<List<String>> LIST_OF_STRING = ConversionContext.of(Argument.LIST_OF_STRING);

import java.util.*;
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        DefaultArgumentConversionContext<?> that = (DefaultArgumentConversionContext<?>) o;
        return Objects.equals(getArgument(), that.getArgument()) &&
            Objects.equals(finalLocale, that.finalLocale) &&
            Objects.equals(finalCharset, that.finalCharset);
    }

    @Override
    public int hashCode() {
        return Objects.hash(argument, finalLocale, finalCharset);
    }

import io.micronaut.core.convert.ArgumentConversionContext;
    /**
     * Find a header and convert it to the given type.
     *
     * @param name              The name of the header
     * @param conversionContext The conversion context
     * @param <T>               The generic type
     * @return If the header is presented and can be converted an optional of the value otherwise {@link Optional#empty()}
     */
    default <T> Optional<T> getFirst(CharSequence name, ArgumentConversionContext<T> conversionContext) {
        V v = get(name);
        if (v != null) {
            return ConversionService.SHARED.convert(v, conversionContext);
        }
        return Optional.empty();
    }

        return get(name, Argument.OBJECT_ARGUMENT).isPresent();
    /**
     * Constant for string argument.
     */
    Argument<String> STRING = Argument.of(String.class);

    Argument<Integer> INT = Argument.of(int.class);
    Argument<Long> LONG = Argument.of(long.class);
    Argument<Float> FLOAT = Argument.of(float.class);
    Argument<Double> DOUBLE = Argument.of(double.class);
    Argument<Void> VOID = Argument.of(void.class);
    Argument<Byte> BYTE = Argument.of(byte.class);
    Argument<Boolean> BOOLEAN = Argument.of(boolean.class);
    Argument<Character> CHAR = Argument.of(char.class);
    Argument<Short> SHORT = Argument.of(short.class);
    /**
     * Constant for List<String> argument.
     */
    Argument<List<String>> LIST_OF_STRING = Argument.listOf(String.class);

        this(type, name, annotationMetadata, initializeTypeParameters(genericTypes), genericTypes);
    private static Map<String, Argument<?>> initializeTypeParameters(Argument[] genericTypes) {
            return Argument.STRING;
    private static final Argument ARGUMENT_PART_DATA = Argument.of(PartData.class);
                                    typeVariable = ARGUMENT_PART_DATA;
                                        typeVariable = ARGUMENT_PART_DATA;
    private static final Argument<CompletableFuture> TYPE = Argument.of(CompletableFuture.class);

        return TYPE;
    private static final Argument<Publisher> TYPE = Argument.of(Publisher.class);
        return TYPE;
import io.micronaut.core.convert.ConversionContext
        0 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String)))
        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
        1 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> ['foo', 'bar']
        2 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['foo'])
        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['X-Header', 'Y-Header'])
        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
        2 * headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.of(Argument.of(List,String))) >> Optional.of(['X-Header', 'Y-Header'])
        1 * headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, ConversionContext.of(HttpMethod.class)) >> Optional.of(HttpMethod.GET)
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionContext;
    private static final ArgumentConversionContext<HttpMethod> CONVERSION_CONTEXT_HTTP_METHOD = ConversionContext.of(HttpMethod.class);

                    Optional<HttpMethod> result = headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, CONVERSION_CONTEXT_HTTP_METHOD);
                    Optional<List<String>> allowedHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.LIST_OF_STRING);
                    HttpMethod methodToMatch = preflight ? headers.getFirst(ACCESS_CONTROL_REQUEST_METHOD, CONVERSION_CONTEXT_HTTP_METHOD).orElse(requestMethod) : requestMethod;
                    Optional<List<String>> accessControlHeaders = headers.get(ACCESS_CONTROL_REQUEST_HEADERS, ConversionContext.LIST_OF_STRING);
import io.micronaut.core.convert.ArgumentConversionContext;
import java.util.List;
    private static final ArgumentConversionContext<List<HttpMethod>> CONVERSION_CONTEXT_LIST_OF_HTTP_METHOD = ConversionContext.of(Argument.listOf(HttpMethod.class));

                .get(ALLOWED_ORIGINS, ConversionContext.LIST_OF_STRING)
                .get(ALLOWED_METHODS, CONVERSION_CONTEXT_LIST_OF_HTTP_METHOD)
                .get(ALLOWED_HEADERS, ConversionContext.LIST_OF_STRING)
                .get(EXPOSED_HEADERS, ConversionContext.LIST_OF_STRING)
                .get(ALLOW_CREDENTIALS, ConversionContext.BOOLEAN)
                .get(MAX_AGE, ConversionContext.LONG)
import io.micronaut.core.convert.ConversionContext;
        return get(name, ConversionContext.INT);
        return getFirst(name, ConversionContext.STRING);
        return getFirst(HttpHeaders.CONTENT_TYPE, MediaType.CONVERSION_CONTEXT);
        Optional<Long> optional = getFirst(HttpHeaders.CONTENT_LENGTH, ConversionContext.LONG);
            .flatMap(s -> ConversionService.SHARED.convert(s, MediaType.CONVERSION_CONTEXT).map(Stream::of).orElse(Stream.empty()))
        return getFirst(CONNECTION, ConversionContext.STRING).map(val -> val.equalsIgnoreCase("keep-alive")).orElse(false);
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.type.Argument;
    @Internal
    static final Argument<MediaType> ARGUMENT = Argument.of(MediaType.class);

    @Internal
    static final ArgumentConversionContext<MediaType> CONVERSION_CONTEXT = ConversionContext.of(ARGUMENT);

            Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
        Argument<?> firstTypeVariable = returnTypeObject.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
import io.micronaut.core.convert.ConversionContext;
                    .getProperty(FileWatchConfiguration.PATHS, ConversionContext.LIST_OF_STRING)
    private static final Argument<Session> TYPE = Argument.of(Session.class);

        return TYPE;