diff --git a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php
index d3bd85a9a9..d5049e85ad 100644
--- a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php
+++ b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php
@@ -117,11 +117,11 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques
       $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
 
 
-      // DX: 400 when no entity type bundle is specified.
+      // DX: 422 when no entity type bundle is specified.
       $response = $this->request($method, $url, $request_options);
       // @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813853.
-      // $this->assertResourceErrorResponse(400, 'The type link relation must be specified.', $response);
-      $this->assertSame(400, $response->getStatusCode());
+      // $this->assertResourceErrorResponse(422, 'The type link relation must be specified.', $response);
+      $this->assertSame(422, $response->getStatusCode());
       $this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
       $this->assertSame($this->serializer->encode(['error' => 'The type link relation must be specified.'], static::$format), (string) $response->getBody());
     }
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 4700b2d4e4..cb3412fdf1 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -13,6 +13,7 @@
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
 use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+use Symfony\Component\Serializer\Exception\InvalidArgumentException;
 
 /**
  * Acts as intermediate request forwarder for resource plugins.
@@ -96,20 +97,34 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
       $request_method = $request->getMethod();
       if (in_array($format, $resource_config->getFormats($request_method))) {
         $definition = $resource->getPluginDefinition();
+
+        // First decode the request data. We can then determine if the
+        // serialized data was malformed.
         try {
-          if (!empty($definition['serialization_class'])) {
-            $unserialized = $serializer->deserialize($received, $definition['serialization_class'], $format, array('request_method' => $method));
-          }
-          // If the plugin does not specify a serialization class just decode
-          // the received data.
-          else {
-            $unserialized = $serializer->decode($received, $format, array('request_method' => $method));
-          }
+          $unserialized = $serializer->decode($received, $format, ['request_method' => $method]);
         }
         catch (UnexpectedValueException $e) {
-          $error['error'] = $e->getMessage();
-          $content = $serializer->serialize($error, $format);
-          return new Response($content, 400, array('Content-Type' => $request->getMimeType($format)));
+          // If an exception was thrown at this stage, there was a problem
+          // decoding the data. Return a 400 response.
+          return $this->createErrorResponse($e, 400, $request, $format);
+        }
+
+        // Then attempt to denormalize if there is a serialization class.
+        if (!empty($definition['serialization_class'])) {
+          try {
+            $unserialized = $serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);
+          }
+          catch (\Exception $e) {
+            if ($e instanceof UnexpectedValueException || $e instanceof InvalidArgumentException) {
+              // If one of the above exceptions was thrown at this stage, there
+              // was a problem with the structure of the decoded data and it's
+              // not valid.
+              return $this->createErrorResponse($e, 422, $request, $format);
+            }
+
+            // Otherwise, re-throw the exception.
+            throw $e;
+          }
         }
       }
       else {
@@ -139,4 +154,23 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
     return $response;
   }
 
+  /**
+   * Creates a format serialized error response.
+   *
+   * @param \Exception $e
+   * @param int $status_code
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   * @param string $format
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   */
+  protected function createErrorResponse(\Exception $e, $status_code, Request $request, $format) {
+    $serializer = $this->container->get('serializer');
+
+    $error = ['error' => $e->getMessage()];
+    $content = $serializer->serialize($error, $format);
+
+    return new Response($content, $status_code, ['Content-Type' => $request->getMimeType($format)]);
+  }
+
 }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index 48ed438b6d..2f10228c40 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -963,12 +963,11 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques
         $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
 
 
-        // DX: 400 when incorrect entity type bundle is specified.
-        // @todo Change to 422 in https://www.drupal.org/node/2827084.
+        // DX: 422 when incorrect entity type bundle is specified.
         $response = $this->request($method, $url, $request_options);
         // @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813853 lands.
-        //      $this->assertResourceErrorResponse(400, '"bad_bundle_name" is not a valid bundle type for denormalization.', $response);
-        $this->assertSame(400, $response->getStatusCode());
+        //      $this->assertResourceErrorResponse(422, '"bad_bundle_name" is not a valid bundle type for denormalization.', $response);
+        $this->assertSame(422, $response->getStatusCode());
         $this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
         $this->assertSame($this->serializer->encode(['error' => '"bad_bundle_name" is not a valid bundle type for denormalization.'], static::$format), (string) $response->getBody());
       }
@@ -978,12 +977,11 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques
       $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
 
 
-      // DX: 400 when no entity type bundle is specified.
-      // @todo Change to 422 in https://www.drupal.org/node/2827084.
+      // DX: 422 when no entity type bundle is specified.
       $response = $this->request($method, $url, $request_options);
       // @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813853 lands.
-      // $this->assertResourceErrorResponse(400, 'A string must be provided as a bundle value.', $response);
-      $this->assertSame(400, $response->getStatusCode());
+      // $this->assertResourceErrorResponse(422, 'A string must be provided as a bundle value.', $response);
+      $this->assertSame(422, $response->getStatusCode());
       $this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
       $this->assertSame($this->serializer->encode(['error' => 'A string must be provided as a bundle value.'], static::$format), (string) $response->getBody());
     }
