diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml
index 153934ba6..6d8b1065c 100644
--- a/actix-web/Cargo.toml
+++ b/actix-web/Cargo.toml
@@ -33,6 +33,7 @@ features = [
     "compress-zstd",
     "cookies",
     "secure-cookies",
+    "beautify-errors"
 ]
 
 [package.metadata.cargo_check_external_types]
@@ -89,6 +90,9 @@ cookies = ["dep:cookie"]
 # Secure & signed cookies
 secure-cookies = ["cookies", "cookie/secure"]
 
+# Field names in deserialization errors
+beautify-errors = ["dep:serde_path_to_error"]
+
 # HTTP/2 support (including h2c).
 http2 = ["actix-http/http2"]
 
@@ -161,6 +165,7 @@ regex = { version = "1.5.5", optional = true }
 regex-lite = "0.1"
 serde = "1.0"
 serde_json = "1.0"
+serde_path_to_error = { version = "0.1", optional = true }
 serde_urlencoded = "0.7"
 smallvec = "1.6.1"
 tracing = "0.1.30"
@@ -213,6 +218,10 @@ required-features = ["compress-gzip"]
 name = "on-connect"
 required-features = []
 
+[[example]]
+name = "error"
+required-features = ["beautify-errors"]
+
 [[bench]]
 name = "server"
 harness = false
diff --git a/actix-web/examples/error.rs b/actix-web/examples/error.rs
new file mode 100644
index 000000000..96944b559
--- /dev/null
+++ b/actix-web/examples/error.rs
@@ -0,0 +1,77 @@
+use actix_web::{
+    get, middleware, post,
+    web::{Json, Query},
+    App, HttpServer,
+};
+use serde::Deserialize;
+
+#[get("/optional")]
+async fn optional_query_params(maybe_qs: Option<Query<OptionalFilters>>) -> String {
+    format!("you asked for the optional query params: {:#?}", maybe_qs)
+}
+
+#[get("/mandatory")]
+async fn mandatory_query_params(qs: Query<MandatoryFilters>) -> String {
+    format!("you asked for the mandatory query params: {:#?}", qs)
+}
+
+#[post("/optional")]
+async fn optional_payload(
+    maybe_qs: Option<Query<OptionalFilters>>,
+    maybe_payload: Option<Json<OptionalPayload>>,
+) -> String {
+    format!(
+        "you asked for the optional query params: {:#?} and optional body: {:#?}",
+        maybe_qs, maybe_payload
+    )
+}
+
+#[post("/mandatory")]
+async fn mandatory_payload(qs: Query<MandatoryFilters>, payload: Json<MandatoryPayload>) -> String {
+    format!(
+        "you asked for the mandatory query params: {:#?} and mandatory body: {:#?}",
+        qs, payload
+    )
+}
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
+
+    HttpServer::new(|| {
+        App::new()
+            .wrap(middleware::Logger::default())
+            .service(optional_query_params)
+            .service(mandatory_query_params)
+            .service(optional_payload)
+            .service(mandatory_payload)
+    })
+    .bind("127.0.0.1:8080")?
+    .workers(1)
+    .run()
+    .await
+}
+
+#[derive(Debug, Deserialize)]
+pub struct OptionalFilters {
+    pub limit: Option<i32>,
+    pub active: Option<bool>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct MandatoryFilters {
+    pub limit: i32,
+    pub active: bool,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct OptionalPayload {
+    pub name: Option<String>,
+    pub age: Option<i32>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct MandatoryPayload {
+    pub name: String,
+    pub age: i32,
+}
diff --git a/actix-web/examples/error_postman.json b/actix-web/examples/error_postman.json
new file mode 100644
index 000000000..26310020b
--- /dev/null
+++ b/actix-web/examples/error_postman.json
@@ -0,0 +1,848 @@
+{
+	"info": {
+		"_postman_id": "1147f102-8d16-40e4-8642-5f0679879e59",
+		"name": "actix-web",
+		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+	},
+	"item": [
+		{
+			"name": "field in deserialize errors",
+			"item": [
+				{
+					"name": "optional filters",
+					"item": [
+						{
+							"name": "without filters",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/optional",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single filter",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=1",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both filters",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=1&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single invalid filter",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=wrong&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both invalid filters",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=wrong&active=wrong",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "wrong"
+										}
+									]
+								}
+							},
+							"response": []
+						}
+					]
+				},
+				{
+					"name": "mandatory filters",
+					"item": [
+						{
+							"name": "without filters",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single filter",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=1",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both filters",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=1&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single invalid filter",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=wrong&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both invalid filters",
+							"request": {
+								"method": "GET",
+								"header": [],
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=wrong&active=wrong",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "wrong"
+										}
+									]
+								}
+							},
+							"response": []
+						}
+					]
+				},
+				{
+					"name": "optional filters and payload",
+					"item": [
+						{
+							"name": "without filters",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/optional",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single filter",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=1",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both filters",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=1&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single invalid filter",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=wrong&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both invalid filters",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=wrong&active=wrong",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "wrong"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both filters and invalid payload",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": \"wrong\"\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=1&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both invalid filters and invalid payload",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": \"wrong\"\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/optional?limit=wrong&active=wrong",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"optional"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "wrong"
+										}
+									]
+								}
+							},
+							"response": []
+						}
+					]
+				},
+				{
+					"name": "mandatory filters and payload",
+					"item": [
+						{
+							"name": "without filters",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single filter",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=1",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both filters",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=1&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with a single invalid filter",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=wrong&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both invalid filters",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": 13\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=wrong&active=wrong",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "wrong"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both filters and invalid payload",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": \"wrong\"\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=1&active=true",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "1"
+										},
+										{
+											"key": "active",
+											"value": "true"
+										}
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "with both invalid filters and invalid payload",
+							"request": {
+								"method": "POST",
+								"header": [],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n    \"name\": \"John\",\n    \"age\": \"wrong\"\n}",
+									"options": {
+										"raw": {
+											"language": "json"
+										}
+									}
+								},
+								"url": {
+									"raw": "127.0.0.1:8080/mandatory?limit=wrong&active=wrong",
+									"host": [
+										"127",
+										"0",
+										"0",
+										"1"
+									],
+									"port": "8080",
+									"path": [
+										"mandatory"
+									],
+									"query": [
+										{
+											"key": "limit",
+											"value": "wrong"
+										},
+										{
+											"key": "active",
+											"value": "wrong"
+										}
+									]
+								}
+							},
+							"response": []
+						}
+					]
+				}
+			]
+		}
+	]
+}
\ No newline at end of file
diff --git a/actix-web/src/types/json.rs b/actix-web/src/types/json.rs
index 22ed624c3..af85a22fe 100644
--- a/actix-web/src/types/json.rs
+++ b/actix-web/src/types/json.rs
@@ -17,6 +17,8 @@ use serde::{de::DeserializeOwned, Serialize};
 
 #[cfg(feature = "__compress")]
 use crate::dev::Decompress;
+#[cfg(feature = "beautify-errors")]
+use crate::web::map_deserialize_error;
 use crate::{
     body::EitherBody,
     error::{Error, JsonPayloadError},
@@ -427,11 +429,28 @@ impl<T: DeserializeOwned> Future for JsonBody<T> {
                             buf.extend_from_slice(&chunk);
                         }
                     }
+                    #[cfg(not(feature = "beautify-errors"))]
                     None => {
                         let json = serde_json::from_slice::<T>(buf)
                             .map_err(JsonPayloadError::Deserialize)?;
                         return Poll::Ready(Ok(json));
                     }
+                    #[cfg(feature = "beautify-errors")]
+                    None => {
+                        let mut deserializer = serde_json::Deserializer::from_slice(buf);
+                        let json =
+                            serde_path_to_error::deserialize(&mut deserializer).map_err(|e| {
+                                JsonPayloadError::Deserialize(
+                                    <serde_json::error::Error as serde::de::Error>::custom(
+                                        map_deserialize_error(
+                                            &e.path().to_string(),
+                                            &e.inner().to_string(),
+                                        ),
+                                    ),
+                                )
+                            })?;
+                        return Poll::Ready(Ok(json));
+                    }
                 }
             },
             JsonBody::Error(err) => Poll::Ready(Err(err.take().unwrap())),
diff --git a/actix-web/src/types/mod.rs b/actix-web/src/types/mod.rs
index cabe53d6a..56fcd146e 100644
--- a/actix-web/src/types/mod.rs
+++ b/actix-web/src/types/mod.rs
@@ -21,3 +21,11 @@ pub use self::{
     query::{Query, QueryConfig},
     readlines::Readlines,
 };
+
+#[cfg(feature = "beautify-errors")]
+pub fn map_deserialize_error(field: &str, original: &str) -> String {
+    if field == "." {
+        return original.to_string();
+    }
+    format!("'{}': {}", field, original)
+}
diff --git a/actix-web/src/types/query.rs b/actix-web/src/types/query.rs
index e5505131e..1257c7453 100644
--- a/actix-web/src/types/query.rs
+++ b/actix-web/src/types/query.rs
@@ -4,7 +4,11 @@ use std::{fmt, ops, sync::Arc};
 
 use actix_utils::future::{ok, ready, Ready};
 use serde::de::DeserializeOwned;
+#[cfg(feature = "beautify-errors")]
+use url::form_urlencoded::parse;
 
+#[cfg(feature = "beautify-errors")]
+use crate::web::map_deserialize_error;
 use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
 
 /// Extract typed information from the request's query.
@@ -61,7 +65,6 @@ use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequ
 pub struct Query<T>(pub T);
 
 impl<T> Query<T> {
-    /// Unwrap into inner `T` value.
     pub fn into_inner(self) -> T {
         self.0
     }
@@ -78,11 +81,27 @@ impl<T: DeserializeOwned> Query<T> {
     /// assert_eq!(numbers.get("two"), Some(&2));
     /// assert!(numbers.get("three").is_none());
     /// ```
+    #[cfg(not(feature = "beautify-errors"))]
     pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError> {
         serde_urlencoded::from_str::<T>(query_str)
             .map(Self)
             .map_err(QueryPayloadError::Deserialize)
     }
+    #[cfg(feature = "beautify-errors")]
+    pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError> {
+        let deserializer = serde_urlencoded::Deserializer::new(parse(query_str.as_bytes()));
+        let qs = serde_path_to_error::deserialize(deserializer)
+            .map(Self)
+            .map_err(|e| {
+                QueryPayloadError::Deserialize(
+                    <serde::de::value::Error as serde::de::Error>::custom(map_deserialize_error(
+                        &e.path().to_string(),
+                        &e.inner().to_string(),
+                    )),
+                )
+            })?;
+        Ok(qs)
+    }
 }
 
 impl<T> ops::Deref for Query<T> {
@@ -110,6 +129,7 @@ impl<T: DeserializeOwned> FromRequest for Query<T> {
     type Error = Error;
     type Future = Ready<Result<Self, Error>>;
 
+    #[cfg(not(feature = "beautify-errors"))]
     #[inline]
     fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
         let error_handler = req
@@ -136,6 +156,41 @@ impl<T: DeserializeOwned> FromRequest for Query<T> {
                 ready(Err(err))
             })
     }
+
+    #[cfg(feature = "beautify-errors")]
+    #[inline]
+    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
+        let error_handler = req
+            .app_data::<QueryConfig>()
+            .and_then(|c| c.err_handler.clone());
+
+        let deserializer =
+            serde_urlencoded::Deserializer::new(parse(req.query_string().as_bytes()));
+        return serde_path_to_error::deserialize(deserializer)
+            .map(|val| ok(Query(val)))
+            .unwrap_or_else(move |e| {
+                let e = QueryPayloadError::Deserialize(
+                    <serde::de::value::Error as serde::de::Error>::custom(map_deserialize_error(
+                        &e.path().to_string(),
+                        &e.inner().to_string(),
+                    )),
+                );
+
+                log::debug!(
+                    "Failed during Query extractor deserialization. \
+                     Request path: {:?}",
+                    req.path()
+                );
+
+                let e = if let Some(error_handler) = error_handler {
+                    (error_handler)(e, req)
+                } else {
+                    e.into()
+                };
+
+                err(e)
+            });
+    }
 }
 
 /// Query extractor configuration.