diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 16de8fb..3e69dca 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -284,7 +284,8 @@ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $reco
    * {@inheritdoc}
    */
   public function validate() {
-    return $this->getTypedData()->validate();
+    $violations = $this->getTypedData()->validate();
+    return new EntityConstraintViolationList(iterator_to_array($violations), $this);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
index 3740600..765a4fc 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -75,8 +75,11 @@ public function form(array $form, FormStateInterface $form_state) {
    * https://www.drupal.org/node/2015613.
    */
   public function validate(array $form, FormStateInterface $form_state) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = $this->buildEntity($form, $form_state);
-    $this->getFormDisplay($form_state)->validateFormValues($entity, $form, $form_state);
+
+    $violations = $entity->validate();
+    $this->flagViolations($violations, $form, $form_state);
 
     // @todo Remove this.
     // Execute legacy global validation handlers.
@@ -86,6 +89,33 @@ public function validate(array $form, FormStateInterface $form_state) {
   }
 
   /**
+   * Flags violations for the current form.
+   *
+   * If the entity form customly adds some fields to the form (i.e. without
+   * using the form display), it needs to overwrite this method in order to
+   * show any violations for those fields; e.g.:
+   * @code
+   * foreach ($violations->filterByField('name') as $violation) {
+   *   $form_state->setErrorByName('name', $violation->getMessage());
+   * }
+   * parent::flagViolations($violations, $form, $form_state);
+   * @endcode
+   *
+   * @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
+   *   The violations to flag.
+   * @param array $form
+   *   A nested array form elements comprising the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
+    // Remove violations of inaccessible fields.
+    $violations->filterByFieldAccess($this->currentUser());
+    // Flag all remaining violations depending on the fields in the display.
+    $this->getFormDisplay($form_state)->flagViolations($violations, $form, $form_state);
+  }
+
+  /**
    * Initializes the form state and the entity before the first form build.
    *
    * @param \Drupal\Core\Form\FormStateInterface $form_state
diff --git a/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php
index 57b5abf..31d8302 100644
--- a/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php
+++ b/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php
@@ -101,10 +101,40 @@
   public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state);
 
   /**
+   * Extracts field values from the submitted widget values into the entity.
+   *
+   * This accounts for drag-and-drop reordering of field values, and filtering
+   * of empty values.
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   The entity.
+   * @param array $form
+   *   The form structure where field elements are attached to. This might be a
+   *   full form structure, or a sub-element of a larger form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   An array whose keys and values are the keys of the top-level entries in
+   *   $form_state->getValues() that have been processed. The remaining entries,
+   *   if any, do not correspond to widgets and should be extracted manually by
+   *   the caller if needed.
+   */
+  public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state);
+
+  /**
    * Validates submitted widget values and sets the corresponding form errors.
    *
-   * There are two levels of validation for fields in forms: widget validation
-   * and field validation.
+   * This method invokes entity validation and takes care of flagging them on
+   * the form. This method is in particular useful if the form consists of the
+   * form display only.
+   * As an alternative, entity validation can be invoked separately such that
+   * some violations can be flagged manually. In that case
+   * \Drupal\Core\Entity\Display\EntityFormDisplayInterface::flagViolations()
+   * must be used for flagging violations related to the form display.
+   *
+   * Note that there are two levels of validation for fields in forms: widget
+   * validation and field validation:
    * - Widget validation steps are specific to a given widget's own form
    *   structure and UI metaphors. They are executed during normal form
    *   validation, usually through Form API's #element_validate property.
@@ -112,12 +142,9 @@ public function buildForm(FieldableEntityInterface $entity, array &$form, FormSt
    *   extraction of proper field values from the submitted form input.
    * - If no form / widget errors were reported for the field, field validation
    *   steps are performed according to the "constraints" specified by the
-   *   field definition. Those are independent of the specific widget being
-   *   used in a given form, and are also performed on REST entity submissions.
-   *
-   * This function performs field validation in the context of a form submission.
-   * It reports field constraint violations as form errors on the correct form
-   * elements.
+   *   field definition as part of the entity validation. That validation is
+   *   independent of the specific widget being used in a given form, and is
+   *   also performed on REST entity submissions.
    *
    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
    *   The entity.
@@ -130,25 +157,25 @@ public function buildForm(FieldableEntityInterface $entity, array &$form, FormSt
   public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state);
 
   /**
-   * Extracts field values from the submitted widget values into the entity.
+   * Flags entity validation violations as form errors.
    *
-   * This accounts for drag-and-drop reordering of field values, and filtering
-   * of empty values.
+   * This method processes all violations passed, thus any violations that it
+   * should not handle should be processed before this method is invoked.
    *
-   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
-   *   The entity.
+   * The method flags constraint violations related to fields shown on the
+   * form as form errors on the correct form elements. Possibly pre-existing
+   * violations of hidden fields (= fields not appearing in the display) are
+   * ignored. Other, non-field related violations are passed through and set as
+   * form errors according to the property path of the violations.
+   *
+   * @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
+   *   The violations to flag.
    * @param array $form
    *   The form structure where field elements are attached to. This might be a
    *   full form structure, or a sub-element of a larger form.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The form state.
-   *
-   * @return array
-   *   An array whose keys and values are the keys of the top-level entries in
-   *   $form_state->getValues() that have been processed. The remaining entries,
-   *   if any, do not correspond to widgets and should be extracted manually by
-   *   the caller if needed.
    */
-  public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state);
+  public function flagViolations(\Drupal\Core\Entity\EntityConstraintViolationListInterface $violations, array &$form, FormStateInterface $form_state);
 
 }
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
index d0280a0..7a68785 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
@@ -7,11 +7,15 @@
 
 namespace Drupal\Core\Entity\Entity;
 
+use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Entity\EntityDisplayPluginCollection;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\Core\Entity\EntityDisplayBase;
 use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationList;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
 
 /**
  * Configuration entity that contains widget options for all components of a
@@ -231,16 +235,65 @@ public function extractFormValues(FieldableEntityInterface $entity, array &$form
    * {@inheritdoc}
    */
   public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
-    foreach ($entity as $field_name => $items) {
-      // Only validate the fields that actually appear in the form, and let the
-      // widget assign the violations to the right form elements.
+    $this->flagViolations($entity->validate(), $form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function flagViolations(EntityConstraintViolationListInterface $violations, array &$form, FormStateInterface $form_state) {
+    $entity = $violations->getEntity();
+    foreach ($violations->getViolatedFieldNames() as $field_name) {
+      $field_violations = $this->movePropertyPathViolationsRelativeToField($field_name, $violations->filterByField($field_name));
+      // Only show violations for fields that actually appear in the form, and
+      // let the widget assign the violations to the right form elements.
       if ($widget = $this->getRenderer($field_name)) {
-        $violations = $items->validate();
-        if (count($violations)) {
-          $widget->flagErrors($items, $violations, $form, $form_state);
+        $widget->flagErrors($entity->get($field_name), $field_violations, $form, $form_state);
+      }
+    }
+    // Flag all remaining violations; e.g., entity level violations.
+    foreach ($violations as $violation) {
+      $form_state->setErrorByName(str_replace('.', '][', $violation->getPropertyPath()), $violation);
+    }
+  }
+
+  /**
+   * Moves the property path to be relative to field level.
+   *
+   * @param string $field_name
+   *   The field name.
+   * @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
+   *   The violations.
+   *
+   * @return \Symfony\Component\Validator\ConstraintViolationList
+   *   A new constraint violation list with the changed propery path.
+   */
+  protected function movePropertyPathViolationsRelativeToField($field_name, ConstraintViolationListInterface $violations) {
+    $new_violations = new ConstraintViolationList();
+    foreach ($violations as $violation) {
+      // All this code below is done in order to change the property path of the
+      // violations to be relative to the item list, so like title.0.value gets
+      // changed to 0.value. Sadly constrains in symfony don't have setters so
+      // we have to create new objects. On top of that, the interface is missing
+      // getConstraint() and getCause(), so we have to rely on a specific
+      // constraint implementation.
+      if ($violation instanceof ConstraintViolation) {
+        /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
+        // Create a new violation object with just a different property path.
+        $violation_path = $violation->getPropertyPath();
+        $path_parts = explode('.', $violation_path);
+        if ($path_parts[0] === $field_name) {
+          unset($path_parts[0]);
         }
+        $new_path = implode('.', $path_parts);
+        $new_violation = new ConstraintViolation($violation->getMessage(), $violation->getMessageTemplate(), $violation->getMessageParameters(), $violation->getRoot(), $new_path, $violation->getInvalidValue(), $violation->getMessagePluralization(), $violation->getCode(), $violation->getConstraint(), $violation->getCause());
+      }
+      else {
+        $new_violation = $violation;
       }
+      $new_violations->add($new_violation);
     }
+    return $new_violations;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityConstraintViolationList.php b/core/lib/Drupal/Core/Entity/EntityConstraintViolationList.php
new file mode 100644
index 0000000..3a28472
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityConstraintViolationList.php
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityConstraintViolationList.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Validator\ConstraintViolationInterface;
+use Symfony\Component\Validator\ConstraintViolationList;
+
+/**
+ * Implements an entity constraint violation list.
+ */
+class EntityConstraintViolationList extends ConstraintViolationList implements EntityConstraintViolationListInterface {
+
+  /**
+   * The entity that has been validated.
+   *
+   * @var \Drupal\Core\Entity\FieldableEntityInterface
+   */
+  protected $entity;
+
+  /**
+   * Violations grouped by field.
+   *
+   * Keys are field names, values are arrays of violation offsets.
+   *
+   * @var array[]|null
+   */
+  protected $violationsByField;
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   The entity that has been validated.
+   */
+  public function __construct(array $violations = array(), FieldableEntityInterface $entity) {
+    parent::__construct($violations);
+
+    $this->entity = $entity;
+  }
+
+  /**
+   * Groups violations by field and sets the $violationsByField property.
+   *
+   * @return array[]
+   *   The violations grouped by field. Keys are field names, values are arrays
+   *   of violation offsets.
+   */
+  protected function groupViolationsbyField() {
+    if (!isset($this->violationsByField)) {
+      $this->violationsByField = [];
+      foreach ($this as $offset => $violation) {
+        if ($path = $violation->getPropertyPath()) {
+          list($field_name) = explode('.', $path, 2);
+          if ($this->entity->hasField($field_name)) {
+            $this->violationsByField[$field_name][$offset] = $offset;
+          }
+        }
+      }
+    }
+    return $this->violationsByField;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function filterByField($field_name) {
+    return $this->filterByFields([$field_name]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function filterByFields(array $field_names) {
+    if (!isset($this->violationsByField)) {
+      $this->groupViolationsbyField();
+    }
+    $violations = [];
+    foreach (array_intersect_key($this->violationsByField, array_flip($field_names)) as $field_name => $offsets) {
+      foreach ($offsets as $offset) {
+        $violations[] = $this->get($offset);
+        // Use parent implementation in order to avoid unnecessary rebuilds of
+        // the violations by field array.
+        parent::remove($offset);
+      }
+      unset($this->violationsByField[$field_name]);
+    }
+    return new ConstraintViolationList($violations);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function filterByFieldAccess(AccountInterface $account = NULL) {
+    $filtered_fields = array();
+    foreach ($this->getViolatedFieldNames() as $field_name) {
+      if (!$this->entity->get($field_name)->access('edit', $account)) {
+        $filtered_fields[] = $field_name;
+      }
+    }
+    return $this->filterByFields($filtered_fields);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViolatedFieldNames() {
+    return array_keys($this->groupViolationsbyField());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntity() {
+    return $this->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(ConstraintViolationInterface $violation) {
+    parent::add($violation);
+    $this->violationsByField = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function remove($offset) {
+    parent::remove($offset);
+    $this->violationsByField = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($offset, ConstraintViolationInterface $violation) {
+    parent::set($offset, $violation);
+    $this->violationsByField = NULL;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityConstraintViolationListInterface.php b/core/lib/Drupal/Core/Entity/EntityConstraintViolationListInterface.php
new file mode 100644
index 0000000..1644e99
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityConstraintViolationListInterface.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityConstraintViolationListInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
+/**
+ * Interface for the result of entity validation.
+ *
+ * The Symfony violation list is extended with methods that allow filtering
+ * violations by fields and field access. Forms leverage that to skip possibly
+ * pre-existing violations that cannot be caused or fixed by the form.
+ */
+interface EntityConstraintViolationListInterface extends ConstraintViolationListInterface {
+
+  /**
+   * Filters the violations of the given field.
+   *
+   * Any violations for the given field are removed and returned for further
+   * processing.
+   *
+   * @param string $field_name
+   *   The name of the field to remove violations for.
+   *
+   * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+   *   The list of violations being removed.
+   */
+  public function filterByField($field_name);
+
+  /**
+   * Filters the violations of the given fields.
+   *
+   * Any violations for the given fields are removed and returned for further
+   * processing.
+   *
+   * @param string[] $field_names
+   *   The names of the fields to remove violations for.
+   *
+   * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+   *   The list of violations being removed.
+   */
+  public function filterByFields(array $field_names);
+
+  /**
+   * Filters the violations of inaccessible fields.
+   *
+   * Any violations for fields that are not accessible are removed and returned
+   * for further processing.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   (optional) The user for which to check access, or NULL to check access
+   *   for the current user. Defaults to NULL.
+   *
+   * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+   *   The list of violations being removed.
+   */
+  public function filterByFieldAccess(AccountInterface $account = NULL);
+
+  /**
+   * Returns the names of all violated fields.
+   *
+   * @return string[]
+   *   An array of field names.
+   */
+  public function getViolatedFieldNames();
+
+  /**
+   * The entity which has been validated.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface
+   *   The entity object.
+   */
+  public function getEntity();
+
+}
diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
index 566f9b2..864d051 100644
--- a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
@@ -206,7 +206,7 @@ public function onChange($field_name);
   /**
    * Validates the currently set values.
    *
-   * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+   * @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
    *   A list of constraint violations. If the list is empty, validation
    *   succeeded.
    */
diff --git a/core/modules/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php
index 12f5bb8..d7d0e67 100644
--- a/core/modules/comment/src/CommentForm.php
+++ b/core/modules/comment/src/CommentForm.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
@@ -314,24 +315,14 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
   /**
    * {@inheritdoc}
    */
-  public function validate(array $form, FormStateInterface $form_state) {
-    $comment = parent::validate($form, $form_state);
-
-    // Customly trigger validation of manually added fields and add in
-    // violations.
-    $violations = $comment->created->validate();
-    foreach ($violations as $violation) {
+  protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
+    foreach ($violations->filterByField('created') as $violation) {
       $form_state->setErrorByName('date', $violation->getMessage());
     }
-    $violations = $comment->validate();
-    // Filter out violations for the name path.
-    foreach ($violations as $violation) {
-      if ($violation->getPropertyPath() === 'name') {
-        $form_state->setErrorByName('name', $violation->getMessage());
-      }
+    foreach ($violations->filterByField('name') as $violation) {
+      $form_state->setErrorByName('name', $violation->getMessage());
     }
-
-    return $comment;
+    parent::flagViolations($violations, $form, $form_state);
   }
 
   /**
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 1f1a0eb..028b475 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -145,6 +145,13 @@ function content_translation_entity_type_alter(array &$entity_types) {
           'access_callback' => 'content_translation_translate_access',
         );
       }
+
+      // Sadly its not possible to use a translation handler here, as its part
+      // of the entity type.
+      if (is_subclass_of($entity_type->getClass(), '\Drupal\Core\Entity\EntityChangedInterface')) {
+        $entity_type->addConstraint('EntityChanged', []);
+      }
+
       $entity_type->set('translation', $translation);
     }
   }
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 0bd26f4..89e4857 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -135,7 +135,6 @@ public function getFieldDefinitions() {
       $definitions['content_translation_changed'] = BaseFieldDefinition::create('changed')
         ->setLabel(t('Translation changed time'))
         ->setDescription(t('The Unix timestamp when the translation was most recently saved.'))
-        ->setPropertyConstraints('value', array('EntityChanged' => array()))
         ->setRevisionable(TRUE)
         ->setTranslatable(TRUE);
     }
@@ -170,7 +169,6 @@ protected function hasPublishedStatus() {
    *   TRUE if metadata is natively supported, FALSE otherwise.
    */
   protected function hasChangedTime() {
-    return is_subclass_of($this->entityType->getClass(), '\Drupal\Core\Entity\EntityChangedInterface');
   }
 
   /**
diff --git a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
index 12f59c1..b776efe 100644
--- a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
+++ b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\content_translation;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormStateInterface;
 
 /**
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 44fccbf..c57baa9 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -285,19 +285,6 @@ protected function actions(array $form, FormStateInterface $form_state) {
 
   /**
    * {@inheritdoc}
-   */
-  public function validate(array $form, FormStateInterface $form_state) {
-    $node = parent::validate($form, $form_state);
-
-    if ($node->id() && (node_last_changed($node->id(), $this->getFormLangcode($form_state)) > $node->getChangedTime())) {
-      $form_state->setErrorByName('changed', $this->t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
-    }
-
-    return $node;
-  }
-
-  /**
-   * {@inheritdoc}
    *
    * Updates the node object by processing the submitted values.
    *
diff --git a/core/modules/quickedit/src/Form/QuickEditFieldForm.php b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
index de8dc5b..aabf492 100644
--- a/core/modules/quickedit/src/Form/QuickEditFieldForm.php
+++ b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
@@ -158,18 +158,6 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
     $entity = $this->buildEntity($form, $form_state);
 
     $form_state->get('form_display')->validateFormValues($entity, $form, $form_state);
-
-    // Run entity-level validation as well, while skipping validation of all
-    // fields. We can do so by fetching and validating the entity-level
-    // constraints manually.
-    // @todo: Improve this in https://www.drupal.org/node/2395831.
-    $typed_entity = $entity->getTypedData();
-    $violations = $this->validator
-      ->validate($typed_entity, $typed_entity->getConstraints());
-
-    foreach ($violations as $violation) {
-      $form_state->setErrorByName($violation->getPropertyPath(), $violation->getMessage());
-    }
   }
 
   /**
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 448a353..25b45ce 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -209,6 +209,11 @@ public function delete(EntityInterface $entity) {
    */
   protected function validate(EntityInterface $entity) {
     $violations = $entity->validate();
+
+    // Remove violation of inaccessible fields as they cannot stem from our
+    // changes.
+    $violations->filterByFieldAccess();
+
     if (count($violations) > 0) {
       $message = "Unprocessable Entity: validation failed.\n";
       foreach ($violations as $violation) {
diff --git a/core/modules/search/src/Tests/SearchTestBase.php b/core/modules/search/src/Tests/SearchTestBase.php
index 1469828..ad7762a 100644
--- a/core/modules/search/src/Tests/SearchTestBase.php
+++ b/core/modules/search/src/Tests/SearchTestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\search\Tests;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 use Drupal\Component\Utility\SafeMarkup;
 
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index fe96f9f..b7e81c6 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -1737,7 +1737,7 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
       if (!$ajax && isset($submit)) {
         $this->assertTrue($submit_matches, format_string('Found the @submit button', array('@submit' => $submit)));
       }
-      $this->fail(format_string('Found the requested form fields at @path', array('@path' => $path)));
+      $this->fail(format_string('Found the requested form fields at @path', array('@path' => ($path instanceof Url) ? $path->toString() : $path)));
     }
   }
 
diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php
index f734574..03e81ab 100644
--- a/core/modules/user/src/AccountForm.php
+++ b/core/modules/user/src/AccountForm.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Form\FormStateInterface;
@@ -361,7 +362,12 @@ public function validate(array $form, FormStateInterface $form_state) {
     // Skip the protected user field constraint if the user came from the
     // password recovery page.
     $account->_skipProtectedUserFieldConstraint = $form_state->get('user_pass_reset');
+  }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
     // Customly trigger validation of manually added fields and add in
     // violations. This is necessary as entity form displays only invoke entity
     // validation for fields contained in the display.
@@ -374,14 +380,11 @@ public function validate(array $form, FormStateInterface $form_state) {
       'preferred_langcode',
       'preferred_admin_langcode'
     );
-    foreach ($field_names as $field_name) {
-      $violations = $account->$field_name->validate();
-      foreach ($violations as $violation) {
-        $form_state->setErrorByName($field_name, $violation->getMessage());
-      }
+    foreach ($violations->filterByFields($field_names) as $violation) {
+      list($field_name) = explode('.', $violation->getPropertyPath(), 2);
+      $form_state->setErrorByName($field_name, $violation->getMessage());
     }
-
-    return $account;
+    parent::flagViolations($violations, $form, $form_state);
   }
 
   /**
