diff --git a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java
index d28816d52b30..06c28a0b5abf 100644
--- a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java
+++ b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java
@@ -184,6 +184,16 @@ public static BaseBuilder<?> options(String urlTemplate, @Nullable Object... uri
 		return method(HttpMethod.OPTIONS, urlTemplate, uriVars);
 	}
 
+	/**
+	 * HTTP POST variant. See {@link #get(String, Object...)} for general info.
+	 * @param urlTemplate a URL template; the resulting URL will be encoded
+	 * @param uriVars zero or more URI variables
+	 * @return the created builder
+	 */
+	public static BodyBuilder query(String urlTemplate, @Nullable Object... uriVars) {
+		return method(HttpMethod.QUERY, urlTemplate, uriVars);
+	}
+
 	/**
 	 * Create a builder with the given HTTP method and a {@link URI}.
 	 * @param method the HTTP method (GET, POST, etc)
diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java
index 60b39d57e76b..84d05bd4b7be 100644
--- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java
+++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java
@@ -165,6 +165,11 @@ public RequestHeadersUriSpec<?> options() {
 		return methodInternal(HttpMethod.OPTIONS);
 	}
 
+	@Override
+	public RequestBodyUriSpec query() {
+		return methodInternal(HttpMethod.QUERY);
+	}
+
 	@Override
 	public RequestBodyUriSpec method(HttpMethod httpMethod) {
 		return methodInternal(httpMethod);
diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
index b7018bcdda2f..c47a046fa1ff 100644
--- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
+++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
@@ -146,6 +146,12 @@ public interface WebTestClient {
 	 */
 	RequestHeadersUriSpec<?> options();
 
+	/**
+	 * Prepare an HTTP QUERY request.
+	 * @return a spec for specifying the target URL
+	 */
+	RequestBodyUriSpec query();
+
 	/**
 	 * Prepare a request for the specified {@code HttpMethod}.
 	 * @return a spec for specifying the target URL
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java
index 9c8cfcc50d03..af3651fee683 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java
@@ -333,6 +333,20 @@ public MockMvcRequestBuilder options() {
 		return method(HttpMethod.OPTIONS);
 	}
 
+	/**
+	 * Prepare an HTTP QUERY request.
+	 * <p>The returned builder can be wrapped in {@code assertThat} to enable
+	 * assertions on the result. For multi-statements assertions, use
+	 * {@link MockMvcRequestBuilder#exchange() exchange()} to assign the
+	 * result. To control the time to wait for asynchronous request to complete
+	 * on a per-request basis, use
+	 * {@link MockMvcRequestBuilder#exchange(Duration) exchange(Duration)}.
+	 * @return a request builder for specifying the target URI
+	 */
+	public MockMvcRequestBuilder query() {
+		return method(HttpMethod.QUERY);
+	}
+
 	/**
 	 * Prepare a request for the specified {@code HttpMethod}.
 	 * <p>The returned builder can be wrapped in {@code assertThat} to enable
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java
index abf479391090..f7560f302e79 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java
@@ -175,6 +175,24 @@ public static MockHttpServletRequestBuilder head(URI uri) {
 		return new MockHttpServletRequestBuilder(HttpMethod.HEAD).uri(uri);
 	}
 
+	/**
+	 * Create a {@link MockHttpServletRequestBuilder} for a QUERY request.
+	 * @param uriTemplate a URI template; the resulting URI will be encoded
+	 * @param uriVariables zero or more URI variables
+	 */
+	public static MockHttpServletRequestBuilder query(String uriTemplate, @Nullable Object... uriVariables) {
+		return new MockHttpServletRequestBuilder(HttpMethod.QUERY).uri(uriTemplate, uriVariables);
+	}
+
+	/**
+	 * Create a {@link MockHttpServletRequestBuilder} for a QUERY request.
+	 * @param uri the URI
+	 * @since x.x.x
+	 */
+	public static MockHttpServletRequestBuilder query(URI uri) {
+		return new MockHttpServletRequestBuilder(HttpMethod.QUERY).uri(uri);
+	}
+
 	/**
 	 * Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP method.
 	 * @param method the HTTP method (GET, POST, etc.)
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java
index 5c09d17fa085..4fc0696ad62d 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java
@@ -32,6 +32,7 @@
 import org.assertj.core.api.ThrowingConsumer;
 import org.junit.jupiter.api.Test;
 
+import org.junit.jupiter.params.ParameterizedTest;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
@@ -53,6 +54,7 @@
 import static org.assertj.core.api.Assertions.entry;
 import static org.springframework.http.HttpMethod.GET;
 import static org.springframework.http.HttpMethod.POST;
+import static org.springframework.http.HttpMethod.QUERY;
 
 /**
  * Tests for building a {@link MockHttpServletRequest} with
@@ -391,17 +393,20 @@ void requestParameterFromMultiValueMap() {
 	}
 
 	@Test
+	@ParameterizedTest()
 	void requestParameterFromRequestBodyFormData() {
 		String contentType = "application/x-www-form-urlencoded;charset=UTF-8";
 		String body = "name+1=value+1&name+2=value+A&name+2=value+B&name+3";
 
-		MockHttpServletRequest request = new MockHttpServletRequestBuilder(POST).uri("/foo")
-				.contentType(contentType).content(body.getBytes(UTF_8))
-				.buildRequest(this.servletContext);
+		for (HttpMethod method : List.of(POST, QUERY)) {
+			MockHttpServletRequest request = new MockHttpServletRequestBuilder(method).uri("/foo")
+					.contentType(contentType).content(body.getBytes(UTF_8))
+					.buildRequest(this.servletContext);
 
-		assertThat(request.getParameterMap().get("name 1")).containsExactly("value 1");
-		assertThat(request.getParameterMap().get("name 2")).containsExactly("value A", "value B");
-		assertThat(request.getParameterMap().get("name 3")).containsExactly((String) null);
+			assertThat(request.getParameterMap().get("name 1")).containsExactly("value 1");
+			assertThat(request.getParameterMap().get("name 2")).containsExactly("value A", "value B");
+			assertThat(request.getParameterMap().get("name 3")).containsExactly((String) null);
+		}
 	}
 
 	@Test
diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
index ee4d794e0e4c..499d757093f5 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
@@ -130,6 +130,12 @@ public class HttpHeaders implements Serializable {
 	 * @see <a href="https://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
 	 */
 	public static final String ACCEPT_RANGES = "Accept-Ranges";
+
+	/**
+	 * The HTTP {@code Accept-Query} header field name.
+	 * @see <a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html">IETF Draft</a>
+	 */
+	public static final String ACCEPT_QUERY = "Accept-Query";
 	/**
 	 * The CORS {@code Access-Control-Allow-Credentials} response header field name.
 	 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
@@ -635,6 +641,27 @@ public List<MediaType> getAcceptPatch() {
 		return MediaType.parseMediaTypes(get(ACCEPT_PATCH));
 	}
 
+	/**
+	 * Set the list of acceptable {@linkplain MediaType media types} for
+	 * {@code QUERY} methods, as specified by the {@code Accept-Query} header.
+	 * @since x.x.x
+	 */
+	public void setAcceptQuery(List<MediaType> mediaTypes) {
+		set(ACCEPT_QUERY, MediaType.toString(mediaTypes));
+	}
+
+	/**
+	 * Return the list of acceptable {@linkplain MediaType media types} for
+	 * {@code QUERY} methods, as specified by the {@code Accept-Query} header.
+	 * <p>Returns an empty list when the acceptable media types are unspecified.
+	 * @since x.x.x
+	 */
+	public List<MediaType> getAcceptQuery() {
+		return MediaType.parseMediaTypes(get(ACCEPT_QUERY));
+	}
+
+
+
 	/**
 	 * Set the (new) value of the {@code Access-Control-Allow-Credentials} response header.
 	 */
diff --git a/spring-web/src/main/java/org/springframework/http/HttpMethod.java b/spring-web/src/main/java/org/springframework/http/HttpMethod.java
index 95debcfbcaa2..390188e0b4fc 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpMethod.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpMethod.java
@@ -37,25 +37,25 @@ public final class HttpMethod implements Comparable<HttpMethod>, Serializable {
 
 	/**
 	 * The HTTP method {@code GET}.
-	 * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3">HTTP 1.1, section 9.3</a>
+	 * @see <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.1">HTTP Semantics, section 9.3.1</a>
 	 */
 	public static final HttpMethod GET = new HttpMethod("GET");
 
 	/**
 	 * The HTTP method {@code HEAD}.
-	 * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4">HTTP 1.1, section 9.4</a>
+	 * @see <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.2">HTTP Semantics, section 9.3.2</a>
 	 */
 	public static final HttpMethod HEAD = new HttpMethod("HEAD");
 
 	/**
 	 * The HTTP method {@code POST}.
-	 * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5">HTTP 1.1, section 9.5</a>
+	 * @see <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3">HTTP Semantics, section 9.3.3</a>
 	 */
 	public static final HttpMethod POST = new HttpMethod("POST");
 
 	/**
 	 * The HTTP method {@code PUT}.
-	 * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6">HTTP 1.1, section 9.6</a>
+	 * @see <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.4">HTTP Semantics, section 9.3.4</a>
 	 */
 	public static final HttpMethod PUT = new HttpMethod("PUT");
 
@@ -67,23 +67,29 @@ public final class HttpMethod implements Comparable<HttpMethod>, Serializable {
 
 	/**
 	 * The HTTP method {@code DELETE}.
-	 * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7">HTTP 1.1, section 9.7</a>
+	 * @see <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.5">HTTP Semantics, section 9.3.5</a>
 	 */
 	public static final HttpMethod DELETE = new HttpMethod("DELETE");
 
 	/**
 	 * The HTTP method {@code OPTIONS}.
-	 * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2">HTTP 1.1, section 9.2</a>
+	 * @see <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.7">HTTP Semantics, section 9.3.7</a>
 	 */
 	public static final HttpMethod OPTIONS = new HttpMethod("OPTIONS");
 
 	/**
 	 * The HTTP method {@code TRACE}.
-	 * @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8">HTTP 1.1, section 9.8</a>
+	 * @see <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.8">HTTP Semantics, section 9.3.8</a>
 	 */
 	public static final HttpMethod TRACE = new HttpMethod("TRACE");
 
-	private static final HttpMethod[] values = new HttpMethod[] { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE };
+	/**
+	 * The HTTP method {@code QUERY}.
+	 * @see <a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html">IETF Draft</a>
+	 */
+	public static final HttpMethod QUERY = new HttpMethod("QUERY");
+
+	private static final HttpMethod[] values = new HttpMethod[] { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, QUERY };
 
 
 	private final String name;
@@ -97,7 +103,7 @@ private HttpMethod(String name) {
 	 * Returns an array containing the standard HTTP methods. Specifically,
 	 * this method returns an array containing {@link #GET}, {@link #HEAD},
 	 * {@link #POST}, {@link #PUT}, {@link #PATCH}, {@link #DELETE},
-	 * {@link #OPTIONS}, and {@link #TRACE}.
+	 * {@link #OPTIONS}, {@link #TRACE}, and {@link #QUERY}.
 	 *
 	 * <p>Note that the returned value does not include any HTTP methods defined
 	 * in WebDav.
@@ -124,6 +130,7 @@ public static HttpMethod valueOf(String method) {
 			case "DELETE" -> DELETE;
 			case "OPTIONS" -> OPTIONS;
 			case "TRACE" -> TRACE;
+			case "QUERY" -> QUERY;
 			default -> new HttpMethod(method);
 		};
 	}
diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java
index 64fc4316f05d..f8847fe814f7 100644
--- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java
+++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java
@@ -382,6 +382,26 @@ public static BodyBuilder post(String uriTemplate, @Nullable Object... uriVariab
 		return method(HttpMethod.POST, uriTemplate, uriVariables);
 	}
 
+	/**
+	 * Create an HTTP QUERY builder with the given url.
+	 * @param url the URL
+	 * @return the created builder
+	 */
+	public static BodyBuilder query(URI url) {
+		return method(HttpMethod.QUERY, url);
+	}
+
+	/**
+	 * Create an HTTP QUERY builder with the given string base uri template.
+	 * @param uriTemplate the uri template to use
+	 * @param uriVariables variables to expand the URI template with
+	 * @return the created builder
+	 * @since x.x.x
+	 */
+	public static BodyBuilder query(String uriTemplate, Object... uriVariables) {
+		return method(HttpMethod.QUERY, uriTemplate, uriVariables);
+	}
+
 	/**
 	 * Create an HTTP PUT builder with the given url.
 	 * @param url the URL
diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
index d366eded4562..c82d608349b4 100644
--- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
+++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
@@ -32,6 +32,7 @@
 import org.apache.hc.client5.http.classic.methods.HttpPost;
 import org.apache.hc.client5.http.classic.methods.HttpPut;
 import org.apache.hc.client5.http.classic.methods.HttpTrace;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
 import org.apache.hc.client5.http.config.Configurable;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.classic.HttpClients;
@@ -345,6 +346,9 @@ else if (HttpMethod.OPTIONS.equals(httpMethod)) {
 		else if (HttpMethod.TRACE.equals(httpMethod)) {
 			return new HttpTrace(uri);
 		}
+		else if (HttpMethod.QUERY.equals(httpMethod)) {
+			return new HttpUriRequestBase(HttpMethod.QUERY.name(), uri);
+		}
 		throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
 	}
 
diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java
index bcba1c9953c1..3441e371e7e3 100644
--- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java
+++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java
@@ -132,6 +132,9 @@ public HttpHeaders getHeaders() {
 		if (HttpMethod.PATCH.equals(this.httpMethod)) {
 			headers.setAcceptPatch(getSupportedMediaTypes());
 		}
+		if (HttpMethod.QUERY.equals(this.httpMethod)) {
+			headers.setAcceptQuery(getSupportedMediaTypes());
+		}
 		return headers;
 	}
 
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java
index ef09e5124056..4a71e0f8ae1d 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java
@@ -44,6 +44,7 @@
  * @see PostMapping
  * @see PutMapping
  * @see PatchMapping
+ * @see QueryMapping
  * @see RequestMapping
  */
 @Target(ElementType.METHOD)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java
index 5c1e962ac419..4135f865c84f 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java
@@ -44,6 +44,7 @@
  * @see PutMapping
  * @see DeleteMapping
  * @see PatchMapping
+ * @see QueryMapping
  * @see RequestMapping
  */
 @Target(ElementType.METHOD)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java
index 1cce89e2545b..60902058c609 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java
@@ -44,6 +44,7 @@
  * @see PostMapping
  * @see PutMapping
  * @see DeleteMapping
+ * @see QueryMapping
  * @see RequestMapping
  */
 @Target(ElementType.METHOD)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java
index 8cfb36a6e389..943bac175015 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java
@@ -44,6 +44,7 @@
  * @see PutMapping
  * @see DeleteMapping
  * @see PatchMapping
+ * @see QueryMapping
  * @see RequestMapping
  */
 @Target(ElementType.METHOD)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java
index 8cfc46101a93..86f551eb783f 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java
@@ -44,6 +44,7 @@
  * @see PostMapping
  * @see DeleteMapping
  * @see PatchMapping
+ * @see QueryMapping
  * @see RequestMapping
  */
 @Target(ElementType.METHOD)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/QueryMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/QueryMapping.java
new file mode 100644
index 000000000000..42af68d8a0d6
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/QueryMapping.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation for mapping HTTP {@code QUERY} requests onto specific handler
+ * methods.
+ *
+ * <p>Specifically, {@code @QueryMapping} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.QUERY)}.
+ *
+ * <p><strong>NOTE:</strong> This annotation cannot be used in conjunction with
+ * other {@code @RequestMapping} annotations that are declared on the same method.
+ * If multiple {@code @RequestMapping} annotations are detected on the same method,
+ * a warning will be logged, and only the first mapping will be used. This applies
+ * to {@code @RequestMapping} as well as composed {@code @RequestMapping} annotations
+ * such as {@code @GetMapping}, {@code @PutMapping}, etc.
+ *
+ * @author Mario Ruiz
+ * @since x.x.x
+ * @see GetMapping
+ * @see PutMapping
+ * @see PostMapping
+ * @see DeleteMapping
+ * @see PatchMapping
+ * @see RequestMapping
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.QUERY)
+public @interface QueryMapping {
+
+	/**
+	 * Alias for {@link RequestMapping#name}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String name() default "";
+
+	/**
+	 * Alias for {@link RequestMapping#value}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String[] value() default {};
+
+	/**
+	 * Alias for {@link RequestMapping#path}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String[] path() default {};
+
+	/**
+	 * Alias for {@link RequestMapping#params}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String[] params() default {};
+
+	/**
+	 * Alias for {@link RequestMapping#headers}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String[] headers() default {};
+
+	/**
+	 * Alias for {@link RequestMapping#consumes}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String[] consumes() default {};
+
+	/**
+	 * Alias for {@link RequestMapping#produces}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String[] produces() default {};
+
+	/**
+	 * Alias for {@link RequestMapping#version()}.
+	 */
+	@AliasFor(annotation = RequestMapping.class)
+	String version() default "";
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index 6cb0386e8482..663f51f5537d 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -51,8 +51,8 @@
  * at the method level. In most cases, at the method level applications will
  * prefer to use one of the HTTP method specific variants
  * {@link GetMapping @GetMapping}, {@link PostMapping @PostMapping},
- * {@link PutMapping @PutMapping}, {@link DeleteMapping @DeleteMapping}, or
- * {@link PatchMapping @PatchMapping}.
+ * {@link PutMapping @PutMapping}, {@link DeleteMapping @DeleteMapping},
+ * {@link PatchMapping @PatchMapping}, or {@link QueryMapping}.
  *
  * <p><strong>NOTE:</strong> This annotation cannot be used in conjunction with
  * other {@code @RequestMapping} annotations that are declared on the same element
@@ -75,6 +75,7 @@
  * @see PutMapping
  * @see DeleteMapping
  * @see PatchMapping
+ * @see QueryMapping
  */
 @Target({ElementType.TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
@@ -121,7 +122,7 @@
 
 	/**
 	 * The HTTP request methods to map to, narrowing the primary mapping:
-	 * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
+	 * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE, QUERY.
 	 * <p><b>Supported at the type level as well as at the method level!</b>
 	 * When used at the type level, all method-level mappings inherit this
 	 * HTTP method restriction.
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
index b1f5438b9048..e1ca546ead43 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
@@ -26,7 +26,7 @@
  * {@link RequestMapping#method()} attribute of the {@link RequestMapping} annotation.
  *
  * <p>Note that, by default, {@link org.springframework.web.servlet.DispatcherServlet}
- * supports GET, HEAD, POST, PUT, PATCH, and DELETE only. DispatcherServlet will
+ * supports GET, QUERY, HEAD, POST, PUT, PATCH, and DELETE only. DispatcherServlet will
  * process TRACE and OPTIONS with the default HttpServlet behavior unless explicitly
  * told to dispatch those request types as well: Check out the "dispatchOptionsRequest"
  * and "dispatchTraceRequest" properties, switching them to "true" if necessary.
@@ -39,7 +39,7 @@
  */
 public enum RequestMethod {
 
-	GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
+	GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, QUERY;
 
 
 	/**
@@ -60,6 +60,7 @@ public enum RequestMethod {
 			case "DELETE" -> DELETE;
 			case "OPTIONS" -> OPTIONS;
 			case "TRACE" -> TRACE;
+			case "QUERY" -> QUERY;
 			default -> null;
 		};
 	}
@@ -92,6 +93,7 @@ public HttpMethod asHttpMethod() {
 			case DELETE -> HttpMethod.DELETE;
 			case OPTIONS -> HttpMethod.OPTIONS;
 			case TRACE -> HttpMethod.TRACE;
+			case QUERY -> HttpMethod.QUERY;
 		};
 	}
 
diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java
index d39e43bd5f20..4db69a3f9b1a 100644
--- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java
+++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java
@@ -193,6 +193,11 @@ public RequestHeadersUriSpec<?> options() {
 		return methodInternal(HttpMethod.OPTIONS);
 	}
 
+	@Override
+	public RequestHeadersUriSpec<?> query() {
+		return methodInternal(HttpMethod.QUERY);
+	}
+
 	@Override
 	public RequestBodyUriSpec method(HttpMethod method) {
 		Assert.notNull(method, "HttpMethod must not be null");
diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClient.java b/spring-web/src/main/java/org/springframework/web/client/RestClient.java
index 9804bf55bab8..4b8b5ce2d9f0 100644
--- a/spring-web/src/main/java/org/springframework/web/client/RestClient.java
+++ b/spring-web/src/main/java/org/springframework/web/client/RestClient.java
@@ -123,6 +123,12 @@ public interface RestClient {
 	 */
 	RequestHeadersUriSpec<?> options();
 
+	/**
+	 * Start building an HTTP QUERY request.
+	 * @return a spec for specifying the target URL
+	 */
+	RequestHeadersUriSpec<?> query();
+
 	/**
 	 * Start building a request for the given {@code HttpMethod}.
 	 * @return a spec for specifying the target URL
diff --git a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
index 6873edf5d665..c8e6049f9d4f 100644
--- a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
+++ b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
@@ -63,9 +63,9 @@ public class CorsConfiguration {
 
 	private static final List<String> DEFAULT_PERMIT_ALL = Collections.singletonList(ALL);
 
-	private static final List<HttpMethod> DEFAULT_METHODS = List.of(HttpMethod.GET, HttpMethod.HEAD);
+	private static final List<HttpMethod> DEFAULT_METHODS = List.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
 
-	private static final List<String> DEFAULT_PERMIT_METHODS = List.of(HttpMethod.GET.name(),
+	private static final List<String> DEFAULT_PERMIT_METHODS = List.of(HttpMethod.GET.name(), HttpMethod.QUERY.name(),
 			HttpMethod.HEAD.name(), HttpMethod.POST.name());
 
 
diff --git a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
index 3564cc8629ca..0b826637e71c 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
@@ -151,7 +151,7 @@ protected boolean isEligibleForEtag(HttpServletRequest request, HttpServletRespo
 
 		if (!response.isCommitted() &&
 				responseStatusCode >= 200 && responseStatusCode < 300 &&
-				HttpMethod.GET.matches(request.getMethod())) {
+				(HttpMethod.GET.matches(request.getMethod()) || HttpMethod.QUERY.matches(request.getMethod()))) {
 
 			String cacheControl = response.getHeader(HttpHeaders.CACHE_CONTROL);
 			return (cacheControl == null || !cacheControl.contains(DIRECTIVE_NO_STORE));
diff --git a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java
index f38dd40b9501..14ca50d4b38e 100644
--- a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java
+++ b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java
@@ -161,6 +161,9 @@ public HttpHeaders getHeaders() {
 		if (this.method == HttpMethod.PATCH) {
 			headers.setAcceptPatch(this.supportedMediaTypes);
 		}
+		if (this.method == HttpMethod.QUERY) {
+			headers.setAcceptQuery(this.supportedMediaTypes);
+		}
 		return headers;
 	}
 
diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java
index a3ec76997b46..6a61fcfd451a 100644
--- a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java
+++ b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java
@@ -66,7 +66,7 @@
  */
 public class DefaultServerWebExchange implements ServerWebExchange {
 
-	private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD);
+	private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
 
 	private static final ResolvableType FORM_DATA_TYPE =
 			ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
diff --git a/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchange.java b/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchange.java
index 745e59533d5b..2b12bed9eaa6 100644
--- a/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchange.java
+++ b/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchange.java
@@ -51,6 +51,7 @@
  * <li>{@link PutExchange}
  * <li>{@link PatchExchange}
  * <li>{@link DeleteExchange}
+ * <li>{@link QueryExchange}
  * </ul>
  *
  * <p>Supported method arguments:
diff --git a/spring-web/src/main/java/org/springframework/web/service/annotation/QueryExchange.java b/spring-web/src/main/java/org/springframework/web/service/annotation/QueryExchange.java
new file mode 100644
index 000000000000..21e04047c4ce
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/service/annotation/QueryExchange.java
@@ -0,0 +1,53 @@
+package org.springframework.web.service.annotation;
+
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * Shortcut for {@link HttpExchange @HttpExchange} for HTTP QUERY requests.
+ *
+ * @author Mario Ruiz
+ * @since x.x.x
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@HttpExchange(method = "QUERY")
+public @interface QueryExchange {
+	/**
+	 * Alias for {@link HttpExchange#value}.
+	 */
+	@AliasFor(annotation = HttpExchange.class)
+	String value() default "";
+
+	/**
+	 * Alias for {@link HttpExchange#url()}.
+	 */
+	@AliasFor(annotation = HttpExchange.class)
+	String url() default "";
+
+	/**
+	 * Alias for {@link HttpExchange#contentType()}.
+	 */
+	@AliasFor(annotation = HttpExchange.class)
+	String contentType() default "";
+
+	/**
+	 * Alias for {@link HttpExchange#accept()}.
+	 */
+	@AliasFor(annotation = HttpExchange.class)
+	String[] accept() default {};
+
+	/**
+	 * Alias for {@link HttpExchange#headers()}.
+	 */
+	@AliasFor(annotation = HttpExchange.class)
+	String[] headers() default {};
+
+	/**
+	 * Alias for {@link HttpExchange#version()}.
+	 */
+	@AliasFor(annotation = HttpExchange.class)
+	String version() default "";
+}
diff --git a/spring-web/src/main/kotlin/org/springframework/web/client/RestOperationsExtensions.kt b/spring-web/src/main/kotlin/org/springframework/web/client/RestOperationsExtensions.kt
index 61ebf19faa1a..6b291f1606de 100644
--- a/spring-web/src/main/kotlin/org/springframework/web/client/RestOperationsExtensions.kt
+++ b/spring-web/src/main/kotlin/org/springframework/web/client/RestOperationsExtensions.kt
@@ -18,9 +18,11 @@ package org.springframework.web.client
 
 import org.springframework.core.ParameterizedTypeReference
 import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
 import org.springframework.http.HttpMethod
 import org.springframework.http.RequestEntity
 import org.springframework.http.ResponseEntity
+import org.springframework.web.client.queryForEntity
 import java.lang.Class
 import java.net.URI
 import kotlin.reflect.KClass
@@ -291,3 +293,45 @@ inline fun <reified T : Any> RestOperations.exchange(url: URI, method: HttpMetho
 @Throws(RestClientException::class)
 inline fun <reified T : Any> RestOperations.exchange(requestEntity: RequestEntity<*>): ResponseEntity<T> =
 		exchange(requestEntity, object : ParameterizedTypeReference<T>() {})
+
+/**
+ * Extension for [RestOperations.postForEntity] providing a `postForEntity<Foo>(...)`
+ * variant leveraging Kotlin reified type parameters. Like the original Java method, this
+ * extension is subject to type erasure. Use [exchange] if you need to retain actual
+ * generic type arguments.
+ *
+ * @author Mario Ruiz
+ * @since x.x.x
+ */
+@Throws(RestClientException::class)
+inline fun <reified T : Any> RestOperations.queryForEntity(url: String, request: Any? = null,
+														  vararg uriVariables: Any?): ResponseEntity<T> =
+	exchange(url = url, method =  HttpMethod.QUERY, requestEntity = HttpEntity(request, null as (HttpHeaders?) ), uriVariables= uriVariables)
+
+/**
+ * Extension for [RestOperations.postForEntity] providing a `postForEntity<Foo>(...)`
+ * variant leveraging Kotlin reified type parameters. Like the original Java method, this
+ * extension is subject to type erasure. Use [exchange] if you need to retain actual
+ * generic type arguments.
+ *
+ * @author Mario Ruiz
+ * @since x.x.x
+ */
+@Throws(RestClientException::class)
+inline fun <reified T : Any> RestOperations.queryForEntity(url: String, request: Any? = null,
+														  uriVariables: Map<String, *>): ResponseEntity<T> =
+	exchange(url = url, method =  HttpMethod.QUERY, requestEntity = HttpEntity(request, null as (HttpHeaders?) ), uriVariables= uriVariables)
+
+/**
+ * Extension for [RestOperations.postForEntity] providing a `postForEntity<Foo>(...)`
+ * variant leveraging Kotlin reified type parameters. Like the original Java method, this
+ * extension is subject to type erasure. Use [exchange] if you need to retain actual
+ * generic type arguments.
+ *
+ * @author Mario Ruiz
+ * @since x.x.x
+ */
+@Throws(RestClientException::class)
+inline fun <reified T: Any> RestOperations.queryForEntity(url: URI, request: Any? = null): ResponseEntity<T> =
+	exchange(url = url, method =  HttpMethod.QUERY, requestEntity = HttpEntity(request, null as (HttpHeaders?) ))
+
diff --git a/spring-web/src/test/java/org/springframework/http/HttpMethodTests.java b/spring-web/src/test/java/org/springframework/http/HttpMethodTests.java
index 055149ea53e1..eb5aff2f6975 100644
--- a/spring-web/src/test/java/org/springframework/http/HttpMethodTests.java
+++ b/spring-web/src/test/java/org/springframework/http/HttpMethodTests.java
@@ -44,12 +44,12 @@ void comparison() {
 	void values() {
 		HttpMethod[] values = HttpMethod.values();
 		assertThat(values).containsExactly(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST, HttpMethod.PUT,
-				HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.OPTIONS, HttpMethod.TRACE);
+				HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.OPTIONS, HttpMethod.TRACE, HttpMethod.QUERY);
 
 		// check defensive copy
 		values[0] = HttpMethod.POST;
 		assertThat(HttpMethod.values()).containsExactly(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST, HttpMethod.PUT,
-				HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.OPTIONS, HttpMethod.TRACE);
+				HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.OPTIONS, HttpMethod.TRACE, HttpMethod.QUERY);
 	}
 
 	@Test
diff --git a/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java b/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java
index b46afe89e954..f8969a3979b0 100644
--- a/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java
@@ -213,6 +213,11 @@ Response get() {
 		void post(@RequestBody Request request) {
 		}
 
+		@QueryMapping
+		Response query(@RequestBody Request request) {
+			return new Response("response");
+		}
+
 		@PostMapping
 		void postForm(@ModelAttribute Request request) {
 		}
@@ -247,6 +252,17 @@ void postRawHttpEntity(HttpEntity entity) {
 		void postPartToConvert(@RequestPart Request request) {
 		}
 
+		@QueryMapping
+		HttpEntity<Response> querytHttpEntity(HttpEntity<Request> entity) {
+			return new HttpEntity<>(new Response("response"));
+		}
+
+		@QueryMapping
+		@SuppressWarnings("rawtypes")
+		HttpEntity queryRawHttpEntity(HttpEntity entity) {
+			return new HttpEntity(new Response("response"));
+		}
+
 	}
 
 	@RestController
diff --git a/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java b/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java
index 18b8815e9595..05384e732423 100644
--- a/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java
@@ -74,7 +74,7 @@ void tearDown() throws Exception {
 
 	private MockResponse getRequest(RecordedRequest request, byte[] body, String contentType) {
 		if (request.getMethod().equals("OPTIONS")) {
-			return new MockResponse().setResponseCode(200).setHeader("Allow", "GET, OPTIONS, HEAD, TRACE");
+			return new MockResponse().setResponseCode(200).setHeader("Allow", "GET, QUERY, OPTIONS, HEAD, TRACE");
 		}
 		Buffer buf = new Buffer();
 		buf.write(body);
@@ -231,6 +231,28 @@ private MockResponse putRequest(RecordedRequest request, String expectedRequestC
 		return new MockResponse().setResponseCode(202);
 	}
 
+	private MockResponse queryRequest(RecordedRequest request, String expectedRequestContent,
+									  String contentType, byte[] responseBody) {
+
+		assertThat(request.getHeaders().values(CONTENT_LENGTH)).hasSize(1);
+		assertThat(Integer.parseInt(request.getHeader(CONTENT_LENGTH))).as("Invalid request content-length").isGreaterThan(0);
+		String requestContentType = request.getHeader(CONTENT_TYPE);
+		assertThat(requestContentType).as("No content-type").isNotNull();
+		Charset charset = StandardCharsets.ISO_8859_1;
+		if (requestContentType.contains("charset=")) {
+			String charsetName = requestContentType.split("charset=")[1];
+			charset = Charset.forName(charsetName);
+		}
+		assertThat(request.getBody().readString(charset)).as("Invalid request body").isEqualTo(expectedRequestContent);
+		Buffer buf = new Buffer();
+		buf.write(responseBody);
+		return new MockResponse()
+				.setHeader(CONTENT_TYPE, contentType)
+				.setHeader(CONTENT_LENGTH, responseBody.length)
+				.setBody(buf)
+				.setResponseCode(200);
+	}
+
 
 	protected class TestDispatcher extends Dispatcher {
 
@@ -293,6 +315,9 @@ else if (request.getPath().equals("/patch")) {
 				else if (request.getPath().equals("/put")) {
 					return putRequest(request, helloWorld);
 				}
+				else if (request.getPath().equals("/query")) {
+					return queryRequest(request, helloWorld, textContentType.toString(), helloWorldBytes);
+				}
 				return new MockResponse().setResponseCode(404);
 			}
 			catch (Throwable ex) {
diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java
index 022309b30813..b8910210dc4d 100644
--- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java
@@ -290,7 +290,7 @@ void optionsForAllow(ClientHttpRequestFactory clientHttpRequestFactory) {
 		setUpClient(clientHttpRequestFactory);
 
 		Set<HttpMethod> allowed = template.optionsForAllow(URI.create(baseUrl + "/get"));
-		assertThat(allowed).as("Invalid response").isEqualTo(Set.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.TRACE));
+		assertThat(allowed).as("Invalid response").isEqualTo(Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.TRACE));
 	}
 
 	@ParameterizedRestTemplateTest
diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
index 4c421a56bded..13b047918e94 100644
--- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
@@ -43,6 +43,7 @@
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.http.RequestEntity;
 import org.springframework.http.client.ClientHttpRequest;
 import org.springframework.http.client.ClientHttpRequestFactory;
 import org.springframework.http.client.ClientHttpRequestInitializer;
@@ -75,6 +76,7 @@
 import static org.springframework.http.HttpMethod.PATCH;
 import static org.springframework.http.HttpMethod.POST;
 import static org.springframework.http.HttpMethod.PUT;
+import static org.springframework.http.HttpMethod.QUERY;
 import static org.springframework.http.MediaType.parseMediaType;
 
 /**
@@ -478,6 +480,46 @@ void postForEntityNull() throws Exception {
 		verify(response).close();
 	}
 
+	@Test
+	void queryForEntity() throws Exception {
+		mockTextPlainHttpMessageConverter();
+		HttpHeaders requestHeaders = new HttpHeaders();
+		mockSentRequest(QUERY, "https://example.com", requestHeaders);
+		mockResponseStatus(HttpStatus.OK);
+		String expected = "42";
+		mockResponseBody(expected, MediaType.TEXT_PLAIN);
+
+		ResponseEntity<String> result = template.exchange(RequestEntity.query("https://example.com").body("Hello World"), String.class);
+		assertThat(result.getBody()).as("Invalid QUERY result").isEqualTo(expected);
+		assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type").isEqualTo(MediaType.TEXT_PLAIN);
+		assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
+		assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
+
+		verify(response).close();
+	}
+
+	@Test
+	void queryForEntityNull() throws Exception {
+		mockTextPlainHttpMessageConverter();
+		HttpHeaders requestHeaders = new HttpHeaders();
+		mockSentRequest(QUERY, "https://example.com", requestHeaders);
+		mockResponseStatus(HttpStatus.OK);
+		HttpHeaders responseHeaders = new HttpHeaders();
+		responseHeaders.setContentType(MediaType.TEXT_PLAIN);
+		responseHeaders.setContentLength(10);
+		given(response.getHeaders()).willReturn(responseHeaders);
+		given(response.getBody()).willReturn(InputStream.nullInputStream());
+		given(converter.read(String.class, response)).willReturn(null);
+
+		ResponseEntity<String> result = template.exchange("https://example.com",QUERY, null, String.class);
+		assertThat(result.hasBody()).as("Invalid QUERY result").isFalse();
+		assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type").isEqualTo(MediaType.TEXT_PLAIN);
+		assertThat(requestHeaders.getContentLength()).as("Invalid content length").isEqualTo(0);
+		assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
+
+		verify(response).close();
+	}
+
 	@Test
 	void put() throws Exception {
 		mockTextPlainHttpMessageConverter();
diff --git a/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
index 092781475f94..1f8fb86ea426 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
@@ -140,7 +140,7 @@ void combineWithDefaultPermitValues() {
 		assertThat(config.getAllowedHeaders()).containsExactly("*");
 		assertThat(combinedConfig).isNotNull();
 		assertThat(combinedConfig.getAllowedMethods())
-				.containsExactly(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
+				.containsExactly(HttpMethod.GET.name(), HttpMethod.QUERY.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
 		assertThat(combinedConfig.getExposedHeaders()).isEmpty();
 
 		combinedConfig = new CorsConfiguration().combine(config);
@@ -148,7 +148,7 @@ void combineWithDefaultPermitValues() {
 		assertThat(config.getAllowedHeaders()).containsExactly("*");
 		assertThat(combinedConfig).isNotNull();
 		assertThat(combinedConfig.getAllowedMethods())
-				.containsExactly(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
+				.containsExactly(HttpMethod.GET.name(), HttpMethod.QUERY.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
 		assertThat(combinedConfig.getExposedHeaders()).isEmpty();
 	}
 
@@ -394,7 +394,9 @@ void checkOriginPatternNotAllowed() {
 	@Test
 	void checkMethodAllowed() {
 		CorsConfiguration config = new CorsConfiguration();
-		assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.HEAD);
+		assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
+		assertThat(config.checkHttpMethod(HttpMethod.QUERY)).containsExactly(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
+
 
 		config.addAllowedMethod("GET");
 		assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET);
@@ -450,7 +452,7 @@ void changePermitDefaultValues() {
 
 		assertThat(config.getAllowedOrigins()).containsExactly("*", "https://domain.com");
 		assertThat(config.getAllowedHeaders()).containsExactly("*", "header1");
-		assertThat(config.getAllowedMethods()).containsExactly("GET", "HEAD", "POST", "PATCH");
+		assertThat(config.getAllowedMethods()).containsExactly("GET", "QUERY", "HEAD", "POST", "PATCH");
 	}
 
 	@Test
diff --git a/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java b/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
index bcb4ae852365..b5add6057149 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
@@ -252,7 +252,7 @@ void preflightRequestMatchedAllowedMethod() throws Exception {
 
 		this.processor.processRequest(this.conf, this.request, this.response);
 		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
-		assertThat(this.response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET,HEAD");
+		assertThat(this.response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET,QUERY,HEAD");
 		assertThat(this.response.getHeaders(HttpHeaders.VARY)).contains(HttpHeaders.ORIGIN,
 				HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
 	}
diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java
index 988175990506..38253acdd0db 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java
@@ -263,7 +263,7 @@ void preflightRequestMatchedAllowedMethod() {
 		assertThat(response.getStatusCode()).isNull();
 		assertThat(response.getHeaders().get(VARY)).contains(ORIGIN,
 				ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS);
-		assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET,HEAD");
+		assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET,QUERY,HEAD");
 	}
 
 	@Test
diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/MvcAnnotationPredicates.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/MvcAnnotationPredicates.java
index 9bde5d3eab40..81bc715fcb48 100644
--- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/MvcAnnotationPredicates.java
+++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/MvcAnnotationPredicates.java
@@ -113,6 +113,10 @@ public static RequestMappingPredicate headMapping(String... path) {
 		return new RequestMappingPredicate(path).method(RequestMethod.HEAD);
 	}
 
+	public static RequestMappingPredicate queryMapping(String... path) {
+		return new RequestMappingPredicate(path).method(RequestMethod.QUERY);
+	}
+
 
 
 	public static class ModelAttributePredicate implements Predicate<MethodParameter> {
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
index f16d0c1a4637..49f025250942 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
@@ -177,6 +177,11 @@ public RequestHeadersUriSpec<?> options() {
 		return methodInternal(HttpMethod.OPTIONS);
 	}
 
+	@Override
+	public RequestHeadersUriSpec<?> query() {
+		return methodInternal(HttpMethod.QUERY);
+	}
+
 	@Override
 	public RequestBodyUriSpec method(HttpMethod httpMethod) {
 		return methodInternal(httpMethod);
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java
index 1b07d1016517..f254a1864e2f 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java
@@ -123,6 +123,12 @@ public interface WebClient {
 	 */
 	RequestHeadersUriSpec<?> options();
 
+	/**
+	 * Start building an HTTP QUERY request.
+	 * @return a spec for specifying the target URL
+	 */
+	RequestHeadersUriSpec<?> query();
+
 	/**
 	 * Start building a request for the given {@code HttpMethod}.
 	 * @return a spec for specifying the target URL
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java
index 090f0450b02e..67151f2c001d 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java
@@ -288,7 +288,7 @@ public Mono<ServerResponse> render(String name, Map<String, ?> model) {
 	 */
 	abstract static class AbstractServerResponse implements ServerResponse {
 
-		private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD);
+		private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
 
 		private final HttpStatusCode statusCode;
 
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java
index 6ee7be2c2c21..d3697d997999 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java
@@ -266,6 +266,18 @@ public static RequestPredicate OPTIONS(String pattern) {
 		return method(HttpMethod.OPTIONS).and(path(pattern));
 	}
 
+	/**
+	 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code QUERY}
+	 * and the given {@code pattern} matches against the request path.
+	 * @param pattern the path pattern to match against
+	 * @return a predicate that matches if the request method is QUERY and if the given pattern
+	 * matches against the request path
+	 * @see org.springframework.web.util.pattern.PathPattern
+	 */
+	public static RequestPredicate QUERY(String pattern) {
+		return method(HttpMethod.QUERY).and(path(pattern));
+	}
+
 	/**
 	 * Return a {@code RequestPredicate} that matches if the request's path has the given extension.
 	 * @param extension the path extension to match against, ignoring case
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java
index 56ea5b1fb735..c82c9c13470f 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ResourceHandlerFunction.java
@@ -43,7 +43,7 @@
 class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
 
 	private static final Set<HttpMethod> SUPPORTED_METHODS =
-			Set.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS);
+			Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD, HttpMethod.OPTIONS);
 
 
 	private final Resource resource;
@@ -66,6 +66,12 @@ public Mono<ServerResponse> handle(ServerRequest request) {
 					.build()
 					.map(response -> response);
 		}
+		else if (HttpMethod.QUERY.equals(method)) {
+			return EntityResponse.fromObject(this.resource)
+					.headers(headers -> this.headersConsumer.accept(this.resource, headers))
+					.build()
+					.map(response -> response);
+		}
 		else if (HttpMethod.HEAD.equals(method)) {
 			Resource headResource = new HeadMethodResource(this.resource);
 			return EntityResponse.fromObject(headResource)
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java
index e8133b2f5cc0..76bd64dcb18d 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java
@@ -231,6 +231,30 @@ public RouterFunctions.Builder OPTIONS(String pattern, RequestPredicate predicat
 		return add(RequestPredicates.OPTIONS(pattern).and(predicate), handlerFunction);
 	}
 
+	// QUERY
+
+	@Override
+	public RouterFunctions.Builder QUERY(HandlerFunction<ServerResponse> handlerFunction) {
+		return add(RequestPredicates.method(HttpMethod.QUERY), handlerFunction);
+	}
+
+	@Override
+	public RouterFunctions.Builder QUERY(RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction) {
+		return add(RequestPredicates.method(HttpMethod.QUERY).and(predicate), handlerFunction);
+	}
+
+	@Override
+	public RouterFunctions.Builder QUERY(String pattern, HandlerFunction<ServerResponse> handlerFunction) {
+		return add(RequestPredicates.QUERY(pattern), handlerFunction);
+	}
+
+	@Override
+	public RouterFunctions.Builder QUERY(String pattern, RequestPredicate predicate,
+			HandlerFunction<ServerResponse> handlerFunction) {
+
+		return add(RequestPredicates.QUERY(pattern).and(predicate), handlerFunction);
+	}
+
 	// other
 
 	@Override
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java
index 8331ceb0f5aa..ad1397f01cd7 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java
@@ -696,6 +696,58 @@ public interface Builder {
 		 */
 		Builder OPTIONS(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
 
+		/**
+		 * Adds a route to the given handler function that handles HTTP {@code QUERY} requests.
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests
+		 * @return this builder
+		 * @since x.x.x
+		 */
+		Builder QUERY(HandlerFunction<ServerResponse> handlerFunction);
+
+		/**
+		 * Adds a route to the given handler function that handles all HTTP {@code QUERY} requests
+		 * that match the given pattern.
+		 * @param pattern the pattern to match to
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests that
+		 * match {@code pattern}
+		 * @return this builder
+		 * @see org.springframework.web.util.pattern.PathPattern
+		 */
+		Builder QUERY(String pattern, HandlerFunction<ServerResponse> handlerFunction);
+
+		/**
+		 * Adds a route to the given handler function that handles all HTTP {@code QUERY} requests
+		 * that match the given predicate.
+		 * @param predicate predicate to match
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests that
+		 * match {@code predicate}
+		 * @return this builder
+		 * @since x.x.x
+		 * @see RequestPredicates
+		 */
+		Builder QUERY(RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
+
+		/**
+		 * Adds a route to the given handler function that handles all HTTP {@code QUERY} requests
+		 * that match the given pattern and predicate.
+		 * <p>For instance, the following example routes QUERY requests for "/user" that contain JSON
+		 * to the {@code addUser} method in {@code userController}:
+		 * <pre class="code">
+		 * RouterFunction&lt;ServerResponse&gt; route =
+		 *   RouterFunctions.route()
+		 *     .QUERY("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON), userController::addUser)
+		 *     .build();
+		 * </pre>
+		 * @param pattern the pattern to match to
+		 * @param predicate additional predicate to match
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests that
+		 * match {@code pattern}
+		 * @return this builder
+		 * @see org.springframework.web.util.pattern.PathPattern
+		 */
+		Builder QUERY(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
+
+
 		/**
 		 * Adds a route to the given handler function that handles all requests that match the
 		 * given predicate.
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java
index d108080a1082..487998146350 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java
@@ -85,7 +85,7 @@
  */
 public class ResourceWebHandler implements WebHandler, InitializingBean {
 
-	private static final Set<HttpMethod> SUPPORTED_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD);
+	private static final Set<HttpMethod> SUPPORTED_METHODS = Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
 
 	private static final Log logger = LogFactory.getLog(ResourceWebHandler.class);
 
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestCondition.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestCondition.java
index 9289aee8fea0..4257c366f246 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestCondition.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestCondition.java
@@ -157,6 +157,9 @@ else if (isEmpty()) {
 			if (requestMethod.equals(RequestMethod.HEAD) && getMethods().contains(RequestMethod.GET)) {
 				return requestMethodConditionCache.get(HttpMethod.GET);
 			}
+			if (requestMethod.equals(RequestMethod.HEAD) && getMethods().contains(RequestMethod.QUERY)) {
+				return requestMethodConditionCache.get(HttpMethod.QUERY);
+			}
 		}
 		return null;
 	}
@@ -184,6 +187,9 @@ else if (this.methods.size() == 1) {
 			else if (this.methods.contains(RequestMethod.GET) && other.methods.contains(RequestMethod.HEAD)) {
 				return 1;
 			}
+			else if (this.methods.contains(RequestMethod.QUERY) && other.methods.contains(RequestMethod.HEAD)) {
+				return 1;
+			}
 		}
 		return 0;
 	}
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java
index 73e8146714fe..d1b345fa0a62 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java
@@ -189,8 +189,9 @@ protected void handleMatch(RequestMappingInfo info, HandlerMethod handlerMethod,
 			HttpMethod httpMethod = request.getMethod();
 			Set<HttpMethod> methods = helper.getAllowedMethods();
 			if (HttpMethod.OPTIONS.equals(httpMethod)) {
-				Set<MediaType> mediaTypes = helper.getConsumablePatchMediaTypes();
-				HttpOptionsHandler handler = new HttpOptionsHandler(methods, mediaTypes);
+				Set<MediaType> patchMediaTypes = helper.getConsumablePatchMediaTypes();
+				Set<MediaType> queryMediaTypes = helper.getConsumableQueryMediaTypes();
+				HttpOptionsHandler handler = new HttpOptionsHandler(methods, patchMediaTypes, queryMediaTypes);
 				return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
 			}
 			throw new MethodNotAllowedException(httpMethod, methods);
@@ -323,14 +324,23 @@ public List<Set<NameValueExpression<String>>> getParamConditions() {
 		 * PATCH specified, or that have no methods at all.
 		 */
 		public Set<MediaType> getConsumablePatchMediaTypes() {
-			Set<MediaType> result = new LinkedHashSet<>();
-			for (PartialMatch match : this.partialMatches) {
-				Set<RequestMethod> methods = match.getInfo().getMethodsCondition().getMethods();
-				if (methods.isEmpty() || methods.contains(RequestMethod.PATCH)) {
-					result.addAll(match.getInfo().getConsumesCondition().getConsumableMediaTypes());
-				}
-			}
-			return result;
+			return getConsumableMediaTypesForMethod(RequestMethod.PATCH);
+		}
+
+		/**
+		 * Return declared "consumable" types but only among those that have
+		 * PATCH specified, or that have no methods at all.
+		 */
+		public Set<MediaType> getConsumableQueryMediaTypes() {
+			return getConsumableMediaTypesForMethod(RequestMethod.QUERY);
+		}
+
+		private Set<MediaType> getConsumableMediaTypesForMethod(RequestMethod method) {
+			return this.partialMatches.stream()
+					.map(PartialMatch::getInfo)
+					.filter(info -> info.getMethodsCondition().getMethods().isEmpty() || info.getMethodsCondition().getMethods().contains(method))
+					.flatMap(info -> info.getConsumesCondition().getConsumableMediaTypes().stream())
+					.collect(Collectors.toCollection(LinkedHashSet::new));
 		}
 
 
@@ -400,9 +410,10 @@ private static class HttpOptionsHandler {
 		private final HttpHeaders headers = new HttpHeaders();
 
 
-		public HttpOptionsHandler(Set<HttpMethod> declaredMethods, Set<MediaType> acceptPatch) {
+		public HttpOptionsHandler(Set<HttpMethod> declaredMethods, Set<MediaType> acceptPatch, Set<MediaType> acceptQuery) {
 			this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
 			this.headers.setAcceptPatch(new ArrayList<>(acceptPatch));
+			this.headers.setAcceptQuery(new ArrayList<>(acceptQuery));
 		}
 
 		private static Set<HttpMethod> initAllowedHttpMethods(Set<HttpMethod> declaredMethods) {
@@ -413,7 +424,7 @@ private static Set<HttpMethod> initAllowedHttpMethods(Set<HttpMethod> declaredMe
 			}
 			else {
 				Set<HttpMethod> result = new LinkedHashSet<>(declaredMethods);
-				if (result.contains(HttpMethod.GET)) {
+				if (result.contains(HttpMethod.GET) || result.contains(HttpMethod.QUERY)) {
 					result.add(HttpMethod.HEAD);
 				}
 				result.add(HttpMethod.OPTIONS);
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java
index 1961429a1d66..077db094b444 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java
@@ -56,7 +56,7 @@
  */
 public class ResponseEntityResultHandler extends AbstractMessageWriterResultHandler implements HandlerResultHandler {
 
-	private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD);
+	private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
 
 
 	/**
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java
index 8d270c98a378..141f60d0a982 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/ResourceHandlerFunctionTests.java
@@ -141,7 +141,7 @@ void options() {
 		Mono<ServerResponse> responseMono = this.handlerFunction.handle(request);
 		Mono<Void> result = responseMono.flatMap(response -> {
 			assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
-			assertThat(response.headers().getAllow()).isEqualTo(Set.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS));
+			assertThat(response.headers().getAllow()).isEqualTo(Set.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS, HttpMethod.QUERY));
 			return response.writeTo(exchange, context);
 		});
 
@@ -150,7 +150,7 @@ void options() {
 				.expectComplete()
 				.verify();
 		assertThat(mockResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
-		assertThat(mockResponse.getHeaders().getAllow()).isEqualTo(Set.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS));
+		assertThat(mockResponse.getHeaders().getAllow()).isEqualTo(Set.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS, HttpMethod.QUERY));
 
 		StepVerifier.create(mockResponse.getBody()).expectComplete().verify();
 	}
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java
index bbb0b7c7643d..85e2e4f2a946 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java
@@ -377,7 +377,7 @@ private void testHttpMediaTypeNotSupportedException(String url) {
 				.isEqualTo(Collections.singletonList(new MediaType("application", "xml"))));
 	}
 
-	private void testHttpOptions(String requestURI, Set<HttpMethod> allowedMethods, @Nullable MediaType acceptPatch) {
+	private void testHttpOptions(String requestURI, Set<HttpMethod> allowedMethods, @Nullable MediaType acceptMediaType) {
 		ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.options(requestURI));
 		HandlerMethod handlerMethod = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
 
@@ -395,9 +395,15 @@ private void testHttpOptions(String requestURI, Set<HttpMethod> allowedMethods,
 		HttpHeaders headers = (HttpHeaders) value;
 		assertThat(headers.getAllow()).hasSameElementsAs(allowedMethods);
 
-		if (acceptPatch != null && headers.getAllow().contains(HttpMethod.PATCH) ) {
-			assertThat(headers.getAcceptPatch()).containsExactly(acceptPatch);
+		if (acceptMediaType != null) {
+			if (headers.getAllow().contains(HttpMethod.PATCH)) {
+				assertThat(headers.getAcceptPatch()).containsExactly(acceptMediaType);
+			}
+			if (headers.getAllow().contains(HttpMethod.QUERY)) {
+				assertThat(headers.getAcceptQuery()).containsExactly(acceptMediaType);
+			}
 		}
+
 	}
 
 	private void testMediaTypeNotAcceptable(String url) {
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java
index 002feb2aa1af..ce4a78c6cf01 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java
@@ -117,7 +117,7 @@ void preFlightRequestWithCorsEnabled(HttpServer httpServer) throws Exception {
 		assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
 		assertThat(entity.getHeaders().getAccessControlAllowOrigin()).isEqualTo("*");
 		assertThat(entity.getHeaders().getAccessControlAllowMethods())
-				.containsExactly(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST);
+				.containsExactly(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD, HttpMethod.POST);
 	}
 
 	@ParameterizedHttpServerTest
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java
index d2288fc3f3d2..db6b8aa34f12 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java
@@ -43,7 +43,7 @@
  */
 abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
 
-	private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD);
+	private static final Set<HttpMethod> SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD);
 
 	private final HttpStatusCode statusCode;
 
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java
index 6333b87c7870..4bf0fd99e98b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java
@@ -265,6 +265,18 @@ public static RequestPredicate OPTIONS(String pattern) {
 		return method(HttpMethod.OPTIONS).and(path(pattern));
 	}
 
+	/**
+	 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code QUERY}
+	 * and the given {@code pattern} matches against the request path.
+	 * @param pattern the path pattern to match against
+	 * @return a predicate that matches if the request method is QUERY and if the given pattern
+	 * matches against the request path
+	 * @see org.springframework.web.util.pattern.PathPattern
+	 */
+	public static RequestPredicate QUERY(String pattern) {
+		return method(HttpMethod.QUERY).and(path(pattern));
+	}
+
 	/**
 	 * Return a {@code RequestPredicate} that matches if the request's path has the given extension.
 	 * @param extension the path extension to match against, ignoring case
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java
index 86ccce7dec01..c16d715ed714 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ResourceHandlerFunction.java
@@ -41,7 +41,7 @@
 class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
 
 	private static final Set<HttpMethod> SUPPORTED_METHODS =
-			Set.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS);
+			Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD, HttpMethod.OPTIONS);
 
 
 	private final Resource resource;
@@ -63,6 +63,11 @@ public ServerResponse handle(ServerRequest request) {
 					.headers(headers -> this.headersConsumer.accept(this.resource, headers))
 					.build();
 		}
+		else if (HttpMethod.QUERY.equals(method)) {
+			return EntityResponse.fromObject(this.resource)
+					.headers(headers -> this.headersConsumer.accept(this.resource, headers))
+					.build();
+		}
 		else if (HttpMethod.HEAD.equals(method)) {
 			Resource headResource = new HeadMethodResource(this.resource);
 			return EntityResponse.fromObject(headResource)
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java
index 6a5c4806b731..7ce41621ec81 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java
@@ -229,6 +229,30 @@ public RouterFunctions.Builder OPTIONS(String pattern, RequestPredicate predicat
 		return add(RequestPredicates.OPTIONS(pattern).and(predicate), handlerFunction);
 	}
 
+	// QUERY
+
+	@Override
+	public RouterFunctions.Builder QUERY(HandlerFunction<ServerResponse> handlerFunction) {
+		return add(RequestPredicates.method(HttpMethod.QUERY), handlerFunction);
+	}
+
+	@Override
+	public RouterFunctions.Builder QUERY(RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction) {
+		return add(RequestPredicates.method(HttpMethod.QUERY).and(predicate), handlerFunction);
+	}
+
+	@Override
+	public RouterFunctions.Builder QUERY(String pattern, HandlerFunction<ServerResponse> handlerFunction) {
+		return add(RequestPredicates.QUERY(pattern), handlerFunction);
+	}
+
+	@Override
+	public RouterFunctions.Builder QUERY(String pattern, RequestPredicate predicate,
+			HandlerFunction<ServerResponse> handlerFunction) {
+
+		return add(RequestPredicates.QUERY(pattern).and(predicate), handlerFunction);
+	}
+
 	// other
 
 	@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java
index f2ea64ea97ab..048670178520 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java
@@ -610,6 +610,57 @@ public interface Builder {
 		 */
 		Builder OPTIONS(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
 
+		/**
+		 * Adds a route to the given handler function that handles HTTP {@code QUERY} requests.
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests
+		 * @return this builder
+		 * @since x.x.x
+		 */
+		Builder QUERY(HandlerFunction<ServerResponse> handlerFunction);
+
+		/**
+		 * Adds a route to the given handler function that handles all HTTP {@code QUERY} requests
+		 * that match the given pattern.
+		 * @param pattern the pattern to match to
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests that
+		 * match {@code pattern}
+		 * @return this builder
+		 * @see org.springframework.web.util.pattern.PathPattern
+		 */
+		Builder QUERY(String pattern, HandlerFunction<ServerResponse> handlerFunction);
+
+		/**
+		 * Adds a route to the given handler function that handles all HTTP {@code QUERY} requests
+		 * that match the given predicate.
+		 * @param predicate predicate to match
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests that
+		 * match {@code predicate}
+		 * @return this builder
+		 * @since x.x.x
+		 * @see RequestPredicates
+		 */
+		Builder QUERY(RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
+
+		/**
+		 * Adds a route to the given handler function that handles all HTTP {@code QUERY} requests
+		 * that match the given pattern and predicate.
+		 * <p>For instance, the following example routes QUERY requests for "/user" that contain JSON
+		 * to the {@code addUser} method in {@code userController}:
+		 * <pre class="code">
+		 * RouterFunction&lt;ServerResponse&gt; route =
+		 *   RouterFunctions.route()
+		 *     .QUERY("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON), userController::addUser)
+		 *     .build();
+		 * </pre>
+		 * @param pattern the pattern to match to
+		 * @param predicate additional predicate to match
+		 * @param handlerFunction the handler function to handle all {@code QUERY} requests that
+		 * match {@code pattern}
+		 * @return this builder
+		 * @see org.springframework.web.util.pattern.PathPattern
+		 */
+		Builder QUERY(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
+
 		/**
 		 * Adds a route to the given handler function that handles all requests that match the
 		 * given predicate.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
index a3d8e0f42f82..09eb0c23cb99 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
@@ -162,6 +162,9 @@ else if (isEmpty()) {
 			if (requestMethod.equals(RequestMethod.HEAD) && getMethods().contains(RequestMethod.GET)) {
 				return requestMethodConditionCache.get(HttpMethod.GET.name());
 			}
+			if (requestMethod.equals(RequestMethod.HEAD) && getMethods().contains(RequestMethod.QUERY)) {
+				return requestMethodConditionCache.get(HttpMethod.QUERY.name());
+			}
 		}
 		return null;
 	}
@@ -189,6 +192,9 @@ else if (this.methods.size() == 1) {
 			else if (this.methods.contains(RequestMethod.GET) && other.methods.contains(RequestMethod.HEAD)) {
 				return 1;
 			}
+			else if (this.methods.contains(RequestMethod.QUERY) && other.methods.contains(RequestMethod.HEAD)) {
+				return 1;
+			}
 		}
 		return 0;
 	}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
index 03c12463ba4f..9512b1e9e63d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
@@ -519,7 +519,7 @@ private static Set<HttpMethod> initAllowedHttpMethods(Set<String> declaredMethod
 				for (String method : declaredMethods) {
 					HttpMethod httpMethod = HttpMethod.valueOf(method);
 					result.add(httpMethod);
-					if (httpMethod == HttpMethod.GET) {
+					if (httpMethod == HttpMethod.GET || httpMethod == HttpMethod.QUERY) {
 						result.add(HttpMethod.HEAD);
 					}
 				}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
index d0a67717dded..0afe5bf30af2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
@@ -243,7 +243,7 @@ else if (returnValue instanceof ProblemDetail detail) {
 			outputMessage.getServletResponse().setStatus(returnStatus.value());
 			if (returnStatus.value() == HttpStatus.OK.value()) {
 				HttpMethod method = inputMessage.getMethod();
-				if ((HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method)) &&
+				if ((HttpMethod.GET.equals(method) || HttpMethod.QUERY.equals(method) || HttpMethod.HEAD.equals(method)) &&
 						isResourceNotModified(inputMessage, outputMessage)) {
 					outputMessage.flush();
 					return;
@@ -292,7 +292,7 @@ private boolean isResourceNotModified(ServletServerHttpRequest request, ServletS
 		HttpHeaders responseHeaders = response.getHeaders();
 		String etag = responseHeaders.getETag();
 		long lastModifiedTimestamp = responseHeaders.getLastModified();
-		if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.HEAD) {
+		if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.QUERY || request.getMethod() == HttpMethod.HEAD) {
 			responseHeaders.remove(HttpHeaders.ETAG);
 			responseHeaders.remove(HttpHeaders.LAST_MODIFIED);
 		}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
index bb971e13f1a7..c1c62f627eb4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
@@ -141,7 +141,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
 
 
 	public ResourceHttpRequestHandler() {
-		super(HttpMethod.GET.name(), HttpMethod.HEAD.name());
+		super(HttpMethod.GET.name(), HttpMethod.QUERY.name(), HttpMethod.HEAD.name());
 	}
 
 
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
index c71d67550e26..8faa0fcf7ce7 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
@@ -931,7 +931,7 @@ void testCorsMinimal() {
 			CorsConfiguration config = configs.get("/**");
 			assertThat(config).isNotNull();
 			assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[]{"*"});
-			assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[]{"GET", "HEAD", "POST"});
+			assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[]{"GET", "QUERY", "HEAD", "POST"});
 			assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[]{"*"});
 			assertThat(config.getExposedHeaders()).isNull();
 			assertThat(config.getAllowCredentials()).isNull();
@@ -964,7 +964,7 @@ void testCors() {
 			assertThat(config.getMaxAge()).isEqualTo(Long.valueOf(123));
 			config = configs.get("/resources/**");
 			assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[]{"https://domain1.com"});
-			assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[]{"GET", "HEAD", "POST"});
+			assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[]{"GET", "QUERY", "HEAD", "POST"});
 			assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[]{"*"});
 			assertThat(config.getExposedHeaders()).isNull();
 			assertThat(config.getAllowCredentials()).isNull();
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java
index 781bad421ceb..b8a8f23a0668 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java
@@ -175,7 +175,7 @@ void options() throws ServletException, IOException {
 
 		ServerResponse response = this.handlerFunction.handle(request);
 		assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
-		assertThat(response.headers().getAllow()).isEqualTo(Set.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS));
+		assertThat(response.headers().getAllow()).isEqualTo(Set.of(HttpMethod.GET, HttpMethod.QUERY, HttpMethod.HEAD, HttpMethod.OPTIONS));
 
 		MockHttpServletResponse servletResponse = new MockHttpServletResponse();
 		ModelAndView mav = response.writeTo(servletRequest, servletResponse, this.context);
@@ -184,7 +184,7 @@ void options() throws ServletException, IOException {
 		assertThat(servletResponse.getStatus()).isEqualTo(200);
 		String allowHeader = servletResponse.getHeader("Allow");
 		String[] methods = StringUtils.tokenizeToStringArray(allowHeader, ",");
-		assertThat(methods).containsExactlyInAnyOrder("GET","HEAD","OPTIONS");
+		assertThat(methods).containsExactlyInAnyOrder("GET","QUERY","HEAD","OPTIONS");
 		byte[] actualBytes = servletResponse.getContentAsByteArray();
 		assertThat(actualBytes).isEmpty();
 	}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java
index 865c209e3605..1b584320807b 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java
@@ -185,7 +185,7 @@ void abortInterceptorInPreFlightRequestWithCorsConfig() throws Exception {
 
 		assertThat(response.getStatus()).isEqualTo(200);
 		assertThat(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain.com");
-		assertThat(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET,HEAD");
+		assertThat(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET,QUERY,HEAD");
 	}
 
 	@Test
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java
index 1e1e1d7b7594..5ee85b6ee91b 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java
@@ -193,9 +193,10 @@ void getHandlerMediaTypeNotSupportedWithParseError(TestRequestMappingInfoHandler
 	void getHandlerHttpOptions(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
 		testHttpOptions(mapping, "/foo", "GET,HEAD,OPTIONS", null);
 		testHttpOptions(mapping, "/person/1", "PUT,OPTIONS", null);
-		testHttpOptions(mapping, "/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", null);
+		testHttpOptions(mapping, "/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,QUERY", null);
 		testHttpOptions(mapping, "/something", "PUT,POST", null);
 		testHttpOptions(mapping, "/qux", "PATCH,GET,HEAD,OPTIONS", new MediaType("foo", "bar"));
+		testHttpOptions(mapping, "/quid", "QUERY,HEAD,OPTIONS", null);
 	}
 
 	@PathPatternsParameterizedTest
@@ -572,6 +573,11 @@ public String getBaz() {
 		@RequestMapping(value = "/qux", method = RequestMethod.PATCH, consumes = "foo/bar")
 		public void patchBaz(String value) {
 		}
+
+		@RequestMapping(value = "/quid", method = RequestMethod.QUERY, consumes = "application/json", produces = "application/json")
+		public String query(@RequestBody String body) {
+			return "{}";
+		}
 	}
 
 
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
index 0e92023a088b..8b83796942b4 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
@@ -149,6 +149,23 @@ void patchHttpMediaTypeNotSupported() {
 		assertThat(headers.getFirst(HttpHeaders.ACCEPT_PATCH)).isEqualTo("application/atom+xml, application/xml");
 	}
 
+	@Test
+	void queryHttpMediaTypeNotSupported() {
+		this.servletRequest = new MockHttpServletRequest("QUERY", "/");
+		this.request = new ServletWebRequest(this.servletRequest, this.servletResponse);
+
+		ResponseEntity<Object> entity = testException(
+				new HttpMediaTypeNotSupportedException(
+						MediaType.APPLICATION_JSON,
+						List.of(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML),
+						HttpMethod.QUERY));
+
+		HttpHeaders headers = entity.getHeaders();
+		assertThat(headers.getFirst(HttpHeaders.ACCEPT)).isEqualTo("application/atom+xml, application/xml");
+		assertThat(headers.getFirst(HttpHeaders.ACCEPT)).isEqualTo("application/atom+xml, application/xml");
+		assertThat(headers.getFirst(HttpHeaders.ACCEPT_QUERY)).isEqualTo("application/atom+xml, application/xml");
+	}
+
 	@Test
 	void httpMediaTypeNotAcceptable() {
 		testException(new HttpMediaTypeNotAcceptableException(""));
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java
index d796b37a4fc5..482786c35330 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java
@@ -114,7 +114,7 @@ void supportsOptionsRequests() throws Exception {
 			this.handler.handleRequest(this.request, this.response);
 
 			assertThat(this.response.getStatus()).isEqualTo(200);
-			assertThat(this.response.getHeader("Allow")).isEqualTo("GET,HEAD,OPTIONS");
+			assertThat(this.response.getHeader("Allow")).isEqualTo("GET,QUERY,HEAD,OPTIONS");
 		}
 
 		@Test
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java
index 43efcda039d8..a28e1f69d19b 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java
@@ -39,7 +39,7 @@ void getAllowHeaderWithConstructorTrue() {
 	@Test
 	void getAllowHeaderWithConstructorFalse() {
 		WebContentGenerator generator = new TestWebContentGenerator(false);
-		assertThat(generator.getAllowHeader()).isEqualTo("GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
+		assertThat(generator.getAllowHeader()).isEqualTo("GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,QUERY");
 	}
 
 	@Test
@@ -59,7 +59,7 @@ void getAllowHeaderWithSupportedMethodsSetter() {
 	void getAllowHeaderWithSupportedMethodsSetterEmpty() {
 		WebContentGenerator generator = new TestWebContentGenerator();
 		generator.setSupportedMethods();
-		assertThat(generator.getAllowHeader()).as("Effectively \"no restriction\" on supported methods").isEqualTo("GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
+		assertThat(generator.getAllowHeader()).as("Effectively \"no restriction\" on supported methods").isEqualTo("GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,QUERY");
 	}
 
 	@Test