diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
index c6743c8..4501623 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
@@ -19,22 +19,11 @@ class BundleConstraintValidator extends ConstraintValidator {
   /**
    * {@inheritdoc}
    */
-  public function validate($entity_adapter, Constraint $constraint) {
-    if (!isset($entity_adapter)) {
+  public function validate($entity, Constraint $constraint) {
+    if (!isset($entity)) {
       return;
     }
 
-    // @todo The $entity_adapter parameter passed to this function should always
-    //   be a typed data object, but due to a bug, the unwrapped entity is
-    //   passed for the computed entity property of entity reference fields.
-    //   Remove this after fixing that in https://www.drupal.org/node/2346373.
-    if (!$entity_adapter instanceof TypedDataInterface) {
-      $entity = $entity_adapter;
-    }
-    else {
-      $entity = $entity_adapter->getValue();
-    }
-
     if (!in_array($entity->bundle(), $constraint->getBundleOption())) {
       $this->context->addViolation($constraint->message, array('%bundle' => implode(', ', $constraint->getBundleOption())));
     }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
index 5138856..36476df 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
@@ -17,24 +17,13 @@
 class EntityTypeConstraintValidator extends ConstraintValidator {
 
   /**
-   * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
+   * {@inheritdoc}
    */
-  public function validate($entity_adapter, Constraint $constraint) {
-    if (!isset($entity_adapter)) {
+  public function validate($entity, Constraint $constraint) {
+    if (!isset($entity)) {
       return;
     }
 
-    // @todo The $entity_adapter parameter passed to this function should always
-    //   be a typed data object, but due to a bug, the unwrapped entity is
-    //   passed for the computed entity property of entity reference fields.
-    //   Remove this after fixing that in https://www.drupal.org/node/2346373.
-    if (!$entity_adapter instanceof TypedDataInterface) {
-      $entity = $entity_adapter;
-    }
-    else {
-      $entity = $entity_adapter->getValue();
-    }
-
     /** @var $entity \Drupal\Core\Entity\EntityInterface */
     if ($entity->getEntityTypeId() != $constraint->type) {
       $this->context->addViolation($constraint->message, array('%type' => $constraint->type));
diff --git a/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php
index ef15e33..c4cfeb1 100644
--- a/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php
+++ b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php
@@ -79,6 +79,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
     foreach ($this->fieldTypePluginManager->getDefinitions() as $plugin_id => $definition) {
       $definition['definition_class'] = '\Drupal\Core\Field\TypedData\FieldItemDataDefinition';
       $definition['list_definition_class'] = '\Drupal\Core\Field\BaseFieldDefinition';
+      $definition['unwrap_for_canonical_representation'] = FALSE;
       $this->derivatives[$plugin_id] = $definition;
     }
     return $this->derivatives;
diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php
index a0ad01f..bbbc4d6 100644
--- a/core/lib/Drupal/Core/Plugin/Context/Context.php
+++ b/core/lib/Drupal/Core/Plugin/Context/Context.php
@@ -46,7 +46,7 @@ public function getContextValue() {
       }
       return NULL;
     }
-    return $this->contextData->getValue();
+    return $this->typedDataManager->getCanonicalRepresentation($this->contextData);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php
index 332c3ed..3f277f2 100644
--- a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php
+++ b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php
@@ -106,4 +106,13 @@ class DataType extends Plugin {
    */
   public $constraints;
 
+  /**
+   * Whether the typed object wraps the canonical representation of the data.
+   *
+   * @var bool
+   *
+   * @see \Drupal\Core\TypedData\TypedDataManager::getCanonicalRepresentation()
+   */
+  public $unwrap_for_canonical_representation = TRUE;
+
 }
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 18310bd..ebdc1e5 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -332,7 +332,7 @@ public function setValidator(ValidatorInterface $validator) {
   public function getValidator() {
     if (!isset($this->validator)) {
       $this->validator = Validation::createValidatorBuilder()
-        ->setMetadataFactory(new MetadataFactory())
+        ->setMetadataFactory(new MetadataFactory($this))
         ->setTranslator(new DrupalTranslator())
         ->setConstraintValidatorFactory(new ConstraintValidatorFactory($this->classResolver))
         ->setApiVersion(Validation::API_VERSION_2_4)
@@ -415,4 +415,39 @@ public function clearCachedDefinitions() {
     $this->prototypes = array();
   }
 
+  /**
+   * Gets the canonical representation of some data.
+   *
+   * The Typed Object often wraps the canonical representation of the
+   * data, while in other cases the Typed Object itself represents the main API
+   * and thus the canonical representation of the data. E.g., primitive values
+   * or entity objects are wrapped by Typed Objects, while field items and their
+   * lists use an object directly implemented the TypedDataInterface as
+   * canonical representation.
+   *
+   * The canonical representation is typically used when data is passed on to
+   * other code components. E.g. when Typed Data is validated the canonical
+   * representation is passed on to constraint validators, such that
+   * entity objects get passed unwrapped and fields stay objects (which directly
+   * implemented TypedDataInterface).
+   *
+   * @param \Drupal\Core\TypedData\TypedDataInterface $data
+   *   The data.
+   *
+   * @return mixed
+   *   The canonical representation of the passed data.
+   */
+  public function getCanonicalRepresentation(TypedDataInterface $data) {
+    $data_definition = $data->getDataDefinition();
+    // In case a list is passed, respect the 'wrapped' key of its data type.
+    if ($data_definition instanceof ListDataDefinitionInterface) {
+      $data_definition = $data_definition->getItemDefinition();
+    }
+    $type_definition = $this->getDefinition($data_definition->getDataType());
+    if (!empty($type_definition['unwrap_for_canonical_representation'])) {
+      return $data->getValue();
+    }
+    return $data;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php
index 73bfc8b..6fe5255 100644
--- a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php
+++ b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\TypedData\Validation;
 
 use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\Core\TypedData\TypedDataManager;
 use Symfony\Component\Validator\ValidationVisitorInterface;
 use Symfony\Component\Validator\PropertyMetadataInterface;
 
@@ -38,6 +39,13 @@ class Metadata implements PropertyMetadataInterface {
   protected $factory;
 
   /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
    * Constructs the object.
    *
    * @param \Drupal\Core\TypedData\TypedDataInterface $typed_data
@@ -47,11 +55,14 @@ class Metadata implements PropertyMetadataInterface {
    *   the data is the root of the typed data tree.
    * @param \Drupal\Core\TypedData\Validation\MetadataFactory $factory
    *   The factory to use for instantiating property metadata.
+   * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
+   *   The typed data manager.
    */
-  public function __construct(TypedDataInterface $typed_data, $name = '', MetadataFactory $factory) {
+  public function __construct(TypedDataInterface $typed_data, $name = '', MetadataFactory $factory, TypedDataManager $typed_data_manager) {
     $this->typedData = $typed_data;
     $this->name = $name;
     $this->factory = $factory;
+    $this->typedDataManager = $typed_data_manager;
   }
 
   /**
@@ -62,7 +73,7 @@ public function accept(ValidationVisitorInterface $visitor, $typed_data, $group,
     // @todo: Do we have to care about groups? Symfony class metadata has
     // $propagatedGroup.
 
-    $visitor->visit($this, $typed_data->getValue(), $group, $propertyPath);
+    $visitor->visit($this, $this->typedDataManager->getCanonicalRepresentation($typed_data), $group, $propertyPath);
   }
 
   /**
@@ -89,7 +100,7 @@ public function getPropertyName() {
    * @return mixed The value of the property.
    */
   public function getPropertyValue($container) {
-    return $this->typedData->getValue();
+    return $this->typedDataManager->getCanonicalRepresentation($this->typedData);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php
index 2858daf..fcd0557 100644
--- a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php
+++ b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php
@@ -10,6 +10,7 @@
 use Drupal\Core\TypedData\ComplexDataInterface;
 use Drupal\Core\TypedData\ListInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\Core\TypedData\TypedDataManager;
 use Symfony\Component\Validator\MetadataFactoryInterface;
 
 /**
@@ -18,7 +19,24 @@
 class MetadataFactory implements MetadataFactoryInterface {
 
   /**
-   * Implements MetadataFactoryInterface::getMetadataFor().
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
+   * Constructs the object.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
+   *   The typed data manager.
+   */
+  public function __construct(TypedDataManager $typed_data_manager) {
+    $this->typedDataManager = $typed_data_manager;
+  }
+
+  /**
+   * {@inheritdoc}
    *
    * @param \Drupal\Core\TypedData\TypedDataInterface $typed_data
    *   Some typed data object containing the value to validate.
@@ -32,7 +50,7 @@ public function getMetadataFor($typed_data, $name = '') {
     }
     $is_container = $typed_data instanceof ComplexDataInterface || $typed_data instanceof ListInterface;
     $class = '\Drupal\Core\TypedData\Validation\\' . ($is_container ? 'PropertyContainerMetadata' : 'Metadata');
-    return new $class($typed_data, $name, $this);
+    return new $class($typed_data, $name, $this, $this->typedDataManager);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php
index f5850eb..80d3320 100644
--- a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php
+++ b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php
@@ -25,12 +25,16 @@ public function accept(ValidationVisitorInterface $visitor, $typed_data, $group,
     // if the data structure is empty. That way existing NotNull or NotBlank
     // constraints work as expected.
     if ($typed_data->isEmpty()) {
-      $typed_data = NULL;
+      $data = NULL;
     }
-    $visitor->visit($this, $typed_data, $group, $propertyPath);
+    else {
+      $data = $this->typedDataManager->getCanonicalRepresentation($typed_data);
+    }
+    $visitor->visit($this, $data, $group, $propertyPath);
     $pathPrefix = isset($propertyPath) && $propertyPath !== '' ? $propertyPath . '.' : '';
 
-    if ($typed_data) {
+    // Only continue validating if the data is not empty.
+    if ($data) {
       foreach ($typed_data as $name => $data) {
         $metadata = $this->factory->getMetadataFor($data, $name);
         $metadata->accept($visitor, $data, $group, $pathPrefix . $name);
@@ -56,10 +60,10 @@ public function hasPropertyMetadata($property_name) {
    */
   public function getPropertyMetadata($property_name) {
     if ($this->typedData instanceof ListInterface) {
-      return array(new Metadata($this->typedData[$property_name], $property_name));
+      return array(new Metadata($this->typedData[$property_name], $property_name, $this->factory, $this->typedDataManager));
     }
     elseif ($this->typedData instanceof ComplexDataInterface) {
-      return array(new Metadata($this->typedData->get($property_name), $property_name));
+      return array(new Metadata($this->typedData->get($property_name), $property_name, $this->factory, $this->typedDataManager));
     }
     else {
       throw new \LogicException("There are no known properties.");
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php
index 31ffc5b..add46eb 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
 
 use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@@ -25,6 +26,10 @@ public function validate($value, Constraint $constraint) {
       return;
     }
 
+    // If un-wrapped data has been passed, fetch the typed data object first.
+    if (!$value instanceof TypedDataInterface) {
+      $value = $this->context->getMetadata()->getTypedData();
+    }
     if (!$value instanceof ComplexDataInterface) {
       throw new UnexpectedTypeException($value, 'ComplexData');
     }
