diff --git a/jsonapi.services.yml b/jsonapi.services.yml
index 7118754..4aa93d4 100644
--- a/jsonapi.services.yml
+++ b/jsonapi.services.yml
@@ -46,6 +46,11 @@ services:
     arguments: ['@current_user']
     tags:
       - { name: normalizer, priority: 1 }
+  serializer.normalizer.unprocessable_entity_exception.jsonapi:
+    class: Drupal\jsonapi\Normalizer\UnprocessableHttpEntityExceptionNormalizer
+    arguments: ['@current_user']
+    tags:
+      - { name: normalizer, priority: 2 }
   serializer.encoder.jsonapi:
     class: Drupal\jsonapi\Encoder\JsonEncoder
     tags:
diff --git a/src/Error/UnprocessableHttpEntityException.php b/src/Error/UnprocessableHttpEntityException.php
new file mode 100644
index 0000000..0e2be6e
--- /dev/null
+++ b/src/Error/UnprocessableHttpEntityException.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\jsonapi\Error;
+
+use Drupal\Core\Entity\EntityConstraintViolationListInterface;
+
+class UnprocessableHttpEntityException extends SerializableHttpException {
+
+  /**
+   * The constraint violations associated with this exception.
+   *
+   * @var \Drupal\Core\Entity\EntityConstraintViolationListInterface
+   */
+  protected $violations;
+
+  /**
+   * UnprocessableHttpEntityException constructor.
+   *
+   * @param array $violations
+   * @param \Exception|NULL $previous
+   * @param array $headers
+   * @param int $code
+   */
+  public function __construct(\Exception $previous = null, array $headers = array(), $code = 0) {
+    parent::__construct(422, "Unprocessable Entity: validation failed.", $previous, $headers, $code);
+  }
+
+  /**
+   * Gets the constraint violations associated with this exception.
+   *
+   * @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
+   */
+  public function getViolations() {
+    return $this->violations;
+  }
+
+  /**
+   * Sets the constraint violations associated with this exception.
+   *
+   * @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
+   *   The constraint violations.
+   */
+  public function setViolations(EntityConstraintViolationListInterface $violations) {
+    $this->violations = $violations;
+  }
+
+}
diff --git a/src/Normalizer/HttpExceptionNormalizer.php b/src/Normalizer/HttpExceptionNormalizer.php
index 4fb9169..ba36bb1 100644
--- a/src/Normalizer/HttpExceptionNormalizer.php
+++ b/src/Normalizer/HttpExceptionNormalizer.php
@@ -8,8 +8,18 @@ use Drupal\jsonapi\Normalizer\Value\FieldItemNormalizerValue;
 use Drupal\jsonapi\Normalizer\Value\HttpExceptionNormalizerValue;
 use Drupal\serialization\Normalizer\NormalizerBase as SerializationNormalizerBase;
 use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 
+/**
+ * Class HttpExceptionNormalizer.
+ *
+ * Normalizes an HttpException object for JSON output which complies with the
+ * JSON API specification.
+ *
+ * @see http://jsonapi.org/format/#error-objects
+ *
+ * @package Drupal\jsonapi\Normalizer
+ */
 class HttpExceptionNormalizer extends SerializationNormalizerBase {
 
   /**
@@ -17,7 +27,7 @@ class HttpExceptionNormalizer extends SerializationNormalizerBase {
    *
    * @var string
    */
-  protected $supportedInterfaceOrClass = HttpExceptionInterface::class;
+  protected $supportedInterfaceOrClass = HttpException::class;
 
   /**
    * The current user making the request.
@@ -40,37 +50,55 @@ class HttpExceptionNormalizer extends SerializationNormalizerBase {
    * {@inheritdoc}
    */
   public function normalize($object, $format = null, array $context = []) {
-    /** @var $object \Symfony\Component\HttpKernel\Exception\HttpException */
+    $errors = $this->buildErrorObjects($object);
+
+    $errors = array_map(function($error) {
+      return new FieldItemNormalizerValue([$error]);
+    }, $errors);
+
+    return new HttpExceptionNormalizerValue(
+      $errors,
+      FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
+    );
+  }
+
+  /**
+   * Builds the normalized JSON API error objects for the response.
+   *
+   * @param \Symfony\Component\HttpKernel\Exception\HttpException $exception
+   *  The Exception.
+   *
+   * @return array
+   *  The error objects to include in the response.
+   */
+  protected function buildErrorObjects(HttpException $exception) {
     $error = [];
-    $status_code = $object->getStatusCode();
+    $status_code = $exception->getStatusCode();
     if (!empty(Response::$statusTexts[$status_code])) {
       $error['title'] = Response::$statusTexts[$status_code];
     }
     $error += [
       'status' => $status_code,
-      'detail' => $object->getMessage(),
+      'detail' => $exception->getMessage(),
       'links' => [
         'info' => $this->getInfoUrl($status_code),
       ],
-      'code' => $object->getCode(),
+      'code' => $exception->getCode(),
     ];
     if ($this->currentUser->hasPermission('access site reports')) {
       // The following information may contain sensitive information. Only show
       // it to authorized users.
       $error['source'] = [
-        'file' => $object->getFile(),
-        'line' => $object->getLine(),
+        'file' => $exception->getFile(),
+        'line' => $exception->getLine(),
       ];
       $error['meta'] = [
-        'exception' => (string) $object,
-        'trace' => $object->getTrace(),
+        'exception' => (string) $exception,
+        'trace' => $exception->getTrace(),
       ];
     }
 
-    return new HttpExceptionNormalizerValue(
-      [new FieldItemNormalizerValue([$error])],
-      FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
-    );
+    return [ $error ];
   }
 
   /**
diff --git a/src/Normalizer/UnprocessableHttpEntityExceptionNormalizer.php b/src/Normalizer/UnprocessableHttpEntityExceptionNormalizer.php
new file mode 100644
index 0000000..440c01b
--- /dev/null
+++ b/src/Normalizer/UnprocessableHttpEntityExceptionNormalizer.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\jsonapi\Normalizer;
+
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\jsonapi\Error\UnprocessableHttpEntityException;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+
+/**
+ * Class UnprocessableHttpEntityExceptionNormalizer.
+ *
+ * Normalizes an UnprocessableHttpEntityException object for JSON output which
+ * complies with the JSON API specification. A source pointer is added to help
+ * client applications report validation errors, for example on an Entity edit
+ * form.
+ *
+ * @see http://jsonapi.org/format/#error-objects
+ *
+ * @package Drupal\jsonapi\Normalizer
+ */
+class UnprocessableHttpEntityExceptionNormalizer extends HttpExceptionNormalizer {
+
+  /**
+   * The interface or class that this Normalizer supports.
+   *
+   * @var string
+   */
+  protected $supportedInterfaceOrClass = UnprocessableHttpEntityException::class;
+
+  /**
+   * UnprocessableHttpEntityException constructor.
+   *
+   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   The current user.
+   */
+  public function __construct(AccountProxyInterface $current_user) {
+    parent::__construct($current_user);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildErrorObjects(HttpException $exception) {
+    /** @var $exception \Drupal\jsonapi\Error\UnprocessableHttpEntityException */
+    $errors = parent::buildErrorObjects($exception);
+    $error = $errors[0];
+    unset($error['links']);
+
+    $errors = [];
+    $violations = $exception->getViolations();
+    $entity_violations = $violations->getEntityViolations();
+    foreach ($entity_violations as $violation) {
+      /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
+      $error['detail'] = 'Entity is not valid: '
+        . $violation->getMessage();
+      $error['source']['pointer'] = '/data';
+      $errors[] = $error;
+    }
+
+    $entity = $violations->getEntity();
+    foreach ($violations->getFieldNames() as $field_name) {
+      $field_violations = $violations->getByField($field_name);
+      $cardinality = $entity->get($field_name)
+        ->getFieldDefinition()
+        ->getFieldStorageDefinition()
+        ->getCardinality();
+
+      foreach ($field_violations as $violation) {
+        /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
+        $error['detail'] = $violation->getPropertyPath() . ': '
+          . $violation->getMessage();
+
+        $pointer = '/data/attributes/'
+          . str_replace('.', '/', $violation->getPropertyPath());
+        if ($cardinality == 1) {
+          // Remove erroneous '/0/' index for single-value fields.
+          $pointer = str_replace("/data/attributes/$field_name/0/", "/data/attributes/$field_name/", $pointer);
+        }
+        $error['source']['pointer'] = $pointer;
+
+        $errors[] = $error;
+      }
+    }
+
+    return $errors;
+  }
+
+}
diff --git a/src/Resource/EntityResource.php b/src/Resource/EntityResource.php
index 65306a9..7af2a60 100644
--- a/src/Resource/EntityResource.php
+++ b/src/Resource/EntityResource.php
@@ -18,6 +18,7 @@ use Drupal\jsonapi\Configuration\ResourceConfigInterface;
 use Drupal\jsonapi\EntityCollection;
 use Drupal\jsonapi\EntityCollectionInterface;
 use Drupal\jsonapi\Error\SerializableHttpException;
+use Drupal\jsonapi\Error\UnprocessableHttpEntityException;
 use Drupal\jsonapi\Query\QueryBuilderInterface;
 use Drupal\jsonapi\Context\CurrentContextInterface;
 use Drupal\jsonapi\ResourceResponse;
@@ -133,15 +134,13 @@ class EntityResource implements EntityResourceInterface {
     $violations->filterByFieldAccess();
 
     if (count($violations) > 0) {
-      $message = "Unprocessable Entity: validation failed.\n";
-      foreach ($violations as $violation) {
-        $message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n";
-      }
       // Instead of returning a generic 400 response we use the more specific
       // 422 Unprocessable Entity code from RFC 4918. That way clients can
       // distinguish between general syntax errors in bad serializations (code
       // 400) and semantic errors in well-formed requests (code 422).
-      throw new SerializableHttpException(422, $message);
+      $exception = new UnprocessableHttpEntityException();
+      $exception->setViolations($violations);
+      throw $exception;
     }
   }
 
diff --git a/tests/src/Functional/JsonApiFunctionalTest.php b/tests/src/Functional/JsonApiFunctionalTest.php
index 96a21d7..a58b348 100644
--- a/tests/src/Functional/JsonApiFunctionalTest.php
+++ b/tests/src/Functional/JsonApiFunctionalTest.php
@@ -572,7 +572,39 @@ class JsonApiFunctionalTest extends BrowserTestBase {
     $this->assertEquals('taxonomy_term--tags', $updated_response['data'][0]['type']);
     $this->assertEquals($this->tags[1]->uuid(), $updated_response['data'][0]['id']);
     // TODO: Successful DELETE to related endpoint.
-    // 11. Successful DELETE.
+    // 11. PATCH with invalid title and body format.
+    $body = [
+      'data' => [
+        'id' => $uuid,
+        'type' => 'node--article',
+        'attributes' => [
+          'title' => '',
+          'body' => [
+            'value' => 'Custom value',
+            'format' => 'invalid_format',
+            'summary' => 'Custom summary',
+          ],
+        ],
+      ],
+    ];
+    $response = $this->request('PATCH', $individual_url, [
+      'body' => Json::encode($body),
+      'auth' => [$this->user->getUsername(), $this->user->pass_raw],
+      'headers' => ['Content-Type' => 'application/vnd.api+json'],
+    ]);
+    $updated_response = Json::decode($response->getBody()->__toString());
+    $this->assertEquals(422, $response->getStatusCode());
+    $this->assertCount(2, $updated_response['errors']);
+    for ($i = 0; $i < 2; $i++) {
+      $this->assertEquals("Unprocessable Entity", $updated_response['errors'][$i]['title']);
+      $this->assertEquals(422, $updated_response['errors'][$i]['status']);
+      $this->assertEquals(0, $updated_response['errors'][$i]['code']);
+    }
+    $this->assertEquals("title: This value should not be null.", $updated_response['errors'][0]['detail']);
+    $this->assertEquals("body.0.format: The value you selected is not a valid choice.", $updated_response['errors'][1]['detail']);
+    $this->assertEquals("/data/attributes/title", $updated_response['errors'][0]['source']['pointer']);
+    $this->assertEquals("/data/attributes/body/format", $updated_response['errors'][1]['source']['pointer']);
+    // 12. Successful DELETE.
     $response = $this->request('DELETE', $individual_url, [
       'auth' => [$this->user->getUsername(), $this->user->pass_raw],
     ]);
diff --git a/tests/src/Kernel/Resource/EntityResourceTest.php b/tests/src/Kernel/Resource/EntityResourceTest.php
index e9fbec9..9ba7607 100644
--- a/tests/src/Kernel/Resource/EntityResourceTest.php
+++ b/tests/src/Kernel/Resource/EntityResourceTest.php
@@ -450,8 +450,7 @@ class EntityResourceTest extends JsonapiKernelTestBase {
     Role::load(Role::ANONYMOUS_ID)
       ->grantPermission('create article content')
       ->save();
-    $this->setExpectedException(HttpException::class, 'Unprocessable Entity: validation failed.
-title: This value should not be null.');
+    $this->setExpectedException(HttpException::class, 'Unprocessable Entity: validation failed.');
     $entity_resource = $this->buildEntityResource('node', 'article', 'id');
     $entity_resource->createIndividual($node, $this->request->reveal());
   }
