diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1b98a37..3eb6b9c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,7 +15,7 @@ repos:
   - id: blacken-docs
     additional_dependencies: [black==19.3b0]
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: v0.720
+  rev: v0.730
   hooks:
   - id: mypy
     language_version: python3
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 363afc7..b1c8efd 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -40,3 +40,4 @@ Contributors (chronological)
 * Xiaoyu Lee <https://github.com/lee3164>
 * Jonathan Angelo <https://github.com/jangelo>
 * @zhenhua32 <https://github.com/zhenhua32>
+* Martin Roy <https://github.com/lindycoder>
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index d038031..224bb84 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,14 @@
 Changelog
 ---------
 
+5.5.2 (unreleased)
+******************
+
+Bug fixes:
+
+* Handle ``UnicodeDecodeError`` when parsing JSON payloads (:issue:`427`).
+  Thanks :user:`lindycoder` for the catch and patch.
+
 5.5.1 (2019-09-15)
 ******************
 
diff --git a/src/webargs/aiohttpparser.py b/src/webargs/aiohttpparser.py
index b36428c..9e76e05 100644
--- a/src/webargs/aiohttpparser.py
+++ b/src/webargs/aiohttpparser.py
@@ -102,6 +102,9 @@ class AIOHTTPParser(AsyncParser):
                     return core.missing
                 else:
                     return self.handle_invalid_json_error(e, req)
+            except UnicodeDecodeError as e:
+                return self.handle_invalid_json_error(e, req)
+
             self._cache["json"] = json_data
         return core.get_value(json_data, name, field, allow_many_nested=True)
 
@@ -164,7 +167,11 @@ class AIOHTTPParser(AsyncParser):
         )
 
     def handle_invalid_json_error(
-        self, error: json.JSONDecodeError, req: Request, *args, **kwargs
+        self,
+        error: typing.Union[json.JSONDecodeError, UnicodeDecodeError],
+        req: Request,
+        *args,
+        **kwargs
     ) -> "typing.NoReturn":
         error_class = exception_map[400]
         messages = {"json": ["Invalid JSON body."]}
diff --git a/src/webargs/bottleparser.py b/src/webargs/bottleparser.py
index a03fb11..568dc65 100644
--- a/src/webargs/bottleparser.py
+++ b/src/webargs/bottleparser.py
@@ -47,6 +47,9 @@ class BottleParser(core.Parser):
                     return core.missing
                 else:
                     return self.handle_invalid_json_error(e, req)
+            except UnicodeDecodeError as e:
+                return self.handle_invalid_json_error(e, req)
+
             if json_data is None:
                 return core.missing
         return core.get_value(json_data, name, field, allow_many_nested=True)
diff --git a/src/webargs/core.py b/src/webargs/core.py
index b3eec6b..fe2f39b 100644
--- a/src/webargs/core.py
+++ b/src/webargs/core.py
@@ -112,7 +112,14 @@ def get_value(data, name, field, allow_many_nested=False):
 
 def parse_json(s, encoding="utf-8"):
     if isinstance(s, bytes):
-        s = s.decode(encoding)
+        try:
+            s = s.decode(encoding)
+        except UnicodeDecodeError as e:
+            raise json.JSONDecodeError(
+                "Bytes decoding error : {}".format(e.reason),
+                doc=str(e.object),
+                pos=e.start,
+            )
     return json.loads(s)
 
 

Non UTF-8 json payload produce a 500 and a stack trace
Hi,

First of all thanks for thie library, while doing some testing with random payload we stumbled upon a corner case:

## Actual Behavior:
If non utf-8 data is sent with header Application/Json the `core`'s `parse_json` will raise a UnicodeDecodeError, which generates a 500.

## Expected Behavior
A `400 BAD REQUEST`

## Notes
- `Python 3.6`
- `webargs==5.5.1`

I would handle the problem my self but as you can see in the stack trace, my code is never reached so i would need a wrapper above `@use_args`

## Reproduce

CODE (From README example with `methods=` added):
```
from flask import Flask
from webargs import fields
from webargs.flaskparser import use_args

app = Flask(__name__)


@app.route("/", methods=['GET', 'POST'])
@use_args({"name": fields.Str(required=True)})
def index(args):
    return "Hello " + args["name"]


if __name__ == "__main__":
    app.run()
 
```

Curl invocation 
```
$ curl http://localhost:5000/ -H 'Content-Type: Application/Json' -d $(python -c "import os; import sys;  os.write(sys.stdout.fileno(), b'\xfe')")
```
Output
```
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
```

Full Stack trace
```
.../.tox/py36/bin/python3.6 .../tests/webargs_poc.py
 * Serving Flask app "webargs_poc" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
[2019-09-23 09:07:54,383] ERROR in app: Exception on / [POST]
Traceback (most recent call last):
  File ".../.tox/py36/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File ".../.tox/py36/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File ".../.tox/py36/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File ".../.tox/py36/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File ".../.tox/py36/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File ".../.tox/py36/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File ".../.tox/py36/lib/python3.6/site-packages/webargs/core.py", line 444, in wrapper
    error_headers=error_headers,
  File ".../.tox/py36/lib/python3.6/site-packages/webargs/core.py", line 347, in parse
    schema=schema, req=req, locations=locations or self.locations
  File ".../.tox/py36/lib/python3.6/site-packages/webargs/core.py", line 263, in _parse_request
    parsed_value = self.parse_arg(argname, field_obj, req, locations)
  File ".../.tox/py36/lib/python3.6/site-packages/webargs/core.py", line 226, in parse_arg
    value = self._get_value(name, field, req=req, location=location)
  File ".../.tox/py36/lib/python3.6/site-packages/webargs/core.py", line 202, in _get_value
    return function(req, name, argobj)
  File ".../.tox/py36/lib/python3.6/site-packages/webargs/flaskparser.py", line 69, in parse_json
    self._cache["json"] = json_data = core.parse_json(data)
  File ".../.tox/py36/lib/python3.6/site-packages/webargs/core.py", line 115, in parse_json
    s = s.decode(encoding)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfe in position 0: invalid start byte
127.0.0.1 - - [23/Sep/2019 09:07:54] "POST / HTTP/1.1" 500 -
```
diff --git a/src/webargs/testing.py b/src/webargs/testing.py
index 2d07684..922bc47 100644
--- a/src/webargs/testing.py
+++ b/src/webargs/testing.py
@@ -117,6 +117,18 @@ class CommonTestCase(object):
         text = u"øˆƒ£ºº∆ƒˆ∆"
         assert testapp.post_json("/echo", {"name": text}).json == {"name": text}
 
+    # https://github.com/marshmallow-code/webargs/issues/427
+    def test_parse_json_with_nonutf8_chars(self, testapp):
+        res = testapp.post(
+            "/echo",
+            b"\xfe",
+            headers={"Accept": "application/json", "Content-Type": "application/json"},
+            expect_errors=True,
+        )
+
+        assert res.status_code == 400
+        assert res.json == {"json": ["Invalid JSON body."]}
+
     def test_validation_error_returns_422_response(self, testapp):
         res = testapp.post("/echo", {"name": "b"}, expect_errors=True)
         assert res.status_code == 422
diff --git a/tests/test_falconparser.py b/tests/test_falconparser.py
index 7162a0d..d6092c7 100644
--- a/tests/test_falconparser.py
+++ b/tests/test_falconparser.py
@@ -16,6 +16,18 @@ class TestFalconParser(CommonTestCase):
     def test_use_args_hook(self, testapp):
         assert testapp.get("/echo_use_args_hook?name=Fred").json == {"name": "Fred"}
 
+    # https://github.com/marshmallow-code/webargs/issues/427
+    def test_parse_json_with_nonutf8_chars(self, testapp):
+        res = testapp.post(
+            "/echo",
+            b"\xfe",
+            headers={"Accept": "application/json", "Content-Type": "application/json"},
+            expect_errors=True,
+        )
+
+        assert res.status_code == 400
+        assert res.json["errors"] == {"json": ["Invalid JSON body."]}
+
     # https://github.com/sloria/webargs/issues/329
     def test_invalid_json(self, testapp):
         res = testapp.post(

pip install -e '.[dev]'
parse_log_pytest
requirements.txt
pytest
3.7
examples/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aiohttp==3.8.6
aiosignal==1.3.1
aniso8601==10.0.0
asgiref==3.7.2
aspy.yaml==1.3.0
async-timeout==4.0.3
asynctest==0.13.0
attrs==24.2.0
beautifulsoup4==4.13.3
bottle==0.13.2
cachetools==5.5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
cfgv==3.3.1
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.6
distlib==0.3.9
Django==3.2.25
entrypoints==0.3
exceptiongroup==1.2.2
falcon==1.4.1
filelock==3.12.2
flake8==3.7.8
flake8-bugbear==19.8.0
Flask==2.2.5
Flask-RESTful==0.3.10
frozenlist==1.3.3
hupper==1.12.1
identify==2.5.24
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
itsdangerous==2.1.2
Jinja2==3.1.6
MarkupSafe==2.1.5
marshmallow==3.19.0
mccabe==0.6.1
mock==5.2.0
multidict==6.0.5
mypy==0.730
mypy-extensions==0.4.4
nodeenv==1.9.1
packaging==24.0
PasteDeploy==3.1.0
plaster==1.1.2
plaster-pastedeploy==1.0.1
platformdirs==4.0.0
pluggy==1.2.0
pre-commit==1.21.0
pycodestyle==2.5.0
pyflakes==2.1.1
pyproject-api==1.5.3
pyramid==2.0.2
pytest==7.4.4
pytest-aiohttp==1.0.5
pytest-asyncio==0.21.2
python-dateutil==2.8.0
python-mimeparse==1.6.0
pytz==2025.2
PyYAML==6.0.1
six==1.17.0
soupsieve==2.4.1
sqlparse==0.4.4
toml==0.10.2
tomli==2.0.1
tornado==6.2
tox==4.8.0
translationstring==1.4
typed-ast==1.4.3
typing_extensions==4.7.1
venusian==3.1.1
virtualenv==20.26.6
waitress==2.1.2
webapp2==3.0.0b1
-e git+https://github.com/marshmallow-code/webargs.git@f2db6bcb2b2f963b819d409da4788411fea3170d#egg=webargs
WebOb==1.8.9
WebTest==2.0.33
webtest-aiohttp==2.0.0
Werkzeug==2.2.3
yarl==1.9.4
zipp==3.15.0
zope.deprecation==5.0
zope.interface==6.4.post2

name: webargs
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aiohttp==3.8.6
      - aiosignal==1.3.1
      - aniso8601==10.0.0
      - asgiref==3.7.2
      - aspy-yaml==1.3.0
      - async-timeout==4.0.3
      - asynctest==0.13.0
      - attrs==24.2.0
      - beautifulsoup4==4.13.3
      - bottle==0.13.2
      - cachetools==5.5.2
      - cfgv==3.3.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - colorama==0.4.6
      - distlib==0.3.9
      - django==3.2.25
      - entrypoints==0.3
      - exceptiongroup==1.2.2
      - falcon==1.4.1
      - filelock==3.12.2
      - flake8==3.7.8
      - flake8-bugbear==19.8.0
      - flask==2.2.5
      - flask-restful==0.3.10
      - frozenlist==1.3.3
      - hupper==1.12.1
      - identify==2.5.24
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - itsdangerous==2.1.2
      - jinja2==3.1.6
      - markupsafe==2.1.5
      - marshmallow==3.19.0
      - mccabe==0.6.1
      - mock==5.2.0
      - multidict==6.0.5
      - mypy==0.730
      - mypy-extensions==0.4.4
      - nodeenv==1.9.1
      - packaging==24.0
      - pastedeploy==3.1.0
      - plaster==1.1.2
      - plaster-pastedeploy==1.0.1
      - platformdirs==4.0.0
      - pluggy==1.2.0
      - pre-commit==1.21.0
      - pycodestyle==2.5.0
      - pyflakes==2.1.1
      - pyproject-api==1.5.3
      - pyramid==2.0.2
      - pytest==7.4.4
      - pytest-aiohttp==1.0.5
      - pytest-asyncio==0.21.2
      - python-dateutil==2.8.0
      - python-mimeparse==1.6.0
      - pytz==2025.2
      - pyyaml==6.0.1
      - six==1.17.0
      - soupsieve==2.4.1
      - sqlparse==0.4.4
      - toml==0.10.2
      - tomli==2.0.1
      - tornado==6.2
      - tox==4.8.0
      - translationstring==1.4
      - typed-ast==1.4.3
      - typing-extensions==4.7.1
      - venusian==3.1.1
      - virtualenv==20.26.6
      - waitress==2.1.2
      - webapp2==3.0.0b1
      - webob==1.8.9
      - webtest==2.0.33
      - webtest-aiohttp==2.0.0
      - werkzeug==2.2.3
      - yarl==1.9.4
      - zipp==3.15.0
      - zope-deprecation==5.0
      - zope-interface==6.4.post2
prefix: /opt/conda/envs/webargs

tests/test_falconparser.py::TestFalconParser::test_parse_json_with_nonutf8_chars
tests/test_falconparser.py::TestFalconParser::test_parse_querystring_args tests/test_falconparser.py::TestFalconParser::test_parse_querystring_with_query_location_specified tests/test_falconparser.py::TestFalconParser::test_parse_form tests/test_falconparser.py::TestFalconParser::test_parse_json tests/test_falconparser.py::TestFalconParser::test_parse_querystring_default tests/test_falconparser.py::TestFalconParser::test_parse_json_default tests/test_falconparser.py::TestFalconParser::test_parse_json_with_charset tests/test_falconparser.py::TestFalconParser::test_parse_json_with_vendor_media_type tests/test_falconparser.py::TestFalconParser::test_parse_json_ignores_extra_data tests/test_falconparser.py::TestFalconParser::test_parse_json_blank tests/test_falconparser.py::TestFalconParser::test_parse_json_ignore_unexpected_int tests/test_falconparser.py::TestFalconParser::test_parse_json_ignore_unexpected_list tests/test_falconparser.py::TestFalconParser::test_parse_json_many_schema_invalid_input tests/test_falconparser.py::TestFalconParser::test_parse_json_many_schema tests/test_falconparser.py::TestFalconParser::test_parse_json_many_schema_ignore_malformed_data tests/test_falconparser.py::TestFalconParser::test_parsing_form_default tests/test_falconparser.py::TestFalconParser::test_parse_querystring_multiple tests/test_falconparser.py::TestFalconParser::test_parse_form_multiple tests/test_falconparser.py::TestFalconParser::test_parse_json_list tests/test_falconparser.py::TestFalconParser::test_parse_json_with_nonascii_chars tests/test_falconparser.py::TestFalconParser::test_validation_error_returns_422_response tests/test_falconparser.py::TestFalconParser::test_user_validation_error_returns_422_response_by_default tests/test_falconparser.py::TestFalconParser::test_use_args_decorator tests/test_falconparser.py::TestFalconParser::test_use_args_with_path_param tests/test_falconparser.py::TestFalconParser::test_use_args_with_validation tests/test_falconparser.py::TestFalconParser::test_use_kwargs_decorator tests/test_falconparser.py::TestFalconParser::test_use_kwargs_with_path_param tests/test_falconparser.py::TestFalconParser::test_parsing_headers tests/test_falconparser.py::TestFalconParser::test_parsing_cookies tests/test_falconparser.py::TestFalconParser::test_parse_nested_json tests/test_falconparser.py::TestFalconParser::test_parse_nested_many_json tests/test_falconparser.py::TestFalconParser::test_parse_nested_many_missing tests/test_falconparser.py::TestFalconParser::test_parse_json_if_no_json tests/test_falconparser.py::TestFalconParser::test_empty_json tests/test_falconparser.py::TestFalconParser::test_use_args_hook tests/test_falconparser.py::TestFalconParser::test_invalid_json
src/webargs/aiohttpparser.py src/webargs/bottleparser.py AUTHORS.rst src/webargs/core.py CHANGELOG.rst .pre-commit-config.yaml
src/webargs/aiohttpparser.py src/webargs/bottleparser.py AUTHORS.rst src/webargs/core.py CHANGELOG.rst .pre-commit-config.yaml