diff --git a/core/modules/user/src/UserDataQuery.php b/core/modules/user/src/UserDataQuery.php
new file mode 100644
index 0000000..c1c5ec2
--- /dev/null
+++ b/core/modules/user/src/UserDataQuery.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\user;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Query\SelectInterface;
+
+/**
+ * Returns data stored for a user account.
+ */
+class UserDataQuery implements UserDataQueryInterface {
+
+  /**
+   * The database connection to use.
+   *
+   * @var \Drupal\Core\Database\Query\SelectInterface
+   */
+  protected $select;
+
+  /**
+   * Constructs a new user data service.
+   *
+   * @param \Drupal\Core\Database\Query\SelectInterface $select
+   *   A select query on the users_data table.
+   */
+  public function __construct(SelectInterface $select) {
+    $this->select = $select->fields('ud');
+  }
+
+  /**
+   * Factory method for the class.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection to use.
+   */
+  public static function create(Connection $connection) {
+    return new static($connection->select('users_data', 'ud'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function module($module) {
+    $this->select->condition('module', $module);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uid($uid) {
+    $this->select->condition('uid', $uid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function name($name) {
+    $this->select->condition('name', $name);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function value($value) {
+    if (!is_scalar($value)) {
+      $value = serialize($value);
+      $this->select->condition('serialized', 1);
+    }
+    $this->select->condition('value', $value);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    foreach ($this->select->execute() as $row) {
+      if ($row->serialized) {
+        $row->value = unserialize($row->value);
+      }
+      unset($row->serialized);
+      yield $row;
+    }
+  }
+
+}
diff --git a/core/modules/user/src/UserDataQueryInterface.php b/core/modules/user/src/UserDataQueryInterface.php
new file mode 100644
index 0000000..0f96d35
--- /dev/null
+++ b/core/modules/user/src/UserDataQueryInterface.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\user;
+
+interface UserDataQueryInterface {
+
+  /**
+   * Set the module value.
+   *
+   * @param string $module
+   *   The name of the module the data is associated with.
+   *
+   * @return $this
+   */
+  public function module($module);
+
+  /**
+   * Set the user account ID the data is associated with.
+   *
+   * @param $uid
+   *   The user account ID the data is associated with.
+   *
+   * @return $this
+   */
+  public function uid($uid);
+
+  /**
+   * Set the name of the data key.
+   *
+   * @param string $name
+   *   The name of the data key.
+   *
+   * @return $this
+   */
+  public function name($name);
+
+  /**
+   * Set the value stored.
+   *
+   * @param string $value
+   *   The value stored.
+   *
+   * @return $this
+   */
+  public function value($value);
+
+  /**
+   * Run the query.
+   *
+   * @return \Traversable
+   *   A list of result objects. Each object has module, uid, name and value
+   *   properties.
+   */
+  public function execute();
+
+}
diff --git a/core/modules/user/tests/src/Kernel/UserDataQueryTest.php b/core/modules/user/tests/src/Kernel/UserDataQueryTest.php
new file mode 100644
index 0000000..eb1bf7c
--- /dev/null
+++ b/core/modules/user/tests/src/Kernel/UserDataQueryTest.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\Tests\user\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\user\UserData
+ * @group user
+ */
+class UserDataQueryTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installSchema('user', ['users_data']);
+  }
+
+  /**
+   * @dataProvider userDataQueryProvider
+   */
+  public function testUserDataQuery($contents, $module, $uid, $name, $value, $expected) {
+    /** @var \Drupal\user\UserData $user_data */
+    $user_data = \Drupal::service('user.data');
+    foreach ($contents as $row) {
+      $user_data->set($row['module'], $row['uid'], $row['name'], $row['value']);
+    }
+    $user_data_query = \Drupal::service('user.data.query');
+    if (isset($module)) {
+      $user_data_query->module($module);
+    }
+    if (isset($uid)) {
+      $user_data_query->uid($uid);
+    }
+    if (isset($name)) {
+      $user_data_query->name($name);
+    }
+    if (isset($value)) {
+      $user_data_query->value($value);
+    }
+    $this->assertEquals([(object) $expected], iterator_to_array($user_data_query->execute()));
+  }
+
+  /**
+   * Data provider for testUserDataQuery.
+   *
+   * @return array
+   */
+  public function userDataQueryProvider() {
+    $uid = mt_rand(1, 10);
+    $name = $this->randomMachineName();
+    $module = $this->randomMachineName();
+    $value = mt_rand(20, 30);
+    $scalar_result = [
+      'module' => $module,
+      'uid' => $uid,
+      'name' => $name,
+      'value' => $value,
+    ];
+    $array_result = [
+      'module' => $module . '_',
+      'uid' => $uid + 1,
+      'name' => $name . '_',
+      'value' => [$value],
+    ];
+    $contents = [
+      $scalar_result,
+      $array_result,
+    ];
+    return [
+      [$contents, $module, $uid, $name, NULL, $scalar_result],
+      [$contents, $module, $uid, NULL, $value, $scalar_result],
+      [$contents, $module, NULL, $name, $value, $scalar_result],
+      [$contents, $module, $uid, NULL, NULL, $scalar_result],
+      [$contents, $module, NULL, $name, NULL, $scalar_result],
+      [$contents, $module, NULL, NULL, $value, $scalar_result],
+      [$contents, $module, NULL, NULL, NULL, $scalar_result],
+      [$contents, $module . '_', $uid + 1, $name . '_', NULL, $array_result],
+      [$contents, $module . '_', $uid + 1, NULL, [$value], $array_result],
+      [$contents, $module . '_', NULL, $name . '_', [$value], $array_result],
+      [$contents, $module . '_', $uid + 1, NULL, NULL, $array_result],
+      [$contents, $module . '_', NULL, $name . '_', NULL, $array_result],
+      [$contents, $module . '_', NULL, NULL, [$value], $array_result],
+      [$contents, $module . '_', NULL, NULL, NULL, $array_result],
+    ];
+  }
+
+}
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index c273cb3..5f0ce2e 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -25,6 +25,13 @@ services:
     arguments: ['@database']
     tags:
       - { name: backend_overridable }
+  user.data.query:
+    class: Drupal\user\UserDataQuery
+    shared: false
+    tags:
+      - { name: backend_overridable }
+    factory: ['Drupal\user\UserDataQuery', 'create']
+    arguments: ['@database']
   user_maintenance_mode_subscriber:
     class: Drupal\user\EventSubscriber\MaintenanceModeSubscriber
     arguments: ['@maintenance_mode', '@current_user']
