diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index d9d17c2..3de29eb 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -12,6 +12,7 @@
 use Drupal\Component\Uuid\Uuid;
 use ArrayIterator;
 use InvalidArgumentException;
+use Symfony\Component\Validator\ConstraintViolationList;
 
 /**
  * Implements Entity Field API specific enhancements to the Entity class.
@@ -526,4 +527,12 @@ public function label($langcode = NULL) {
     }
     return $label;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate() {
+    // @todo: Add the typed data manager as proper dependency.
+    return \Drupal::typedData()->getValidator()->validate($this);
+  }
 }
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 195e32d..4bfc5c9 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -304,28 +304,6 @@ public function getValidationConstraintManager() {
   }
 
   /**
-   * Creates a validation constraint plugin.
-   *
-   * @param string $name
-   *   The name or plugin id of the constraint.
-   * @param mixed $options
-   *   The options to pass to the constraint class. Required and supported
-   *   options depend on the constraint class.
-   *
-   * @return \Symfony\Component\Validator\Constraint
-   *   A validation constraint plugin.
-   */
-  public function createValidationConstraint($name, $options) {
-    if (!is_array($options)) {
-      // Plugins need an array as configuration, so make sure we have one.
-      // The constraint classes support passing the options as part of the
-      // 'value' key also.
-      $options = array('value' => $options);
-    }
-    return $this->getValidationConstraintManager()->createInstance($name, $options);
-  }
-
-  /**
    * Gets configured constraints from a data definition.
    *
    * Any constraints defined for the data type, i.e. below the 'constraint' key
@@ -365,29 +343,28 @@ public function createValidationConstraint($name, $options) {
    */
   public function getConstraints($definition) {
     $constraints = array();
-    // @todo: Figure out how to handle nested constraint structures as
-    // collections.
+    $validation_manager = $this->getValidationConstraintManager();
+
     $type_definition = $this->getDefinition($definition['type']);
     // Auto-generate a constraint for the primitive type if we have a mapping.
     if (isset($type_definition['primitive type'])) {
-      $constraints[] = $this->getValidationConstraintManager()->
-        createInstance('PrimitiveType', array('type' => $type_definition['primitive type']));
+      $constraints[] = $validation_manager->create('PrimitiveType', array('type' => $type_definition['primitive type']));
     }
     // Add in constraints specified by the data type.
     if (isset($type_definition['constraints'])) {
       foreach ($type_definition['constraints'] as $name => $options) {
-        $constraints[] = $this->createValidationConstraint($name, $options);
+        $constraints[] = $validation_manager->create($name, $options);
       }
     }
     // Add any constraints specified as part of the data definition.
     if (isset($definition['constraints'])) {
       foreach ($definition['constraints'] as $name => $options) {
-        $constraints[] = $this->createValidationConstraint($name, $options);
+        $constraints[] = $validation_manager->create($name, $options);
       }
     }
     // Add the NotNull constraint for required data.
     if (!empty($definition['required']) && empty($definition['constraints']['NotNull'])) {
-      $constraints[] = $this->createValidationConstraint('NotNull', array());
+      $constraints[] = $validation_manager->create('NotNull', array());
     }
     return $constraints;
   }
diff --git a/core/lib/Drupal/Core/Validation/ConstraintManager.php b/core/lib/Drupal/Core/Validation/ConstraintManager.php
index 8afd195..3a8114c 100644
--- a/core/lib/Drupal/Core/Validation/ConstraintManager.php
+++ b/core/lib/Drupal/Core/Validation/ConstraintManager.php
@@ -55,6 +55,28 @@ public function __construct(\Traversable $namespaces) {
   }
 
   /**
+   * Creates a validation constraint.
+   *
+   * @param string $name
+   *   The name or plugin id of the constraint.
+   * @param mixed $options
+   *   The options to pass to the constraint class. Required and supported
+   *   options depend on the constraint class.
+   *
+   * @return \Symfony\Component\Validator\Constraint
+   *   A validation constraint plugin.
+   */
+  public function create($name, $options) {
+    if (!is_array($options)) {
+      // Plugins need an array as configuration, so make sure we have one.
+      // The constraint classes support passing the options as part of the
+      // 'value' key also.
+      $options = array('value' => $options);
+    }
+    return $this->createInstance($name, $options);
+  }
+
+  /**
    * Callback for registering definitions for constraints shipped with Symfony.
    *
    * @see ConstraintManager::__construct()
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraint.php
new file mode 100644
index 0000000..5719051
--- /dev/null
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraint.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\CollectionConstraint.
+ */
+
+namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
+
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Complex data constraint.
+ *
+ * Validates properties of complex data structures.
+ *
+ * @Plugin(
+ *   id = "ComplexData",
+ *   label = @Translation("Complex data", context = "Validation")
+ * )
+ */
+class ComplexDataConstraint extends Constraint {
+
+  /**
+   * An array of constraints for contained properties, keyed by property name.
+   *
+   * @var string
+   */
+  public $properties;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($options = NULL) {
+    // Allow skipping the 'properties' key in the options.
+    if (is_array($options) && !array_key_exists('properties', $options)) {
+      $options = array('properties' => $options);
+    }
+    parent::__construct($options);
+    $constraint_manager = \Drupal::service('validation.constraint');
+
+    // Instantiate constraint objects for array definitions.
+    foreach ($this->properties as &$constraints) {
+      foreach ($constraints as $id => $options) {
+        if (!is_object($options)) {
+          $constraints[$id] = $constraint_manager->create($id, $options);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultOption() {
+    return 'properties';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRequiredOptions() {
+    return array('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
new file mode 100644
index 0000000..1dbcc94
--- /dev/null
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\ComplexDataConstraintValidator.
+ */
+
+namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
+
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * Validates complex data.
+ */
+class ComplexDataConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritDoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    if (!isset($value)) {
+      return;
+    }
+
+    if (!$value instanceof ComplexDataInterface) {
+      throw new UnexpectedTypeException($value, 'ComplexData');
+    }
+
+    $group = $this->context->getGroup();
+
+    foreach ($constraint->properties as $name => $constraints) {
+      $property = $value->get($name);
+      $is_container = $property instanceof ComplexDataInterface || $property instanceof ListInterface;
+      if (!$is_container) {
+        $property = $property->getValue();
+      }
+      elseif ($property->isEmpty()) {
+        // @see \Drupal\Core\TypedData\Validation\PropertyContainerMetadata::accept();
+        $property = NULL;
+      }
+      $this->context->validateValue($property, $constraints, '[' . $name . ']', $group);
+    }
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php
new file mode 100644
index 0000000..bf02cdd
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Entity\EntityValidationTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Field\FieldInterface;
+use Drupal\Core\Entity\Field\FieldItemInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+
+/**
+ * Tests Entity API base functionality.
+ */
+class EntityValidationTest extends EntityUnitTestBase  {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('filter', 'text');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity Validation API',
+      'description' => 'Tests the Entity Validation API',
+      'group' => 'Entity API',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    $this->installSchema('user', array('users_roles', 'users_data'));
+    $this->installSchema('entity_test', array(
+      'entity_test_mul',
+      'entity_test_mul_property_data',
+      'entity_test_rev',
+      'entity_test_rev_revision',
+      'entity_test_mulrev',
+      'entity_test_mulrev_property_data',
+      'entity_test_mulrev_property_revision'
+    ));
+
+    // Create the test field.
+    entity_test_install();
+
+    // Install required default configuration for filter module.
+    $this->installConfig(array('system', 'filter'));
+  }
+
+  /**
+   * Creates a test entity.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   */
+  protected function createTestEntity($entity_type) {
+    $this->entity_name = $this->randomName();
+    $this->entity_user = $this->createUser();
+    $this->entity_field_text = $this->randomName();
+
+    // Pass in the value of the name field when creating. With the user
+    // field we test setting a field after creation.
+    $entity = entity_create($entity_type, array());
+    $entity->user_id->target_id = $this->entity_user->uid;
+    $entity->name->value = $this->entity_name;
+
+    // Set a value for the test field.
+    $entity->field_test_text->value = $this->entity_field_text;
+
+    return $entity;
+  }
+
+  /**
+   * Tests validating test entity types.
+   */
+  public function testValidation() {
+    // All entity variations have to have the same results.
+    foreach (entity_test_entity_types() as $entity_type) {
+      $this->assertValidation($entity_type);
+    }
+  }
+
+  /**
+   * Executes the validation test set for a defined entity type.
+   *
+   * @param string $entity_type
+   *   The entity type to run the tests with.
+   */
+  protected function assertValidation($entity_type) {
+    $entity = $this->createTestEntity($entity_type);
+    $violations = $entity->validate();
+    $this->assertEqual($violations->count(), 0, 'Validation passes.');
+
+    // Test triggering a fail for each of the constraints specified.
+    $test_entity = clone $entity;
+    $test_entity->uuid->value = $this->randomString(150);
+    $this->assertEqual($test_entity->validate()->count(), 1, 'Validation failed.');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
index 8c5e23f..03543c9 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
@@ -41,22 +41,43 @@ public function baseFieldDefinitions() {
       'label' => t('UUID'),
       'description' => t('The UUID of the test entity.'),
       'type' => 'string_field',
+      'constraints' => array(
+        'ComplexData' => array(
+          'value' => array(
+            'Length' => array('max' => 128),
+          ),
+        ),
+      ),
+      // @todo: Support that.
+      'property_constraints' => array(
+        'value' => array(
+          'Length' => array('max' => 128),
+        ),
+      ),
     );
     $fields['langcode'] = array(
       'label' => t('Language code'),
       'description' => t('The language code of the test entity.'),
       'type' => 'language_field',
+      'constraints' => array(
+      //  'Length' => array('max' => 12),
+      ),
     );
     $fields['name'] = array(
       'label' => t('Name'),
       'description' => t('The name of the test entity.'),
       'type' => 'string_field',
       'translatable' => TRUE,
+      'constraints' => array(
+     //   'Length' => array('max' => 32),
+      ),
     );
     $fields['type'] = array(
       'label' => t('Type'),
       'description' => t('The bundle of the test entity.'),
       'type' => 'string_field',
+      'required' => TRUE,
+      // @todo: Add allowed values validation.
     );
     $fields['user_id'] = array(
       'label' => t('User ID'),
