diff --git a/core/core.services.yml b/core/core.services.yml
index 6049d3b..b1fa2ca 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -33,6 +33,7 @@ parameters:
     - sftp
     - webcal
     - rtsp
+  pager_class: Drupal\Core\Pager\Pager
 services:
   # Simple cache contexts, directly derived from the request context.
   cache_context.ip:
@@ -1607,3 +1608,6 @@ services:
     arguments: ['@current_user', '@path.current', '@path.matcher', '@language_manager']
     tags:
       - { name: event_subscriber }
+  pager.factory:
+    class: Drupal\Core\Pager\PagerFactory
+    arguments: ['%pager_class%', '@service_container', '@request_stack']
diff --git a/core/globals.api.php b/core/globals.api.php
index 1487b19..0d0a6a8 100644
--- a/core/globals.api.php
+++ b/core/globals.api.php
@@ -78,7 +78,14 @@
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Do not directly set or get values from this array. Use the methods
+ *   provided by \Drupal\Core\Pager\PagerFactoryInterface and
+ *   \Drupal\Core\Pager\PagerInterface instead.
+ *
+ * @see \Drupal\Core\Pager\PagerFactoryInterface::get()
+ * @see \Drupal\Core\Pager\PagerInterface::init()
+ * @see \Drupal\Core\Pager\PagerInterface::getLimit()
  */
 global $pager_limits;
 
@@ -87,7 +94,14 @@
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Do not directly set or get values from this array. Use the methods
+ *   provided by \Drupal\Core\Pager\PagerFactoryInterface and
+ *   \Drupal\Core\Pager\PagerInterface instead.
+ *
+ * @see \Drupal\Core\Pager\PagerFactoryInterface::get()
+ * @see \Drupal\Core\Pager\PagerInterface::init()
+ * @see \Drupal\Core\Pager\PagerInterface::getCurrentPage()
  */
 global $pager_page_array;
 
@@ -96,7 +110,14 @@
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Do not directly set or get values from this array. Use the methods
+ *   provided by \Drupal\Core\Pager\PagerFactoryInterface and
+ *   \Drupal\Core\Pager\PagerInterface instead.
+ *
+ * @see \Drupal\Core\Pager\PagerFactoryInterface::get()
+ * @see \Drupal\Core\Pager\PagerInterface::init()
+ * @see \Drupal\Core\Pager\PagerInterface::getTotalPages()
  */
 global $pager_total;
 
@@ -105,6 +126,13 @@
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Do not directly set or get values from this array. Use the methods
+ *   provided by \Drupal\Core\Pager\PagerFactoryInterface and
+ *   \Drupal\Core\Pager\PagerInterface instead.
+ *
+ * @see \Drupal\Core\Pager\PagerFactoryInterface::get()
+ * @see \Drupal\Core\Pager\PagerInterface::init()
+ * @see \Drupal\Core\Pager\PagerInterface::getTotalItems()
  */
 global $pager_total_items;
diff --git a/core/includes/pager.inc b/core/includes/pager.inc
index 6b6bbb7..0cd17bf 100644
--- a/core/includes/pager.inc
+++ b/core/includes/pager.inc
@@ -5,8 +5,6 @@
  * Functions to aid in presenting database results as a set of pages.
  */
 
-use Drupal\Component\Utility\UrlHelper;
-
 /**
  * Returns the current page being requested for display within a pager.
  *
@@ -23,15 +21,17 @@
  *   even though the default pager implementation adjusts for this and still
  *   displays the third page of search results at that URL.
  *
- * @see pager_default_initialize()
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Normally, it should not be necessary to call this function as finding the
+ *   current page is part of the pager initialization. In such case, use
+ *   \Drupal::service('pager.factory')->get($element)->getCurrentPage()
+ *   instead. Only in the case a pager has not been initialized yet, use
+ *   \Drupal::service('pager.factory')->get($element)->findCurrentPage().
+ *
+ * @see \Drupal\Core\Pager\PagerInterface::init()
  */
 function pager_find_page($element = 0) {
-  $page = \Drupal::request()->query->get('page', '');
-  $page_array = explode(',', $page);
-  if (!isset($page_array[$element])) {
-    $page_array[$element] = 0;
-  }
-  return (int) $page_array[$element];
+  return \Drupal::service('pager.factory')->get($element)->findCurrentPage();
 }
 
 /**
@@ -61,7 +61,7 @@ function pager_find_page($element = 0) {
  *   $where = "status = 1";
  *   $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
  *   $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
- *   $page = pager_default_initialize($total, $num_per_page);
+ *   $page = \Drupal::service('pager.factory')->get(0)->init($total, $num_per_page)->getCurrentPage();
  *
  *   // Next, retrieve the items for the current page and put them into a
  *   // render array.
@@ -84,17 +84,20 @@ function pager_find_page($element = 0) {
  * this information). Here, we call pager_find_page() to calculate the desired
  * offset before the search is invoked:
  * @code
- *   // Perform the query, using the requested offset from pager_find_page().
- *   // This comes from a URL parameter, so here we are assuming that the URL
- *   // parameter corresponds to an actual page of results that will exist
- *   // within the set.
- *   $page = pager_find_page();
+ *   // First, get a pager for the element '0'.
+ *   $pager = \Drupal::service('pager.factory')->get(0);
+ *
+ *   // Perform the query, using the requested offset from
+ *   // $pager->findCurrentPage(). This comes from a URL parameter, so here we
+ *   // are assuming that the URL parameter corresponds to an actual page of
+ *   // results that will exist within the set.
+ *   $page = $pager->findCurrentPage();
  *   $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
  *   $offset = $num_per_page * $page;
  *   $result = mymodule_remote_search($keywords, $offset, $num_per_page);
  *
  *   // Now that we have the total number of results, initialize the pager.
- *   pager_default_initialize($result->total, $num_per_page);
+ *   $pager->init($result->total, $num_per_page);
  *
  *   // Create a render array with the search results.
  *   $render = [];
@@ -123,18 +126,15 @@ function pager_find_page($element = 0) {
  *   that does not correspond to the actual range of the result set was
  *   requested, this function will return the closest page actually within the
  *   result set.
+ *
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Use \Drupal::service('pager.factory')->get($element)->init($total, $limit)
+ *   to initialize the pager, and
+ *   \Drupal::service('pager.factory')->get($element)->getCurrentPage() to
+ *   retrieve the current page.
  */
 function pager_default_initialize($total, $limit, $element = 0) {
-  global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
-
-  $page = pager_find_page($element);
-
-  // We calculate the total of pages as ceil(items / limit).
-  $pager_total_items[$element] = $total;
-  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
-  $pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
-  $pager_limits[$element] = $limit;
-  return $pager_page_array[$element];
+  return \Drupal::service('pager.factory')->get($element)->init($total, $limit)->getCurrentPage();
 }
 
 /**
@@ -143,13 +143,14 @@ function pager_default_initialize($total, $limit, $element = 0) {
  * @return array
  *   A URL query parameter array that consists of all components of the current
  *   page request except for those pertaining to paging.
+ *
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Use
+ *   \Drupal\Component\Utility\UrlHelper::filterQueryParameters(\Drupal::request()->query->all(), ['page'])
+ *   instead.
  */
 function pager_get_query_parameters() {
-  $query = &drupal_static(__FUNCTION__);
-  if (!isset($query)) {
-    $query = UrlHelper::filterQueryParameters(\Drupal::request()->query->all(), array('page'));
-  }
-  return $query;
+  return \Drupal::service('pager.factory')->getCurrentRequestQueryParameters(['page']);
 }
 
 /**
@@ -173,15 +174,17 @@ function pager_get_query_parameters() {
  *     - #quantity: The number of pages in the list.
  */
 function template_preprocess_pager(&$variables) {
-  $element = $variables['pager']['#element'];
-  $parameters = $variables['pager']['#parameters'];
   $quantity = $variables['pager']['#quantity'];
-  $route_name = $variables['pager']['#route_name'];
-  $route_parameters = isset($variables['pager']['#route_parameters']) ? $variables['pager']['#route_parameters'] : [];
-  global $pager_page_array, $pager_total;
+
+  // Prepare the pager object.
+  $pager = \Drupal::service('pager.factory')
+    ->get($variables['pager']['#element'])
+    ->setRouteName($variables['pager']['#route_name'])
+    ->setRouteParameters(isset($variables['pager']['#route_parameters']) ? $variables['pager']['#route_parameters'] : [])
+    ->setInputQueryParameters($variables['pager']['#parameters']);
 
   // Nothing to do if there is only one page.
-  if ($pager_total[$element] <= 1) {
+  if ($pager->getTotalPages() <= 1) {
     return;
   }
 
@@ -191,13 +194,13 @@ function template_preprocess_pager(&$variables) {
   // Middle is used to "center" pages around the current page.
   $pager_middle = ceil($quantity / 2);
   // current is the page we are currently paged to.
-  $pager_current = $pager_page_array[$element] + 1;
+  $pager_current = $pager->getCurrentPage() + 1;
   // first is the first page listed by this pager piece (re quantity).
   $pager_first = $pager_current - $pager_middle + 1;
   // last is the last page listed by this pager piece (re quantity).
   $pager_last = $pager_current + $quantity - $pager_middle;
   // max is the maximum page number.
-  $pager_max = $pager_total[$element];
+  $pager_max = $pager->getTotalPages();
   // End of marker calculations.
 
   // Prepare for generation loop.
@@ -215,21 +218,17 @@ function template_preprocess_pager(&$variables) {
   // End of generation loop preparation.
 
   // Create the "first" and "previous" links if we are not on the first page.
-  if ($pager_page_array[$element] > 0) {
-    $items['first'] = array();
-    $options = array(
-      'query' => pager_query_add_page($parameters, $element, 0),
-    );
-    $items['first']['href'] = \Drupal::url($route_name, $route_parameters, $options);
+  if ($pager->getCurrentPage() > 0) {
+    $items['first'] = [
+      'href' => $pager->getLink(0),
+    ];
     if (isset($tags[0])) {
       $items['first']['text'] = $tags[0];
     }
 
-    $items['previous'] = array();
-    $options = array(
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
-    );
-    $items['previous']['href'] = \Drupal::url($route_name, $route_parameters, $options);
+    $items['previous'] = [
+      'href' => $pager->getLink($pager->getCurrentPage() - 1),
+    ];
     if (isset($tags[1])) {
       $items['previous']['text'] = $tags[1];
     }
@@ -242,10 +241,7 @@ function template_preprocess_pager(&$variables) {
     }
     // Now generate the actual pager piece.
     for (; $i <= $pager_last && $i <= $pager_max; $i++) {
-      $options = array(
-        'query' => pager_query_add_page($parameters, $element, $i - 1),
-      );
-      $items['pages'][$i]['href'] = \Drupal::url($route_name, $route_parameters, $options);
+      $items['pages'][$i]['href'] = $pager->getLink($i - 1);
       if ($i == $pager_current) {
         $variables['current'] = $i;
       }
@@ -257,21 +253,17 @@ function template_preprocess_pager(&$variables) {
   }
 
   // Create the "next" and "last" links if we are not on the last page.
-  if ($pager_page_array[$element] < ($pager_max - 1)) {
-    $items['next'] = array();
-    $options = array(
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
-    );
-    $items['next']['href'] = \Drupal::url($route_name, $route_parameters, $options);
+  if ($pager->getCurrentPage() < ($pager_max - 1)) {
+    $items['next'] = [
+      'href' => $pager->getLink($pager->getCurrentPage() + 1),
+    ];
     if (isset($tags[3])) {
       $items['next']['text'] = $tags[3];
     }
 
-    $items['last'] = array();
-    $options = array(
-      'query' => pager_query_add_page($parameters, $element, $pager_max - 1),
-    );
-    $items['last']['href'] = \Drupal::url($route_name, $route_parameters, $options);
+    $items['last'] = [
+      'href' => $pager->getLink($pager_max - 1),
+    ];
     if (isset($tags[4])) {
       $items['last']['text'] = $tags[4];
     }
@@ -307,25 +299,16 @@ function template_preprocess_pager(&$variables) {
  *
  * @return array
  *   The altered $query parameter array.
+ *
+ * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Use
+ *   \Drupal::service('pager.factory')->get($element)->setInputQueryParameters($query)->getLinkQueryParameters($index)
+ *   instead. However, normally this function should no longer be called, the
+ *   the PagerInterface object can return a full URL link via
+ *   PagerInterface::getLink().
+ *
+ * @see \Drupal\Core\Pager\PagerInterface
  */
 function pager_query_add_page(array $query, $element, $index) {
-  global $pager_page_array;
-
-  // Build the 'page' query parameter. This is built based on the current
-  // page of each pager element (or NULL if the pager is not set), with the
-  // exception of the requested page index for the current element.
-  $max_element = max(array_keys($pager_page_array));
-  $element_pages = [];
-  for ($i = 0; $i <= $max_element; $i++) {
-    $element_pages[] = ($i == $element) ? $index : (isset($pager_page_array[$i]) ? $pager_page_array[$i] : NULL);
-  }
-  $query['page'] = implode(',', $element_pages);
-
-  // Merge the query parameters passed to this function with the parameters
-  // from the current request. In case of collision, the parameters passed into
-  // this function take precedence.
-  if ($current_request_query = pager_get_query_parameters()) {
-    $query = array_merge($current_request_query, $query);
-  }
-  return $query;
+  return \Drupal::service('pager.factory')->get($element)->setInputQueryParameters($query)->getLinkQueryParameters($index);
 }
diff --git a/core/lib/Drupal/Core/Pager/Pager.php b/core/lib/Drupal/Core/Pager/Pager.php
new file mode 100644
index 0000000..5d3856e
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,323 @@
+<?php
+namespace Drupal\Core\Pager;
+
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Pager class.
+ */
+class Pager implements PagerInterface {
+
+  /**
+   * The pager element.
+   *
+   * This is the index used by query extenders to identify the query
+   * to be paged, and reflected in the 'page=x,y,z' query parameter
+   * of the HTTP request.
+   *
+   * @var int
+   */
+  protected $element;
+
+  /**
+   * Number of items per page.
+   *
+   * @var int
+   */
+  protected $limit = 0;
+
+  /**
+   * Current page number, 0-indexed.
+   *
+   * @var int
+   */
+  protected $currentPage = 0;
+
+  /**
+   * Total number of pages.
+   *
+   * @var int
+   */
+  protected $totalPages = 0;
+
+  /**
+   * Total number of items.
+   *
+   * @var int
+   */
+  protected $totalItems = 0;
+
+  /**
+   * The route name.
+   *
+   * @var string
+   */
+  protected $routeName;
+
+  /**
+   * The route parameters.
+   *
+   * @var string[]
+   */
+  protected $routeParameters = [];
+
+  /**
+   * The input query parameters for this pager's links.
+   *
+   * @var string[]
+   */
+  protected $inputQueryParameters;
+
+  /**
+   * The pager factory.
+   *
+   * @var \Drupal\Core\Pager\PagerFactoryInterface
+   */
+  protected $factory;
+
+  /**
+   * Constructs a new Pager object.
+   *
+   * @param \Drupal\Core\Pager\PagerFactoryInterface $pager_factory
+   *   The pager factory.
+   */
+  public function __construct(PagerFactoryInterface $pager_factory) {
+    $this->factory = $pager_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $element = NULL) {
+    $instance = new static(
+      $container->get('pager.factory')
+    );
+
+    // Set the pager element.
+    $instance->element = $element;
+
+    return $instance;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return $this->routeName;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRouteName($route_name) {
+    $this->routeName = $route_name;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteParameters() {
+    return $this->routeParameters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRouteParameters(array $route_parameters) {
+    $this->routeParameters = $route_parameters;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInputQueryParameters() {
+    return $this->inputQueryParameters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setInputQueryParameters(array $query_parameters) {
+    $this->inputQueryParameters = $query_parameters;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getElement() {
+    return $this->element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function init($total, $limit) {
+    // Gets the element's current page from the current request.
+    $page = $this->findCurrentPage();
+
+    // We calculate the total of pages as ceil(items / limit).
+    $this
+      ->setTotalItems($total)
+      ->setTotalPages(ceil($this->getTotalItems() / $limit))
+      ->setCurrentPage(max(0, min($page, ((int) $this->getTotalPages() - 1))))
+      ->setLimit($limit);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findCurrentPage() {
+    $page_query = $this->factory->getCurrentRequestQueryParameter('page');
+    $page_array = explode(',', $page_query);
+    if (!isset($page_array[$this->element])) {
+      $page_array[$this->element] = 0;
+    }
+    return (int) $page_array[$this->element];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCurrentPage() {
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_page_array;
+    $this->currentPage = isset($pager_page_array[$this->element]) ? $pager_page_array[$this->element] : NULL;
+
+    return $this->currentPage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCurrentPage($page) {
+    $this->currentPage = $page;
+
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_page_array;
+    $pager_page_array[$this->element] = $page;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTotalPages() {
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_total;
+    $this->totalPages = isset($pager_total[$this->element]) ? $pager_total[$this->element] : NULL;
+
+    return $this->totalPages;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTotalPages($total_pages) {
+    $this->totalPages = $total_pages;
+
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_total;
+    $pager_total[$this->element] = $total_pages;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLastPage() {
+    return max(0, $this->getTotalPages() - 1);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTotalItems() {
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_total_items;
+    $this->totalItems = isset($pager_total_items[$this->element]) ? $pager_total_items[$this->element] : NULL;
+
+    return $this->totalItems;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTotalItems($total_items) {
+    $this->totalItems = $total_items;
+
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_total_items;
+    $pager_total_items[$this->element] = $total_items;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLimit() {
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_limits;
+    $this->limit = isset($pager_limits[$this->element]) ? $pager_limits[$this->element] : NULL;
+
+    return $this->limit;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLimit($limit) {
+    $this->limit = $limit;
+
+    // @todo: BC layer. When removing the globals, remove the following.
+    global $pager_limits;
+    $pager_limits[$this->element] = $limit;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLinkQueryParameters($page) {
+    // Get all defined pagers.
+    $pagers = $this->factory->all();
+
+    // Build the 'page' query parameter. This is built based on the current
+    // page of each pager element (or NULL if the pager is not set), with the
+    // exception of the requested page index for the current element.
+    $query = $this->getInputQueryParameters();
+    $max_element = max(array_keys($pagers));
+    $element_pages = [];
+    for ($i = 0; $i <= $max_element; $i++) {
+      $element_pages[] = ($i == $this->getElement()) ? $page : (isset($pagers[$i]) ? $pagers[$i]->getCurrentPage() : NULL);
+    }
+    $query['page'] = implode(',', $element_pages);
+
+    // Merge the query parameters passed to this function with the parameters
+    // from the current request. In case of collision, the parameters passed into
+    // this function take precedence.
+    if ($current_request_query = $this->factory->getCurrentRequestQueryParameters()) {
+      $query = array_merge($current_request_query, $query);
+    }
+
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLink($page) {
+    $options = ['query' => $this->getLinkQueryParameters($page)];
+    return Url::fromRoute($this->getRouteName(), $this->getRouteParameters(), $options);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Pager/PagerFactory.php b/core/lib/Drupal/Core/Pager/PagerFactory.php
new file mode 100644
index 0000000..0aa5635
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
@@ -0,0 +1,115 @@
+<?php
+namespace Drupal\Core\Pager;
+
+use Drupal\Component\Utility\UrlHelper;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Provides a factory for pagers.
+ */
+class PagerFactory implements PagerFactoryInterface {
+
+  /**
+   * The class of PagerInterface objects.
+   *
+   * @var string
+   */
+  protected $pagerClass;
+
+  /**
+   * The array of pager objects.
+   *
+   * @var \Drupal\Core\Pager\PagerInterface[]
+   */
+  protected $pagers = [];
+
+  /**
+   * The service container.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * The request stack object.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs a new PagerFactory object.
+   *
+   * @param string $pager_class
+   *   The class to be used to instantiate PagerInterface objects.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct($pager_class, ContainerInterface $container, RequestStack $request_stack) {
+    $this->pagerClass = $pager_class;
+    $this->container = $container;
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($element) {
+    if (!isset($this->pagers[$element])) {
+      $class = $this->pagerClass;
+      $this->pagers[$element] = $class::create($this->container, $element);
+    }
+    return $this->pagers[$element];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function all() {
+    // @todo: BC layer. When removing the globals, remove the following and
+    // return $this->pagers directly.
+    global $pager_total;
+    if (!empty($pager_total)) {
+      foreach (array_keys($pager_total) as $element) {
+        $this->get($element);
+      }
+    }
+    return $this->pagers;
+  }
+
+  /**
+   * Returns the current request object.
+   *
+   * @return \Symfony\Component\HttpFoundation\Request
+   *   The request object.
+   */
+  protected function getCurrentRequest() {
+    return $this->requestStack->getCurrentRequest();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCurrentRequestQueryParameter($parameter) {
+    return $this->getCurrentRequest()->query->get($parameter, '');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCurrentRequestQueryParameters(array $filter = []) {
+    // @todo: BC layer. In Drupal 9.0.0, remove the $filter argument and just
+    // return the current request query.
+    if (!$filter) {
+      return $this->getCurrentRequest()->query->all();
+    }
+    static $query, $filtered;
+    if (!isset($query) || $filter !== $filtered) {
+      $query = UrlHelper::filterQueryParameters($this->getCurrentRequest()->query->all(), ['page']);
+      $filtered = $filter;
+    }
+    return $query;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
new file mode 100644
index 0000000..e841716
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
@@ -0,0 +1,56 @@
+<?php
+namespace Drupal\Core\Pager;
+
+/**
+ * Provides an interface for pager factory objects.
+ */
+interface PagerFactoryInterface {
+
+  /**
+   * Returns the pager object for the specified pager element.
+   *
+   * The pager gets created if not already existing.
+   *
+   * @param int $element
+   *   The pager element.
+   *
+   * @return \Drupal\Core\Pager\Pager
+   *   The pager object.
+   */
+  public function get($element);
+
+  /**
+   * Returns the current array of pager objects.
+   *
+   * @return \Drupal\Core\Pager\Pager[]
+   *   The array of pager objects.
+   */
+  public function all();
+
+  /**
+   * Returns a query string parameter from the current request.
+   *
+   * @param string $parameter
+   *   The query string parameter to return, e.g. 'page'.
+   *
+   * @return string[]
+   *   The requested query parameter.
+   */
+  public function getCurrentRequestQueryParameter($parameter);
+
+  /**
+   * Returns the current request query string, with optional filtering.
+   *
+   * @param array $filter
+   *   An array of query string parameters to remove from the returned query
+   *   parameters, e.g. ['page']. Defaults to empty array.
+   *
+   * @todo: BC layer. In Drupal 9.0.0, remove the $filter argument.
+   *
+   * @return array
+   *   A URL query parameter array that consists of all components of the current
+   *   page request except for those filtered out.
+   */
+  public function getCurrentRequestQueryParameters(array $filter = []);
+
+}
diff --git a/core/lib/Drupal/Core/Pager/PagerInterface.php b/core/lib/Drupal/Core/Pager/PagerInterface.php
new file mode 100644
index 0000000..3fc2470
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
@@ -0,0 +1,208 @@
+<?php
+namespace Drupal\Core\Pager;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+
+/**
+ * Provides an interface for pager objects.
+ */
+interface PagerInterface extends ContainerInjectionInterface {
+
+  /**
+   * Gets the route name for this pager.
+   *
+   * @return string
+   *   The route name.
+   */
+  public function getRouteName();
+
+  /**
+   * Sets the route name for this pager.
+   *
+   * @param string $route_name
+   *   The route name.
+   *
+   * @return \Drupal\Core\Pager\PagerInterface
+   *   The Pager object.
+   */
+  public function setRouteName($route_name);
+
+  /**
+   * Gets the route parameters for this pager.
+   *
+   * @return string[]
+   *   The route parameters.
+   */
+  public function getRouteParameters();
+
+  /**
+   * Sets the route parameters for this pager.
+   *
+   * @param string[] $route_parameters
+   *   The route parameters.
+   *
+   * @return \Drupal\Core\Pager\PagerInterface
+   *   The Pager object.
+   */
+  public function setRouteParameters(array $route_parameters);
+
+  /**
+   * Gets the query parameters for this pager.
+   *
+   * @return string[]
+   *   The query parameters array.
+   */
+  public function getInputQueryParameters();
+
+  /**
+   * Sets the query parameters for this pager.
+   *
+   * @var string[] $query_parameters
+   *   The query parameters array.
+   *
+   * @return \Drupal\Core\Pager\PagerInterface
+   *   The Pager object.
+   */
+  public function setInputQueryParameters(array $query_parameters);
+
+  /**
+   * Gets the pager element.
+   *
+   * @return int
+   *   The pager element.
+   */
+  public function getElement();
+
+  /**
+   * Initializes the pager.
+   *
+   * @param int $total
+   *   The total number of items to be paged.
+   * @param int $limit
+   *   The number of items the calling code will display per page.
+   *
+   * @return \Drupal\Core\Pager\PagerInterface
+   *   The Pager object.
+   */
+  public function init($total, $limit);
+
+  /**
+   * Gets the current page (0-index), from the request query parameters.
+   *
+   * @return int
+   *   The page to which the pager is currently positioned to.
+   *
+   * @deprecated as of Drupal @todo, will be removed in Drupal 9.0.0.
+   *   BC layer, @todo protected
+   */
+  public function findCurrentPage();
+
+  /**
+   * Gets the current page (0-index).
+   *
+   * @return int
+   *   The page to which the pager is currently positioned to.
+   */
+  public function getCurrentPage();
+
+  /**
+   * Sets the current page for this pager (0-index).
+   *
+   * @param int $page
+   *   The page to which the pager is currently positioned to.
+   *
+   * @return \Drupal\Core\Pager\Pager
+   *   The Pager object.
+   */
+  public function setCurrentPage($page);
+
+  /**
+   * Gets total pages in the pager.
+   *
+   * @return int
+   *   The total number of pages managed by the pager.
+   */
+  public function getTotalPages();
+
+  /**
+   * Sets total pages in the pager.
+   *
+   * @var $total_pages int
+   *   The total number of pages managed by the pager.
+   *
+   * @return \Drupal\Core\Pager\Pager
+   *   The Pager object.
+   */
+  public function setTotalPages($total_pages);
+
+  /**
+   * Gets last page in the pager (0-index).
+   *
+   * @return int
+   *   The index of the last page in the pager.
+   */
+  public function getLastPage();
+
+  /**
+   * Gets total items in the pager.
+   *
+   * @return int
+   *   The total number of items (records) managed by the pager.
+   */
+  public function getTotalItems();
+
+  /**
+   * Sets total items in the pager.
+   *
+   * @var $total_items int
+   *   The total number of items (records) managed by the pager.
+   *
+   * @return \Drupal\Core\Pager\Pager
+   *   The Pager object.
+   */
+  public function setTotalItems($total_items);
+
+  /**
+   * Gets the items per page.
+   *
+   * @return int
+   *   The number of items (records) in each page.
+   */
+  public function getLimit();
+
+  /**
+   * Sets the items per page.
+   *
+   * @var $limit int
+   *   The number of items (records) in each page.
+   *
+   * @return \Drupal\Core\Pager\Pager
+   *   The Pager object.
+   */
+  public function setLimit($limit);
+
+  /**
+   * Gets a pager link query parameters.
+   *
+   * @param int $page
+   *   The target page.
+   *
+   * @return string[]
+   *   The query parameters array.
+   *
+   * @todo change to protected when pager_query_add_page() is removed maybe?
+   */
+  public function getLinkQueryParameters($page);
+
+  /**
+   * Gets a pager link.
+   *
+   * @param int $page
+   *   The target page.
+   *
+   * @return string
+   *   The URL of the pager link.
+   */
+  public function getLink($page);
+
+}
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index 94c519e..b543578 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -1012,31 +1012,27 @@ function template_preprocess_views_exposed_form(&$variables) {
  *     exposed input.
  */
 function template_preprocess_views_mini_pager(&$variables) {
-  global $pager_page_array, $pager_total;
-
   $tags = &$variables['tags'];
-  $element = $variables['element'];
-  $parameters = $variables['parameters'];
+
+  // Prepare the pager object.
+  $pager = \Drupal::service('pager.factory')
+    ->get($variables['element'])
+    ->setRouteName('<current>')
+    ->setInputQueryParameters($variables['parameters']);
 
   // Current is the page we are currently paged to.
-  $variables['items']['current'] = $pager_page_array[$element] + 1;
+  $variables['items']['current'] = $pager->getCurrentPage() + 1;
 
-  if ($pager_total[$element] > 1 && $pager_page_array[$element] > 0) {
-    $options = array(
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
-    );
-    $variables['items']['previous']['href'] = \Drupal::url('<current>', [], $options);
+  if ($pager->getTotalPages() > 1 && $pager->getCurrentPage() > 0) {
+    $variables['items']['previous']['href'] = $pager->getLink($pager->getCurrentPage() - 1);
     if (isset($tags[1])) {
       $variables['items']['previous']['text'] = $tags[1];
     }
     $variables['items']['previous']['attributes'] = new Attribute();
   }
 
-  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
-    $options = array(
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
-    );
-    $variables['items']['next']['href'] = \Drupal::url('<current>', [], $options);
+  if ($pager->getCurrentPage() < ($pager->getTotalPages() - 1)) {
+    $variables['items']['next']['href'] = $pager->getLink($pager->getCurrentPage() + 1);
     if (isset($tags[3])) {
       $variables['items']['next']['text'] = $tags[3];
     }
