diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
index ea4e06c..8324d4a 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
@@ -190,7 +190,8 @@ public function invokeFieldItemPrepareCache(EntityInterface $entity) {
             // of making LegacyConfigFieldItem implement PrepareCacheInterface.
             // @todo Remove once all core field types have been converted (see
             // http://drupal.org/node/2014671).
-            || (is_subclass_of($type_definition['class'], '\Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem') && function_exists($type_definition['module'] . '_field_load'))) {
+            || (is_subclass_of($type_definition['class'], '\Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem')
+              && isset($type_definition['module']) && function_exists($type_definition['module'] . '_field_load'))) {
 
             // Call the prepareCache() method directly on each item
             // individually.
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EmailItem.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EmailItem.php
index b147976..254505c 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EmailItem.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EmailItem.php
@@ -52,6 +52,7 @@ public function getPropertyDefinitions() {
    * {@inheritdoc}
    */
   public function isEmpty() {
-    return !isset($this->values['value']) || $this->values['value'] === '';
+    $value = isset($this->values['value']) ? $this->values['value'] : $this->get('value')->getValue();
+    return !isset($value) || $value === '';
   }
 }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReferenceItem.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReferenceItem.php
index 3e67f7e..cbf2f1b 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReferenceItem.php
@@ -43,23 +43,39 @@ class EntityReferenceItem extends FieldItemBase {
    * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
    */
   public function getPropertyDefinitions() {
+    $entity_type = $this->definition['settings']['target_type'];
     // Definitions vary by entity type and bundle, so key them accordingly.
-    $key = $this->definition['settings']['target_type'] . ':';
+    $key = $entity_type . ':';
     $key .= isset($this->definition['settings']['target_bundle']) ? $this->definition['settings']['target_bundle'] : '';
 
     if (!isset(static::$propertyDefinitions[$key])) {
+      // Determine the type of the entity ID field.
+      $entity_manager = \Drupal::entityManager();
+      $entity_info = $entity_manager->getDefinition($entity_type);
+      $id_key = $entity_info['entity_keys']['id'];
+      $controller = $entity_manager->getStorageController($entity_type);
+      $base_definitions = $controller->baseFieldDefinitions();
+      // If we cannot find a field definition we fallback to just the string
+      // type.
+      // @todo Remove this check once all storage controllers properly return
+      // their field definitions.
+      $id_type = isset($base_definitions[$id_key]['type']) ? $base_definitions[$id_key]['type'] : 'string';
+
       static::$propertyDefinitions[$key]['target_id'] = array(
-        // @todo: Lookup the entity type's ID data type and use it here.
-        'type' => 'integer',
+        'type' => $id_type,
         'label' => t('Entity ID'),
-        'constraints' => array(
-          'Range' => array('min' => 0),
-        ),
+        // Don't copy over the field constraints from the base field definitions
+        // of the entity type because we don't know what they depend on.
       );
+      if ($id_type == 'integer') {
+        static::$propertyDefinitions[$key]['target_id']['constraints'] = array(
+          'Range' => array('min' => 0),
+        );
+      }
       static::$propertyDefinitions[$key]['entity'] = array(
         'type' => 'entity_reference',
         'constraints' => array(
-          'EntityType' => $this->definition['settings']['target_type'],
+          'EntityType' => $entity_type,
         ),
         'label' => t('Entity'),
         'description' => t('The referenced entity'),
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UniqueValidator.php b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UniqueValidator.php
new file mode 100644
index 0000000..919aaf7
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UniqueValidator.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Validation\Constraint\UniqueValidator.
+ */
+
+namespace Drupal\user\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the unique user proeprty constraint, such as name and e-mail.
+ */
+class UniqueValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    $field = $this->context->getMetadata()->getTypedData()->getParent();
+    $field_name = $field->getName();
+    $user = $field->getParent();
+    $uid = $user->get('uid')->value;
+    $value_taken = (bool) db_select('users')
+      ->fields('users', array('uid'))
+      ->condition('uid', (int) $uid, '<>')
+      ->condition($field_name, db_like($value), 'LIKE')
+      ->range(0, 1)
+      ->execute()
+      ->fetchField();
+
+    if ($value_taken) {
+      $this->context->addViolation($constraint->message, array("%value" => $value));
+    }
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserMailUnique.php b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserMailUnique.php
new file mode 100644
index 0000000..9c2563e
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserMailUnique.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Validation\Constraint\UserMailUnique.
+ */
+
+namespace Drupal\user\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+
+/**
+ * Checks if a user's e-mail address is unique on the site.
+ *
+ * @Plugin(
+ *   id = "UserMailUnique",
+ *   label = @Translation("User e-mail unique", context = "Validation")
+ * )
+ */
+class UserMailUnique extends Constraint {
+
+  public $message = 'The e-mail address %value is already taken.';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validatedBy() {
+    return '\Drupal\user\Plugin\Validation\Constraint\UniqueValidator';
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameConstraint.php b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameConstraint.php
new file mode 100644
index 0000000..a709087
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameConstraint.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Validation\Constraint\UserNameConstraint.
+ */
+
+namespace Drupal\user\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+
+/**
+ * Checks if a value is a valid user name.
+ *
+ * @Plugin(
+ *   id = "UserName",
+ *   label = @Translation("User name", context = "Validation")
+ * )
+ */
+class UserNameConstraint extends Constraint {
+
+  public $emptyMessage = 'You must enter a username.';
+  public $spaceBeginMessage = 'The username cannot begin with a space.';
+  public $spaceEndMessage = 'The username cannot end with a space.';
+  public $multipleSpacesMessage = 'The username cannot contain multiple spaces in a row.';
+  public $illegalMessage = 'The username contains an illegal character.';
+  public $tooLongMessage = 'The username %name is too long: it must be %max characters or less.';
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameConstraintValidator.php b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameConstraintValidator.php
new file mode 100644
index 0000000..6a4da6c
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameConstraintValidator.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Validation\Constraint\UserNameConstraintValidator.
+ */
+
+namespace Drupal\user\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the UserName constraint.
+ */
+class UserNameConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($name, Constraint $constraint) {
+    if (!$name) {
+      $this->context->addViolation($constraint->emptyMessage);
+      return;
+    }
+    if (substr($name, 0, 1) == ' ') {
+      $this->context->addViolation($constraint->spaceBeginMessage);
+    }
+    if (substr($name, -1) == ' ') {
+      $this->context->addViolation($constraint->spaceEndMessage);
+    }
+    if (strpos($name, '  ') !== FALSE) {
+      $this->context->addViolation($constraint->multipleSpacesMessage);
+    }
+    if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
+      $this->context->addViolation($constraint->illegalMessage);
+    }
+    elseif (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
+                    '\x{AD}' .                // Soft-hyphen
+                    '\x{2000}-\x{200F}' .     // Various space characters
+                    '\x{2028}-\x{202F}' .     // Bidirectional text overrides
+                    '\x{205F}-\x{206F}' .     // Various text hinting characters
+                    '\x{FEFF}' .              // Byte order mark
+                    '\x{FF01}-\x{FF60}' .     // Full-width latin
+                    '\x{FFF9}-\x{FFFD}' .     // Replacement characters
+                    '\x{0}-\x{1F}]/u',        // NULL byte and control characters
+                    $name)) {
+      $this->context->addViolation($constraint->illegalMessage);
+    }
+    if (drupal_strlen($name) > USERNAME_MAX_LENGTH) {
+      $this->context->addViolation($constraint->tooLongMessage, array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
+    }
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameUnique.php b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameUnique.php
new file mode 100644
index 0000000..62957aa
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserNameUnique.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Validation\Constraint\UserNameUnique.
+ */
+
+namespace Drupal\user\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+
+/**
+ * Checks if a user name is unique on the site.
+ *
+ * @Plugin(
+ *   id = "UserNameUnique",
+ *   label = @Translation("User name unique", context = "Validation")
+ * )
+ */
+class UserNameUnique extends Constraint {
+
+  public $message = 'The name %value is already taken.';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validatedBy() {
+    return '\Drupal\user\Plugin\Validation\Constraint\UniqueValidator';
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php b/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php
index 8801b1d..14cf2af 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php
@@ -7,18 +7,41 @@
 
 namespace Drupal\user\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Performs validation tests on user fields.
+ */
+class UserValidationTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('field', 'user', 'system');
 
-class UserValidationTest extends WebTestBase {
   public static function getInfo() {
     return array(
-      'name' => 'Username/e-mail validation',
-      'description' => 'Verify that username/email validity checks behave as designed.',
+      'name' => 'User validation',
+      'description' => 'Verify that user validity checks behave as designed.',
       'group' => 'User'
     );
   }
 
-  // Username validation.
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installSchema('user', array('users'));
+    $this->installSchema('system', array('sequences'));
+  }
+
+  /**
+   * Tests user name validation.
+   */
   function testUsernames() {
     $test_cases = array( // '<username>' => array('<description>', 'assert<testName>'),
       'foo'                    => array('Valid username', 'assertNull'),
@@ -44,4 +67,96 @@ function testUsernames() {
       $this->$test($result, $description . ' (' . $name . ')');
     }
   }
+
+  /**
+   * Runs entity validation checks.
+   */
+  function testValidation() {
+    $user = entity_create('user', array('name' => 'test'));
+    $violations = $user->validate();
+    $this->assertEqual(count($violations), 0, 'No violations when validating a default user.');
+
+    // Only test one example invalid name here, the rest is already covered in
+    // the testUsernames() method in this class.
+    $name = $this->randomName(61);
+    $user->set('name', $name);
+    $violations = $user->validate();
+    $this->assertEqual(count($violations), 1, 'Violation found when name is too long.');
+    $this->assertEqual($violations[0]->getPropertyPath(), 'name.0.value');
+    $this->assertEqual($violations[0]->getMessage(), t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => 60)));
+
+    // Create a second test user to provoke a name collision.
+    $user2 = entity_create('user', array(
+      'name' => 'existing',
+      'mail' => 'existing@exmaple.com',
+    ));
+    $user2->save();
+    $user->set('name', 'existing');
+    $violations = $user->validate();
+    $this->assertEqual(count($violations), 1, 'Violation found on name collision.');
+    $this->assertEqual($violations[0]->getPropertyPath(), 'name.0.value');
+    $this->assertEqual($violations[0]->getMessage(), t('The name %name is already taken.', array('%name' => 'existing')));
+
+    // Make the name valid.
+    $user->set('name', $this->randomName());
+
+    $user->set('mail', 'invalid');
+    $violations = $user->validate();
+    $this->assertEqual(count($violations), 1, 'Violation found when email is invalid');
+    $this->assertEqual($violations[0]->getPropertyPath(), 'mail.0.value');
+    $this->assertEqual($violations[0]->getMessage(), t('This value is not a valid email address.'));
+
+    $mail = $this->randomName(EMAIL_MAX_LENGTH - 11) . '@example.com';
+    $user->set('mail', $mail);
+    $violations = $user->validate();
+    $this->assertEqual(count($violations), 1, 'Violation found when email is too long');
+    $this->assertEqual($violations[0]->getPropertyPath(), 'mail.0.value');
+    $this->assertEqual($violations[0]->getMessage(), t('This value is not a valid email address.'));
+
+    // Provoke a e-mail collision with an exsiting user.
+    $user->set('mail', 'existing@exmaple.com');
+    $violations = $user->validate();
+    $this->assertEqual(count($violations), 1, 'Violation found when e-mail already exists.');
+    $this->assertEqual($violations[0]->getPropertyPath(), 'mail.0.value');
+    $this->assertEqual($violations[0]->getMessage(), t('The e-mail address %mail is already taken.', array('%mail' => 'existing@exmaple.com')));
+    $user->set('mail', NULL);
+
+    $user->set('signature', $this->randomString(256));
+    $this->assertLengthViolation($user, 'signature', 255);
+    $user->set('signature', NULL);
+
+    $user->set('theme', $this->randomString(DRUPAL_EXTENSION_NAME_MAX_LENGTH + 1));
+    $this->assertLengthViolation($user, 'theme', DRUPAL_EXTENSION_NAME_MAX_LENGTH);
+    $user->set('theme', NULL);
+
+    $user->set('timezone', $this->randomString(33));
+    $this->assertLengthViolation($user, 'timezone', 32);
+    $user->set('timezone', NULL);
+
+    $user->set('init', 'invalid');
+    $violations = $user->validate();
+    $this->assertEqual(count($violations), 1, 'Violation found when init email is invalid');
+    $this->assertEqual($violations[0]->getPropertyPath(), 'init.0.value');
+    $this->assertEqual($violations[0]->getMessage(), t('This value is not a valid email address.'));
+
+    // @todo Test user role validation once https://drupal.org/node/2015701 got
+    // committed.
+  }
+
+  /**
+   * Verifies that a length violation exists for the given field.
+   *
+   * @param \Drupal\core\Entity\EntityInterface $entity
+   *   The entity object to validate.
+   * @param string $field_name
+   *   The field that violates the maximum length.
+   * @param int $length
+   *   Number of characters that was exceeded.
+  */
+  protected function assertLengthViolation(EntityInterface $entity, $field_name, $length) {
+    $violations = $entity->validate();
+    $this->assertEqual(count($violations), 1, "Violation found when $field_name is too long.");
+    $this->assertEqual($violations[0]->getPropertyPath(), "$field_name.0.value");
+    $this->assertEqual($violations[0]->getMessage(), t('This value is too long. It should have %limit characters or less.', array('%limit' => $length)));
+  }
 }
diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php
index 8b4653c..c3e56d5 100644
--- a/core/modules/user/lib/Drupal/user/UserStorageController.php
+++ b/core/modules/user/lib/Drupal/user/UserStorageController.php
@@ -207,37 +207,59 @@ public function baseFieldDefinitions() {
       'description' => t('The name of this user'),
       'type' => 'string_field',
       'settings' => array('default_value' => ''),
+      'property_constraints' => array(
+        // No Length contraint here because the UserName constraint also covers
+        // that.
+        'value' => array(
+          'UserName' => array(),
+          'UserNameUnique' => array(),
+        ),
+      ),
     );
     $properties['pass'] = array(
-      'label' => t('Name'),
+      'label' => t('Password'),
       'description' => t('The password of this user (hashed)'),
       'type' => 'string_field',
     );
     $properties['mail'] = array(
-      'label' => t('Name'),
+      'label' => t('E-mail'),
       'description' => t('The e-mail of this user'),
-      'type' => 'string_field',
+      'type' => 'email_field',
       'settings' => array('default_value' => ''),
+      'property_constraints' => array(
+        'value' => array('UserMailUnique' => array()),
+      ),
     );
     $properties['signature'] = array(
-      'label' => t('Name'),
+      'label' => t('Signature'),
       'description' => t('The signature of this user'),
       'type' => 'string_field',
+      'property_constraints' => array(
+        'value' => array('Length' => array('max' => 255)),
+      ),
     );
     $properties['signature_format'] = array(
-      'label' => t('Name'),
+      'label' => t('Signature format'),
       'description' => t('The signature format of this user'),
+      // @todo Convert the type to filter_format once
+      // https://drupal.org/node/1758622 is comitted
       'type' => 'string_field',
     );
     $properties['theme'] = array(
       'label' => t('Theme'),
       'description' => t('The default theme of this user'),
       'type' => 'string_field',
+      'property_constraints' => array(
+        'value' => array('Length' => array('max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH)),
+      ),
     );
     $properties['timezone'] = array(
       'label' => t('Timeone'),
       'description' => t('The timezone of this user'),
       'type' => 'string_field',
+      'property_constraints' => array(
+        'value' => array('Length' => array('max' => 32)),
+      ),
     );
     $properties['status'] = array(
       'label' => t('User status'),
@@ -265,13 +287,14 @@ public function baseFieldDefinitions() {
     $properties['init'] = array(
       'label' => t('Init'),
       'description' => t('The email address used for initial account creation.'),
-      'type' => 'string_field',
+      'type' => 'email_field',
       'settings' => array('default_value' => ''),
     );
     $properties['roles'] = array(
       'label' => t('Roles'),
       'description' => t('The roles the user has.'),
-      'type' => 'string_field',
+      'type' => 'entity_reference_field',
+      'settings' => array('target_type' => 'user_role'),
     );
     return $properties;
   }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index cb1ab7e..a886903 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -27,7 +27,7 @@
 /**
  * Maximum length of user e-mail text field.
  */
-const EMAIL_MAX_LENGTH = 254;
+const EMAIL_MAX_LENGTH = 255;
 
 /**
  * Only administrators can create user accounts.
@@ -333,37 +333,24 @@ function user_load_by_name($name) {
 
 /**
  * Verify the syntax of the given name.
+ *
+ * @param string $name
+ *   The user name to validate.
+ *
+ * @return string|null
+ *   A translated violation message if the name is invalid or NULL if the name
+ *   is valid.
+ *
  */
 function user_validate_name($name) {
-  if (!$name) {
-    return t('You must enter a username.');
-  }
-  if (substr($name, 0, 1) == ' ') {
-    return t('The username cannot begin with a space.');
-  }
-  if (substr($name, -1) == ' ') {
-    return t('The username cannot end with a space.');
-  }
-  if (strpos($name, '  ') !== FALSE) {
-    return t('The username cannot contain multiple spaces in a row.');
-  }
-  if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
-    return t('The username contains an illegal character.');
-  }
-  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
-                  '\x{AD}' .                // Soft-hyphen
-                  '\x{2000}-\x{200F}' .     // Various space characters
-                  '\x{2028}-\x{202F}' .     // Bidirectional text overrides
-                  '\x{205F}-\x{206F}' .     // Various text hinting characters
-                  '\x{FEFF}' .              // Byte order mark
-                  '\x{FF01}-\x{FF60}' .     // Full-width latin
-                  '\x{FFF9}-\x{FFFD}' .     // Replacement characters
-                  '\x{0}-\x{1F}]/u',        // NULL byte and control characters
-                  $name)) {
-    return t('The username contains an illegal character.');
-  }
-  if (drupal_strlen($name) > USERNAME_MAX_LENGTH) {
-    return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
+  $data = \Drupal::typedData()->create(array(
+    'type' => 'string',
+    'constraints' => array('UserName' => array()),
+  ));
+  $data->setValue($name);
+  $violations = $data->validate();
+  if (count($violations) > 0) {
+    return $violations[0]->getMessage();
   }
 }
 
