diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 1087468..3c52c48 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -251,7 +251,7 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity
     }
 
     // Validate the received data before saving.
-    $this->validate($original_entity);
+    $this->validateWithFilteredFields($original_entity, $entity->_restSubmittedFields);
     try {
       $original_entity->save();
       $this->logger->notice('Updated entity %type with ID %id.', ['%type' => $original_entity->getEntityTypeId(), '%id' => $original_entity->id()]);
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php b/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php
index 09b4b64..773ecec 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php
@@ -3,6 +3,7 @@
 namespace Drupal\rest\Plugin\rest\resource;
 
 use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
@@ -23,6 +24,41 @@
    *   If validation errors are found.
    */
   protected function validate(EntityInterface $entity) {
+    if ($violations = $this->doValidation($entity)) {
+      $this->processViolations($violations);
+    }
+  }
+
+  /**
+   * Validates an entity but limits the validation errors to certain fields.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param string[] $changed_fields
+   *   An array of changed fields. If specified filter out the non applying
+   *   violations.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
+   *   If validation errors are found.
+   */
+  protected function validateWithFilteredFields(EntityInterface $entity, array $changed_fields) {
+    if ($entity instanceof FieldableEntityInterface && $violations = $this->doValidation($entity)) {
+      $relevant_violations = $violations->getEntityViolations();
+      $relevant_violations->addAll($violations->filterByFields(array_diff(array_keys($entity->getFieldDefinitions()), $changed_fields)));
+      $this->processViolations($relevant_violations);
+    }
+  }
+
+
+  /**
+   * Executes validation for an entity and returns the violations.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to validate.
+   *
+   * @return \Drupal\Core\Entity\EntityConstraintViolationListInterface|null
+   */
+  protected function doValidation(EntityInterface $entity) {
     // @todo Remove when https://www.drupal.org/node/2164373 is committed.
     if (!$entity instanceof FieldableEntityInterface) {
       return;
@@ -33,6 +69,16 @@ protected function validate(EntityInterface $entity) {
     // changes.
     $violations->filterByFieldAccess();
 
+    return $violations;
+  }
+
+  /**
+   * Processes violations by throwing an exception with a helpful error message.
+   *
+   * @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
+   *   The violations to process.
+   */
+  protected function processViolations(EntityConstraintViolationListInterface $violations) {
     if ($violations->count() > 0) {
       $message = "Unprocessable Entity: validation failed.\n";
       foreach ($violations as $violation) {
diff --git a/core/modules/rest/tests/modules/rest_test/rest_test.module b/core/modules/rest/tests/modules/rest_test/rest_test.module
index 469b4c7..7ee3aa6 100644
--- a/core/modules/rest/tests/modules/rest_test/rest_test.module
+++ b/core/modules/rest/tests/modules/rest_test/rest_test.module
@@ -5,6 +5,8 @@
  * Contains hook implementations for testing REST module.
  */
 
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Field\FieldItemListInterface;
@@ -31,3 +33,15 @@ function rest_test_entity_field_access($operation, FieldDefinitionInterface $fie
   // No opinion.
   return AccessResult::neutral();
 }
+
+/**
+ * Implements hook_entity_bundle_field_info_alter().
+ *
+ * @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::setUp()
+ * @todo Remove this in https://www.drupal.org/node/2905890.
+ */
+function rest_test_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
+  if (!empty($fields['rest_test_validation'])) {
+    $fields['rest_test_validation']->addConstraint('rest_test_validation', []);
+  }
+}
diff --git a/core/modules/rest/tests/modules/rest_test/src/Plugin/Validation/Constraint/RestTestConstraint.php b/core/modules/rest/tests/modules/rest_test/src/Plugin/Validation/Constraint/RestTestConstraint.php
new file mode 100644
index 0000000..ab4f1fe
--- /dev/null
+++ b/core/modules/rest/tests/modules/rest_test/src/Plugin/Validation/Constraint/RestTestConstraint.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\rest_test\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Adds some validations for a REST test field.
+ *
+ * @Constraint(
+ *   id = "rest_test_validation",
+ *   label = @Translation("REST test validation", context = "Validation")
+ * )
+ *
+ * @see \Drupal\Core\TypedData\OptionsProviderInterface
+ */
+class RestTestConstraint extends Constraint {
+
+  public $message = 'REST test validation failed';
+
+}
diff --git a/core/modules/rest/tests/modules/rest_test/src/Plugin/Validation/Constraint/RestTestConstraintValidator.php b/core/modules/rest/tests/modules/rest_test/src/Plugin/Validation/Constraint/RestTestConstraintValidator.php
new file mode 100644
index 0000000..e71c148
--- /dev/null
+++ b/core/modules/rest/tests/modules/rest_test/src/Plugin/Validation/Constraint/RestTestConstraintValidator.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\rest_test\Plugin\Validation\Constraint;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validator for \Drupal\rest_test\Plugin\Validation\Constraint\RestTestConstraint.
+ */
+class RestTestConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    if ($value instanceof FieldItemListInterface) {
+      $value = $value->getValue();
+      if (!empty($value[0]['value']) && $value[0]['value'] === 'ALWAYS_FAIL') {
+        $this->context->addViolation($constraint->message);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index 72589bd..9517b62 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -180,11 +180,31 @@ public function setUp() {
         ->setTranslatable(FALSE)
         ->save();
 
-      // Reload entity so that it has the new field.
+      // Add field for testing validation.
+      // @see rest_test_entity_bundle_field_info_alter()
+      FieldStorageConfig::create([
+        'entity_type' => static::$entityTypeId,
+        'field_name' => 'rest_test_validation',
+        'type' => 'string',
+      ])
+        ->setCardinality(2)
+        ->save();
+      FieldConfig::create([
+        'entity_type' => static::$entityTypeId,
+        'field_name' => 'rest_test_validation',
+        'bundle' => $this->entity->bundle(),
+        'description' => 'A text field with some special validations attached used for testing purposes',
+      ])
+        ->setLabel('REST test validation field')
+        ->setTranslatable(FALSE)
+        ->save();
+
+      // Reload entity so that it has the new fields.
       $this->entity = $this->entityStorage->loadUnchanged($this->entity->id());
 
       // Set a default value on the field.
       $this->entity->set('field_rest_test', ['value' => 'All the faith he had had had had no effect on the outcome of his life.']);
+      $this->entity->set('rest_test_validation', ['value' => 'allowed value']);
       $this->entity->save();
     }
   }
@@ -460,6 +480,15 @@ public function testGet() {
     // for the keys with the array order the same (it needs to match with
     // identical comparison).
     $expected = $this->getExpectedNormalizedEntity();
+    if ($this->entity instanceof FieldableEntityInterface) {
+      $expected += [
+        'rest_test_validation' => [
+          [
+            'value' => 'allowed value',
+          ],
+        ]
+      ];
+    }
     static::recursiveKSort($expected);
     $actual = $this->serializer->decode((string) $response->getBody(), static::$format);
     static::recursiveKSort($actual);
@@ -522,6 +551,15 @@ public function testGet() {
       // normalized entity's values to strings. This ensures the BC layer for
       // bc_primitives_as_strings works as expected.
       $expected = $this->getExpectedNormalizedEntity();
+      if ($this->entity instanceof FieldableEntityInterface) {
+        $expected += [
+          'rest_test_validation' => [
+            [
+              'value' => 'allowed value',
+            ],
+          ]
+        ];
+      }
       // Config entities are not affected.
       // @see \Drupal\serialization\Normalizer\ConfigEntityNormalizer::normalize()
       $expected = static::castToString($expected);
@@ -557,6 +595,14 @@ public function testGet() {
       // ::formatExpectedTimestampValue() to generate the timestamp value. This
       // will take into account the above config setting.
       $expected = $this->getExpectedNormalizedEntity();
+      $expected += [
+        'rest_test_validation' => [
+          [
+            'value' => 'allowed value',
+          ],
+        ]
+      ];
+
       // Config entities are not affected.
       // @see \Drupal\serialization\Normalizer\ConfigEntityNormalizer::normalize()
       static::recursiveKSort($expected);
@@ -1051,23 +1097,41 @@ public function testPatch() {
     $response = $this->request('PATCH', $url, $request_options);
     $this->assertResourceErrorResponse(403, "Access denied on updating field 'field_rest_test'.", $response);
 
+    if ($this->entity instanceof FieldableEntityInterface) {
+      // DX: 403 when sending PATCH request with updated read-only fields.
+      list($modified_entity, $original_values) = static::getModifiedEntityForPatchTesting($this->entity);
+      // Send PATCH request by serializing the modified entity, assert the error
+      // response, change the modified entity field that caused the error response
+      // back to its original value, repeat.
+      for ($i = 0; $i < count(static::$patchProtectedFieldNames); $i++) {
+        $patch_protected_field_name = static::$patchProtectedFieldNames[$i];
+        $request_options[RequestOptions::BODY] = $this->serializer->serialize($modified_entity, static::$format);
+        $response = $this->request('PATCH', $url, $request_options);
+        $this->assertResourceErrorResponse(403, "Access denied on updating field '" . $patch_protected_field_name . "'.", $response);
+        $modified_entity->get($patch_protected_field_name)->setValue($original_values[$patch_protected_field_name]);
+      }
 
-    // DX: 403 when sending PATCH request with updated read-only fields.
-    list($modified_entity, $original_values) = static::getModifiedEntityForPatchTesting($this->entity);
-    // Send PATCH request by serializing the modified entity, assert the error
-    // response, change the modified entity field that caused the error response
-    // back to its original value, repeat.
-    for ($i = 0; $i < count(static::$patchProtectedFieldNames); $i++) {
-      $patch_protected_field_name = static::$patchProtectedFieldNames[$i];
-      $request_options[RequestOptions::BODY] = $this->serializer->serialize($modified_entity, static::$format);
-      $response = $this->request('PATCH', $url, $request_options);
-      $this->assertResourceErrorResponse(403, "Access denied on updating field '" . $patch_protected_field_name . "'.", $response);
-      $modified_entity->get($patch_protected_field_name)->setValue($original_values[$patch_protected_field_name]);
+      if ($this->entity->hasField('rest_test_validation')) {
+        // Set the rest_test_validation field to always fail validation, which
+        // allows asserting that not modifying that field does not trigger
+        // validation errors.
+        $this->entity->set('rest_test_validation', 'ALWAYS_FAIL');
+        $this->entity->save();
+
+        // Change the rest_test_validation field to prove that then its validation
+        // does run. In subsequent test assertions, it will not be modified, and
+        // then should not trigger validation errors.
+        $valid_request_body = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
+        $request_options[RequestOptions::BODY] = $this->serializer->serialize($valid_request_body, static::$format);
+        $response = $this->request('PATCH', $url, $request_options);
+        $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nrest_test_validation: REST test validation failed\n", $response);
+      }
     }
 
     // 200 for well-formed PATCH request that sends all fields (even including
     // read-only ones, but with unchanged values).
     $valid_request_body = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
+    $valid_request_body = array_diff_key($valid_request_body, ['rest_test_validation' => NULL]);
     $request_options[RequestOptions::BODY] = $this->serializer->serialize($valid_request_body, static::$format);
     $response = $this->request('PATCH', $url, $request_options);
     $this->assertResourceResponse(200, FALSE, $response);
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php
index e2c0ccd..947e172 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php
@@ -106,6 +106,11 @@ protected function getExpectedNormalizedEntity() {
         ]
       ],
       'field_test_text' => [],
+      'rest_test_validation' => [
+        [
+          'value' => 'allowed value',
+        ]
+      ]
     ];
 
     return $normalization;
