diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index eeacd7a..4ac8538 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -393,7 +393,7 @@ function _drupal_set_preferred_header_name($name = NULL) {
  * names or link URLs into translated text. Variable substitution looks like
  * this:
  * @code
- * $text = t("@name's blog", array('@name' => user_format_name($account)));
+ * $text = t("@name's blog", array('@name' => $account->getUsername()));
  * @endcode
  * Basically, you can put variables like @name into your string, and t() will
  * substitute their sanitized values at translation time. (See the
diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module
index 5d4c26b..c5f9d58 100644
--- a/core/modules/contact/contact.module
+++ b/core/modules/contact/contact.module
@@ -126,7 +126,7 @@ function contact_mail($key, &$message, $params) {
     '!subject' => $contact_message->getSubject(),
     '!form' => !empty($params['contact_form']) ? $params['contact_form']->label() : NULL,
     '!form-url' => \Drupal::url('<current>', [], ['absolute' => TRUE, 'language' => $language]),
-    '!sender-name' => user_format_name($sender),
+    '!sender-name' => $sender->getUsername(),
   );
   if ($sender->isAuthenticated()) {
     $variables['!sender-url'] = $sender->url('canonical', array('absolute' => TRUE, 'language' => $language));
@@ -154,7 +154,7 @@ function contact_mail($key, &$message, $params) {
     case 'user_mail':
     case 'user_copy':
       $variables += array(
-        '!recipient-name' => user_format_name($params['recipient']),
+        '!recipient-name' => $params['recipient']->getUsername(),
         '!recipient-edit-url' => $params['recipient']->url('edit-form', array('absolute' => TRUE, 'language' => $language)),
       );
       $message['subject'] .= t('[!site-name] !subject', $variables, $options);
diff --git a/core/modules/file/src/Tests/FileTokenReplaceTest.php b/core/modules/file/src/Tests/FileTokenReplaceTest.php
index eaf2308..9122b84 100644
--- a/core/modules/file/src/Tests/FileTokenReplaceTest.php
+++ b/core/modules/file/src/Tests/FileTokenReplaceTest.php
@@ -54,7 +54,7 @@ function testFileTokenReplacement() {
     $tests['[file:created:short]'] = format_date($file->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
     $tests['[file:changed]'] = format_date($file->getChangedTime(), 'medium', '', NULL, $language_interface->getId());
     $tests['[file:changed:short]'] = format_date($file->getChangedTime(), 'short', '', NULL, $language_interface->getId());
-    $tests['[file:owner]'] = SafeMarkup::checkPlain(user_format_name($this->adminUser));
+    $tests['[file:owner]'] = SafeMarkup::checkPlain($this->adminUser->getUsername());
     $tests['[file:owner:uid]'] = $file->getOwnerId();
 
     // Test to make sure that we generated something for each token.
diff --git a/core/modules/system/core.api.php b/core/modules/system/core.api.php
index 14c894a..c1fe998 100644
--- a/core/modules/system/core.api.php
+++ b/core/modules/system/core.api.php
@@ -1792,7 +1792,7 @@ function hook_mail($key, &$message, $params) {
   $context = $params['context'];
   $variables = array(
     '%site_name' => \Drupal::config('system.site')->get('name'),
-    '%username' => user_format_name($account),
+    '%username' => $account->getUsername(),
   );
   if ($context['hook'] == 'taxonomy') {
     $entity = $params['entity'];
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index fa4db6f..a26b39a 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -43,7 +43,7 @@
  *   admin_permission = "administer users",
  *   base_table = "users",
  *   data_table = "users_field_data",
- *   label_callback = "user_format_name",
+ *   label_callback = "getUsername",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "uid",
diff --git a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
index e2dd03d..ac36673 100644
--- a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
+++ b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
@@ -183,6 +183,7 @@ public function entityQueryAlter(SelectInterface $query) {
       // match the anonymous user, that doesn't actually have a name in the
       // database.
       $conditions = &$query->conditions();
+      $anonymous_user_name = $this->userStorage->load(0)->getUsername();
       foreach ($conditions as $key => $condition) {
         if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users_field_data.name') {
           // Remove the condition.
@@ -202,7 +203,10 @@ public function entityQueryAlter(SelectInterface $query) {
           $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
           $value_part->compile($this->connection, $query);
           $or->condition(db_and()
-            ->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + array(':anonymous_name' => user_format_name($this->userStorage->load(0))))
+            ->where(
+                str_replace('anonymous_name', ':anonymous_name', (string) $value_part),
+                $value_part->arguments() + array(':anonymous_name' => $anonymous_user_name)
+              )
             ->condition('base_table.uid', 0)
           );
           $query->condition($or);
diff --git a/core/modules/user/src/Plugin/entity_reference/selection/UserSelection.php b/core/modules/user/src/Plugin/entity_reference/selection/UserSelection.php
new file mode 100644
index 0000000..012ea25
--- /dev/null
+++ b/core/modules/user/src/Plugin/entity_reference/selection/UserSelection.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\entity_reference\selection\UserSelection.
+ */
+
+namespace Drupal\user\Plugin\entity_reference\selection;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\entity_reference\Plugin\entity_reference\selection\SelectionBase;
+
+/**
+ * Provides specific access control for the user entity type.
+ *
+ * @EntityReferenceSelection(
+ *   id = "user_default",
+ *   label = @Translation("User selection"),
+ *   entity_types = {"user"},
+ *   group = "default",
+ *   weight = 1
+ * )
+ */
+class UserSelection extends SelectionBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function settingsForm(FieldDefinitionInterface $field_definition) {
+    $selection_handler_settings = $field_definition->getSetting('handler_settings');
+
+    // Merge in default values.
+    $selection_handler_settings += array(
+      'filter' => array(
+        'type' => '_none',
+      ),
+    );
+
+    // Add user specific filter options.
+    $form['filter']['type'] = array(
+      '#type' => 'select',
+      '#title' => t('Filter by'),
+      '#options' => array(
+        '_none' => t('- None -'),
+        'role' => t('User role'),
+      ),
+      '#ajax' => TRUE,
+      '#limit_validation_errors' => array(),
+      '#default_value' => $selection_handler_settings['filter']['type'],
+    );
+
+    $form['filter']['settings'] = array(
+      '#type' => 'container',
+      '#attributes' => array('class' => array('entity_reference-settings')),
+      '#process' => array('_entity_reference_form_process_merge_parent'),
+    );
+
+    if ($selection_handler_settings['filter']['type'] == 'role') {
+      // Merge in default values.
+      $selection_handler_settings['filter'] += array(
+        'role' => NULL,
+      );
+
+      $form['filter']['settings']['role'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Restrict to the selected roles'),
+        '#required' => TRUE,
+        '#options' => array_diff_key(user_role_names(TRUE), array(DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID)),
+        '#default_value' => $selection_handler_settings['filter']['role'],
+      );
+    }
+
+    $form += parent::settingsForm($field_definition);
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
+    $query = parent::buildEntityQuery($match, $match_operator);
+
+    // The user entity doesn't have a label column.
+    if (isset($match)) {
+      $query->condition('name', $match, $match_operator);
+    }
+
+    // Adding the permission check is sadly insufficient for users: core
+    // requires us to also know about the concept of 'blocked' and 'active'.
+    if (!\Drupal::currentUser()->hasPermission('administer users')) {
+      $query->condition('status', 1);
+    }
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function entityQueryAlter(SelectInterface $query) {
+    if (\Drupal::currentUser()->hasPermission('administer users')) {
+      // In addition, if the user is administrator, we need to make sure to
+      // match the anonymous user, that doesn't actually have a name in the
+      // database.
+      $conditions = &$query->conditions();
+      foreach ($conditions as $key => $condition) {
+        if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users.name') {
+          // Remove the condition.
+          unset($conditions[$key]);
+
+          // Re-add the condition and a condition on uid = 0 so that we end up
+          // with a query in the form:
+          // WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
+          $or = db_or();
+          $or->condition($condition['field'], $condition['value'], $condition['operator']);
+          // Sadly, the Database layer doesn't allow us to build a condition
+          // in the form ':placeholder = :placeholder2', because the 'field'
+          // part of a condition is always escaped.
+          // As a (cheap) workaround, we separately build a condition with no
+          // field, and concatenate the field and the condition separately.
+          $value_part = db_and();
+          $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
+          $value_part->compile(Database::getConnection(), $query);
+          $or->condition(db_and()
+            ->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + array(':anonymous_name' => user_load(0)->getUsername()))
+            ->condition('users.uid', 0)
+          );
+          $query->condition($or);
+        }
+      }
+    }
+
+    // Add the filter by role option.
+    if (!empty($this->fieldDefinition->getSetting('handler_settings')['filter'])) {
+      $filter_settings = $this->fieldDefinition->getSetting('handler_settings')['filter'];
+      if ($filter_settings['type'] == 'role') {
+        $tables = $query->getTables();
+        $base_table = $tables['base_table']['alias'];
+        $query->join('users_roles', 'ur', $base_table . '.uid = ur.uid');
+        $query->condition('ur.rid', $filter_settings['role']);
+      }
+    }
+  }
+}
diff --git a/core/modules/user/src/Plugin/views/field/Name.php b/core/modules/user/src/Plugin/views/field/Name.php
new file mode 100644
index 0000000..e987017
--- /dev/null
+++ b/core/modules/user/src/Plugin/views/field/Name.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\Plugin\views\field\Name.
+ */
+
+namespace Drupal\user\Plugin\views\field;
+
+use Drupal\Component\Utility\String;
+use Drupal\user\Plugin\views\field\User;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
+use Drupal\views\ResultRow;
+use Drupal\views\ViewExecutable;
+
+/**
+ * Field handler to provide simple renderer that allows using a themed user link.
+ *
+ * @ingroup views_field_handlers
+ *
+ * @ViewsField("user_name")
+ */
+class Name extends User {
+
+  /**
+   * Overrides \Drupal\user\Plugin\views\field\User::init().
+   *
+   * Add uid in the query so we can test for anonymous if needed.
+   */
+  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
+    parent::init($view, $display, $options);
+
+    if (!empty($this->options['overwrite_anonymous']) || !empty($this->options['format_username'])) {
+      $this->additional_fields['uid'] = 'uid';
+    }
+  }
+
+  protected function defineOptions() {
+    $options = parent::defineOptions();
+
+    $options['overwrite_anonymous'] = array('default' => FALSE, 'bool' => TRUE);
+    $options['anonymous_text'] = array('default' => '', 'translatable' => TRUE);
+    $options['format_username'] = array('default' => TRUE, 'bool' => TRUE);
+
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildOptionsForm(&$form, &$form_state) {
+    parent::buildOptionsForm($form, $form_state);
+
+    $form['format_username'] = array(
+      '#title' => t('Use formatted username'),
+      '#type' => 'checkbox',
+      '#default_value' => !empty($this->options['format_username']),
+      '#description' => t('If checked, the username will be formatted by the system. If unchecked, it will be displayed raw.'),
+    );
+    $form['overwrite_anonymous'] = array(
+      '#title' => t('Overwrite the value to display for anonymous users'),
+      '#type' => 'checkbox',
+      '#default_value' => !empty($this->options['overwrite_anonymous']),
+      '#description' => t('Enable to display different text for anonymous users.'),
+    );
+    $form['anonymous_text'] = array(
+      '#title' => t('Text to display for anonymous users'),
+      '#type' => 'textfield',
+      '#default_value' => $this->options['anonymous_text'],
+      '#states' => array(
+        'visible' => array(
+          ':input[name="options[overwrite_anonymous]"]' => array('checked' => TRUE),
+        ),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function renderLink($data, ResultRow $values) {
+    $account = entity_create('user');
+    $account->uid = $this->getValue($values, 'uid');
+    $account->name = $this->getValue($values);
+    if (!empty($this->options['link_to_user']) || !empty($this->options['overwrite_anonymous'])) {
+      if (!empty($this->options['overwrite_anonymous']) && !$account->id()) {
+        // This is an anonymous user, and we're overriting the text.
+        return String::checkPlain($this->options['anonymous_text']);
+      }
+      elseif (!empty($this->options['link_to_user'])) {
+        $account->name = $this->getValue($values);
+        $username = array(
+          '#theme' => 'username',
+          '#account' => $account,
+        );
+        return drupal_render($username);
+      }
+    }
+    // If we want a formatted username, do that.
+    if (!empty($this->options['format_username'])) {
+      return $account->getUsername();
+    }
+    // Otherwise, there's no special handling, so return the data directly.
+    return $data;
+  }
+
+}
diff --git a/core/modules/user/src/Tests/UserTokenReplaceTest.php b/core/modules/user/src/Tests/UserTokenReplaceTest.php
index 2a64e19..eecf4b2 100644
--- a/core/modules/user/src/Tests/UserTokenReplaceTest.php
+++ b/core/modules/user/src/Tests/UserTokenReplaceTest.php
@@ -56,7 +56,7 @@ function testUserTokenReplacement() {
     // Generate and test sanitized tokens.
     $tests = array();
     $tests['[user:uid]'] = $account->id();
-    $tests['[user:name]'] = SafeMarkup::checkPlain(user_format_name($account));
+    $tests['[user:name]'] = SafeMarkup::checkPlain($account->getUsername());
     $tests['[user:mail]'] = SafeMarkup::checkPlain($account->getEmail());
     $tests['[user:url]'] = $account->url('canonical', $url_options);
     $tests['[user:edit-url]'] = $account->url('edit-form', $url_options);
@@ -64,7 +64,7 @@ function testUserTokenReplacement() {
     $tests['[user:last-login:short]'] = format_date($account->getLastLoginTime(), 'short', '', NULL, $language_interface->getId());
     $tests['[user:created]'] = format_date($account->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
     $tests['[user:created:short]'] = format_date($account->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
-    $tests['[current-user:name]'] = SafeMarkup::checkPlain(user_format_name($global_account));
+    $tests['[current-user:name]'] = SafeMarkup::checkPlain($global_account->getUsername());
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
@@ -75,9 +75,9 @@ function testUserTokenReplacement() {
     }
 
     // Generate and test unsanitized tokens.
-    $tests['[user:name]'] = user_format_name($account);
+    $tests['[user:name]'] = $account->getUsername();
     $tests['[user:mail]'] = $account->getEmail();
-    $tests['[current-user:name]'] = user_format_name($global_account);
+    $tests['[current-user:name]'] = $global_account->getUsername();
 
     foreach ($tests as $input => $expected) {
       $output = $token_service->replace($input, array('user' => $account), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index 74469f3..164088a 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -107,19 +107,19 @@ function hook_user_cancel_methods_alter(&$methods) {
 /**
  * Alter the username that is displayed for a user.
  *
- * Called by user_format_name() to allow modules to alter the username that's
+ * Called by \Drupal\user\Entity\User to allow modules to alter the username that's
  * displayed. Can be used to ensure user privacy in situations where
  * $account->name is too revealing.
  *
  * @param $name
- *   The string that user_format_name() will return.
+ *   The account username.
  *
- * @param $account
- *   The account object passed to user_format_name().
+ * @param \Drupal\user\Entity\User $account
+ *   The account object.
  *
- * @see user_format_name()
+ * @see \Drupal\user\Entity\User
  */
-function hook_user_format_name_alter(&$name, $account) {
+function hook_user_format_name_alter(&$name, \Drupal\user\Entity\User $account) {
   // Display the user's uid instead of name.
   if ($account->id()) {
     $name = t('User !uid', array('!uid' => $account->id()));
diff --git a/core/modules/user/user.tokens.inc b/core/modules/user/user.tokens.inc
index 0439015..42dc78e 100644
--- a/core/modules/user/user.tokens.inc
+++ b/core/modules/user/user.tokens.inc
@@ -90,7 +90,7 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
           break;
 
         case 'name':
-          $name = user_format_name($account);
+          $name = $account->getUsername();
           $replacements[$original] = $sanitize ? SafeMarkup::checkPlain($name) : $name;
           break;
 
