diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
index 22eb4724ca060f8090c7151a536b5342aad29843..6069cecd1fa4f583ecebfc58e8b5cceb5d61d09c 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['provider'] . '_field_load'))) {
+            || (is_subclass_of($type_definition['class'], '\Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem')
+              && isset($type_definition['provider']) && function_exists($type_definition['provider'] . '_field_load'))) {
 
             // Call the prepareCache() method directly on each item
             // individually.
diff --git a/core/modules/user/lib/Drupal/user/Entity/User.php b/core/modules/user/lib/Drupal/user/Entity/User.php
index 3f1a54fe1910f4398e9b0fc95f7c8322a12c502f..21accca8bb8d1a4e1b4db3a0f8064be0ba4d6add 100644
--- a/core/modules/user/lib/Drupal/user/Entity/User.php
+++ b/core/modules/user/lib/Drupal/user/Entity/User.php
@@ -446,37 +446,59 @@ public static function baseFieldDefinitions($entity_type) {
       '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('Timezone'),
       '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'),
@@ -504,12 +526,14 @@ public static function baseFieldDefinitions($entity_type) {
     $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.'),
+      // @todo Convert this to entity_reference_field, see
+      // https://drupal.org/node/2044859
       'type' => 'string_field',
     );
     return $properties;
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 0000000000000000000000000000000000000000..652abaaee7e557f707cb1d64702b19a5c2351cfe
--- /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\UserUniqueValidator';
+  }
+}
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 0000000000000000000000000000000000000000..a70908758e0d3162374c055f15260abd89e54633
--- /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 0000000000000000000000000000000000000000..ffd4d82017af85da77eb8eeeabd7b88cff094bc3
--- /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)
+      || 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 0000000000000000000000000000000000000000..72bfcb8bb8bbc7da3ddd9f560c6503bfeedd4971
--- /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\UserUniqueValidator';
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserUniqueValidator.php b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserUniqueValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..945223f445057d5ec57821ab738ac6c7f407e4c0
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserUniqueValidator.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Validation\Constraint\UserUniqueValidator.
+ */
+
+namespace Drupal\user\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the unique user property constraint, such as name and e-mail.
+ */
+class UserUniqueValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    $field = $this->context->getMetadata()->getTypedData()->getParent();
+    $uid = $field->getParent()->id();
+
+    $value_taken = (bool) db_select('users')
+      ->fields('users', array('uid'))
+      // The UID could be NULL, so we cast it to 0 in that case.
+      ->condition('uid', (int) $uid, '<>')
+      ->condition($field->getName(), 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/Tests/UserValidationTest.php b/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php
index 8801b1d8aabf6d859a28da2a35e5f453eb50ff24..14cf2af41da6561543b4bcc6cd157e3af4161e12 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/user.module b/core/modules/user/user.module
index 91e7ee174b9fcfec4574abc599136c61ef573445..4768f937a264133f515df138d9192c3a2ff46900 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.
@@ -321,37 +321,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();
   }
 }