diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index be5171e7310320d01da45ef02fd51f34202cf1c4..c5aae17f36355518fd08ad7f079c98f4df025797 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -344,19 +344,9 @@ function template_preprocess_menu_local_task(&$variables) {
   $variables['link'] = array(
     '#type' => 'link',
     '#title' => $link_text,
+    '#url' => $link['url'],
     '#options' => $link['localized_options'],
   );
-
-  if (!empty($link['href'])) {
-    // @todo - Remove this once all pages are converted to routes.
-    $variables['link']['#href'] = $link['href'];
-  }
-  else {
-    $variables['link'] += array(
-      '#route_name' => $link['route_name'],
-      '#route_parameters' => $link['route_parameters'],
-    );
-  }
 }
 
 /**
@@ -373,9 +363,7 @@ function template_preprocess_menu_local_task(&$variables) {
 function template_preprocess_menu_local_action(&$variables) {
   $link = $variables['element']['#link'];
   $link += array(
-    'href' => '',
     'localized_options' => array(),
-    'route_parameters' => array(),
   );
   $link['localized_options']['attributes']['class'][] = 'button';
   $link['localized_options']['attributes']['class'][] = 'button-action';
@@ -385,19 +373,8 @@ function template_preprocess_menu_local_action(&$variables) {
     '#type' => 'link',
     '#title' => $link['title'],
     '#options' => $link['localized_options'],
+    '#url' => $link['url'],
   );
-
-  // @todo Figure out how to support local actions without a href properly.
-  if ($link['href'] === '' && !empty($link['route_name'])) {
-    $variables['link'] += array(
-      '#route_name' => $link['route_name'],
-      '#route_parameters' => $link['route_parameters'],
-    );
-  }
-  else {
-    // @todo - Remove this once all pages are converted to routes.
-    $variables['link']['#href'] = $link['href'];
-  }
 }
 
 /**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index b84302918545ca104acd116e1d27c17643f5c0c6..47fda818a718389ea9754787452fa4f109f901cc 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -902,14 +902,8 @@ function template_preprocess_status_messages(&$variables) {
  *     is used as its CSS class. Each link should be itself an array, with the
  *     following elements:
  *     - title: The link text.
- *     - route_name: (optional) The name of the route to link to. If omitted
- *       (and if 'href' is omitted as well), the 'title' is shown as
- *       a plain text item in the links list.
- *     - route_parameters: (optional) An array of route parameters for the link.
- *     - href: (optional) The link URL. It is preferred to use 'route_name' and
- *       'route parameters' for internal links. Use 'href' for links to external
- *       URLs. If omitted (and if 'route_name' is omitted as well), the 'title'
- *       is shown as a plain text item in the links list.
+ *     - url: (optional) The url object to link to. If omitted, no a tag is
+ *       printed out.
  *     - html: (optional) Whether or not 'title' is HTML. If set, the title
  *       will not be passed through
  *       \Drupal\Component\Utility\String::checkPlain().
@@ -986,30 +980,26 @@ function template_preprocess_links(&$variables) {
     foreach ($links as $key => $link) {
       $item = array();
       $link += array(
-        'href' => NULL,
-        'route_name' => NULL,
-        'route_parameters' => NULL,
         'ajax' => NULL,
+        'url' => NULL,
       );
 
       $li_attributes = array('class' => array());
       // Use the array key as class name.
       $li_attributes['class'][] = drupal_html_class($key);
 
-      $keys = array('title', 'href', 'route_name', 'route_parameters');
+      $keys = ['title', 'url'];
       $link_element = array(
         '#type' => 'link',
         '#title' => $link['title'],
         '#options' => array_diff_key($link, array_combine($keys, $keys)),
-        '#href' => $link['href'],
-        '#route_name' => $link['route_name'],
-        '#route_parameters' => $link['route_parameters'],
+        '#url' => $link['url'],
         '#ajax' => $link['ajax'],
       );
 
       // Handle links and ensure that the active class is added on the LIs, but
       // only if the 'set_active_class' option is not empty.
-      if (isset($link['href']) || isset($link['route_name'])) {
+      if (isset($link['url'])) {
         if (!empty($variables['set_active_class'])) {
 
           // Also enable set_active_class for the contained link.
@@ -1027,16 +1017,16 @@ function template_preprocess_links(&$variables) {
             $li_attributes['data-drupal-link-query'] = Json::encode($query);
           }
 
-          if (isset($link['route_name'])) {
-            $path = \Drupal::service('url_generator')->getPathFromRoute($link['route_name'], $link['route_parameters']);
+          /** @var \Drupal\Core\Url $url */
+          $url = $link['url'];
+          if ($url->isRouted()) {
+            // Add a "data-drupal-link-system-path" attribute to let the
+            // drupal.active-link library know the path in a standardized manner.
+            $system_path = $url->getInternalPath();
+            // @todo System path is deprecated - use the route name and parameters.
+            // Special case for the front page.
+            $li_attributes['data-drupal-link-system-path'] = $system_path == '' ? '<front>' : $system_path;
           }
-          else {
-            $path = $link['href'];
-          }
-
-          // Add a "data-drupal-link-system-path" attribute to let the
-          // drupal.active-link library know the path in a standardized manner.
-          $li_attributes['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager')->getPathByAlias($path);
         }
 
         $item['link'] = $link_element;
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php
index 01fb7dd6b4ce9bd7098df77d59c1f46539ac126b..a0ecdcf30c3eb90f015b660a63d8360c5e5e7218 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php
@@ -41,13 +41,15 @@ public function getDefaultOperations(EntityInterface $entity) {
         $operations['enable'] = array(
           'title' => t('Enable'),
           'weight' => -10,
-        ) + $entity->urlInfo('enable')->toArray();
+          'url' => $entity->urlInfo('enable'),
+        );
       }
       elseif ($entity->hasLinkTemplate('disable')) {
         $operations['disable'] = array(
           'title' => t('Disable'),
           'weight' => 40,
-        ) + $entity->urlInfo('disable')->toArray();
+          'url' => $entity->urlInfo('disable'),
+        );
       }
     }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php
index 0a46a2ee21c41d791b366668a9766b22d6ccef23..25acfab241892c0cc12b8a4c2a33b4be9139b7c5 100644
--- a/core/lib/Drupal/Core/Entity/EntityForm.php
+++ b/core/lib/Drupal/Core/Entity/EntityForm.php
@@ -213,7 +213,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
           'class' => array('button', 'button--danger'),
         ),
       );
-      $actions['delete'] += $route_info->toRenderArray();
+      $actions['delete']['#url'] = $route_info;
     }
 
     return $actions;
diff --git a/core/lib/Drupal/Core/Entity/EntityListBuilder.php b/core/lib/Drupal/Core/Entity/EntityListBuilder.php
index eee643b5704852046629017414564a56c3e8d82f..9e41b08337330e6f62f954303bf0a20553e6d8bb 100644
--- a/core/lib/Drupal/Core/Entity/EntityListBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityListBuilder.php
@@ -118,13 +118,15 @@ protected function getDefaultOperations(EntityInterface $entity) {
       $operations['edit'] = array(
         'title' => $this->t('Edit'),
         'weight' => 10,
-      ) + $entity->urlInfo('edit-form')->toArray();
+        'url' => $entity->urlInfo('edit-form'),
+      );
     }
     if ($entity->access('delete') && $entity->hasLinkTemplate('delete-form')) {
       $operations['delete'] = array(
         'title' => $this->t('Delete'),
         'weight' => 100,
-      ) + $entity->urlInfo('delete-form')->toArray();
+        'url' => $entity->urlInfo('delete-form'),
+      );
     }
 
     return $operations;
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php
index 58f6c6b300551f7b983b4835b18275a66b7ece29..19311b5695c213cd211e907f105d2e73da5e87fe 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Field\FormatterBase;
 use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Url;
 
 /**
  * Plugin implementation of the 'email_mailto' formatter.
@@ -33,7 +34,7 @@ public function viewElements(FieldItemListInterface $items) {
       $elements[$delta] = array(
         '#type' => 'link',
         '#title' => $item->value,
-        '#href' => 'mailto:' . $item->value,
+        '#url' => Url::fromUri('mailto:' . $item->value),
       );
     }
 
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php
index 762d5f38531b8d75799aed424117b713ae9ef80c..51e0c3bd30f2f6238c7489de1be2373735520b3e 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Field\FormatterBase;
 use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Url;
 
 /**
  * Plugin implementation of the 'uri_link' formatter.
@@ -30,11 +31,13 @@ public function viewElements(FieldItemListInterface $items) {
     $elements = array();
 
     foreach ($items as $delta => $item) {
-      $elements[$delta] = array(
-        '#type' => 'link',
-        '#href' => $item->value,
-        '#title' => $item->value,
-      );
+      if (!$item->isEmpty()) {
+        $elements[$delta] = [
+          '#type' => 'link',
+          '#url' => Url::fromUri($item->value),
+          '#title' => $item->value,
+        ];
+      }
     }
 
     return $elements;
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php
index a823cac3ae054c0b2fa755f2dbcd0a115957d72b..a50696b2ee7608e4e80f4063eb1289b7c5f26166 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php
@@ -61,4 +61,15 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    $value = $this->getValue();
+    if (!isset($value['value']) || $value['value'] === '') {
+      return TRUE;
+    }
+    return parent::isEmpty();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Form/ConfirmFormHelper.php b/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
index de98053d7ce1c3b61ba913770dd7521b878cd082..b58078d39e1633a7a5aa84e6661fc2754ba2e047 100644
--- a/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
+++ b/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
@@ -34,19 +34,20 @@ public static function buildCancelLink(ConfirmFormInterface $form, Request $requ
     // If a destination is specified, that serves as the cancel link.
     if ($query->has('destination')) {
       $options = UrlHelper::parse($query->get('destination'));
-      $link = array(
-        '#href' => $options['path'],
-        '#options' => $options,
-      );
+      // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
+      //   resolved.
+      $url = Url::fromUri('base://' . $options['path'], $options);
     }
     // Check for a route-based cancel link.
-    elseif ($url = $form->getCancelUrl()) {
-      $link = $url->toRenderArray();
+    else {
+      $url = $form->getCancelUrl();
     }
 
-    $link['#type'] = 'link';
-    $link['#title'] = $form->getCancelText();
-    return $link;
+    return [
+      '#type' => 'link',
+      '#title' => $form->getCancelText(),
+      '#url' => $url,
+    ];
   }
 
 }
diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php
index 6fe99d6f7531020df20b7a18b2c47a632d5b11c8..ee8bd2acff961df86d0ad8b979acb90d4db597e4 100644
--- a/core/lib/Drupal/Core/Language/LanguageManager.php
+++ b/core/lib/Drupal/Core/Language/LanguageManager.php
@@ -11,6 +11,7 @@
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\StringTranslation\TranslationWrapper;
+use Drupal\Core\Url;
 
 /**
  * Class responsible for providing language support on language-unaware sites.
@@ -239,7 +240,7 @@ public function getFallbackCandidates(array $context = array()) {
   /**
    * {@inheritdoc}
    */
-  public function getLanguageSwitchLinks($type, $path) {
+  public function getLanguageSwitchLinks($type, Url $url) {
     return array();
   }
 
diff --git a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php
index e163c0cde97953d1eaa1e81c1659313fa918c821..783496c443c24e8f61973382f0060fcbe9c4fdad 100644
--- a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php
+++ b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Language;
 
 use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Url;
 
 /**
  * Common interface for the language manager service.
@@ -181,13 +182,13 @@ public function getFallbackCandidates(array $context = array());
    *
    * @param string $type
    *   The language type.
-   * @param string $path
-   *   The internal path the switch links will be relative to.
+   * @param \Drupal\Core\Url $url
+   *   The URL the switch links will be relative to.
    *
    * @return array
    *   A keyed array of links ready to be themed.
    */
-  public function getLanguageSwitchLinks($type, $path);
+  public function getLanguageSwitchLinks($type, Url $url);
 
   /**
    * Sets the configuration override language.
diff --git a/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
index f6ee14bef183c99443b8c69bb225d7bda66c28ac..bf86365bb3459fa7773538b66087e5a2345d598f 100644
--- a/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
+++ b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
@@ -113,7 +113,8 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
     $link = array(
       '#type' => 'link',
       '#title' => $this->menuLink->getTitle(),
-    ) + $this->menuLink->getUrlObject()->toRenderArray();
+      '#url' => $this->menuLink->getUrlObject(),
+    );
     $form['path'] = array(
       'link' => $link,
       '#type' => 'item',
diff --git a/core/lib/Drupal/Core/Menu/LocalActionManager.php b/core/lib/Drupal/Core/Menu/LocalActionManager.php
index 2e4eb9258a6063b0cbab2dde3e541fff395cb963..68550bc86992171ff8c4dfde0ab3ba9e319f00d9 100644
--- a/core/lib/Drupal/Core/Menu/LocalActionManager.php
+++ b/core/lib/Drupal/Core/Menu/LocalActionManager.php
@@ -16,6 +16,7 @@
 use Drupal\Core\Plugin\Discovery\YamlDiscovery;
 use Drupal\Core\Plugin\Factory\ContainerFactory;
 use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -166,8 +167,7 @@ public function getActionsForRoute($route_appears) {
         '#theme' => 'menu_local_action',
         '#link' => array(
           'title' => $this->getTitle($plugin),
-          'route_name' => $route_name,
-          'route_parameters' => $route_parameters,
+          'url' => Url::fromRoute($route_name, $route_parameters),
           'localized_options' => $plugin->getOptions($request),
         ),
         '#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account),
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
index 3914d8edfc6995d3c96e4b960d70a87976d5c35b..5b85d7ff4dc38cc4bd61aedb23d5b6bd22066655 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
@@ -21,6 +21,7 @@
 use Drupal\Core\Routing\RouteBuilderInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
@@ -306,8 +307,7 @@ public function getTasksBuild($current_route_name) {
 
           $link = array(
             'title' => $this->getTitle($child),
-            'route_name' => $route_name,
-            'route_parameters' => $route_parameters,
+            'url' => Url::fromRoute($route_name, $route_parameters),
             'localized_options' => $child->getOptions($request),
           );
           $build[$level][$plugin_id] = array(
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkInterface.php b/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
index fdc61df666cfdb4505341d9db5f2389a3277c2fb..ffef25584510c62df165e408330516700fc9afbd 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
@@ -218,9 +218,9 @@ public function getFormClass();
   /**
    * Returns route information for a route to delete the menu link.
    *
-   * @return array|null
-   *   An array with keys route_name and route_parameters, or NULL if there is
-   *   no route (e.g. when the link is not deletable).
+   * @return \Drupal\Core\Url|null
+   *   A Url object, or NULL if there is no route (e.g. when the link is not
+   *   deletable).
    */
   public function getDeleteRoute();
 
@@ -231,18 +231,18 @@ public function getDeleteRoute();
    * they need to define additional local tasks, local actions, etc. that are
    * visible from the edit form.
    *
-   * @return array|null
-   *   An array with keys route_name and route_parameters, or NULL if there is
-   *   no route because there is no custom edit route for this instance.
+   * @return \Drupal\Core\Url|null
+   *   A Url object, or NULL if there is no route because there is no custom
+   *   edit route for this instance.
    */
   public function getEditRoute();
 
   /**
    * Returns route information for a route to translate the menu link.
    *
-   * @return array
-   *   An array with keys route_name and route_parameters, or NULL if there is
-   *   no route (e.g. when the link is not translatable).
+   * @return \Drupal\Core\Url|null
+   *   A Url object, or NULL if there is no route (e.g. when the link is not
+   *   translatable).
    */
   public function getTranslateRoute();
 
diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php
index 474e9f5531591ae9f5f4465264478f3f891e3a57..b785bd63af1e3b29279ed6cbf62b07d753bb9cb1 100644
--- a/core/lib/Drupal/Core/Render/Element/Link.php
+++ b/core/lib/Drupal/Core/Render/Element/Link.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Render\Element;
 
-use Drupal\Core\Url as UrlObject;
+use Drupal\Component\Utility\NestedArray;
 
 /**
  * Provides a link render element.
@@ -36,11 +36,7 @@ public function getInfo() {
    * @param array $element
    *   A structured array whose keys form the arguments to _l():
    *   - #title: The link text to pass as argument to _l().
-   *   - One of the following
-   *     - #route_name and (optionally) a #route_parameters array; The route
-   *       name and route parameters which will be passed into the link
-   *       generator.
-   *     - #href: The system path or URL to pass as argument to _l().
+   *   - #url: The URL info either pointing to a route or a non routed path.
    *   - #options: (optional) An array of options to pass to _l() or the link
    *     generator.
    *
@@ -75,21 +71,12 @@ public static function preRenderLink($element) {
       if (!isset($element['#id'])) {
         $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link');
       }
-      // If #ajax['path] was not specified, use the href as Ajax request URL.
-      if (!isset($element['#ajax']['path'])) {
-        $element['#ajax']['path'] = $element['#href'];
-        $element['#ajax']['options'] = $element['#options'];
-      }
       $element = static::preRenderAjaxForm($element);
     }
 
-    if (isset($element['#route_name'])) {
-      $element['#route_parameters'] = empty($element['#route_parameters']) ? array() : $element['#route_parameters'];
-      $element['#markup'] = \Drupal::l($element['#title'], new UrlObject($element['#route_name'], $element['#route_parameters'], $element['#options']));
-    }
-    else {
-      // @todo Convert to \Drupal::l(): https://www.drupal.org/node/2347045.
-      $element['#markup'] = _l($element['#title'], $element['#href'], $element['#options']);
+    if (!empty($element['#url'])) {
+      $options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']);
+      $element['#markup'] = \Drupal::l($element['#title'], $element['#url']->setOptions($options));
     }
     return $element;
   }
diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php
index d82fda8d1c8206e18e149543791ccd192fb5fb6d..08c539a14791570f522e6b1b5a879b9296740f74 100644
--- a/core/lib/Drupal/Core/Render/Element/Table.php
+++ b/core/lib/Drupal/Core/Render/Element/Table.php
@@ -281,7 +281,7 @@ public static function validateTable(&$element, FormStateInterface $form_state,
    *   $form['table'][$row]['edit'] = array(
    *     '#type' => 'link',
    *     '#title' => t('Edit'),
-   *     '#href' => 'thing/' . $row . '/edit',
+   *     '#url' => Url::fromRoute('entity.test_entity.edit_form', ['test_entity' => $row]),
    *   );
    * }
    * @endcode
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index a6f0220710fa2b6d0097dd874ed460bb8c7a1668..9f78645a238d7e431fe37cf3d4be8ef1551ae9eb 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core;
 
+use Drupal\Component\Utility\String;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -203,7 +204,7 @@ public static function fromRoute($route_name, $route_parameters = array(), $opti
    */
   public static function fromUri($uri, $options = array()) {
     if (!parse_url($uri, PHP_URL_SCHEME)) {
-      throw new \InvalidArgumentException('You must use a valid URI scheme. Use base:// for a path, e.g., to a Drupal file that needs the base path. Do not use this for internal paths controlled by Drupal.');
+      throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base:// for a path, e.g., to a Drupal file that needs the base path. Do not use this for internal paths controlled by Drupal.', ['@uri' => $uri]));
     }
 
     $url = new static($uri, array(), $options);
@@ -455,22 +456,16 @@ public function toString() {
    *
    * @return array
    *   An associative array containing all the properties of the route.
+   *
+   * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.
+   *   Most usecases should use the URL object directly, like #type links. Other
+   *   usecases should get the information from the URL object manually.
    */
   public function toArray() {
-    if ($this->unrouted) {
-      return array(
-        // @todo Change 'path' to 'href': https://www.drupal.org/node/2347025.
-        'path' => $this->getUri(),
-        'options' => $this->getOptions(),
-      );
-    }
-    else {
-      return array(
-        'route_name' => $this->getRouteName(),
-        'route_parameters' => $this->getRouteParameters(),
-        'options' => $this->getOptions(),
-      );
-    }
+    return [
+      'url' => $this,
+      'options' => $this->getOptions(),
+    ];
   }
 
   /**
@@ -480,20 +475,14 @@ public function toArray() {
    *   An associative array suitable for a render array.
    */
   public function toRenderArray() {
-    if ($this->unrouted) {
-      return array(
-        '#href' => $this->getUri(),
-        '#options' => $this->getOptions(),
-      );
-    }
-    else {
-      return array(
-        '#route_name' => $this->getRouteName(),
-        '#route_parameters' => $this->getRouteParameters(),
-        '#options' => $this->getOptions(),
-        '#access_callback' => array(get_class(), 'renderAccess'),
-      );
+    $render_array = [
+      '#url' => $this,
+      '#options' => $this->getOptions(),
+    ];
+    if (!$this->unrouted) {
+      $render_array['#access_callback'] = [get_class(), 'renderAccess'];
     }
+    return $render_array;
   }
 
   /**
@@ -543,7 +532,7 @@ public function access(AccountInterface $account = NULL) {
    *   Returns TRUE if the current user has access to the url, otherwise FALSE.
    */
   public static function renderAccess(array $element) {
-    return (new static($element['#route_name'], $element['#route_parameters'], $element['#options']))->access();
+    return $element['#url']->access();
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
index f56755819745ab4a76e72e89b6e02e055fbd142f..b029327a9935e447c24cd6c8eda6f480312b3ce4 100644
--- a/core/lib/Drupal/Core/Utility/LinkGenerator.php
+++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php
@@ -109,7 +109,7 @@ public function generate($text, Url $url) {
 
       // Add a "data-drupal-link-system-path" attribute to let the
       // drupal.active-link library know the path in a standardized manner.
-      if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
+      if ($url->isRouted() && !isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
         // @todo System path is deprecated - use the route name and parameters.
         $system_path = $url->getInternalPath();
         // Special case for the front page.
diff --git a/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
index 52d1d1397b50ce74ab8c8da7d667c299615fffb5..08d00834c67b951b575bdc324c0215d2a697f5dc 100644
--- a/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
+++ b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Utility;
 
+use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -56,7 +57,7 @@ public function assemble($uri, array $options = []) {
       // UrlHelper::isExternal() only returns true for safe protocols.
       return $this->buildExternalUrl($uri, $options);
     }
-    throw new \InvalidArgumentException('You must use a valid URI scheme. Use base:// for a path e.g. to a Drupal file that needs the base path.');
+    throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base:// for a path, e.g., to a Drupal file that needs the base path. Do not use this for internal paths controlled by Drupal.', ['@uri' => $uri]));
   }
 
   /**
diff --git a/core/modules/aggregator/src/Controller/AggregatorController.php b/core/modules/aggregator/src/Controller/AggregatorController.php
index a37a81d29c986c4843b5b60205a509bd01e07517..a7e25fff804ae8abd3961d9660688c8e0d484129 100644
--- a/core/modules/aggregator/src/Controller/AggregatorController.php
+++ b/core/modules/aggregator/src/Controller/AggregatorController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Datetime\DateFormatter;
 use Drupal\aggregator\FeedInterface;
+use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -128,25 +129,21 @@ public function adminOverview() {
       $refresh_rate = $feed->getRefreshRate();
       $row[] = ($last_checked ? $this->t('@time ago', array('@time' => $this->dateFormatter->formatInterval(REQUEST_TIME - $last_checked))) : $this->t('never'));
       $row[] = ($last_checked && $refresh_rate ? $this->t('%time left', array('%time' => $this->dateFormatter->formatInterval($last_checked + $refresh_rate - REQUEST_TIME))) : $this->t('never'));
-      $links['edit'] = array(
+      $links['edit'] = [
         'title' => $this->t('Edit'),
-        'route_name' => 'entity.aggregator_feed.edit_form',
-        'route_parameters' => array('aggregator_feed' => $feed->id()),
-      );
+        'url' => Url::fromRoute('entity.aggregator_feed.edit_form', ['aggregator_feed' => $feed->id()]),
+      ];
       $links['delete'] = array(
         'title' => $this->t('Delete'),
-        'route_name' => 'entity.aggregator_feed.delete_form',
-        'route_parameters' => array('aggregator_feed' => $feed->id()),
+        'url' => Url::fromRoute('entity.aggregator_feed.delete_form', ['aggregator_feed' => $feed->id()]),
       );
       $links['delete_items'] = array(
         'title' => $this->t('Delete items'),
-        'route_name' => 'aggregator.feed_items_delete',
-        'route_parameters' => array('aggregator_feed' => $feed->id()),
+        'url' => Url::fromRoute('aggregator.feed_items_delete', ['aggregator_feed' => $feed->id()]),
       );
       $links['update'] = array(
         'title' => $this->t('Update items'),
-        'route_name' => 'aggregator.feed_refresh',
-        'route_parameters' => array('aggregator_feed' => $feed->id()),
+        'url' => Url::fromRoute('aggregator.feed_refresh', ['aggregator_feed' => $feed->id()])
       );
       $row[] = array(
         'data' => array(
diff --git a/core/modules/aggregator/src/FeedViewBuilder.php b/core/modules/aggregator/src/FeedViewBuilder.php
index 26523ab6c44e415cab125b859e35bac9ecf753b3..944d9a890269f0d1803ad0067914e8ebde826950 100644
--- a/core/modules/aggregator/src/FeedViewBuilder.php
+++ b/core/modules/aggregator/src/FeedViewBuilder.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityViewBuilder;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -99,7 +100,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
           $image_link = array(
             '#type' => 'link',
             '#title' => $link_title,
-            '#href' => $link_href,
+            '#url' => Url::fromUri($link_href),
             '#options' => array(
               'attributes' => array('class' => array('feed-image')),
               'html' => TRUE,
@@ -124,8 +125,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
           '#title' => t('More<span class="visually-hidden"> posts about @title</span>', array(
             '@title' => $title_stripped,
           )),
-          '#route_name' => 'entity.aggregator_feed.canonical',
-          '#route_parameters' => array('aggregator_feed' => $entity->id()),
+          '#url' => Url::fromRoute('entity.aggregator_feed.canonical', ['aggregator_feed' => $entity->id()]),
           '#options' => array(
             'html' => TRUE,
             'attributes' => array(
diff --git a/core/modules/aggregator/src/ItemsImporter.php b/core/modules/aggregator/src/ItemsImporter.php
index af193253f39967444729b62d7eb1d673dae1c902..3098f085749db0c8dd7d02073bb8af6aafed61e4 100644
--- a/core/modules/aggregator/src/ItemsImporter.php
+++ b/core/modules/aggregator/src/ItemsImporter.php
@@ -120,7 +120,7 @@ public function refresh(FeedInterface $feed) {
       // Parse the feed.
       try {
         if ($this->parserManager->createInstance($this->config->get('parser'))->parse($feed)) {
-          if ($feed->getWebsiteUrl()) {
+          if (!$feed->getWebsiteUrl()) {
             $feed->setWebsiteUrl($feed->getUrl());
           }
           $feed->setHash($hash);
diff --git a/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php b/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
index f28ff0d0cfcdd3ada9ef770933e2bb66288e4de6..2710cedb86bb73f7255b184f1dfcfea0269591b5 100644
--- a/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
+++ b/core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
@@ -164,7 +164,7 @@ public function build() {
 
       $more_link = array(
         '#type' => 'more_link',
-        '#href' => 'aggregator/sources/' . $feed->id(),
+        '#url' => $feed->urlInfo(),
         '#attributes' => array('title' => $this->t("View this feed's recent news.")),
       );
       $read_more = drupal_render($more_link);
diff --git a/core/modules/aggregator/src/Tests/AddFeedTest.php b/core/modules/aggregator/src/Tests/AddFeedTest.php
index 5b15d02c9f6e4f61f073c6adc176424a592d4dcd..bbeae854fd581920e5133e2c8ac53f987b075756 100644
--- a/core/modules/aggregator/src/Tests/AddFeedTest.php
+++ b/core/modules/aggregator/src/Tests/AddFeedTest.php
@@ -18,6 +18,7 @@ class AddFeedTest extends AggregatorTestBase {
    */
   function testAddFeed() {
     $feed = $this->createFeed();
+    $feed->refreshItems();
 
     // Check feed data.
     $this->assertUrl(\Drupal::url('aggregator.feed_add', [], ['absolute' => TRUE]), [], 'Directed to correct url.');
@@ -27,7 +28,7 @@ function testAddFeed() {
     $this->drupalGet('aggregator/sources/' . $feed->id());
     $this->assertResponse(200, 'Feed source exists.');
     $this->assertText($feed->label(), 'Page title');
-    $this->assertText($feed->label());
+    $this->assertRaw($feed->getWebsiteUrl());
 
     // Delete feed.
     $this->deleteFeed($feed);
@@ -40,11 +41,13 @@ function testAddLongFeed() {
     // Create a feed with a URL of > 255 characters.
     $long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb";
     $feed = $this->createFeed($long_url);
+    $feed->refreshItems();
 
     // Create a second feed of > 255 characters, where the only difference is
     // after the 255th character.
     $long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889";
     $feed_2 = $this->createFeed($long_url_2);
+    $feed->refreshItems();
 
     // Check feed data.
     $this->assertTrue($this->uniqueFeed($feed->label(), $feed->getUrl()), 'The first long URL feed is unique.');
@@ -54,7 +57,6 @@ function testAddLongFeed() {
     $this->drupalGet('aggregator/sources/' . $feed->id());
     $this->assertResponse(200, 'Long URL feed source exists.');
     $this->assertText($feed->label(), 'Page title');
-    $this->assertText($feed->label());
 
     // Delete feeds.
     $this->deleteFeed($feed);
diff --git a/core/modules/ban/src/Form/BanAdmin.php b/core/modules/ban/src/Form/BanAdmin.php
index 94c1d4f0b77d6a5bd47df8e706fd3e696ae9635d..1690b91035a63f28726a42ed3b110ed7b75a28d8 100644
--- a/core/modules/ban/src/Form/BanAdmin.php
+++ b/core/modules/ban/src/Form/BanAdmin.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\ban\BanIpManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -65,8 +66,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $default_
       $links = array();
       $links['delete'] = array(
         'title' => $this->t('Delete'),
-        'route_name' => 'ban.delete',
-        'route_parameters' => array('ban_id' => $ip->iid),
+        'url' => Url::fromRoute('ban.delete', ['ban_id' => $ip->iid]),
       );
       $row[] = array(
         'data' => array(
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index cbf56ab3b6b2c9e579daddd9b5bdfb1fe4cd150e..952bad2b97b7f9db822727bdfab4a9fea6928c16 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -93,10 +93,15 @@ function block_page_build(&$page) {
     $page['page_top']['backlink'] = array(
       '#type' => 'link',
       '#title' => t('Exit block region demonstration'),
-      '#href' => 'admin/structure/block' . (\Drupal::config('system.theme')->get('default') == $theme ? '' : '/list/' . $theme),
       '#options' => array('attributes' => array('class' => array('block-demo-backlink'))),
       '#weight' => -10,
     );
+    if (\Drupal::config('system.theme')->get('default') == $theme) {
+      $page['page_top']['backlink']['#url'] = Url::fromRoute('block.admin_display');
+    }
+    else {
+      $page['page_top']['backlink']['#url'] = Url::fromRoute('block.admin_display_theme', ['theme' => $theme]);
+    }
   }
 }
 
diff --git a/core/modules/block/src/BlockListBuilder.php b/core/modules/block/src/BlockListBuilder.php
index e5da65d1342c0fa97aaa262a8575d481d88d4075..1d11f9979a18e5b405ddcaf4c989c242d797905c 100644
--- a/core/modules/block/src/BlockListBuilder.php
+++ b/core/modules/block/src/BlockListBuilder.php
@@ -16,6 +16,7 @@
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -356,11 +357,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       }
       $form['place_blocks']['list'][$category_key]['content']['#links'][$plugin_id] = array(
         'title' => $plugin_definition['admin_label'],
-        'route_name' => 'block.admin_add',
-        'route_parameters' => array(
+        'url' => Url::fromRoute('block.admin_add', [
           'plugin_id' => $plugin_id,
           'theme' => $this->theme
-        ),
+        ]),
         'attributes' => array(
           'class' => array('use-ajax', 'block-filter-text-source'),
           'data-accepts' => 'application/vnd.drupal-modal',
diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc
index 469267c99dd8b844050fdffd1b4ed44a6d82f347..ced19d920ca09a3f20417786ea1ab8745e5401d6 100644
--- a/core/modules/book/book.admin.inc
+++ b/core/modules/book/book.admin.inc
@@ -8,6 +8,7 @@
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\book\BookManager;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
 
 /**
  * Returns HTML for a book administration form.
@@ -45,19 +46,17 @@ function theme_book_admin_table($variables) {
     $links = array();
     $links['view'] = array(
       'title' => t('View'),
-      'href' => $href,
+      'url' => Url::fromRoute('entity.node.canonical', ['node' => $nid]),
     );
     if ($access) {
       $links['edit'] = array(
         'title' => t('Edit'),
-        'route_name' => 'entity.node.edit_form',
-        'route_parameters' => array('node' => $nid),
+        'url' => Url::fromRoute('entity.node.edit_form', ['node' => $nid]),
         'query' => $destination,
       );
       $links['delete'] = array(
         'title' => t('Delete'),
-        'route_name' => 'entity.node.delete_form',
-        'route_parameters' => array('node' => $nid),
+        'url' => Url::fromRoute('entity.node.delete_form', ['node' => $nid]),
         'query' => $destination,
       );
     }
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index ee4410c7f73b830234c6bd7c013cb01f44c02fc0..a5cbb9cb67a18769f581a87d7c4da4c3c8f1229d 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -12,6 +12,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
 use Drupal\node\NodeInterface;
 use Drupal\node\NodeTypeInterface;
 use Drupal\node\Entity\Node;
@@ -110,15 +111,17 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$
         if (($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines')) && $access_control_handler->createAccess($child_type) && $node->isPublished() && $node->book['depth'] < BookManager::BOOK_MAX_DEPTH) {
           $links['book_add_child'] = array(
             'title' => t('Add child page'),
-            'href' => 'node/add/' . $child_type,
-            'query' => array('parent' => $node->id()),
+            'url' => Url::fromRoute('node.add', ['node_type' => $child_type], ['query' => ['parent' => $node->id()]]),
           );
         }
 
         if ($account->hasPermission('access printer-friendly version')) {
           $links['book_printer'] = array(
             'title' => t('Printer-friendly version'),
-            'href' => 'book/export/html/' . $node->id(),
+            'url' => Url::fromRoute('book.export', [
+              'type' => 'html',
+              'node' => $node->id(),
+            ]),
             'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.'))
           );
         }
diff --git a/core/modules/book/src/Controller/BookController.php b/core/modules/book/src/Controller/BookController.php
index 3079032223c3536c9f4bff0dd39798d4fee94d20..2f899371e7a034121b5850eb84886cbe862fac23 100644
--- a/core/modules/book/src/Controller/BookController.php
+++ b/core/modules/book/src/Controller/BookController.php
@@ -10,6 +10,7 @@
 use Drupal\book\BookExport;
 use Drupal\book\BookManagerInterface;
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
 use Drupal\node\Entity\Node;
 use Drupal\node\NodeInterface;
 use Symfony\Component\DependencyInjection\Container;
@@ -83,8 +84,7 @@ public function adminOverview() {
       $links = array();
       $links['edit'] = array(
         'title' => t('Edit order and titles'),
-        'route_name' => 'book.admin_edit',
-        'route_parameters' => array('node' => $book['nid']),
+        'url' => Url::fromRoute('book.admin_edit', ['node' => $book['nid']]),
       );
       $row[] = array(
         'data' => array(
diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php
index 5430ee9f4acd90d4bdb212153d18632710cb789f..b460a7168b5e874bef59ddb62357bf383dc5a932 100644
--- a/core/modules/comment/comment.api.php
+++ b/core/modules/comment/comment.api.php
@@ -2,6 +2,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\comment\CommentInterface;
+use Drupal\Core\Url;
 
 /**
  * @file
@@ -37,9 +38,8 @@ function hook_comment_links_alter(array &$links, CommentInterface $entity, array
     '#links' => array(
       'comment-report' => array(
         'title' => t('Report'),
-        'href' => "comment/{$entity->id()}/report",
+        'url' => Url::fromRoute('comment_test.report', ['comment' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")]]),
         'html' => TRUE,
-        'query' => array('token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")),
       ),
     ),
   );
diff --git a/core/modules/comment/src/CommentLinkBuilder.php b/core/modules/comment/src/CommentLinkBuilder.php
index 972671269981f5b0151c5e98181647a7b55fd660..f689b1ab039ca577ecb0bd2786a69accbfeb4e6a 100644
--- a/core/modules/comment/src/CommentLinkBuilder.php
+++ b/core/modules/comment/src/CommentLinkBuilder.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Url;
 
 /**
  * Defines a class for building markup for comment links on a commented entity.
@@ -110,11 +111,12 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
                 'title' => $this->formatPlural($entity->get($field_name)->comment_count, '1 comment', '@count comments'),
                 'attributes' => array('title' => $this->t('Jump to the first comment of this posting.')),
                 'fragment' => 'comments',
-              ) + $entity->urlInfo()->toArray();
+                'url' => $entity->urlInfo(),
+              );
               if ($this->moduleHandler->moduleExists('history')) {
                 $links['comment-new-comments'] = array(
                   'title' => '',
-                  'href' => '',
+                  'url' => Url::fromRoute('<current>'),
                   'attributes' => array(
                     'class' => 'hidden',
                     'title' => $this->t('Jump to the first new comment of this posting.'),
@@ -136,15 +138,14 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
                 'fragment' => 'comment-form',
               );
               if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) {
-                $links['comment-add']['route_name'] = 'comment.reply';
-                $links['comment-add']['route_parameters'] = array(
+                $links['comment-add']['url'] = Url::fromRoute('comment.reply', [
                   'entity_type' => $entity->getEntityTypeId(),
                   'entity' => $entity->id(),
                   'field_name' => $field_name,
-                );
+                ]);
               }
               else {
-                $links['comment-add'] += $entity->urlInfo()->toArray();
+                $links['comment-add'] += ['url' => $entity->urlInfo()];
               }
             }
             elseif ($this->currentUser->isAnonymous()) {
@@ -171,15 +172,14 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
                   'fragment' => 'comment-form',
                 );
                 if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) {
-                  $links['comment-add']['route_name'] = 'comment.reply';
-                  $links['comment-add']['route_parameters'] = array(
+                  $links['comment-add']['url'] = Url::fromRoute('comment.reply', [
                     'entity_type' => $entity->getEntityTypeId(),
                     'entity' => $entity->id(),
                     'field_name' => $field_name,
-                  );
+                  ]);
                 }
                 else {
-                  $links['comment-add'] += $entity->urlInfo()->toArray();
+                  $links['comment-add']['url'] = $entity->urlInfo();
                 }
               }
             }
diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
index 01856239f594c60867d6c01647a559597c02a66d..4314990033bb1180a6cd331ee70f216eccb9d730 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -17,6 +17,7 @@
 use Drupal\Core\Entity\EntityViewBuilder;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -245,7 +246,7 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
       if ($entity->access('delete')) {
         $links['comment-delete'] = array(
           'title' => t('Delete'),
-          'href' => "comment/{$entity->id()}/delete",
+          'url' => $entity->urlInfo('delete-form'),
           'html' => TRUE,
         );
       }
@@ -253,22 +254,26 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
       if ($entity->access('update')) {
         $links['comment-edit'] = array(
           'title' => t('Edit'),
-          'href' => "comment/{$entity->id()}/edit",
+          'url' => $entity->urlInfo('edit-form'),
           'html' => TRUE,
         );
       }
       if ($entity->access('create')) {
         $links['comment-reply'] = array(
           'title' => t('Reply'),
-          'href' => "comment/reply/{$entity->getCommentedEntityTypeId()}/{$entity->getCommentedEntityId()}/{$entity->getFieldName()}/{$entity->id()}",
+          'url' => Url::fromRoute('comment.reply', [
+            'entity_type' => $entity->getCommentedEntityTypeId(),
+            'entity' => $entity->getCommentedEntityId(),
+            'field_name' => $entity->getFieldName(),
+            'pid' => $entity->id(),
+          ]),
           'html' => TRUE,
         );
       }
       if (!$entity->isPublished() && $entity->access('approve')) {
         $links['comment-approve'] = array(
           'title' => t('Approve'),
-          'route_name' => 'comment.approve',
-          'route_parameters' => array('comment' => $entity->id()),
+          'url' => Url::fromRoute('comment.approve', ['comment' => $entity->id()]),
           'html' => TRUE,
         );
       }
@@ -282,7 +287,7 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
     if (\Drupal::moduleHandler()->moduleExists('content_translation') && content_translation_translate_access($entity)->isAllowed()) {
       $links['comment-translations'] = array(
         'title' => t('Translate'),
-        'href' => 'comment/' . $entity->id() . '/translations',
+        'url' => $entity->urlInfo('drupal:content-translation-overview'),
         'html' => TRUE,
       );
     }
diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php
index 723241ad9b908e555eb312e11a9ebcf7de837fd9..4abff6820acdb1453e0f280ffc6ca17ab7898607 100644
--- a/core/modules/comment/src/Form/CommentAdminOverview.php
+++ b/core/modules/comment/src/Form/CommentAdminOverview.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -200,7 +201,8 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = '
           'data' => array(
             '#type' => 'link',
             '#title' => $comment->getSubject(),
-          ) + $comment_permalink->toRenderArray(),
+            '#url' => $comment_permalink,
+          ),
         ),
         'author' => drupal_render($username),
         'posted_in' => array(
@@ -208,7 +210,8 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = '
             '#type' => 'link',
             '#title' => $commented_entity->label(),
             '#access' => $commented_entity->access('view'),
-          ) + $commented_entity->urlInfo()->toRenderArray(),
+            '#url' => $commented_entity->urlInfo(),
+          ),
         ),
         'changed' => $this->dateFormatter->format($comment->getChangedTime(), 'short'),
       );
@@ -216,18 +219,12 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = '
       $links = array();
       $links['edit'] = array(
         'title' => $this->t('Edit'),
-        'route_name' => 'entity.comment.edit_form',
-        'route_parameters' => array('comment' => $comment->id()),
-        'options' => $comment_uri_options,
-        'query' => $destination,
+        'url' => Url::fromRoute('entity.comment.edit_form', ['comment' => $comment->id()], $comment_uri_options + ['query' => $destination]),
       );
       if ($this->moduleHandler->moduleExists('content_translation') && $this->moduleHandler->invoke('content_translation', 'translate_access', array($comment))->isAllowed()) {
         $links['translate'] = array(
           'title' => $this->t('Translate'),
-          'route_name' => 'content_translation.translation_overview_comment',
-          'route_parameters' => array('comment' => $comment->id()),
-          'options' => $comment_uri_options,
-          'query' => $destination,
+          'url' => Url::fromRoute('content_translation.translation_overview_comment', ['comment' => $comment->id()], $comment_uri_options + ['query' => $destination]),
         );
       }
       $options[$comment->id()]['operations']['data'] = array(
diff --git a/core/modules/comment/tests/modules/comment_test/comment_test.module b/core/modules/comment/tests/modules/comment_test/comment_test.module
index 7ff9376b1a4bdc15dd46d4bdf5d9f4c41488ab33..9fb600f6414a4d5dd39bad117d12f6c45834d334 100644
--- a/core/modules/comment/tests/modules/comment_test/comment_test.module
+++ b/core/modules/comment/tests/modules/comment_test/comment_test.module
@@ -7,6 +7,7 @@
  */
 
 use Drupal\comment\CommentInterface;
+use Drupal\Core\Url;
 
 /**
  * Implements hook_entity_type_alter().
@@ -36,9 +37,8 @@ function comment_test_comment_links_alter(array &$links, CommentInterface &$enti
     '#links' => array(
       'comment-report' => array(
         'title' => t('Report'),
-        'href' => "comment/{$entity->id()}/report",
+        'url' => Url::fromRoute('comment_test.report', ['comment' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")]]),
         'html' => TRUE,
-        'query' => array('token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")),
       ),
     ),
   );
diff --git a/core/modules/comment/tests/modules/comment_test/comment_test.routing.yml b/core/modules/comment/tests/modules/comment_test/comment_test.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7841048caae9699c76e2eb1cbe404d6da83cab47
--- /dev/null
+++ b/core/modules/comment/tests/modules/comment_test/comment_test.routing.yml
@@ -0,0 +1,8 @@
+comment_test.report:
+  path: '/comment/{comment}/report'
+  defaults:
+    _title: 'Report'
+    _content: '\Drupal\comment_test\Controller\CommentTestController::commentReport'
+  requirements:
+    _access: 'TRUE'
+    _csrf_token: 'TRUE'
diff --git a/core/modules/comment/tests/modules/comment_test/src/Controller/CommentTestController.php b/core/modules/comment/tests/modules/comment_test/src/Controller/CommentTestController.php
new file mode 100644
index 0000000000000000000000000000000000000000..4f717552c3acc459c001faa079e519fb46f522a5
--- /dev/null
+++ b/core/modules/comment/tests/modules/comment_test/src/Controller/CommentTestController.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\comment_test\Controller\CommentTestController.
+ */
+
+namespace Drupal\comment_test\Controller;
+
+use Drupal\comment\CommentInterface;
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Controller for the comment_test.module.
+ */
+class CommentTestController extends ControllerBase {
+
+  /**
+   * Provides a comment report.
+   */
+  public function commentReport(CommentInterface $comment) {
+    return ['#markup' => $this->t('Report for a comment')];
+  }
+
+}
diff --git a/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php b/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php
index 2993f2830099074622b0c8beece8e676b8886fea..fdcb8f5eab9f680989f02a872f43b58891ba5ea3 100644
--- a/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php
+++ b/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\comment\CommentLinkBuilder;
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
+use Drupal\Core\Url;
 use Drupal\node\NodeInterface;
 use Drupal\simpletest\TestBase;
 use Drupal\Tests\UnitTestCase;
@@ -129,12 +130,12 @@ public function testCommentLinkBuilder(NodeInterface $node, $context, $has_acces
           if (is_array($detail)) {
             // Array of link attributes.
             foreach ($detail as $key => $value) {
-              $this->assertEquals($links['comment__comment']['#links'][$link][$key], $value);
+              $this->assertEquals($value, $links['comment__comment']['#links'][$link][$key]);
             }
           }
           else {
             // Just the title.
-            $this->assertEquals($links['comment__comment']['#links'][$link]['title'], $detail);
+            $this->assertEquals($detail, $links['comment__comment']['#links'][$link]['title']);
           }
         }
       }
@@ -236,11 +237,11 @@ public function getLinkCombinations() {
               $expected['comment-add'] = array('title' => 'Add new comment');
               if ($combination['form_location'] == CommentItemInterface::FORM_BELOW) {
                 // On the same page.
-                $expected['comment-add']['route_name'] = 'node.view';
+                $expected['comment-add']['url'] = Url::fromRoute('node.view');
               }
               else {
                 // On a separate page.
-                $expected['comment-add']['route_name'] = 'comment.reply';
+                $expected['comment-add']['url'] = Url::fromRoute('comment.reply', ['entity_type' => 'node', 'entity' => 1, 'field_name' => 'comment']);
               }
             }
           }
@@ -314,12 +315,7 @@ protected function getMockNode($has_field, $comment_status, $form_location, $com
       ->method('id')
       ->willReturn(1);
 
-    $url = $this->getMockBuilder('\Drupal\Core\Url')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $url->expects($this->any())
-      ->method('toArray')
-      ->willReturn(array('route_name' => 'node.view'));
+    $url = Url::fromRoute('node.view');
     $node->expects($this->any())
       ->method('urlInfo')
       ->willReturn($url);
diff --git a/core/modules/config/src/Controller/ConfigController.php b/core/modules/config/src/Controller/ConfigController.php
index 48b31df57d9ac04c9ba44cd8824899473e5e051a..8eb713f7784bac6801044aa5a9d4489048f64fb1 100644
--- a/core/modules/config/src/Controller/ConfigController.php
+++ b/core/modules/config/src/Controller/ConfigController.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Config\StorageInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Diff\DiffFormatter;
+use Drupal\Core\Url;
 use Drupal\system\FileDownloadController;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -156,7 +157,7 @@ public function diff($source_name, $target_name = NULL, $collection = NULL) {
         ),
       ),
       '#title' => "Back to 'Synchronize configuration' page.",
-      '#href' => 'admin/config/development/configuration',
+      '#url' => Url::fromRoute('config.sync'),
     );
 
     return $build;
diff --git a/core/modules/config/src/Form/ConfigSync.php b/core/modules/config/src/Form/ConfigSync.php
index 9edfab6f59b4a3ba93b3f5915211c4a31efa5596..21840a5ce6334812a565b5a71f8431267c68e104 100644
--- a/core/modules/config/src/Form/ConfigSync.php
+++ b/core/modules/config/src/Form/ConfigSync.php
@@ -20,7 +20,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\Core\Config\StorageComparer;
-use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Url;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -71,13 +71,6 @@ class ConfigSync extends FormBase {
    */
   protected $configManager;
 
-  /**
-   * URL generator service.
-   *
-   * @var \Drupal\Core\Routing\UrlGeneratorInterface
-   */
-  protected $urlGenerator;
-
   /**
    * The typed config manager.
    *
@@ -114,8 +107,6 @@ class ConfigSync extends FormBase {
    *   Event dispatcher.
    * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
    *   Configuration manager.
-   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
-   *   The url generator service.
    * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
    *   The typed configuration manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
@@ -123,14 +114,13 @@ class ConfigSync extends FormBase {
    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
    *   The theme handler
    */
-  public function __construct(StorageInterface $staging_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
+  public function __construct(StorageInterface $staging_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
     $this->stagingStorage = $staging_storage;
     $this->activeStorage = $active_storage;
     $this->snapshotStorage = $snapshot_storage;
     $this->lock = $lock;
     $this->eventDispatcher = $event_dispatcher;
     $this->configManager = $config_manager;
-    $this->urlGenerator = $url_generator;
     $this->typedConfigManager = $typed_config;
     $this->moduleHandler = $module_handler;
     $this->themeHandler = $theme_handler;
@@ -147,7 +137,6 @@ public static function create(ContainerInterface $container) {
       $container->get('lock'),
       $container->get('event_dispatcher'),
       $container->get('config.manager'),
-      $container->get('url_generator'),
       $container->get('config.typed'),
       $container->get('module_handler'),
       $container->get('theme_handler')
@@ -268,15 +257,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
             $route_options = array('source_name' => $config_name);
           }
           if ($collection != StorageInterface::DEFAULT_COLLECTION) {
+            $route_name = 'config.diff_collection';
             $route_options['collection'] = $collection;
-            $href = $this->urlGenerator->getPathFromRoute('config.diff_collection', $route_options);
           }
           else {
-            $href = $this->urlGenerator->getPathFromRoute('config.diff', $route_options);
+            $route_name = 'config.diff';
           }
           $links['view_diff'] = array(
             'title' => $this->t('View differences'),
-            'href' => $href,
+            'url' => Url::fromRoute($route_name, $route_options),
             'attributes' => array(
               'class' => array('use-ajax'),
               'data-accepts' => 'application/vnd.drupal-modal',
diff --git a/core/modules/config/src/Tests/ConfigEntityListTest.php b/core/modules/config/src/Tests/ConfigEntityListTest.php
index b0818b9ae419cec735018be6a65df0e3c5039e03..c578ff26028661d9260879bec8b101c66f01b743 100644
--- a/core/modules/config/src/Tests/ConfigEntityListTest.php
+++ b/core/modules/config/src/Tests/ConfigEntityListTest.php
@@ -48,21 +48,24 @@ function testList() {
       'edit' => array (
         'title' => t('Edit'),
         'weight' => 10,
-      ) + $entity->urlInfo()->toArray(),
+        'url' => $entity->urlInfo(),
+      ),
       'disable' => array(
         'title' => t('Disable'),
         'weight' => 40,
-      ) + $entity->urlInfo('disable')->toArray(),
+        'url' => $entity->urlInfo('disable'),
+      ),
       'delete' => array (
         'title' => t('Delete'),
         'weight' => 100,
-      ) + $entity->urlInfo('delete-form')->toArray(),
+        'url' => $entity->urlInfo('delete-form'),
+      ),
     );
 
     $actual_operations = $controller->getOperations($entity);
     // Sort the operations to normalize link order.
     uasort($actual_operations, array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
-    $this->assertIdentical($expected_operations, $actual_operations, 'The operations are identical.');
+    $this->assertEqual($expected_operations, $actual_operations, 'The operations are identical.');
 
     // Test buildHeader() method.
     $expected_items = array(
@@ -71,7 +74,7 @@ function testList() {
       'operations' => 'Operations',
     );
     $actual_items = $controller->buildHeader();
-    $this->assertIdentical($expected_items, $actual_items, 'Return value from buildHeader matches expected.');
+    $this->assertEqual($expected_items, $actual_items, 'Return value from buildHeader matches expected.');
 
     // Test buildRow() method.
     $build_operations = $controller->buildOperations($entity);
@@ -83,7 +86,7 @@ function testList() {
       ),
     );
     $actual_items = $controller->buildRow($entity);
-    $this->assertIdentical($expected_items, $actual_items, 'Return value from buildRow matches expected.');
+    $this->assertEqual($expected_items, $actual_items, 'Return value from buildRow matches expected.');
     // Test sorting.
     $storage = $controller->getStorage();
     $entity = $storage->create(array(
@@ -120,17 +123,19 @@ function testList() {
       'edit' => array(
         'title' => t('Edit'),
         'weight' => 10,
-      ) + $entity->urlInfo()->toArray(),
+        'url' => $entity->urlInfo(),
+      ),
       'delete' => array(
         'title' => t('Delete'),
         'weight' => 100,
-      ) + $entity->urlInfo('delete-form')->toArray(),
+        'url' => $entity->urlInfo('delete-form'),
+      ),
     );
 
     $actual_operations = $controller->getOperations($entity);
     // Sort the operations to normalize link order.
     uasort($actual_operations, array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
-    $this->assertIdentical($expected_operations, $actual_operations, 'The operations are identical.');
+    $this->assertEqual($expected_operations, $actual_operations, 'The operations are identical.');
   }
 
   /**
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index 29ea6eb8660df2cb23a4c6a321122555673e4e29..32b330743b88d7ab3b8d53df01838c06ceaee9fb 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -162,7 +162,8 @@ function config_translation_entity_operation(EntityInterface $entity) {
     $operations['translate'] = array(
       'title' => t('Translate'),
       'weight' => 50,
-    ) + $entity->urlInfo('drupal:config-translation-overview')->toArray();
+      'url' => $entity->urlInfo('drupal:config-translation-overview'),
+    );
   }
 
   return $operations;
diff --git a/core/modules/config_translation/src/ConfigEntityMapper.php b/core/modules/config_translation/src/ConfigEntityMapper.php
index ce66ff670f7b3b2b7c228ba729561804285df0ae..2cbc78e38edc04ae305bd20216b50d1bb23a431b 100644
--- a/core/modules/config_translation/src/ConfigEntityMapper.php
+++ b/core/modules/config_translation/src/ConfigEntityMapper.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Url;
 use Drupal\locale\LocaleConfigManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -216,7 +217,9 @@ public function getOperations() {
     return array(
       'list' => array(
         'title' => $this->t('List'),
-        'href' => 'admin/config/regional/config-translation/' . $this->getPluginId(),
+        'url' => Url::fromRoute('config_translation.entity_list', [
+          'mapper_id' => $this->getPluginId(),
+        ]),
       ),
     );
   }
diff --git a/core/modules/config_translation/src/ConfigNamesMapper.php b/core/modules/config_translation/src/ConfigNamesMapper.php
index 57811d915ef106abd1cb23423061c0deefff11cb..ed796af5a8dd3d2ca9ef76139f2f5bf2f4a3ac83 100644
--- a/core/modules/config_translation/src/ConfigNamesMapper.php
+++ b/core/modules/config_translation/src/ConfigNamesMapper.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
 use Drupal\locale\LocaleConfigManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -200,7 +201,7 @@ protected function processRoute(Route $route) {
    * {@inheritdoc}
    */
   public function getBasePath() {
-    return $this->getPathFromRoute($this->getBaseRoute(), $this->getBaseRouteParameters());
+    return Url::fromRoute($this->getBaseRouteName(), $this->getBaseRouteParameters())->getInternalPath();
   }
 
   /**
@@ -237,7 +238,7 @@ public function getOverviewRoute() {
    * {@inheritdoc}
    */
   public function getOverviewPath() {
-    return $this->getPathFromRoute($this->getOverviewRoute(), $this->getOverviewRouteParameters());
+    return Url::fromRoute($this->getOverviewRouteName(), $this->getOverviewRouteParameters())->getInternalPath();
   }
 
   /**
@@ -334,25 +335,6 @@ public function getDeleteRoute() {
     return $route;
   }
 
-  /**
-   * Gets the path for a certain route, given a set of route parameters.
-   *
-   * @param \Symfony\Component\Routing\Route $route
-   *   The route object.
-   * @param array $parameters
-   *   An array of route parameters.
-   *
-   * @return string
-   *   Processed path with placeholders replaced.
-   */
-  public function getPathFromRoute(Route $route, array $parameters) {
-    $path = $route->getPath();
-    foreach ($parameters as $key => $value) {
-      $path = str_replace('{' . $key . '}', $value, $path);
-    }
-    return $path;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -492,7 +474,7 @@ public function getOperations() {
     return array(
       'translate' => array(
         'title' => $this->t('Translate'),
-        'href' => $this->getOverviewPath(),
+        'url' => Url::fromRoute($this->getOverviewRouteName(), $this->getOverviewRouteParameters()),
       ),
     );
   }
diff --git a/core/modules/config_translation/src/Controller/ConfigTranslationController.php b/core/modules/config_translation/src/Controller/ConfigTranslationController.php
index 646d3b8531b550614b4c2bb04ef8268447c40ef2..b6b016c9795be2164c340194654d02622f354974 100644
--- a/core/modules/config_translation/src/Controller/ConfigTranslationController.php
+++ b/core/modules/config_translation/src/Controller/ConfigTranslationController.php
@@ -15,6 +15,7 @@
 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
@@ -174,9 +175,7 @@ public function itemPage(Request $request, RouteMatchInterface $route_match, $pl
         if ($edit_access) {
           $operations['edit'] = array(
             'title' => $this->t('Edit'),
-            'route_name' => $mapper->getBaseRouteName(),
-            'route_parameters' => $mapper->getBaseRouteParameters(),
-            'query' => array('destination' => $mapper->getOverviewPath()),
+            'url' => Url::fromRoute($mapper->getBaseRouteName(), $mapper->getBaseRouteParameters(), ['query' => ['destination' => $mapper->getOverviewPath()]]),
           );
         }
       }
@@ -188,22 +187,19 @@ public function itemPage(Request $request, RouteMatchInterface $route_match, $pl
         if (!$mapper->hasTranslation($language)) {
           $operations['add'] = array(
             'title' => $this->t('Add'),
-            'route_name' => $mapper->getAddRouteName(),
-            'route_parameters' => $mapper->getAddRouteParameters(),
+            'url' => Url::fromRoute($mapper->getAddRouteName(), $mapper->getAddRouteParameters()),
           );
         }
         else {
           // Otherwise, link to edit the existing translation.
           $operations['edit'] = array(
             'title' => $this->t('Edit'),
-            'route_name' => $mapper->getEditRouteName(),
-            'route_parameters' => $mapper->getEditRouteParameters(),
+            'url' => Url::fromRoute($mapper->getEditRouteName(), $mapper->getEditRouteParameters()),
           );
 
           $operations['delete'] = array(
             'title' => $this->t('Delete'),
-            'route_name' => $mapper->getDeleteRouteName(),
-            'route_parameters' => $mapper->getDeleteRouteParameters(),
+            'url' => Url::fromRoute($mapper->getDeleteRouteName(), $mapper->getDeleteRouteParameters()),
           );
         }
       }
diff --git a/core/modules/config_translation/tests/src/Unit/ConfigEntityMapperTest.php b/core/modules/config_translation/tests/src/Unit/ConfigEntityMapperTest.php
index 69828f6be6f43582ae878f1264b41b36d83564ec..2b1c67e94d7ec38352b5f3c793399c9166266973 100644
--- a/core/modules/config_translation/tests/src/Unit/ConfigEntityMapperTest.php
+++ b/core/modules/config_translation/tests/src/Unit/ConfigEntityMapperTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\config_translation\Unit;
 
 use Drupal\config_translation\ConfigEntityMapper;
+use Drupal\Core\Url;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\Routing\Route;
 
@@ -188,11 +189,11 @@ public function testGetOperations() {
     $expected = array(
       'list' => array(
         'title' => 'List',
-        'href' => 'admin/config/regional/config-translation/configurable_language',
-      )
+        'url' => Url::fromRoute('config_translation.entity_list', ['mapper_id' => 'configurable_language']),
+      ),
     );
 
-    $this->assertSame($expected, $result);
+    $this->assertEquals($expected, $result);
   }
 
 }
diff --git a/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
index 5b19122555f9772fdc508d40ebe43828d07e721f..50e2712a917eca44555ffb1df08ca35dbe7131bf 100644
--- a/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
+++ b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
@@ -9,7 +9,9 @@
 
 use Drupal\config_translation\ConfigNamesMapper;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Url;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -72,6 +74,13 @@ class ConfigNamesMapperTest extends UnitTestCase {
    */
   protected $routeProvider;
 
+  /**
+   * The mocked URL generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $urlGenerator;
+
   protected function setUp() {
     $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
 
@@ -91,6 +100,11 @@ protected function setUp() {
 
     $this->configMapperManager = $this->getMock('Drupal\config_translation\ConfigMapperManagerInterface');
 
+    $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
+    $container = new ContainerBuilder();
+    $container->set('url_generator', $this->urlGenerator);
+    \Drupal::setContainer($container);
+
     $this->baseRoute = new Route('/admin/config/system/site-information');
 
     $this->routeProvider
@@ -147,6 +161,10 @@ public function testGetBaseRoute() {
    * Tests ConfigNamesMapper::getBasePath().
    */
   public function testGetBasePath() {
+    $this->urlGenerator->expects($this->once())
+      ->method('getPathFromRoute')
+      ->with('system.site_information_settings', [])
+      ->willReturn('/admin/config/system/site-information');
     $result = $this->configNamesMapper->getBasePath();
     $this->assertSame('/admin/config/system/site-information', $result);
   }
@@ -189,6 +207,11 @@ public function testGetOverviewRoute() {
    * Tests ConfigNamesMapper::getOverviewPath().
    */
   public function testGetOverviewPath() {
+    $this->urlGenerator->expects($this->once())
+      ->method('getPathFromRoute')
+      ->with('config_translation.item.overview.system.site_information_settings', [])
+      ->willReturn('/admin/config/system/site-information/translate');
+
     $result = $this->configNamesMapper->getOverviewPath();
     $this->assertSame('/admin/config/system/site-information/translate', $result);
   }
@@ -605,7 +628,7 @@ public function testGetOperations() {
     $expected = array(
       'translate' => array(
         'title' => 'Translate',
-        'href' => '/admin/config/system/site-information/translate',
+        'url' => Url::fromRoute('config_translation.item.overview.system.site_information_settings'),
       ),
     );
     $result = $this->configNamesMapper->getOperations();
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 102c67a6ecc225c41e99df7102a59b5eb2621381..4d3c794429b6c1da5dfaf865268cf0bbd22e2a6a 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -169,7 +169,8 @@ function content_translation_entity_operation_alter(array &$operations, \Drupal\
   if ($entity instanceof NodeInterface && $entity->isTranslatable()) {
     $operations['translate'] = array(
       'title' => t('Translate'),
-    ) + $entity->urlInfo('drupal:content-translation-overview')->toArray();
+      'url' => $entity->urlInfo('drupal:content-translation-overview'),
+    );
   }
 }
 
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index b00a11fff78b0de04cc07df010aad439c9da9a5f..999a509fc0c46989b0d226220d8a30a8f405cd37 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -136,10 +136,11 @@ public function overview(Request $request, $entity_type_id = NULL) {
           // the entity form, otherwise if we are not dealing with the original
           // language we point the link to the translation form.
           if ($entity->access('update')) {
-            $links['edit'] = $entity->urlInfo('edit-form')->toArray() + ['language' => $language];
+            $links['edit']['url'] = $entity->urlInfo('edit-form');
+            $links['edit']['language'] = $language;
           }
           elseif (!$is_original && $handler->getTranslationAccess($entity, 'update')->isAllowed()) {
-            $links['edit'] = $edit_url->toArray();
+            $links['edit']['url'] = $edit_url;
           }
 
           if (isset($links['edit'])) {
@@ -164,7 +165,8 @@ public function overview(Request $request, $entity_type_id = NULL) {
             if ($handler->getTranslationAccess($entity, 'delete')->isAllowed()) {
               $links['delete'] = array(
                 'title' => $this->t('Delete'),
-              ) + $delete_url->toArray();
+                'url' => $delete_url,
+              );
             }
           }
         }
@@ -177,7 +179,8 @@ public function overview(Request $request, $entity_type_id = NULL) {
             if ($translatable) {
               $links['add'] = array(
                 'title' => $this->t('Add'),
-              ) + $add_url->toArray();
+                'url' => $add_url,
+              );
             }
             elseif ($field_ui) {
               $url = new Url('language.content_settings_page');
@@ -186,7 +189,8 @@ public function overview(Request $request, $entity_type_id = NULL) {
               // setting to enable translation on fields.
               $links['nofields'] = array(
                 'title' => $this->t('No translatable fields'),
-              ) + $url->toArray();
+                'url' => $url,
+              );
             }
           }
 
diff --git a/core/modules/contextual/src/Element/ContextualLinks.php b/core/modules/contextual/src/Element/ContextualLinks.php
index 180c01fb07fbb48852e5d09ebcd31081882b0e41..e2037c38e2e31f6956e1983878837ed3aaec7238 100644
--- a/core/modules/contextual/src/Element/ContextualLinks.php
+++ b/core/modules/contextual/src/Element/ContextualLinks.php
@@ -8,6 +8,7 @@
 namespace Drupal\contextual\Element;
 
 use Drupal\Core\Render\Element\RenderElement;
+use Drupal\Core\Url;
 
 /**
  * Provides a contextual_links element.
@@ -81,8 +82,7 @@ public static function preRenderLinks(array $element) {
       $class = drupal_html_class($class);
       $links[$class] = array(
         'title' => $item['title'],
-        'route_name' => isset($item['route_name']) ? $item['route_name'] : '',
-        'route_parameters' => isset($item['route_parameters']) ? $item['route_parameters'] : array(),
+        'url' => Url::fromRoute(isset($item['route_name']) ? $item['route_name'] : '', isset($item['route_parameters']) ? $item['route_parameters'] : []),
       );
     }
     $element['#links'] = $links;
diff --git a/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php b/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php
index a64a4c32017589e844fc4b61490395f31eb181c8..61abdb6818f2f6921cb9856f6e58e5fa355ed832 100644
--- a/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php
+++ b/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php
@@ -67,10 +67,12 @@ public function viewElements(FieldItemListInterface $items) {
       // If the link is to be displayed and the entity has a uri, display a
       // link.
       if ($this->getSetting('link') && $uri = $entity->urlInfo()) {
-        $elements[$delta] = array(
+        $elements[$delta] = [
           '#type' => 'link',
           '#title' => $label,
-        ) + $uri->toRenderArray();
+          '#url' => $uri,
+          '#options' => $uri->getOptions(),
+        ];
 
         if (!empty($items[$delta]->_attributes)) {
           $elements[$delta]['#options'] += array('attributes' => array());
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index b5f6f6ef2efbf1d44178a986e321651e920c30b0..19a8f8ec74a37495feeb8c35bc89d36edbcc3658 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -143,19 +143,22 @@ function field_ui_entity_operation(EntityInterface $entity) {
       $operations['manage-fields'] = array(
         'title' => t('Manage fields'),
         'weight' => 15,
-      ) + $entity->urlInfo('field_ui-fields')->toArray();
+        'url' => $entity->urlInfo('field_ui-fields'),
+      );
     }
     if ($account->hasPermission('administer '. $bundle_of . ' form display')) {
       $operations['manage-form-display'] = array(
         'title' => t('Manage form display'),
         'weight' => 20,
-      ) + $entity->urlInfo('field_ui-form-display')->toArray();
+        'url' => $entity->urlInfo('field_ui-form-display'),
+      );
     }
     if ($account->hasPermission('administer '. $bundle_of . ' display')) {
       $operations['manage-display'] = array(
         'title' => t('Manage display'),
         'weight' => 25,
-      ) + $entity->urlInfo('field_ui-display')->toArray();
+        'url' => $entity->urlInfo('field_ui-display'),
+      );
     }
   }
 
diff --git a/core/modules/field_ui/src/DisplayOverview.php b/core/modules/field_ui/src/DisplayOverview.php
index 2bc07c15e180d5b5615c9b098b654776b34cb1fb..137449461934078bda65ac6a84f17c38f0351bc4 100644
--- a/core/modules/field_ui/src/DisplayOverview.php
+++ b/core/modules/field_ui/src/DisplayOverview.php
@@ -17,6 +17,7 @@
 use Drupal\Core\Field\FormatterInterface;
 use Drupal\Core\Field\PluginSettingsInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -206,14 +207,10 @@ protected function getTableHeader() {
    * {@inheritdoc}
    */
   protected function getOverviewRoute($mode) {
-    return array(
-      'route_name' => 'field_ui.display_overview_view_mode_' . $this->entity_type,
-      'route_parameters' => array(
-        $this->bundleEntityType => $this->bundle,
-        'view_mode_name' => $mode,
-      ),
-      'options' => array(),
-    );
+    return Url::fromRoute('field_ui.display_overview_view_mode_' . $this->entity_type, [
+      $this->bundleEntityType => $this->bundle,
+      'view_mode_name' => $mode,
+    ]);
   }
 
   /**
diff --git a/core/modules/field_ui/src/DisplayOverviewBase.php b/core/modules/field_ui/src/DisplayOverviewBase.php
index 94db60a595c7686462e902282d69f612707a20f3..d41c7cb6a712f9440c2dc769d8a11dbb8ee0be22 100644
--- a/core/modules/field_ui/src/DisplayOverviewBase.php
+++ b/core/modules/field_ui/src/DisplayOverviewBase.php
@@ -595,8 +595,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
           }
 
           $display_mode_label = $display_modes[$mode]['label'];
-          $route = $this->getOverviewRoute($mode);
-          drupal_set_message($this->t('The %display_mode mode now uses custom display settings. You might want to <a href="@url">configure them</a>.', array('%display_mode' => $display_mode_label, '@url' => $this->url($route['route_name'], $route['route_parameters'], $route['options']))));
+          $url = $this->getOverviewRoute($mode);
+          drupal_set_message($this->t('The %display_mode mode now uses custom display settings. You might want to <a href="@url">configure them</a>.', ['%display_mode' => $display_mode_label, '@url' => $url->toString()]));
         }
         $statuses[$mode] = !empty($value);
       }
@@ -872,14 +872,8 @@ abstract protected function getTableHeader();
    * @param string $mode
    *   The form or view mode.
    *
-   * @return array
-   *   An associative array with the following keys:
-   *   - route_name: The name of the route.
-   *   - route_parameters: (optional) An associative array of parameter names
-   *     and values.
-   *   - options: (optional) An associative array of additional options. See
-   *     \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
-   *     comprehensive documentation.
+   * @return \Drupal\Core\Url
+   *   A Url object for the overview route.
    */
   abstract protected function getOverviewRoute($mode);
 
diff --git a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
index 2a73834dac6b1024c8fe884ed491bae34530239d..6056508ca467e41bf8031215e171098f7cf03715 100644
--- a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
+++ b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -118,7 +119,7 @@ public function render() {
       $table['#rows']['_add_new'][] = array(
         'data' => array(
           '#type' => 'link',
-          '#href' => "admin/structure/display-modes/$short_type/add/$entity_type",
+          '#url' => Url::fromRoute($short_type == 'view' ? 'field_ui.entity_view_mode_add_type' : 'field_ui.entity_form_mode_add_type', ['entity_type_id' => $entity_type]),
           '#title' => t('Add new %label @entity-type', array('%label' => $this->entityTypes[$entity_type]->getLabel(), '@entity-type' => $this->entityType->getLowercaseLabel())),
           '#options' => array(
             'html' => TRUE,
diff --git a/core/modules/field_ui/src/FieldConfigListBuilder.php b/core/modules/field_ui/src/FieldConfigListBuilder.php
index f4f76b59612160032d143a854f2b02f3871bc359..cae4250932ee4ce34917d2c1fed52e97bae5da49 100644
--- a/core/modules/field_ui/src/FieldConfigListBuilder.php
+++ b/core/modules/field_ui/src/FieldConfigListBuilder.php
@@ -66,7 +66,8 @@ public function getDefaultOperations(EntityInterface $entity) {
       'title' => $this->t('Storage settings'),
       'weight' => 20,
       'attributes' => array('title' => $this->t('Edit storage settings.')),
-    ) + $entity->urlInfo('storage-edit-form')->toArray();
+      'url' => $entity->urlInfo('storage-edit-form'),
+    );
     $operations['edit']['attributes']['title'] = $this->t('Edit field settings.');
     $operations['delete']['attributes']['title'] = $this->t('Delete field.');
 
diff --git a/core/modules/field_ui/src/FieldOverview.php b/core/modules/field_ui/src/FieldOverview.php
index 0e42fcd6f37a1351330354f2516b048882d4307b..838c116ea37a2af6513fd2b046c8152369f73a9c 100644
--- a/core/modules/field_ui/src/FieldOverview.php
+++ b/core/modules/field_ui/src/FieldOverview.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Field\FieldTypePluginManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
 use Drupal\field_ui\OverviewBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -135,8 +136,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
         'type' => array(
           '#type' => 'link',
           '#title' => $field_types[$field_storage->getType()]['label'],
-          '#route_name' => 'field_ui.storage_edit_' . $this->entity_type,
-          '#route_parameters' => $route_parameters,
+          '#url' => Url::fromRoute('field_ui.storage_edit_' . $this->entity_type, $route_parameters),
           '#options' => array('attributes' => array('title' => $this->t('Edit field settings.'))),
         ),
       );
diff --git a/core/modules/field_ui/src/FieldUI.php b/core/modules/field_ui/src/FieldUI.php
index 7edb1c6d8311e2af2eb2537ac64dababa3323a7d..1658c797a5202433bfa20dffe12ccbf5596e0418 100644
--- a/core/modules/field_ui/src/FieldUI.php
+++ b/core/modules/field_ui/src/FieldUI.php
@@ -59,6 +59,8 @@ public static function getNextDestination(array $destinations) {
         $options['query']['destinations'] = $destinations;
       }
       // Redirect to any given path within the same domain.
+      // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
+      //   resolved.
       $next_destination = Url::fromUri('base://' . $options['path']);
     }
     return $next_destination;
diff --git a/core/modules/field_ui/src/FormDisplayOverview.php b/core/modules/field_ui/src/FormDisplayOverview.php
index aacd42333e0137d7530c7cbceddb9d520f9575f4..c317fd55854faee6ab28130de611fb2c0e4c52b5 100644
--- a/core/modules/field_ui/src/FormDisplayOverview.php
+++ b/core/modules/field_ui/src/FormDisplayOverview.php
@@ -16,6 +16,7 @@
 use Drupal\Core\Field\FieldTypePluginManager;
 use Drupal\Core\Field\PluginSettingsInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -171,14 +172,10 @@ protected function getTableHeader() {
    * {@inheritdoc}
    */
   protected function getOverviewRoute($mode) {
-    return array(
-      'route_name' => 'field_ui.form_display_overview_form_mode_' . $this->entity_type,
-      'route_parameters' => array(
-        $this->bundleEntityType => $this->bundle,
-        'form_mode_name' => $mode,
-      ),
-      'options' => array(),
-    );
+    return Url::fromRoute('field_ui.form_display_overview_form_mode_' . $this->entity_type, [
+      $this->bundleEntityType => $this->bundle,
+      'form_mode_name' => $mode,
+    ]);
   }
 
   /**
diff --git a/core/modules/field_ui/src/Tests/ManageFieldsTest.php b/core/modules/field_ui/src/Tests/ManageFieldsTest.php
index ff2139541a72d11b85d2f15c7f02c1742d4503c4..2379899165d02e30943b1f93322d10d4c4f8c26b 100644
--- a/core/modules/field_ui/src/Tests/ManageFieldsTest.php
+++ b/core/modules/field_ui/src/Tests/ManageFieldsTest.php
@@ -106,7 +106,7 @@ function manageFieldsPage($type = '') {
     $url = base_path() . "admin/structure/types/manage/$type/fields/node.$type.body";
     $this->assertIdentical($url, (string) $result[0]['href']);
     $this->assertIdentical("$url/storage", (string) $result[1]['href']);
-    $this->assertIdentical("$url/delete", (string) $result[3]['href']);
+    $this->assertIdentical("$url/delete", (string) $result[2]['href']);
   }
 
   /**
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 1b15b3e899b3a056d84c4b0d2a7f005fc21a768f..10b7366b9f900e2806295e3a88f9b32e0fa7dfce 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -52,8 +52,7 @@ function forum_help($route_name, RouteMatchInterface $route_match) {
       $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
       $more_help_link = array(
         '#type' => 'link',
-        '#route_name' => 'help.page',
-        '#route_parameters' => array('name' => 'forum'),
+        '#url' => Url::fromRoute('help.page', ['name' => 'forum']),
         '#title' => t('More help'),
       );
       $container = array(
@@ -105,8 +104,6 @@ function forum_theme() {
  * Implements hook_menu_local_tasks().
  */
 function forum_menu_local_tasks(&$data, $route_name) {
-  $user = \Drupal::currentUser();
-
   // Add action link to 'node/add/forum' on 'forum' sub-pages.
   if (in_array($route_name, array('forum.index', 'forum.page'))) {
     $forum_term = \Drupal::routeMatch()->getParameter('taxonomy_term');
@@ -120,7 +117,7 @@ function forum_menu_local_tasks(&$data, $route_name) {
           '#theme' => 'menu_local_action',
           '#link' => array(
             'title' => t('Add new @node_type', array('@node_type' => entity_load('node_type', $type)->label())),
-            'href' => 'node/add/' . $type,
+            'url' => Url::fromRoute('node.add', ['node_type' => $type]),
           ),
         );
         if ($forum_term && $forum_term->bundle() == $vid) {
@@ -130,31 +127,9 @@ function forum_menu_local_tasks(&$data, $route_name) {
         }
       }
     }
-    if (empty($links)) {
-      // Authenticated user does not have access to create new topics.
-      if ($user->isAuthenticated()) {
-        $links['disallowed'] = array(
-          '#theme' => 'menu_local_action',
-          '#link' => array(
-            'title' => t('You are not allowed to post new content in the forum.'),
-          ),
-        );
-      }
-      // Anonymous user does not have access to create new topics.
-      else {
-        $links['login'] = array(
-          '#theme' => 'menu_local_action',
-          '#link' => array(
-            'title' => t('<a href="@login">Log in</a> to post new content in the forum.', array(
-              '@login' => \Drupal::url('user.login', [], array('query' => drupal_get_destination())),
-            )),
-            'localized_options' => array('html' => TRUE),
-          ),
-        );
-      }
-    }
     $data['actions'] += $links;
   }
+  // @todo Bring back functionality in https://www.drupal.org/node/1853072.
 }
 
 /**
@@ -186,12 +161,7 @@ function forum_entity_bundle_info_alter(&$bundles) {
  * Entity URI callback used in forum_entity_bundle_info_alter().
  */
 function forum_uri($forum) {
-  return array(
-    'route_name' => 'forum.page',
-    'route_parameters' => array(
-      'taxonomy_term' => $forum->id(),
-    ),
-  );
+  return Url::fromRoute('forum.page', ['taxonomy_term' => $forum->id()]);
 }
 
 /**
diff --git a/core/modules/forum/src/Form/ForumForm.php b/core/modules/forum/src/Form/ForumForm.php
index 6a8131a115f889ce7bdd70535a8d68d08f12cfbf..fc51a32eb1b5f3fb03433dbdc14f5a935fb6e669 100644
--- a/core/modules/forum/src/Form/ForumForm.php
+++ b/core/modules/forum/src/Form/ForumForm.php
@@ -107,10 +107,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
     $actions = parent::actions($form, $form_state);
 
     if (!$this->entity->isNew() && $this->entity->hasLinkTemplate('forum-delete-form')) {
-      $route_info = $this->entity->urlInfo('forum-delete-form');
-      $actions['delete']['#options'] = $route_info->getOptions();
-      $actions['delete']['#route_name'] = $route_info->getRouteName();
-      $actions['delete']['#route_parameters'] = $route_info->getRouteParameters();
+      $actions['delete']['#url'] = $this->entity->urlInfo('forum-delete-form');
     }
     else {
       unset($actions['delete']);
diff --git a/core/modules/forum/src/Form/Overview.php b/core/modules/forum/src/Form/Overview.php
index eb05b3ac6df11bb4fb37f748bf377e7d30381a6d..8469f55a521f4c45e211676664cabfc8f3becc1e 100644
--- a/core/modules/forum/src/Form/Overview.php
+++ b/core/modules/forum/src/Form/Overview.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
 use Drupal\taxonomy\Form\OverviewTerms;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -65,22 +66,20 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     foreach (Element::children($form['terms']) as $key) {
       if (isset($form['terms'][$key]['#term'])) {
         $term = $form['terms'][$key]['#term'];
-        $form['terms'][$key]['term']['#href'] = 'forum/' . $term->id();
+        $form['terms'][$key]['term']['#url'] = Url::fromRoute('forum.page', ['taxonomy_term' => $term->id()]);
         unset($form['terms'][$key]['operations']['#links']['delete']);
+        $route_parameters = $form['terms'][$key]['operations']['#links']['edit']['url']->getRouteParameters();
         if (!empty($term->forum_container->value)) {
           $form['terms'][$key]['operations']['#links']['edit']['title'] = $this->t('edit container');
-          $form['terms'][$key]['operations']['#links']['edit']['route_name'] = 'forum.edit_container';
-          // We don't want the redirect from the link so we can redirect the
-          // delete action.
-          unset($form['terms'][$key]['operations']['#links']['edit']['query']['destination']);
+          $form['terms'][$key]['operations']['#links']['edit']['url'] = Url::fromRoute('forum.edit_container', $route_parameters);
         }
         else {
           $form['terms'][$key]['operations']['#links']['edit']['title'] = $this->t('edit forum');
-          $form['terms'][$key]['operations']['#links']['edit']['route_name'] = 'forum.edit_forum';
-          // We don't want the redirect from the link so we can redirect the
-          // delete action.
-          unset($form['terms'][$key]['operations']['#links']['edit']['query']['destination']);
+          $form['terms'][$key]['operations']['#links']['edit']['url'] = Url::fromRoute('forum.edit_forum', $route_parameters);
         }
+        // We don't want the redirect from the link so we can redirect the
+        // delete action.
+        unset($form['terms'][$key]['operations']['#links']['edit']['query']['destination']);
       }
     }
 
diff --git a/core/modules/forum/src/Plugin/Block/ForumBlockBase.php b/core/modules/forum/src/Plugin/Block/ForumBlockBase.php
index f480892c53082a8e92b3a0e6bd8102a543806389..17c31470b32d0fb11b932813d6a74354e7bd5483 100644
--- a/core/modules/forum/src/Plugin/Block/ForumBlockBase.php
+++ b/core/modules/forum/src/Plugin/Block/ForumBlockBase.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Url;
 
 /**
  * Provides a base class for Forum blocks.
@@ -27,7 +28,7 @@ public function build() {
       $elements['forum_list'] = $node_title_list;
       $elements['forum_more'] = array(
         '#type' => 'more_link',
-        '#href' => 'forum',
+        '#url' => Url::fromRoute('forum.index'),
         '#attributes' => array('title' => $this->t('Read the latest forum topics.')),
       );
     }
diff --git a/core/modules/forum/src/Tests/ForumTest.php b/core/modules/forum/src/Tests/ForumTest.php
index ebda138ac36f9a1defd39bc668d64ea585ce851a..adbd2a4456df78cc3cf679a276cf8046883af275 100644
--- a/core/modules/forum/src/Tests/ForumTest.php
+++ b/core/modules/forum/src/Tests/ForumTest.php
@@ -121,7 +121,8 @@ function testForum() {
     $this->drupalLogin($this->web_user);
     // Verify that this user is shown a message that they may not post content.
     $this->drupalGet('forum/' . $this->forum['tid']);
-    $this->assertText(t('You are not allowed to post new content in the forum'), "Authenticated user without permission to post forum content is shown message in local tasks to that effect.");
+    // @todo Restore test coverage in https://www.drupal.org/node/1853072.
+    //$this->assertText(t('You are not allowed to post new content in the forum'), "Authenticated user without permission to post forum content is shown message in local tasks to that effect.");
 
 
     // Log in, and do basic tests for a user with permission to edit any forum
diff --git a/core/modules/help/src/Controller/HelpController.php b/core/modules/help/src/Controller/HelpController.php
index 80ae8f44565c85976b02d5335521bf3159b68cf0..2de0a53905c5c741d9cc53a83b17ecce6c4a9151 100644
--- a/core/modules/help/src/Controller/HelpController.php
+++ b/core/modules/help/src/Controller/HelpController.php
@@ -126,7 +126,7 @@ public function helpPage($name) {
       if (!empty($admin_tasks)) {
         $links = array();
         foreach ($admin_tasks as $task) {
-          $link = $task['url']->toArray();
+          $link['url'] = $task['url'];
           $link['title'] = $task['title'];
           $links[] = $link;
         }
diff --git a/core/modules/image/src/Form/ImageEffectFormBase.php b/core/modules/image/src/Form/ImageEffectFormBase.php
index 52d5b2c505ebccfe9de449a3e3c4f2d04ec233c8..addbcf6451e168f65967af270f7ea61a65739346 100644
--- a/core/modules/image/src/Form/ImageEffectFormBase.php
+++ b/core/modules/image/src/Form/ImageEffectFormBase.php
@@ -96,7 +96,8 @@ public function buildForm(array $form, FormStateInterface $form_state, ImageStyl
     $form['actions']['cancel'] = array(
       '#type' => 'link',
       '#title' => $this->t('Cancel'),
-    ) + $this->imageStyle->urlInfo('edit-form')->toRenderArray();
+      '#url' => $this->imageStyle->urlInfo('edit-form'),
+    );
     return $form;
   }
 
diff --git a/core/modules/image/src/Form/ImageStyleEditForm.php b/core/modules/image/src/Form/ImageStyleEditForm.php
index 8703ee6aa8209b0c96700bd82460600379c43004..4edd31c79457f0d40b7060a952dbd7d5324186cd 100644
--- a/core/modules/image/src/Form/ImageStyleEditForm.php
+++ b/core/modules/image/src/Form/ImageStyleEditForm.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\image\ConfigurableImageEffectInterface;
 use Drupal\image\ImageEffectManager;
 use Drupal\Component\Utility\String;
@@ -126,12 +127,18 @@ public function form(array $form, FormStateInterface $form_state) {
       if ($is_configurable) {
         $links['edit'] = array(
           'title' => $this->t('Edit'),
-          'href' => 'admin/config/media/image-styles/manage/' . $this->entity->id() . '/effects/' . $key,
+          'url' => Url::fromRoute('image.effect_edit_form', [
+            'image_style' => $this->entity->id(),
+            'image_effect' => $key,
+          ]),
         );
       }
       $links['delete'] = array(
         'title' => $this->t('Delete'),
-        'href' => 'admin/config/media/image-styles/manage/' . $this->entity->id() . '/effects/' . $key . '/delete',
+        'url' => Url::fromRoute('image.effect_delete', [
+          'image_style' => $this->entity->id(),
+          'image_effect' => $key,
+        ]),
       );
       $form['effects'][$key]['operations'] = array(
         '#type' => 'operations',
diff --git a/core/modules/image/src/ImageStyleListBuilder.php b/core/modules/image/src/ImageStyleListBuilder.php
index 73dfe6c80eabd82b1ec555d5d469ed932ed59ee7..b0ad2275714c9e2c2ab3a40b5240d91a48ad1e7e 100644
--- a/core/modules/image/src/ImageStyleListBuilder.php
+++ b/core/modules/image/src/ImageStyleListBuilder.php
@@ -78,7 +78,8 @@ public function getDefaultOperations(EntityInterface $entity) {
     $flush = array(
       'title' => t('Flush'),
       'weight' => 200,
-    ) + $entity->urlInfo('flush-form')->toArray();
+      'url' => $entity->urlInfo('flush-form'),
+    );
 
     return parent::getDefaultOperations($entity) + array(
       'flush' => $flush,
diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc
index d5e5b7f7306d0e80b9a547c4a63fdc3e73800854..d7a041514c237e8479a9ad68c338ef0d3d095ea4 100644
--- a/core/modules/language/language.admin.inc
+++ b/core/modules/language/language.admin.inc
@@ -8,6 +8,7 @@
 use Drupal\Component\Utility\String;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Template\Attribute;
+use Drupal\Core\Url;
 
 /**
  * Prepares variables for language negotiation configuration form.
@@ -113,7 +114,7 @@ function theme_language_negotiation_configure_browser_form_table($variables) {
     $links = array();
     $links['delete'] = array(
       'title' => t('Delete'),
-      'href' => "admin/config/regional/language/detection/browser/delete/$key",
+      'url' => Url::fromRoute('language.negotiation_browser_delete', ['browser_langcode' => $key]),
       'attributes' => array(
         'class' => array('image-style-link'),
       ),
diff --git a/core/modules/language/src/ConfigurableLanguageManager.php b/core/modules/language/src/ConfigurableLanguageManager.php
index dbb68d21bbc53434ae6b1458db5ed843c8fbf81f..15c40ae95041bbc75e13a6aeda09cdc78bd914d1 100644
--- a/core/modules/language/src/ConfigurableLanguageManager.php
+++ b/core/modules/language/src/ConfigurableLanguageManager.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageDefault;
 use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Url;
 use Drupal\language\Config\LanguageConfigFactoryOverrideInterface;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Symfony\Component\HttpFoundation\Request;
@@ -395,7 +396,7 @@ public function getFallbackCandidates(array $context = array()) {
   /**
    * {@inheritdoc}
    */
-  public function getLanguageSwitchLinks($type, $path) {
+  public function getLanguageSwitchLinks($type, Url $url) {
     $links = FALSE;
 
     if ($this->negotiator) {
@@ -403,7 +404,7 @@ public function getLanguageSwitchLinks($type, $path) {
         $reflector = new \ReflectionClass($method['class']);
 
         if ($reflector->implementsInterface('\Drupal\language\LanguageSwitcherInterface')) {
-          $result = $this->negotiator->getNegotiationMethodInstance($method_id)->getLanguageSwitchLinks($this->requestStack->getCurrentRequest(), $type, $path);
+          $result = $this->negotiator->getNegotiationMethodInstance($method_id)->getLanguageSwitchLinks($this->requestStack->getCurrentRequest(), $type, $url);
 
           if (!empty($result)) {
             // Allow modules to provide translations for specific links.
diff --git a/core/modules/language/src/Form/NegotiationConfigureForm.php b/core/modules/language/src/Form/NegotiationConfigureForm.php
index 83eb5670ebcf9d2096183fc10ec2a8dc1c5978bc..c7495e9bf8a20a9af0583895f9154847f0719589 100644
--- a/core/modules/language/src/Form/NegotiationConfigureForm.php
+++ b/core/modules/language/src/Form/NegotiationConfigureForm.php
@@ -16,6 +16,7 @@
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\language\ConfigurableLanguageManagerInterface;
 use Drupal\language\LanguageNegotiatorInterface;
 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected;
@@ -295,10 +296,10 @@ protected function configureFormTable(array &$form, $type)  {
         $table_form['description'][$method_id] = array('#markup' => Xss::filterAdmin($method['description']));
 
         $config_op = array();
-        if (isset($method['config_path'])) {
+        if (isset($method['config_route_name'])) {
           $config_op['configure'] = array(
             'title' => $this->t('Configure'),
-            'href' => $method['config_path'],
+            'url' => Url::fromRoute($method['config_route_name']),
           );
           // If there is at least one operation enabled show the operation
           // column.
diff --git a/core/modules/language/src/LanguageSwitcherInterface.php b/core/modules/language/src/LanguageSwitcherInterface.php
index d6edd5b2e6c3b67be417c21ea5cfa8466db8f53f..0550dd47f6acc379c3584fdea10854f8f4cb08ec 100644
--- a/core/modules/language/src/LanguageSwitcherInterface.php
+++ b/core/modules/language/src/LanguageSwitcherInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\language;
 
+use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -21,12 +22,12 @@ interface LanguageSwitcherInterface {
    *   The current request.
    * @param string $type
    *   The language type.
-   * @param string $path
-   *   The path links should point to.
+   * @param \Drupal\Core\Url $url
+   *   The URL the switch links will be relative to.
    *
    * @return array
    *   An array of link arrays keyed by language code.
    */
-  public function getLanguageSwitchLinks(Request $request, $type, $path);
+  public function getLanguageSwitchLinks(Request $request, $type, Url $url);
 
 }
diff --git a/core/modules/language/src/Plugin/Block/LanguageBlock.php b/core/modules/language/src/Plugin/Block/LanguageBlock.php
index a3aba84ef2c1e30ea077ff016b48534658ea9c7f..0502d2d610cc19165e5c7c3b03ba89f81679ea95 100644
--- a/core/modules/language/src/Plugin/Block/LanguageBlock.php
+++ b/core/modules/language/src/Plugin/Block/LanguageBlock.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -75,9 +76,9 @@ protected function blockAccess(AccountInterface $account) {
    */
   public function build() {
     $build = array();
-    $path = drupal_is_front_page() ? '<front>' : current_path();
+    $route_name = drupal_is_front_page() ? '<front>' : '<current>';
     $type = $this->getDerivativeId();
-    $links = $this->languageManager->getLanguageSwitchLinks($type, $path);
+    $links = $this->languageManager->getLanguageSwitchLinks($type, Url::fromRoute($route_name));
 
     if (isset($links->links)) {
       $build = array(
diff --git a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationBrowser.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationBrowser.php
index 0ce2c86c69457b3e7b8f3d0b6cd300de0261725b..e9fc38e00e8eee69e440a911c8c19a61cd9d6bea 100644
--- a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationBrowser.php
+++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationBrowser.php
@@ -20,7 +20,7 @@
  *   cache = 0,
  *   name = @Translation("Browser"),
  *   description = @Translation("Language from the browser's language settings."),
- *   config_path = "admin/config/regional/language/detection/browser"
+ *   config_route_name = "language.negotiation_browser"
  * )
  */
 class LanguageNegotiationBrowser extends LanguageNegotiationMethodBase {
diff --git a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSelected.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSelected.php
index 4819abfd865016913800528fc3856d6aaac610f2..c4cd150b3b413de31a3b5231756be2a3182a2eea 100644
--- a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSelected.php
+++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSelected.php
@@ -18,7 +18,7 @@
  *   weight = 12,
  *   name = @Translation("Selected language"),
  *   description = @Translation("Language based on a selected language."),
- *   config_path = "admin/config/regional/language/detection/selected"
+ *   config_route_name = "language.negotiation_selected"
  * )
  */
 class LanguageNegotiationSelected extends LanguageNegotiationMethodBase {
diff --git a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php
index dc5617d20bf59aafb8230ff749857c1eba8abe67..7f0085d93172133fa2e45ede54c877f89ca6458e 100644
--- a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php
+++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+use Drupal\Core\Url;
 use Drupal\language\LanguageNegotiationMethodBase;
 use Drupal\language\LanguageSwitcherInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -21,7 +22,7 @@
  *   weight = -6,
  *   name = @Translation("Session"),
  *   description = @Translation("Language from a request/session parameter."),
- *   config_path = "admin/config/regional/language/detection/session"
+ *   config_route_name = "language.negotiation_session"
  * )
  */
 class LanguageNegotiationSession extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface {
@@ -121,7 +122,7 @@ public function processOutbound($path, &$options = array(), Request $request = N
   /**
    * {@inheritdoc}
    */
-  function getLanguageSwitchLinks(Request $request, $type, $path) {
+  public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
     $links = array();
     $config = $this->config->get('language.negotiation')->get('session');
     $param = $config['parameter'];
@@ -132,7 +133,7 @@ function getLanguageSwitchLinks(Request $request, $type, $path) {
     foreach ($this->languageManager->getNativeLanguages() as $language) {
       $langcode = $language->id;
       $links[$langcode] = array(
-        'href' => $path,
+        'url' => $url,
         'title' => $language->getName(),
         'attributes' => array('class' => array('language-link')),
         'query' => $query,
diff --git a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php
index be9f91561b9820f3a4b12cc74203c39bf8f3a78b..0b46727008ddc5c666b825e7bbe2d24800872e08 100644
--- a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php
+++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+use Drupal\Core\Url;
 use Drupal\language\LanguageNegotiationMethodBase;
 use Drupal\language\LanguageSwitcherInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -25,7 +26,7 @@
  *   weight = -8,
  *   name = @Translation("URL"),
  *   description = @Translation("Language from the URL (Path prefix or domain)."),
- *   config_path = "admin/config/regional/language/detection/url"
+ *   config_route_name = "language.negotiation_url"
  * )
  */
 class LanguageNegotiationUrl extends LanguageNegotiationMethodBase implements InboundPathProcessorInterface, OutboundPathProcessorInterface, LanguageSwitcherInterface {
@@ -187,12 +188,12 @@ public function processOutbound($path, &$options = array(), Request $request = N
   /**
    * {@inheritdoc}
    */
-  function getLanguageSwitchLinks(Request $request, $type, $path) {
+  public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
     $links = array();
 
     foreach ($this->languageManager->getNativeLanguages() as $language) {
       $links[$language->id] = array(
-        'href' => $path,
+        'url' => $url,
         'title' => $language->getName(),
         'language' => $language,
         'attributes' => array('class' => array('language-link')),
diff --git a/core/modules/language/tests/language_test/src/Controller/LanguageTestController.php b/core/modules/language/tests/language_test/src/Controller/LanguageTestController.php
index a5cb033a3bec00ca463b3e15bcc7fefe2d580401..6766d4a7634c688b9e40e3f86adf107f92877a5f 100644
--- a/core/modules/language/tests/language_test/src/Controller/LanguageTestController.php
+++ b/core/modules/language/tests/language_test/src/Controller/LanguageTestController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -62,7 +63,7 @@ public function typeLinkActiveClass() {
       'no_language' => array(
         '#type' => 'link',
         '#title' => t('Link to the current path with no langcode provided.'),
-        '#route_name' => '<current>',
+        '#url' => Url::fromRoute('<current>'),
         '#options' => array(
           'attributes' => array(
             'id' => 'no_lang_link',
@@ -73,7 +74,7 @@ public function typeLinkActiveClass() {
       'fr' => array(
         '#type' => 'link',
         '#title' => t('Link to a French version of the current path.'),
-        '#route_name' => '<current>',
+        '#url' => Url::fromRoute('<current>'),
         '#options' => array(
           'language' => $languages['fr'],
           'attributes' => array(
@@ -85,7 +86,7 @@ public function typeLinkActiveClass() {
       'en' => array(
         '#type' => 'link',
         '#title' => t('Link to an English version of the current path.'),
-        '#route_name' => '<current>',
+        '#url' => Url::fromRoute('<current>'),
         '#options' => array(
           'language' => $languages['en'],
           'attributes' => array(
diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
index ca753f8827b09d2e5b5f28fbf68e2c376a807bd5..3aabb67a10e8a96a343fc3364038a6a5664ccbf1 100644
--- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
+++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
@@ -163,13 +163,7 @@ public function viewElements(FieldItemListInterface $items) {
           '#title' => $link_title,
           '#options' => $url->getOptions(),
         );
-        if ($url->isExternal()) {
-          $element[$delta]['#href'] = $url->getUri();
-        }
-        else {
-          $element[$delta]['#route_name'] = $url->getRouteName();
-          $element[$delta]['#route_parameters'] = $url->getRouteParameters();
-        }
+        $element[$delta]['#url'] = $url;
 
         if (!empty($item->_attributes)) {
           $element[$delta]['#options'] += array ('attributes' => array());
diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
index 21e078f12bac69362f7fbc7e20951f0a206ea175..e0666d90d30529591a53f81eef0afc4dfa108a9e 100644
--- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
+++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
@@ -205,6 +205,8 @@ public function massageFormValues(array $values, array $form, FormStateInterface
           return $values;
         }
 
+        // @todo Don't use the toArray method here. Removed once it is
+        //   deprecated.
         $value += $url->toArray();
 
         // Reset the URL value to contain only the path.
diff --git a/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php b/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
index ea47019859ada0d15ffe211a6916e86d90cb7a13..8e3433bc0e1b7c3e05adb06dd2d7f23b6468dcba 100644
--- a/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
@@ -180,31 +180,21 @@ public function getDescription() {
    * {@inheritdoc}
    */
   public function getDeleteRoute() {
-    return array(
-      'route_name' => 'entity.menu_link_content.delete_form',
-      'route_parameters' => array('menu_link_content' => $this->getEntity()->id()),
-    );
+    return $this->getEntity()->urlInfo('delete-form');
   }
 
   /**
    * {@inheritdoc}
    */
   public function getEditRoute() {
-    return array(
-      'route_name' => 'entity.menu_link_content.canonical',
-      'route_parameters' => array('menu_link_content' => $this->getEntity()->id()),
-    );
+    return $this->getEntity()->urlInfo();
   }
 
   /**
    * {@inheritdoc}
    */
   public function getTranslateRoute() {
-    $entity_type = 'menu_link_content';
-    return array(
-      'route_name' => 'content_translation.translation_overview_' . $entity_type,
-      'route_parameters' => array( $entity_type => $this->getEntity()->id()),
-    );
+    return $this->getEntity()->urlInfo('drupal:content-translation-overview');
   }
 
   /**
diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php
index 720aa2b2908028a36d91f87109824d6f02ad3f1e..d0b904edfe53d289de012ccb8e9ea081b3ed613d 100644
--- a/core/modules/menu_ui/src/MenuForm.php
+++ b/core/modules/menu_ui/src/MenuForm.php
@@ -18,6 +18,7 @@
 use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\UrlGeneratorTrait;
+use Drupal\Core\Url;
 use Drupal\Core\Utility\LinkGeneratorInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -306,23 +307,21 @@ protected function buildOverviewTreeForm($tree, $delta) {
         // Allow for a custom edit link per plugin.
         $edit_route = $link->getEditRoute();
         if ($edit_route) {
-          $operations['edit'] += $edit_route;
+          $operations['edit']['url'] = $edit_route;
           // Bring the user back to the menu overview.
           $operations['edit']['query']['destination'] = $this->entity->url();
         }
         else {
           // Fall back to the standard edit link.
           $operations['edit'] += array(
-            'route_name' => 'menu_ui.link_edit',
-            'route_parameters' => array('menu_link_plugin' => $link->getPluginId()),
+            'url' => Url::fromRoute('menu_ui.link_edit', ['menu_link_plugin' => $link->getPluginId()]),
           );
         }
         // Links can either be reset or deleted, not both.
         if ($link->isResettable()) {
           $operations['reset'] = array(
             'title' => $this->t('Reset'),
-            'route_name' => 'menu_ui.link_reset',
-            'route_parameters' => array('menu_link_plugin' => $link->getPluginId()),
+            'url' => Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $link->getPluginId()]),
           );
         }
         elseif ($delete_link = $link->getDeleteRoute()) {
@@ -333,7 +332,8 @@ protected function buildOverviewTreeForm($tree, $delta) {
         if ($link->isTranslatable()) {
           $operations['translate'] = array(
             'title' => $this->t('Translate'),
-          ) + (array) $link->getTranslateRoute();
+            'url' => $link->getTranslateRoute(),
+          );
         }
         $form[$id]['operations'] = array(
           '#type' => 'operations',
diff --git a/core/modules/menu_ui/src/MenuListBuilder.php b/core/modules/menu_ui/src/MenuListBuilder.php
index 4fdb29841c8cd538cd7e03ec823149a2dd17d654..7e987f1e0a23f4e6babd77e815ac0f4c15e5b2dd 100644
--- a/core/modules/menu_ui/src/MenuListBuilder.php
+++ b/core/modules/menu_ui/src/MenuListBuilder.php
@@ -54,7 +54,8 @@ public function getDefaultOperations(EntityInterface $entity) {
       $operations['add'] = array(
         'title' => t('Add link'),
         'weight' => 20,
-      ) + $entity->urlInfo('add-link-form')->toArray();
+        'url' => $entity->urlInfo('add-link-form'),
+      );
     }
     if (isset($operations['delete'])) {
       $operations['delete']['title'] = t('Delete menu');
diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php
index 64fc4f6ce65cb749d67cad9f2e92b0ba6704367f..33c5989751bbcfab704ed97c904c26eca6ac86c5 100644
--- a/core/modules/node/src/Controller/NodeController.php
+++ b/core/modules/node/src/Controller/NodeController.php
@@ -184,16 +184,14 @@ public function revisionOverview(NodeInterface $node) {
           if ($revert_permission) {
             $links['revert'] = array(
               'title' => $this->t('Revert'),
-              'route_name' => 'node.revision_revert_confirm',
-              'route_parameters' => array('node' => $node->id(), 'node_revision' => $vid),
+              'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
             );
           }
 
           if ($delete_permission) {
             $links['delete'] = array(
               'title' => $this->t('Delete'),
-              'route_name' => 'node.revision_delete_confirm',
-              'route_parameters' => array('node' => $node->id(), 'node_revision' => $vid),
+              'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
             );
           }
 
diff --git a/core/modules/node/src/Form/NodePreviewForm.php b/core/modules/node/src/Form/NodePreviewForm.php
index 81fd61c206e6732c6b52b347ac378bff54ffbed7..52f0267d3000cd962005cf8ed3a5e0c54c4223e2 100644
--- a/core/modules/node/src/Form/NodePreviewForm.php
+++ b/core/modules/node/src/Form/NodePreviewForm.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -81,7 +82,7 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
     $form['backlink'] = array(
       '#type' => 'link',
       '#title' => $this->t('Back to content editing'),
-      '#href' => $node->isNew() ? 'node/add/' . $node->bundle() :  'node/' . $node->id() . '/edit',
+      '#url' => $node->isNew() ? Url::fromRoute('node.add', ['node_type' => $node->bundle()]) : $node->urlInfo('edit-form'),
       '#options' => array('attributes' => array('class' => array('node-preview-backlink'))) + $query_options,
     );
 
diff --git a/core/modules/node/src/NodeListBuilder.php b/core/modules/node/src/NodeListBuilder.php
index c78915d66660e399b10952045f1360d19ff1fba9..606da79b6a1ed1f2153824411d8eecfe8fbe86cd 100644
--- a/core/modules/node/src/NodeListBuilder.php
+++ b/core/modules/node/src/NodeListBuilder.php
@@ -105,7 +105,8 @@ public function buildRow(EntityInterface $entity) {
       '#type' => 'link',
       '#title' => $entity->label(),
       '#suffix' => ' ' . drupal_render($mark),
-    ) + $uri->toRenderArray();
+      '#url' => $uri,
+    );
     $row['type'] = String::checkPlain(node_get_type_label($entity));
     $row['author']['data'] = array(
       '#theme' => 'username',
diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php
index 8cf1c040f4349a5bbaaef4a565f5aca8f847b8e4..11364282e0eaacfa1a82442cdbe5e3af464aea9c 100644
--- a/core/modules/node/src/NodeViewBuilder.php
+++ b/core/modules/node/src/NodeViewBuilder.php
@@ -146,7 +146,7 @@ protected static function buildLinks(NodeInterface $entity, $view_mode) {
         'title' => t('Read more<span class="visually-hidden"> about @title</span>', array(
           '@title' => $node_title_stripped,
         )),
-        'href' => 'node/' . $entity->id(),
+        'url' => $entity->urlInfo(),
         'language' => $entity->language(),
         'html' => TRUE,
         'attributes' => array(
diff --git a/core/modules/node/src/Plugin/views/area/ListingEmpty.php b/core/modules/node/src/Plugin/views/area/ListingEmpty.php
index cba41c288c7f1ec9322fef70e991bdf65e5b922a..8804f2e9a708be4ecdc4aab61e90938757e83d57 100644
--- a/core/modules/node/src/Plugin/views/area/ListingEmpty.php
+++ b/core/modules/node/src/Plugin/views/area/ListingEmpty.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Plugin\views\area;
 
 use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Url;
 use Drupal\views\Plugin\views\area\AreaPluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -67,7 +68,7 @@ public function render($empty = FALSE) {
         '#theme' => 'links',
         '#links' => array(
           array(
-            'href' => 'node/add',
+            'url' => Url::fromRoute('node.add_page'),
             'title' => $this->t('Add content'),
           ),
         ),
diff --git a/core/modules/path/src/Controller/PathController.php b/core/modules/path/src/Controller/PathController.php
index 96dd34b7bdc49d1a9fd4992a91adbf1903cb4836..5f6f3c6bf9d6e07b8c636b88697d356a2f95ff9a 100644
--- a/core/modules/path/src/Controller/PathController.php
+++ b/core/modules/path/src/Controller/PathController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Path\AliasStorageInterface;
 use Drupal\Core\Path\AliasManagerInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -87,19 +88,11 @@ public function adminOverview($keys) {
       $operations = array();
       $operations['edit'] = array(
         'title' => $this->t('Edit'),
-        'route_name' => 'path.admin_edit',
-        'route_parameters' => array(
-          'pid' => $data->pid,
-        ),
-        'query' => $destination,
+        'url' => Url::fromRoute('path.admin_edit', ['pid' => $data->pid], ['query' => $destination]),
       );
       $operations['delete'] = array(
         'title' => $this->t('Delete'),
-        'route_name' => 'path.delete',
-        'route_parameters' => array(
-          'pid' => $data->pid,
-        ),
-        'query' => $destination,
+        'url' => Url::fromRoute('path.delete', ['pid' => $data->pid], ['query' => $destination]),
       );
       $row['data']['operations'] = array(
         'data' => array(
diff --git a/core/modules/responsive_image/src/ResponsiveImageMappingListBuilder.php b/core/modules/responsive_image/src/ResponsiveImageMappingListBuilder.php
index b12fb61f8eee9310d828288c940146736eb6aa17..6032b7c12d53c92409adc43666a03d83cb72c1c4 100644
--- a/core/modules/responsive_image/src/ResponsiveImageMappingListBuilder.php
+++ b/core/modules/responsive_image/src/ResponsiveImageMappingListBuilder.php
@@ -41,7 +41,8 @@ public function getDefaultOperations(EntityInterface $entity) {
     $operations['duplicate'] = array(
       'title' => t('Duplicate'),
       'weight' => 15,
-    ) + $entity->urlInfo('duplicate-form')->toArray();
+      'url' => $entity->urlInfo('duplicate-form'),
+    );
     return $operations;
   }
 
diff --git a/core/modules/search/src/SearchPageListBuilder.php b/core/modules/search/src/SearchPageListBuilder.php
index 5714f0a3545b6a63ea7ec493647bd5347b97d1eb..93b87ef3f3ca5d1d2eb1442818fa54e89efb7800 100644
--- a/core/modules/search/src/SearchPageListBuilder.php
+++ b/core/modules/search/src/SearchPageListBuilder.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -118,7 +119,7 @@ public function buildRow(EntityInterface $entity) {
       $row['url'] = array(
         '#type' => 'link',
         '#title' => $row['url'],
-        '#route_name' => 'search.view_' . $entity->id(),
+        '#url' => Url::fromRoute('search.view_' . $entity->id()),
       );
     }
 
@@ -305,10 +306,9 @@ public function getDefaultOperations(EntityInterface $entity) {
     else {
       $operations['default'] = array(
         'title' => $this->t('Set as default'),
-        'route_name' => 'entity.search_page.set_default',
-        'route_parameters' => array(
+        'url' => Url::fromRoute('entity.search_page.set_default', [
           'search_page' => $entity->id(),
-        ),
+        ]),
         'weight' => 50,
       );
     }
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 2575128894ac2a7fef492d5b933fa50f27e5391e..f21d00fa9365d2ef0eebaed3ed8aa8753bf2611f 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -258,7 +258,7 @@ function shortcut_renderable_links($shortcut_set = NULL) {
     $shortcut = \Drupal::entityManager()->getTranslationFromContext($shortcut);
     $links[] = array(
       'title' => $shortcut->label(),
-      'href' => $shortcut->path->value,
+      'url' => Url::fromRoute($shortcut->getRouteName(), $shortcut->getRouteParams()),
     );
     $cache_tags = Cache::mergeTags($cache_tags, $shortcut->getCacheTag());
   }
@@ -343,8 +343,7 @@ function shortcut_preprocess_page(&$variables) {
         '#prefix' => '<div class="add-or-remove-shortcuts ' . $link_mode . '-shortcut">',
         '#type' => 'link',
         '#title' => '<span class="icon"></span><span class="text">'. $link_text .'</span>',
-        '#route_name' => $route_name,
-        '#route_parameters' => $route_parameters,
+        '#url' => Url::fromRoute($route_name, $route_parameters),
         '#options' => array('query' => $query, 'html' => TRUE),
         '#suffix' => '</div>',
       );
@@ -367,8 +366,7 @@ function shortcut_toolbar() {
       $configure_link = array(
         '#type' => 'link',
         '#title' => t('Edit shortcuts'),
-        '#route_name' => 'entity.shortcut_set.customize_form',
-        '#route_parameters' => array('shortcut_set' => $shortcut_set->id()),
+        '#url' => Url::fromRoute('entity.shortcut_set.customize_form', ['shortcut_set' => $shortcut_set->id()]),
         '#options' => array('attributes' => array('class' => array('edit-shortcuts'))),
       );
     }
@@ -378,7 +376,7 @@ function shortcut_toolbar() {
         'tab' => array(
           '#type' => 'link',
           '#title' => t('Shortcuts'),
-          '#href' => 'admin/config/user-interface/shortcut',
+          '#url' => Url::fromRoute('shortcut.set_admin'),
           '#attributes' => array(
             'title' => t('Shortcuts'),
             'class' => array('toolbar-icon', 'toolbar-icon-shortcut'),
diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php
index 75d5b2283d51a45cb6e9ec1c9017a607c944e325..9734109fcd368cbdfb74fefcd1f5e911bdb9933f 100644
--- a/core/modules/shortcut/src/Entity/Shortcut.php
+++ b/core/modules/shortcut/src/Entity/Shortcut.php
@@ -115,7 +115,7 @@ public function getRouteParams() {
    * {@inheritdoc}
    */
   public function setRouteParams($route_parameters) {
-    $this->set('route_parameters', array($route_parameters));
+    $this->set('route_parameters', $route_parameters);
     return $this;
   }
 
diff --git a/core/modules/shortcut/src/Form/SetCustomize.php b/core/modules/shortcut/src/Form/SetCustomize.php
index e778d8c769e15d7b76a0220ed5cfcdc3d03a027e..2d026b560963d27a2b2013a93d382e5f34193931 100644
--- a/core/modules/shortcut/src/Form/SetCustomize.php
+++ b/core/modules/shortcut/src/Form/SetCustomize.php
@@ -64,11 +64,11 @@ public function form(array $form, FormStateInterface $form_state) {
 
       $links['edit'] = array(
         'title' => t('Edit'),
-        'href' => "admin/config/user-interface/shortcut/link/$id",
+        'url' => $shortcut->urlInfo(),
       );
       $links['delete'] = array(
         'title' => t('Delete'),
-        'href' => "admin/config/user-interface/shortcut/link/$id/delete",
+        'url' => $shortcut->urlInfo('delete-form'),
       );
       $form['shortcuts']['links'][$id]['operations'] = array(
         '#type' => 'operations',
diff --git a/core/modules/shortcut/src/ShortcutSetListBuilder.php b/core/modules/shortcut/src/ShortcutSetListBuilder.php
index 907f470c65f0de89919925e7143167d1db7de237..9de658d668f5f8673d748317d99986fa10e6921f 100644
--- a/core/modules/shortcut/src/ShortcutSetListBuilder.php
+++ b/core/modules/shortcut/src/ShortcutSetListBuilder.php
@@ -37,7 +37,8 @@ public function getDefaultOperations(EntityInterface $entity) {
 
     $operations['list'] = array(
       'title' => t('List links'),
-    ) + $entity->urlInfo('customize-form')->toArray();
+      'url' => $entity->urlInfo('customize-form'),
+    );
     return $operations;
   }
 
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index 7c28b042c562196b639889cf5caebe884c9b2a45..0836987bf0b08274447bc8000cb3d248b3ed9f5f 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -742,8 +742,8 @@ protected function assertNoTitle($title, $message = '', $group = 'Other') {
    */
   protected function assertThemeOutput($callback, array $variables = array(), $expected = '', $message = '', $group = 'Other') {
     $output = \Drupal::theme()->render($callback, $variables);
-    $this->verbose('Variables:' . '<pre>' . String::checkPlain(var_export($variables, TRUE)) . '</pre>'
-      . '<hr />' . 'Result:' . '<pre>' . String::checkPlain(var_export($output, TRUE)) . '</pre>'
+    $this->verbose(
+      '<hr />' . 'Result:' . '<pre>' . String::checkPlain(var_export($output, TRUE)) . '</pre>'
       . '<hr />' . 'Expected:' . '<pre>' . String::checkPlain(var_export($expected, TRUE)) . '</pre>'
       . '<hr />' . $output
     );
diff --git a/core/modules/simpletest/src/Form/SimpletestResultsForm.php b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
index 56d4f59a2bd19a877b59624270d6a71f470ff096..7d39496a13fcea6b9505c1e221d15c4ba5ffdcd3 100644
--- a/core/modules/simpletest/src/Form/SimpletestResultsForm.php
+++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\simpletest\TestDiscovery;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -238,7 +239,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $test_id
     $form['action']['return'] = array(
       '#type' => 'link',
       '#title' => $this->t('Return to list'),
-      '#href' => 'admin/config/development/testing',
+      '#url' => Url::fromRoute('simpletest.test_form'),
     );
 
     if (is_numeric($test_id)) {
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index 2f987720acd8d1bdd772553a9bb7610825ebece9..7dff04a586ae97b46903b9fe6954aa5af2e4b100 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -216,7 +216,8 @@ protected function info() {
       '#type' => 'link',
       '#title' => $this->t('Continue'),
       '#attributes' => array('class' => array('button', 'button--primary')),
-    ) + $url->toRenderArray();
+      '#url' => $url,
+    );
     return $build;
   }
 
@@ -347,7 +348,8 @@ protected function selection() {
         '#title' => $this->t('Apply pending updates'),
         '#attributes' => array('class' => array('button', 'button--primary')),
         '#weight' => 5,
-      ) + $url->toRenderArray();
+        '#url' => $url,
+      );
     }
 
     return $build;
@@ -620,12 +622,12 @@ public static function batchFinished($success, $results, $operations) {
   protected function helpfulLinks() {
     $links['front'] = array(
       'title' => $this->t('Front page'),
-      'href' => '<front>',
+      'url' => Url::fromRoute('<front>'),
     );
     if ($this->account->hasPermission('access administration pages')) {
       $links['admin-pages'] = array(
         'title' => $this->t('Administration pages'),
-        'href' => 'admin',
+        'url' => Url::fromRoute('system.admin'),
       );
     }
     return $links;
diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php
index 5f2ca3f70e63493eb7b049bf0352f5d5f59496d0..0980b0a3f6a6c815078e61e243a8ae7095dd2612 100644
--- a/core/modules/system/src/Controller/SystemController.php
+++ b/core/modules/system/src/Controller/SystemController.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Menu\MenuLinkTreeInterface;
 use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Theme\ThemeAccessCheck;
+use Drupal\Core\Url;
 use Drupal\system\SystemManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -243,8 +244,7 @@ public function themesPage() {
         if ($this->themeAccess->checkAccess($theme->getName())) {
           $theme->operations[] = array(
             'title' => $this->t('Settings'),
-            'route_name' => 'system.theme_settings_theme',
-            'route_parameters' => array('theme' => $theme->getName()),
+            'url' => Url::fromRoute('system.theme_settings_theme', ['theme' => $theme->getName()]),
             'attributes' => array('title' => $this->t('Settings for !theme theme', array('!theme' => $theme->info['name']))),
           );
         }
@@ -253,14 +253,14 @@ public function themesPage() {
             if ($theme->getName() != $admin_theme) {
               $theme->operations[] = array(
                 'title' => $this->t('Uninstall'),
-                'route_name' => 'system.theme_uninstall',
+                'url' => Url::fromRoute('system.theme_uninstall'),
                 'query' => $query,
                 'attributes' => array('title' => $this->t('Uninstall !theme theme', array('!theme' => $theme->info['name']))),
               );
             }
             $theme->operations[] = array(
               'title' => $this->t('Set as default'),
-              'route_name' => 'system.theme_set_default',
+              'url' => Url::fromRoute('system.theme_set_default'),
               'query' => $query,
               'attributes' => array('title' => $this->t('Set !theme as default theme', array('!theme' => $theme->info['name']))),
             );
@@ -270,13 +270,13 @@ public function themesPage() {
         else {
           $theme->operations[] = array(
             'title' => $this->t('Install'),
-            'route_name' => 'system.theme_install',
+            'url' => Url::fromRoute('system.theme_install'),
             'query' => $query,
             'attributes' => array('title' => $this->t('Install !theme theme', array('!theme' => $theme->info['name']))),
           );
           $theme->operations[] = array(
             'title' => $this->t('Install and set as default'),
-            'route_name' => 'system.theme_set_default',
+            'url' => Url::fromRoute('system.theme_set_default'),
             'query' => $query,
             'attributes' => array('title' => $this->t('Install !theme as default theme', array('!theme' => $theme->info['name']))),
           );
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 23505fa39dd64522edf53be85e73bff3e5385e80..5f85624ffb76254df3f5c67dc82eace255e5ae08 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -22,6 +22,7 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -258,7 +259,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
         $row['links']['help'] = array(
           '#type' => 'link',
           '#title' => $this->t('Help'),
-          '#href' => 'admin/help/' . $module->getName(),
+          '#url' => Url::fromRoute('help.page', ['name' => $module->getName()]),
           '#options' => array('attributes' => array('class' =>  array('module-link', 'module-link-help'), 'title' => $this->t('Help'))),
         );
       }
@@ -270,7 +271,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
       $row['links']['permissions'] = array(
         '#type' => 'link',
         '#title' => $this->t('Permissions'),
-        '#href' => 'admin/people/permissions',
+        '#url' => Url::fromRoute('user.admin_permissions'),
         '#options' => array('fragment' => 'module-' . $module->getName(), 'attributes' => array('class' => array('module-link', 'module-link-permissions'), 'title' => $this->t('Configure permissions'))),
       );
     }
@@ -301,8 +302,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
         $row['links']['configure'] = array(
           '#type' => 'link',
           '#title' => $this->t('Configure'),
-          '#route_name' => $module->info['configure'],
-          '#route_parameters' => $route_parameters,
+          '#url' => Url::fromRoute($module->info['configure'], $route_parameters),
           '#options' => array(
             'attributes' => array(
               'class' => array('module-link', 'module-link-configure'),
diff --git a/core/modules/system/src/Tests/Common/RenderElementTypesTest.php b/core/modules/system/src/Tests/Common/RenderElementTypesTest.php
index 9757a2b67a58256f46c1f904fd8d7358f87f7a2e..f3f27abd4e8e8d10f3bf78b085cf3d9d980fcad4 100644
--- a/core/modules/system/src/Tests/Common/RenderElementTypesTest.php
+++ b/core/modules/system/src/Tests/Common/RenderElementTypesTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Common;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Url;
 use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
@@ -112,7 +113,7 @@ function testMoreLink() {
         'name' => "#type 'more_link' anchor tag generation without extra classes",
         'value' => array(
           '#type' => 'more_link',
-          '#href' => 'http://drupal.org',
+          '#url' => Url::fromUri('http://drupal.org'),
         ),
         'expected' => '//div[@class="more-link"]/a[@href="http://drupal.org" and text()="More"]',
       ),
@@ -120,7 +121,7 @@ function testMoreLink() {
         'name' => "#type 'more_link' anchor tag generation with different link text",
         'value' => array(
           '#type' => 'more_link',
-          '#href' => 'http://drupal.org',
+          '#url' => Url::fromUri('http://drupal.org'),
           '#title' => 'More Titles',
         ),
         'expected' => '//div[@class="more-link"]/a[@href="http://drupal.org" and text()="More Titles"]',
@@ -129,7 +130,7 @@ function testMoreLink() {
         'name' => "#type 'more_link' anchor tag generation with attributes on wrapper",
         'value' => array(
           '#type' => 'more_link',
-          '#href' => 'http://drupal.org',
+          '#url' => Url::fromUri('http://drupal.org'),
           '#theme_wrappers' => array(
             'container' => array(
               '#attributes' => array(
@@ -145,16 +146,15 @@ function testMoreLink() {
         'name' => "#type 'more_link' anchor tag with a relative path",
         'value' => array(
           '#type' => 'more_link',
-          '#href' => 'a/link',
+          '#url' => Url::fromRoute('router_test.1'),
         ),
-        'expected' => '//div[@class="more-link"]/a[@href="' . _url('a/link') . '" and text()="More"]',
+        'expected' => '//div[@class="more-link"]/a[@href="' . _url('router_test/test1') . '" and text()="More"]',
       ),
       array(
         'name' => "#type 'more_link' anchor tag with a route",
         'value' => array(
           '#type' => 'more_link',
-          '#route_name' => 'router_test.1',
-          '#route_parameters' => array(),
+          '#url' => Url::fromRoute('router_test.1'),
         ),
         'expected' => '//div[@class="more-link"]/a[@href="' . \Drupal::urlGenerator()->generate('router_test.1') . '" and text()="More"]',
       ),
@@ -162,7 +162,7 @@ function testMoreLink() {
         'name' => "#type 'more_link' anchor tag with an absolute path",
         'value' => array(
           '#type' => 'more_link',
-          '#href' => 'admin/content',
+          '#url' => Url::fromRoute('system.admin_content'),
           '#options' => array('absolute' => TRUE),
         ),
         'expected' => '//div[@class="more-link"]/a[@href="' . _url('admin/content', array('absolute' => TRUE)) . '" and text()="More"]',
@@ -171,7 +171,7 @@ function testMoreLink() {
         'name' => "#type 'more_link' anchor tag to the front page",
         'value' => array(
           '#type' => 'more_link',
-          '#href' => '<front>',
+          '#url' => Url::fromRoute('<front>'),
         ),
         'expected' => '//div[@class="more-link"]/a[@href="' . _url('<front>') . '" and text()="More"]',
       ),
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index 6c286cab01630b7017394a9317bed80db12ef08c..56c2f3909b187473a5ab86dd34cdd19fc023dcc8 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
 use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
@@ -120,7 +121,7 @@ function testDrupalRenderBasics() {
             ),
           ),
           '#attributes' => array('id' => 'foo'),
-          '#href' => 'http://drupal.org',
+          '#url' => Url::fromUri('http://drupal.org'),
           '#title' => 'bar',
         ),
         'expected' => '<div class="baz"><a href="http://drupal.org" id="foo">bar</a></div>' . "\n",
@@ -131,7 +132,7 @@ function testDrupalRenderBasics() {
         'name' => '#theme_wrappers attribute disambiguation with undefined #theme attribute',
         'value' => array(
           '#type' => 'link',
-          '#href' => 'http://drupal.org',
+          '#url' => Url::fromUri('http://drupal.org'),
           '#title' => 'foo',
           '#theme_wrappers' => array(
             'container' => array(
diff --git a/core/modules/system/src/Tests/Common/RenderWebTest.php b/core/modules/system/src/Tests/Common/RenderWebTest.php
index 3e58fb0e849466b384abe30856292793416f8c5b..05a9cbe754333f0cfe3eef7eaa5800f31a4d49b8 100644
--- a/core/modules/system/src/Tests/Common/RenderWebTest.php
+++ b/core/modules/system/src/Tests/Common/RenderWebTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Common;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -105,13 +106,13 @@ function testDrupalRenderFormElements() {
     $element = array(
       '#type' => 'link',
       '#title' => $this->randomMachineName(),
-      '#href' => $this->randomMachineName(),
+      '#url' => Url::fromRoute('common_test.destination'),
       '#options' => array(
         'absolute' => TRUE,
       ),
     );
     $this->assertRenderedElement($element, '//a[@href=:href and contains(., :title)]', array(
-      ':href' => _url($element['#href'], array('absolute' => TRUE)),
+      ':href' => \Drupal::urlGenerator()->generateFromPath('common-test/destination', ['absolute' => TRUE]),
       ':title' => $element['#title'],
     ));
 
@@ -148,10 +149,7 @@ function testDrupalRenderFormElements() {
   protected function assertRenderedElement(array $element, $xpath, array $xpath_args = array()) {
     $original_element = $element;
     $this->drupalSetContent(drupal_render($element));
-    $this->verbose('<pre>' .  String::checkPlain(var_export($original_element, TRUE)) . '</pre>'
-      . '<pre>' .  String::checkPlain(var_export($element, TRUE)) . '</pre>'
-      . '<hr />' . $this->drupalGetContent()
-    );
+    $this->verbose('<hr />' . $this->drupalGetContent());
 
     // @see \Drupal\simpletest\WebTestBase::xpath()
     $xpath = $this->buildXPathQuery($xpath, $xpath_args);
diff --git a/core/modules/system/src/Tests/Common/UrlTest.php b/core/modules/system/src/Tests/Common/UrlTest.php
index 3ac4bc4ebca624466b173c2afd5c7d0da4adcd08..e316eaa6fbc86a7debd6653aa69ccba22d680e7b 100644
--- a/core/modules/system/src/Tests/Common/UrlTest.php
+++ b/core/modules/system/src/Tests/Common/UrlTest.php
@@ -39,15 +39,10 @@ function testLinkXSS() {
     $sanitized_path = check_url(_url($path));
     $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by _l().', array('@path' => $path)));
 
-    // Test #type 'link'.
-    $link_array =  array(
-      '#type' => 'link',
-      '#title' => $this->randomMachineName(),
-      '#href' => $path,
-    );
-    $type_link = drupal_render($link_array);
+    // Test _url().
+    $link = _url($path);
     $sanitized_path = check_url(_url($path));
-    $this->assertTrue(strpos($type_link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by #theme', array('@path' => $path)));
+    $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by #theme', ['@path' => $path]));
   }
 
   /**
@@ -61,7 +56,7 @@ function testLinkAttributes() {
       '#options' => array(
         'language' => $language,
       ),
-      '#href' => 'http://drupal.org',
+      '#url' => Url::fromUri('http://drupal.org'),
       '#title' => 'bar',
     );
     $langcode = $language->id;
@@ -123,7 +118,7 @@ function testLinkAttributes() {
     $type_link = array(
       '#type' => 'link',
       '#title' => $this->randomMachineName(),
-      '#route_name' => '<current>',
+      '#url' => Url::fromRoute('<current>'),
       '#options' => array(
         'attributes' => array(
           'class' => array($class_theme),
@@ -150,7 +145,7 @@ function testLinkRenderArrayText() {
     $type_link_plain_array = array(
       '#type' => 'link',
       '#title' => 'foo',
-      '#href' => 'http://drupal.org',
+      '#url' => Url::fromUri('http://drupal.org'),
     );
     $type_link_plain = drupal_render($type_link_plain_array);
     $this->assertEqual($type_link_plain, $l);
@@ -159,7 +154,7 @@ function testLinkRenderArrayText() {
     $type_link_nested_array = array(
       '#type' => 'link',
       '#title' => array('#markup' => 'foo'),
-      '#href' => 'http://drupal.org',
+      '#url' => Url::fromUri('http://drupal.org'),
     );
     $type_link_nested = drupal_render($type_link_nested_array);
     $this->assertEqual($type_link_nested, $l);
diff --git a/core/modules/system/src/Tests/Theme/FunctionsTest.php b/core/modules/system/src/Tests/Theme/FunctionsTest.php
index c8d068cb747d71348a805993aba324ba58c90179..6433a6495a2d1782d021ceb3966aafbfc9e23418 100644
--- a/core/modules/system/src/Tests/Theme/FunctionsTest.php
+++ b/core/modules/system/src/Tests/Theme/FunctionsTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Session\UserSession;
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -190,24 +191,22 @@ function testLinks() {
     $variables['links'] = array(
       'a link' => array(
         'title' => 'A <link>',
-        'href' => 'a/link',
+        'url' => Url::fromUri('base://a/link'),
       ),
       'plain text' => array(
         'title' => 'Plain "text"',
       ),
       'front page' => array(
         'title' => 'Front page',
-        'href' => '<front>',
+        'url' => Url::fromRoute('<front>'),
       ),
       'router-test' => array(
         'title' => 'Test route',
-        'route_name' => 'router_test.1',
-        'route_parameters' => array(),
+        'url' => Url::fromRoute('router_test.1'),
       ),
       'query-test' => array(
         'title' => 'Query test route',
-        'route_name' => 'router_test.1',
-        'route_parameters' => array(),
+        'url' => Url::fromRoute('router_test.1'),
         'query' => array(
           'key' => 'value',
         )
@@ -216,7 +215,7 @@ function testLinks() {
 
     $expected_links = '';
     $expected_links .= '<ul id="somelinks">';
-    $expected_links .= '<li class="a-link"><a href="' . _url('a/link') . '">' . String::checkPlain('A <link>') . '</a></li>';
+    $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base://a/link')->toString() . '">' . String::checkPlain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text">' . String::checkPlain('Plain "text"') . '</li>';
     $expected_links .= '<li class="front-page"><a href="' . _url('<front>') . '">' . String::checkPlain('Front page') . '</a></li>';
     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
@@ -250,15 +249,12 @@ function testLinks() {
     $this->assertThemeOutput('links', $variables, $expected);
 
     // Verify that passing attributes for the links work.
-    $variables['links']['a link']['attributes'] = array(
-      'class' => array('a/class'),
-    );
     $variables['links']['plain text']['attributes'] = array(
       'class' => array('a/class'),
     );
     $expected_links = '';
     $expected_links .= '<ul id="somelinks">';
-    $expected_links .= '<li class="a-link"><a href="' . _url('a/link') . '" class="a/class">' . String::checkPlain('A <link>') . '</a></li>';
+    $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base://a/link')->toString() . '">' . String::checkPlain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text"><span class="a/class">' . String::checkPlain('Plain "text"') . '</span></li>';
     $expected_links .= '<li class="front-page"><a href="' . _url('<front>') . '">' . String::checkPlain('Front page') . '</a></li>';
     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
@@ -273,7 +269,7 @@ function testLinks() {
     $variables['set_active_class'] = TRUE;
     $expected_links = '';
     $expected_links .= '<ul id="somelinks">';
-    $expected_links .= '<li class="a-link" data-drupal-link-system-path="a/link"><a href="' . _url('a/link') . '" class="a/class" data-drupal-link-system-path="a/link">' . String::checkPlain('A <link>') . '</a></li>';
+    $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base://a/link')->toString() . '">' . String::checkPlain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text"><span class="a/class">' . String::checkPlain('Plain "text"') . '</span></li>';
     $expected_links .= '<li class="front-page" data-drupal-link-system-path="&lt;front&gt;"><a href="' . _url('<front>') . '" data-drupal-link-system-path="&lt;front&gt;">' . String::checkPlain('Front page') . '</a></li>';
     $expected_links .= '<li class="router-test" data-drupal-link-system-path="router_test/test1"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '" data-drupal-link-system-path="router_test/test1">' . String::checkPlain('Test route') . '</a></li>';
@@ -297,7 +293,7 @@ function testDrupalPreRenderLinks() {
       '#links' => array(
         'parent_link' => array(
           'title' => 'Parent link original',
-          'href' => 'parent-link-original',
+          'url' => Url::fromRoute('router_test.1'),
         ),
       ),
       'first_child' => array(
@@ -308,12 +304,12 @@ function testDrupalPreRenderLinks() {
           // one of the parent's links).
           'parent_link' => array(
             'title' => 'Parent link copy',
-            'href' => 'parent-link-copy',
+            'url' => Url::fromRoute('router_test.6'),
           ),
           // This should always be rendered.
           'first_child_link' => array(
             'title' => 'First child link',
-            'href' => 'first-child-link',
+            'url' => Url::fromRoute('router_test.7'),
           ),
         ),
       ),
@@ -323,7 +319,7 @@ function testDrupalPreRenderLinks() {
         '#links' => array(
           'second_child_link' => array(
             'title' => 'Second child link',
-            'href' => 'second-child-link',
+            'url' => Url::fromRoute('router_test.8'),
           ),
         ),
       ),
@@ -334,7 +330,7 @@ function testDrupalPreRenderLinks() {
         '#links' => array(
           'third_child_link' => array(
             'title' => 'Third child link',
-            'href' => 'third-child-link',
+            'url' => Url::fromRoute('router_test.9'),
           ),
         ),
         '#access' => FALSE,
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index a0ee2947f2f642525e4f508435fac84007d43d2a..417f6f062084b44d7151f9160476d9ca1cff9af4 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -412,7 +412,7 @@ function hook_menu_local_tasks(&$data, $route_name) {
     '#theme' => 'menu_local_action',
     '#link' => array(
       'title' => t('Add content'),
-      'href' => 'node/add',
+      'url' => Url::fromRoute('node.add_page'),
       'localized_options' => array(
         'attributes' => array(
           'title' => t('Add content'),
@@ -426,7 +426,7 @@ function hook_menu_local_tasks(&$data, $route_name) {
     '#theme' => 'menu_local_task',
     '#link' => array(
       'title' => t('Example tab'),
-      'href' => 'node/add',
+      'url' => Url::fromRoute('node.add_page'),
       'localized_options' => array(
         'attributes' => array(
           'title' => t('Add content'),
@@ -2277,7 +2277,7 @@ function hook_system_themes_page_alter(&$theme_groups) {
       // Add a foo link to each list of theme operations.
       $theme->operations[] = array(
         'title' => t('Foo'),
-        'href' => 'admin/appearance/foo',
+        'url' => Url::fromRoute('system.themes_page'),
         'query' => array('theme' => $theme->getName())
       );
     }
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module
index 6127b2e60a8b56ba9da4864596fe3e50a5211837..870a1577b7493261234475bad91c1020c0c1688d 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.module
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module
@@ -11,6 +11,7 @@
 use Drupal\Core\Ajax\OpenModalDialogCommand;
 use Drupal\Core\Ajax\CloseDialogCommand;
 use Drupal\Core\Ajax\HtmlCommand;
+use Drupal\Core\Url;
 
 /**
  * Menu callback: Returns an element suitable for use by
@@ -108,7 +109,7 @@ function ajax_test_dialog_contents() {
     'cancel' => array(
       '#type' => 'link',
       '#title' => 'Cancel',
-      '#href' => '',
+      '#url' => Url::fromRoute('<front>'),
       '#attributes' => array(
         // This is a special class to which JavaScript assigns dialog closing
         // behavior.
diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
index ac0399f67b59d285d2036d9a116ff549352db3dd..ab25fdee277ecd3bb0035be248fd7992119fd584 100644
--- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
+++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\ajax_test\Controller;
 
+use Drupal\Core\Url;
+
 /**
  * Provides content for dialog tests.
  */
@@ -56,7 +58,7 @@ public function dialog() {
     $build['link'] = array(
       '#type' => 'link',
       '#title' => 'Link 1 (modal)',
-      '#href' => 'ajax-test/dialog-contents',
+      '#url' => Url::fromRoute('ajax_test.dialog_contents'),
       '#attributes' => array(
         'class' => array('use-ajax'),
         'data-accepts' => 'application/vnd.drupal-modal',
@@ -69,7 +71,7 @@ public function dialog() {
       '#links' => array(
         'link2' => array(
           'title' => 'Link 2 (modal)',
-          'href' => 'ajax-test/dialog-contents',
+          'url' => Url::fromRoute('ajax_test.dialog_contents'),
           'attributes' => array(
             'class' => array('use-ajax'),
             'data-accepts' => 'application/vnd.drupal-modal',
@@ -80,7 +82,7 @@ public function dialog() {
         ),
         'link3' => array(
           'title' => 'Link 3 (non-modal)',
-          'href' => 'ajax-test/dialog-contents',
+          'url' => Url::fromRoute('ajax_test.dialog_contents'),
           'attributes' => array(
             'class' => array('use-ajax'),
             'data-accepts' => 'application/vnd.drupal-dialog',
@@ -92,14 +94,14 @@ public function dialog() {
         ),
         'link4' => array(
           'title' => 'Link 4 (close non-modal if open)',
-          'href' => 'ajax-test/dialog-close',
+          'url' => Url::fromRoute('ajax_test.dialog_close'),
           'attributes' => array(
             'class' => array('use-ajax'),
           ),
         ),
         'link5' => array(
           'title' => 'Link 5 (form)',
-          'href' => 'ajax-test/dialog-form',
+          'url' => Url::fromRoute('ajax_test.dialog_form'),
           'attributes' => array(
             'class' => array('use-ajax'),
             'data-accepts' => 'application/vnd.drupal-modal',
@@ -107,7 +109,7 @@ public function dialog() {
         ),
         'link6' => array(
           'title' => 'Link 6 (entity form)',
-          'href' => 'admin/structure/contact/add',
+          'url' => Url::fromRoute('contact.form_add'),
           'attributes' => array(
             'class' => array('use-ajax'),
             'data-accepts' => 'application/vnd.drupal-modal',
@@ -119,7 +121,7 @@ public function dialog() {
         ),
         'link7' => array(
           'title' => 'Link 7 (non-modal, no target)',
-          'href' => 'ajax-test/dialog-contents',
+          'url' => Url::fromRoute('ajax_test.dialog_contents'),
           'attributes' => array(
             'class' => array('use-ajax'),
             'data-accepts' => 'application/vnd.drupal-dialog',
diff --git a/core/modules/system/tests/modules/common_test/src/Controller/CommonTestController.php b/core/modules/system/tests/modules/common_test/src/Controller/CommonTestController.php
index 161aabe32e265936a400205ab062bfef356eea1a..cf82dba1f716120f645a6152f9ababc131875fad 100644
--- a/core/modules/system/tests/modules/common_test/src/Controller/CommonTestController.php
+++ b/core/modules/system/tests/modules/common_test/src/Controller/CommonTestController.php
@@ -8,6 +8,7 @@
 namespace Drupal\common_test\Controller;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\Response;
 
 /**
@@ -25,7 +26,7 @@ public function typeLinkActiveClass() {
       'no_query' => array(
         '#type' => 'link',
         '#title' => t('Link with no query string'),
-        '#route_name' => '<current>',
+        '#url' => Url::fromRoute('<current>'),
         '#options' => array(
           'set_active_class' => TRUE,
         ),
@@ -33,7 +34,7 @@ public function typeLinkActiveClass() {
       'with_query' => array(
         '#type' => 'link',
         '#title' => t('Link with a query string'),
-        '#route_name' => '<current>',
+        '#url' => Url::fromRoute('<current>'),
         '#options' => array(
           'query' => array(
             'foo' => 'bar',
@@ -45,7 +46,7 @@ public function typeLinkActiveClass() {
       'with_query_reversed' => array(
         '#type' => 'link',
         '#title' => t('Link with the same query string in reverse order'),
-        '#route_name' => '<current>',
+        '#url' => Url::fromRoute('<current>'),
         '#options' => array(
           'query' => array(
             'one' => 'two',
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index e17eb0175dc1e81e04ab8772a9f5c252878149e8..41906a18b64689258e3df61e90f120490c17f316 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -14,6 +14,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Url;
 
 /**
  * Filter that limits test entity list to revisable ones.
@@ -384,11 +385,19 @@ function entity_test_entity_predelete(EntityInterface $entity) {
  * Implements hook_entity_operation_alter().
  */
 function entity_test_entity_operation_alter(array &$operations, EntityInterface $entity) {
-  $operations['test_operation'] = array(
-    'title' => format_string('Test Operation: @label', array('@label' => $entity->label())),
-    'href' => $entity->url() . '/test_operation',
-    'weight' => 50,
-  );
+  $valid_entity_type_ids = [
+    'user_role',
+    'block',
+  ];
+  if (in_array($entity->getEntityTypeId(), $valid_entity_type_ids)) {
+    if (\Drupal::service('router.route_provider')->getRouteByName("entity.{$entity->getEntityTypeId()}.test_operation")) {
+      $operations['test_operation'] = [
+        'title' => format_string('Test Operation: @label', ['@label' => $entity->label()]),
+        'url' => Url::fromRoute("entity.{$entity->getEntityTypeId()}.test_operation", [$entity->getEntityTypeId() => $entity->id()]),
+        'weight' => 50,
+      ];
+    }
+  }
 }
 
 /**
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.routing.yml b/core/modules/system/tests/modules/entity_test/entity_test.routing.yml
index e3d0adca2faa6bf908b8ed3227d31c3885bd1df5..34cbc5692e8c8af038504219beeaac3085ca754f 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.routing.yml
+++ b/core/modules/system/tests/modules/entity_test/entity_test.routing.yml
@@ -76,6 +76,19 @@ entity.entity_test.list_empty:
   requirements:
     _access: 'TRUE'
 
+entity.block.test_operation:
+  path: '/admin/structure/block/manage/{block}/test_operation'
+  defaults:
+    _entity_view: 'entity_test'
+  requirements:
+    _access: 'TRUE'
+
+entity.user_role.test_operation:
+  path: '/admin/people/roles/manage/{user_role}/test_operation'
+  defaults:
+    _entity_view: 'entity_test'
+  requirements:
+    _access: 'TRUE'
 
 route_callbacks:
   - '\Drupal\entity_test\Routing\EntityTestRoutes::routes'
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php
index f7c0d4a671867a5f4477a5da9aa3b078d3d8d6bb..925c84e32d4f37b2a9068a5e2bcd0bfe24d0e173 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php
@@ -54,6 +54,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     if (!$form_state->isValueEmpty('redirection')) {
       if (!$form_state->isValueEmpty('destination')) {
+        // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
+        //   resolved.
         $form_state->setRedirectUrl(Url::fromUri('base://' . $form_state->getValue('destination')));
       }
     }
diff --git a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php
index a02be18db03e384e946081c402a0ad67e92991d4..b4e4393a8b0185db49c84f45e3ff62e632bb1f56 100644
--- a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php
+++ b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\theme_test\EventSubscriber;
 
+use Drupal\Core\Url;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -44,7 +45,7 @@ public function onRequest(GetResponseEvent $event) {
       // returning output and theming the page as a whole.
       $more_link = array(
         '#type' => 'more_link',
-        '#href' => 'user',
+        '#url' => Url::fromRoute('user.page'),
         '#attributes' => array('title' => 'Themed output generated in a KernelEvents::REQUEST listener'),
       );
       $GLOBALS['theme_test_output'] = drupal_render($more_link);
diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php
index 95e6178253f4e6b7e397622982aa4621737911cc..65ed68ac7db0190e0fd4d22afb4c7fd63d493479 100644
--- a/core/modules/taxonomy/src/Form/OverviewTerms.php
+++ b/core/modules/taxonomy/src/Form/OverviewTerms.php
@@ -225,7 +225,8 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         '#prefix' => !empty($indentation) ? drupal_render($indentation) : '',
         '#type' => 'link',
         '#title' => $term->getName(),
-      ) + $term->urlInfo()->toRenderArray();
+        '#url' => $term->urlInfo(),
+      );
       if ($taxonomy_vocabulary->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) {
         $parent_fields = TRUE;
         $form['terms'][$key]['term']['tid'] = array(
@@ -268,17 +269,20 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         'edit' => array(
           'title' => $this->t('Edit'),
           'query' => $destination,
-        ) + $term->urlInfo('edit-form')->toArray(),
+          'url' => $term->urlInfo('edit-form'),
+        ),
         'delete' => array(
           'title' => $this->t('Delete'),
           'query' => $destination,
-        ) + $term->urlInfo('delete-form')->toArray(),
+          'url' => $term->urlInfo('delete-form'),
+        ),
       );
       if ($this->moduleHandler->moduleExists('content_translation') && content_translation_translate_access($term)->isAllowed()) {
         $operations['translate'] = array(
           'title' => $this->t('Translate'),
           'query' => $destination,
-        ) + $term->urlInfo('drupal:content-translation-overview')->toArray();
+          'url' => $term->urlInfo('drupal:content-translation-overview'),
+        );
       }
       $form['terms'][$key]['operations'] = array(
         '#type' => 'operations',
diff --git a/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/LinkFormatter.php
index 856b73e0ad0b9933917d15477cb3df2cb561b886..ec1f86283f80eb70b0baabf8058c91dbb2b7e234 100644
--- a/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/LinkFormatter.php
+++ b/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/LinkFormatter.php
@@ -42,11 +42,14 @@ public function viewElements(FieldItemListInterface $items) {
         $elements[$delta] = array(
           '#type' => 'link',
           '#title' => $term->getName(),
-        ) + $term->urlInfo()->toRenderArray();
+          '#url' => $term->urlInfo(),
+        );
 
         if (!empty($item->_attributes)) {
-          $elements[$delta]['#options'] += array('attributes' => array());
-          $elements[$delta]['#options']['attributes'] += $item->_attributes;
+          $options = $elements[$delta]['#url']->getOptions();
+          $options += ['attributes' => []];
+          $options['attributes'] += $item->_attributes;
+          $elements[$delta]['#url']->setOptions($options);
           // Unset field item attributes since they have been included in the
           // formatter output and should not be rendered in the field template.
           unset($item->_attributes);
diff --git a/core/modules/taxonomy/src/VocabularyListBuilder.php b/core/modules/taxonomy/src/VocabularyListBuilder.php
index a5a02fc0f792c5179bcc7c7bdf753fb174267a8b..bb385df487ea7457e725d442cbc9e2b80e4221c9 100644
--- a/core/modules/taxonomy/src/VocabularyListBuilder.php
+++ b/core/modules/taxonomy/src/VocabularyListBuilder.php
@@ -43,11 +43,13 @@ public function getDefaultOperations(EntityInterface $entity) {
     $operations['list'] = array(
       'title' => t('List terms'),
       'weight' => 0,
-    ) + $entity->urlInfo('overview-form')->toArray();
+      'url' => $entity->urlInfo('overview-form'),
+    );
     $operations['add'] = array(
       'title' => t('Add terms'),
       'weight' => 10,
-    ) + $entity->urlInfo('add-form')->toArray();
+      'url' => $entity->urlInfo('add-form'),
+    );
     unset($operations['delete']);
 
     return $operations;
diff --git a/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php b/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php
index ef45d4efcac643ed3ac106616ddd0419a5b9a944..5d83b7c6020243078b44a12920e4c044567dc127 100644
--- a/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php
+++ b/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Field\FormatterBase;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 
 /**
  * Plugin implementation of the 'telephone_link' formatter.
@@ -78,7 +79,7 @@ public function viewElements(FieldItemListInterface $items) {
         // itself as title.
         '#title' => $title_setting ?: $item->value,
         // Prepend 'tel:' to the telephone number.
-        '#href' => 'tel:' . rawurlencode(preg_replace('/\s+/', '', $item->value)),
+        '#url' => Url::fromUri('tel:' . rawurlencode(preg_replace('/\s+/', '', $item->value))),
         '#options' => array('external' => TRUE),
       );
 
diff --git a/core/modules/toolbar/src/Element/ToolbarItem.php b/core/modules/toolbar/src/Element/ToolbarItem.php
index 0eeb5c4b4db7058aa27869ea02cce165485df418..5926a37804eef051882aafde34c88971537f1205 100644
--- a/core/modules/toolbar/src/Element/ToolbarItem.php
+++ b/core/modules/toolbar/src/Element/ToolbarItem.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Render\Element\RenderElement;
+use Drupal\Core\Url;
 
 /**
  * Provides a toolbar item that is wrapped in markup for common styling.
@@ -32,7 +33,7 @@ public function getInfo() {
       'tab' => array(
         '#type' => 'link',
         '#title' => NULL,
-        '#href' => '',
+        '#url' => Url::fromRoute('<front>'),
       ),
     );
   }
diff --git a/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module b/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module
index 69dac7ab176ed1bcb56e75ded62ffd24621ff361..9d4e89f6370bb14965e7d3882cf2daa70a218e3b 100644
--- a/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module
+++ b/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module
@@ -17,7 +17,7 @@ function toolbar_test_toolbar() {
     'tab' => array(
       '#type' => 'link',
       '#title' => t('Test tab'),
-      '#href' => '',
+      '#url' => Url::fromRoute('<front>'),
       '#options' => array(
         'html' => FALSE,
         'attributes' => array(
diff --git a/core/modules/toolbar/toolbar.api.php b/core/modules/toolbar/toolbar.api.php
index ab9b75a758b44e1393b31a1445d33e844c5a38e8..bce69aeae66a847fcb78c839f0e4409ce05a2bcd 100644
--- a/core/modules/toolbar/toolbar.api.php
+++ b/core/modules/toolbar/toolbar.api.php
@@ -4,6 +4,7 @@
  * @file
  * Hooks provided by the toolbar module.
  */
+use Drupal\Core\Url;
 
 /**
  * @addtogroup hooks
@@ -72,7 +73,7 @@ function hook_toolbar() {
     'tab' => array(
       '#type' => 'link',
       '#title' => t('Home'),
-      '#href' => '<front>',
+      '#url' => Url::fromRoute('<front>'),
       '#options' => array(
         'attributes' => array(
           'title' => t('Home page'),
@@ -96,7 +97,7 @@ function hook_toolbar() {
     'tab' => array(
       '#type' => 'link',
       '#title' => t('Shopping cart'),
-      '#href' => '/cart',
+      '#url' => Url::fromRoute('cart'),
       '#options' => array(
         'html' => FALSE,
         'attributes' => array(
@@ -131,7 +132,7 @@ function hook_toolbar() {
       '#theme' => 'user_message_toolbar_tab',
       '#theme_wrappers' => array(),
       '#title' => t('Messages'),
-      '#href' => '/user/messages',
+      '#url' => Url::fromRoute('user.message'),
       '#options' => array(
         'attributes' => array(
           'title' => t('Messages'),
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index 3d960a8b5f61e27ca4324bb6cc985211470cabd3..ab0eb01b3be2a59a967fab16f52d4ce27cc0a2bd 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -14,6 +14,7 @@
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\String;
 use Drupal\user\Entity\Role;
+use Drupal\Core\Url;
 
 /**
  * Implements hook_help().
@@ -125,7 +126,7 @@ function toolbar_toolbar() {
     'tab' => array(
       '#type' => 'link',
       '#title' => t('Back to site'),
-      '#href' => '<front>',
+      '#url' => Url::fromRoute('<front>'),
       '#attributes' => array(
         'title' => t('Return to site content'),
         'class' => array('toolbar-icon', 'toolbar-icon-escape-admin'),
@@ -164,7 +165,7 @@ function toolbar_toolbar() {
     'tab' => array(
       '#type' => 'link',
       '#title' => t('Manage'),
-      '#href' => 'admin',
+      '#url' => Url::fromRoute('system.admin'),
       '#attributes' => array(
         'title' => t('Admin menu'),
         'class' => array('toolbar-icon', 'toolbar-icon-menu'),
diff --git a/core/modules/user/src/RoleListBuilder.php b/core/modules/user/src/RoleListBuilder.php
index 8e7feecb08e9c819ac05bdfa4bc44460ed92e72f..4e0dd7398b0cee6ee76b68640fdacf9a7680c94a 100644
--- a/core/modules/user/src/RoleListBuilder.php
+++ b/core/modules/user/src/RoleListBuilder.php
@@ -51,7 +51,8 @@ public function getDefaultOperations(EntityInterface $entity) {
       $operations['permissions'] = array(
         'title' => t('Edit permissions'),
         'weight' => 20,
-      ) + $entity->urlInfo('edit-permissions-form')->toArray();
+        'url' => $entity->urlInfo('edit-permissions-form'),
+      );
     }
     return $operations;
   }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 92bebb0079f3547457215b931d40f2ad08081125..3e4efe6c38d3e5df358a057b9aed159012d081ec 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1455,7 +1455,7 @@ function user_toolbar() {
     $links = array(
       'account' => array(
         'title' => t('View profile'),
-        'href' => 'user',
+        'url' => Url::fromRoute('user.page'),
         'html' => TRUE,
         'attributes' => array(
           'title' => t('User account'),
@@ -1463,7 +1463,7 @@ function user_toolbar() {
       ),
       'account_edit' => array(
         'title' => t('Edit profile'),
-        'href' => 'user/' . $user->id() . '/edit',
+        'url' => Url::fromRoute('entity.user.edit_form', ['user' => $user->id()]),
         'html' => TRUE,
         'attributes' => array(
           'title' => t('Edit user account'),
@@ -1471,7 +1471,7 @@ function user_toolbar() {
       ),
       'logout' => array(
         'title' => t('Log out'),
-        'href' => 'user/logout',
+        'url' => Url::fromRoute('user.logout'),
       ),
     );
   }
@@ -1479,7 +1479,7 @@ function user_toolbar() {
      $links = array(
       'login' => array(
         'title' => t('Log in'),
-        'href' => 'user',
+        'url' => Url::fromRoute('user.page'),
       ),
     );
   }
@@ -1489,7 +1489,7 @@ function user_toolbar() {
     'tab' => array(
       '#type' => 'link',
       '#title' => $user->getUsername(),
-      '#href' => 'user',
+      '#url' => Url::fromRoute('user.page'),
       '#attributes' => array(
         'title' => t('My account'),
         'class' => array('toolbar-icon', 'toolbar-icon-user'),
diff --git a/core/modules/views/src/Plugin/views/field/Links.php b/core/modules/views/src/Plugin/views/field/Links.php
index 5bb0ab765d5d83e8845277e83e938aa36e5cbde4..591266437d39e642d2cf4e3aa308acba413bde23 100644
--- a/core/modules/views/src/Plugin/views/field/Links.php
+++ b/core/modules/views/src/Plugin/views/field/Links.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url as UrlObject;
 
 /**
  * A abstract handler which provides a collection of links.
@@ -79,7 +80,7 @@ protected function getLinks() {
       $path = strip_tags(decode_entities(strtr($path, $tokens)));
 
       $links[$field] = array(
-        'href' => $path,
+        'url' => UrlObject::fromUri('base://' . $path),
         'title' => $title,
       );
       if (!empty($this->options['destination'])) {
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index 02f017569b10601cfff42bdaa94f27083b918a2e..f089f565501a7b46ea053ea5313801439a4f9bae 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -1049,7 +1049,7 @@ function template_preprocess_views_mini_pager(&$variables) {
     $li_previous = array(
       '#type' => 'link',
       '#title' => $tags[1],
-      '#route_name' => '<current>',
+      '#url' => Url::fromRoute('<current>'),
       '#options' => array(
         'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
         'attributes' => array(
@@ -1072,7 +1072,7 @@ function template_preprocess_views_mini_pager(&$variables) {
     $li_next = array(
       '#type' => 'link',
       '#title' => $tags[3],
-      '#route_name' => '<current>',
+      '#url' => Url::fromRoute('<current>'),
       '#options' => array(
         'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
         'attributes' => array(
diff --git a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
index c6e24db0ae64ead09193ee0baa451b61033f1f9d..72dae7c308a8e314faa9cffa7d8848e3307002d4 100644
--- a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
+++ b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
@@ -8,6 +8,7 @@
 namespace Drupal\views_ui\Form\Ajax;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views_ui\ViewUI;
 
 /**
@@ -120,7 +121,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         'link' => array(
           '#type' => 'link',
           '#title' => '<span>' . $this->t('Remove') . '</span>',
-          '#href' => 'javascript:void()',
+          '#url' => Url::fromRoute('<none>'),
           '#options' => array(
             'html' => TRUE,
           ),
diff --git a/core/modules/views_ui/src/Tests/DisplayPathTest.php b/core/modules/views_ui/src/Tests/DisplayPathTest.php
index 7e553e3807ee26a804e538116fb6bee66a088f12..3f79d64fa4cfb539373641fc9130ac4045911a48 100644
--- a/core/modules/views_ui/src/Tests/DisplayPathTest.php
+++ b/core/modules/views_ui/src/Tests/DisplayPathTest.php
@@ -48,6 +48,10 @@ protected function doBasicPathUITest() {
 
     // Save a path and make sure the summary appears as expected.
     $random_path = $this->randomMachineName();
+    // @todo Once https://www.drupal.org/node/2351379 is resolved, Views will no
+    //   longer use Url::fromUri(), and this path will be able to contain ':'.
+    $random_path = str_replace(':', '', $random_path);
+
     $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/path', array('path' => $random_path), t('Apply'));
     $this->assertText('/' . $random_path, 'The custom path appears in the summary.');
     $this->assertLink(t('View @display', array('@display' => 'Page')), 0, 'view page link found on the page.');
diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php
index dcd81c2f321159789e835544cd54d89c756ec05e..ea3ba889aaa3a4ffe7f49e90c18e1c21039971d5 100644
--- a/core/modules/views_ui/src/ViewEditForm.php
+++ b/core/modules/views_ui/src/ViewEditForm.php
@@ -320,6 +320,8 @@ public function save(array $form, FormStateInterface $form_state) {
           $query->remove('destination');
         }
       }
+      // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
+      //   resolved.
       $form_state->setRedirectUrl(Url::fromUri("base://$destination"));
     }
 
@@ -424,7 +426,9 @@ public function getDisplayDetails($view, $display) {
               '#type' => 'link',
               '#title' => $this->t('View !display_title', array('!display_title' => $display_title)),
               '#options' => array('alt' => array($this->t("Go to the real page for this display"))),
-              '#href' => $path,
+              // @todo Use Url::fromPath() once
+              //   https://www.drupal.org/node/2351379 is resolved.
+              '#url' => Url::fromUri("base://$path"),
               '#prefix' => '<li class="view">',
               "#suffix" => '</li>',
             );
@@ -692,20 +696,21 @@ public function renderDisplayTop(ViewUI $view) {
       '#links' => array(
         'edit-details' => array(
           'title' => $this->t('Edit view name/description'),
-          'href' => "admin/structure/views/nojs/edit-details/{$view->id()}/$display_id",
+          'url' => Url::fromRoute('views_ui.form_edit_details', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
           'attributes' => array('class' => array('views-ajax-link')),
         ),
         'analyze' => array(
           'title' => $this->t('Analyze view'),
-          'href' => "admin/structure/views/nojs/analyze/{$view->id()}/$display_id",
+          'url' => Url::fromRoute('views_ui.form_analyze', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
           'attributes' => array('class' => array('views-ajax-link')),
         ),
         'duplicate' => array(
           'title' => $this->t('Duplicate view'),
-        ) + $view->urlInfo('duplicate-form')->toArray(),
+          'url' => $view->urlInfo('duplicate-form'),
+        ),
         'reorder' => array(
           'title' => $this->t('Reorder displays'),
-          'href' => "admin/structure/views/nojs/reorder-displays/{$view->id()}/$display_id",
+          'url' => Url::fromRoute('views_ui.form_reorder_displays', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
           'attributes' => array('class' => array('views-ajax-link')),
         ),
       ),
@@ -714,7 +719,8 @@ public function renderDisplayTop(ViewUI $view) {
     if ($view->access('delete')) {
       $element['extra_actions']['#links']['delete'] = array(
         'title' => $this->t('Delete view'),
-      ) + $view->urlInfo('delete-form')->toArray();
+        'url' => $view->urlInfo('delete-form'),
+      );
     }
 
     // Let other modules add additional links here.
@@ -731,7 +737,7 @@ public function renderDisplayTop(ViewUI $view) {
       else {
         $element['extra_actions']['#links']['delete'] = array(
           'title' => $this->t('Delete view'),
-          'href' => "admin/structure/views/view/{$view->id()}/delete",
+          'url' => $view->urlInfo('delete-form'),
         );
       }
     }
@@ -941,7 +947,7 @@ public function getFormBucket(ViewUI $view, $type, $display) {
 
     $build['#name'] = $build['#title'] = $types[$type]['title'];
 
-    $rearrange_url = "admin/structure/views/nojs/rearrange/{$view->id()}/{$display['id']}/$type";
+    $rearrange_url = Url::fromRoute('views_ui.form_rearrange', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]);
     $class = 'icon compact rearrange';
 
     // Different types now have different rearrange forms, so we use this switch
@@ -950,7 +956,7 @@ public function getFormBucket(ViewUI $view, $type, $display) {
       case 'filter':
         // The rearrange form for filters contains the and/or UI, so override
         // the used path.
-        $rearrange_url = "admin/structure/views/nojs/rearrange-filter/{$view->id()}/{$display['id']}";
+        $rearrange_url = Url::fromRoute('views_ui.form_rearrange_filter', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id']]);
         // TODO: Add another class to have another symbol for filter rearrange.
         $class = 'icon compact rearrange';
         break;
@@ -990,7 +996,7 @@ public function getFormBucket(ViewUI $view, $type, $display) {
 
     $actions['add'] = array(
       'title' => $add_text,
-      'href' => "admin/structure/views/nojs/add-handler/{$view->id()}/{$display['id']}/$type",
+      'url' => Url::fromRoute('views_ui.form_add_handler', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]),
       'attributes' => array('class' => array('icon compact add', 'views-ajax-link'), 'id' => 'views-add-' . $type),
       'html' => TRUE,
     );
@@ -1000,7 +1006,7 @@ public function getFormBucket(ViewUI $view, $type, $display) {
 
       $actions['rearrange'] = array(
         'title' => $rearrange_text,
-        'href' => $rearrange_url,
+        'url' => $rearrange_url,
         'attributes' => array('class' => array($class, 'views-ajax-link'), 'id' => 'views-rearrange-' . $type),
         'html' => TRUE,
       );
diff --git a/core/modules/views_ui/src/ViewFormBase.php b/core/modules/views_ui/src/ViewFormBase.php
index 9544040eff35fa5db4f5efcf987c02f3c735cacb..39f1a0dce01c4c4d78ac8aaee65e6ad8b4f63481 100644
--- a/core/modules/views_ui/src/ViewFormBase.php
+++ b/core/modules/views_ui/src/ViewFormBase.php
@@ -122,9 +122,9 @@ public function getDisplayTabs(ViewUI $view) {
         '#link' => array(
           'title' => $this->getDisplayLabel($view, $id),
           'localized_options' => array(),
-        ) + $view->urlInfo('edit-display-form')->toArray(),
+          'url' => $view->urlInfo('edit-display-form')->setRouteParameter('display_id', $id),
+        ),
       );
-      $tabs[$id]['#link']['route_parameters']['display_id'] = $id;
       if (!empty($display['deleted'])) {
         $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-deleted-link';
       }
diff --git a/core/modules/views_ui/src/ViewListBuilder.php b/core/modules/views_ui/src/ViewListBuilder.php
index 9d12e5a3dc79b168690e948e8e13ceca5625c74b..c8dcbe105df947a902ec4cca0b31a0edbacce247 100644
--- a/core/modules/views_ui/src/ViewListBuilder.php
+++ b/core/modules/views_ui/src/ViewListBuilder.php
@@ -149,15 +149,14 @@ public function getDefaultOperations(EntityInterface $entity) {
       $operations['duplicate'] = array(
         'title' => $this->t('Duplicate'),
         'weight' => 15,
-      ) + $entity->urlInfo('duplicate-form')->toArray();
+        'url' => $entity->urlInfo('duplicate-form'),
+      );
     }
 
     // Add AJAX functionality to enable/disable operations.
     foreach (array('enable', 'disable') as $op) {
       if (isset($operations[$op])) {
-        $operations[$op]['route_name'] = "entity.view.{$op}";
-        $operations[$op]['route_parameters'] = array('view' => $entity->id());
-
+        $operations[$op]['url'] = $entity->urlInfo($op);
         // Enable and disable operations should use AJAX.
         $operations[$op]['attributes']['class'][] = 'use-ajax';
       }
diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module
index 46adb0f1ef9e184b26003777958ed8c0c77c21a3..a2beebac43ae5d0c342bb9854802d272d53d0700 100644
--- a/core/modules/views_ui/views_ui.module
+++ b/core/modules/views_ui/views_ui.module
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
 use Drupal\views\Views;
 use Drupal\views\ViewExecutable;
 use Drupal\views\ViewStorageInterface;
@@ -181,13 +182,13 @@ function views_ui_view_preview_section_handler_links(ViewExecutable $view, $type
     $field_name = $handler->adminLabel(TRUE);
     $links[$type . '-edit-' . $id] = array(
       'title' => t('Edit @section', array('@section' => $field_name)),
-      'href' => "admin/structure/views/nojs/handler/{$view->storage->id()}/{$display['id']}/$type/$id",
+      'url' => Url::fromRoute('views_ui.form_handler', ['js' => 'nojs', 'view' => $view->storage->id(), 'display_id' => $display['id'], 'type' => $type, 'id' => $id]),
       'attributes' => array('class' => array('views-ajax-link')),
     );
   }
   $links[$type . '-add'] = array(
     'title' => t('Add new'),
-    'href' => "admin/structure/views/nojs/add-handler/{$view->storage->id()}/{$display['id']}/$type",
+    'url' => Url::fromRoute('views_ui.form_add_handler', ['js' => 'nojs', 'view' => $view->storage->id(), 'display_id' => $display['id'], 'type' => $type]),
     'attributes' => array('class' => array('views-ajax-link')),
   );
 
@@ -202,7 +203,7 @@ function views_ui_view_preview_section_display_category_links(ViewExecutable $vi
   $links = array(
     $type . '-edit' => array(
       'title' => t('Edit @section', array('@section' => $title)),
-      'href' => "admin/structure/views/nojs/display/{$view->storage->id()}/{$display['id']}/$type",
+      'url' => Url::fromRoute('views_ui.form_display', ['js' => 'nojs', 'view' => $view->storage->id(), 'display_id' => $display['id'], 'type' => $type]),
       'attributes' => array('class' => array('views-ajax-link')),
     ),
   );
@@ -253,8 +254,8 @@ function views_ui_contextual_links_view_alter(&$element, $items) {
   // screen.
   elseif (!empty($element['#links']['entityviewedit-form'])) {
     $display_id = $items['entity.view.edit_form']['metadata']['display_id'];
-    $element['#links']['entityviewedit-form']['route_parameters']['display_id'] = $display_id;
-    $element['#links']['entityviewedit-form']['route_name'] = 'entity.view.edit_display_form';
+    $route_parameters = $element['#links']['entityviewedit-form']['url']->getRouteParameters() + ['display_id' => $display_id];
+    $element['#links']['entityviewedit-form']['url'] = Url::fromRoute('entity.view.edit_display_form', $route_parameters);
   }
 }
 
diff --git a/core/modules/views_ui/views_ui.theme.inc b/core/modules/views_ui/views_ui.theme.inc
index 80f2f45c863f2eedf5cb5647e09fea50f5315a4e..4528490a10395a94b53a3dddbd05a17d8602cdda 100644
--- a/core/modules/views_ui/views_ui.theme.inc
+++ b/core/modules/views_ui/views_ui.theme.inc
@@ -9,6 +9,7 @@
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Template\Attribute;
+use Drupal\Core\Url;
 
 /**
  * Prepares variables for Views UI display tab setting templates.
@@ -283,7 +284,7 @@ function template_preprocess_views_ui_rearrange_filter_form(&$variables) {
 
         $remove_link = array(
           '#type' => 'link',
-          '#href' => '',
+          '#url' => Url::fromRoute('<none>'),
           '#title' => '<span>' . t('Remove') . '</span>',
           '#weight' => '1',
           '#options' => array(
diff --git a/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php b/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php
index 176d5b475f3ca534a0a24693bd92bd275006b563..b80567fff508db4c7e09ddfddaac062c7aa6f629 100644
--- a/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php
@@ -47,7 +47,7 @@ public function testCancelLinkRoute() {
       ->method('getCancelUrl')
       ->will($this->returnValue($cancel_route));
     $link = ConfirmFormHelper::buildCancelLink($form, new Request());
-    $this->assertSame($route_name, $link['#route_name']);
+    $this->assertEquals(Url::fromRoute($route_name), $link['#url']);
   }
 
   /**
@@ -56,23 +56,13 @@ public function testCancelLinkRoute() {
    * Tests a cancel link route with parameters.
    */
   public function testCancelLinkRouteWithParams() {
-    $cancel_route = array(
-      'route_name' => 'foo_bar.baz',
-      'route_parameters' => array(
-        'baz' => 'banana',
-      ),
-      'options' => array(
-        'absolute' => TRUE,
-      ),
-    );
+    $expected = Url::fromRoute('foo_bar.baz', ['baz' => 'banana'], ['absolute' => TRUE]);
     $form = $this->getMock('Drupal\Core\Form\ConfirmFormInterface');
     $form->expects($this->any())
       ->method('getCancelUrl')
-      ->will($this->returnValue(new Url($cancel_route['route_name'], $cancel_route['route_parameters'], $cancel_route['options'])));
+      ->will($this->returnValue($expected));
     $link = ConfirmFormHelper::buildCancelLink($form, new Request());
-    $this->assertSame($cancel_route['route_name'], $link['#route_name']);
-    $this->assertSame($cancel_route['route_parameters'], $link['#route_parameters']);
-    $this->assertSame($cancel_route['options'], $link['#options']);
+    $this->assertEquals($expected, $link['#url']);
   }
 
   /**
@@ -94,9 +84,7 @@ public function testCancelLinkRouteWithUrl() {
       ->method('getCancelUrl')
       ->will($this->returnValue($cancel_route));
     $link = ConfirmFormHelper::buildCancelLink($form, new Request());
-    $this->assertSame($cancel_route->getRouteName(), $link['#route_name']);
-    $this->assertSame($cancel_route->getRouteParameters(), $link['#route_parameters']);
-    $this->assertSame($cancel_route->getOptions(), $link['#options']);
+    $this->assertSame($cancel_route, $link['#url']);
   }
 
   /**
@@ -108,7 +96,7 @@ public function testCancelLinkDestination() {
     $query = array('destination' => 'baz');
     $form = $this->getMock('Drupal\Core\Form\ConfirmFormInterface');
     $link = ConfirmFormHelper::buildCancelLink($form, new Request($query));
-    $this->assertSame($query['destination'], $link['#href']);
+    $this->assertSame('base://' . $query['destination'], $link['#url']->getUri());
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php
index 1942fc37648b7cf15b34c2b7c6a59ae7acb6e41b..219ef0399489a58526e4705d863f5c4061e23f6b 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php
@@ -16,6 +16,7 @@
 use Drupal\Core\Menu\LocalActionManager;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -196,8 +197,7 @@ public function getActionsForRouteProvider() {
           '#theme' => 'menu_local_action',
           '#link' => array(
             'title' => 'Plugin ID 1',
-            'route_name' => 'test_route_2',
-            'route_parameters' => array(),
+            'url' => Url::fromRoute('test_route_2'),
             'localized_options' => '',
           ),
           '#access' => FALSE,
@@ -231,8 +231,7 @@ public function getActionsForRouteProvider() {
           '#theme' => 'menu_local_action',
           '#link' => array(
             'title' => 'Plugin ID 1',
-            'route_name' => 'test_route_2',
-            'route_parameters' => array(),
+            'url' => Url::fromRoute('test_route_2'),
             'localized_options' => '',
           ),
           '#access' => FALSE,
@@ -267,8 +266,7 @@ public function getActionsForRouteProvider() {
           '#theme' => 'menu_local_action',
           '#link' => array(
             'title' => 'Plugin ID 1',
-            'route_name' => 'test_route_2',
-            'route_parameters' => array(),
+            'url' => Url::fromRoute('test_route_2'),
             'localized_options' => '',
           ),
           '#access' => FALSE,
@@ -278,8 +276,7 @@ public function getActionsForRouteProvider() {
           '#theme' => 'menu_local_action',
           '#link' => array(
             'title' => 'Plugin ID 2',
-            'route_name' => 'test_route_3',
-            'route_parameters' => array(),
+            'url' => Url::fromRoute('test_route_3'),
             'localized_options' => '',
           ),
           '#access' => FALSE,
@@ -316,8 +313,7 @@ public function getActionsForRouteProvider() {
           '#theme' => 'menu_local_action',
           '#link' => array(
             'title' => 'Plugin ID 1',
-            'route_name' => 'test_route_2',
-            'route_parameters' => array('test1'),
+            'url' => Url::fromRoute('test_route_2', ['test1']),
             'localized_options' => '',
           ),
           '#access' => FALSE,
@@ -327,8 +323,7 @@ public function getActionsForRouteProvider() {
           '#theme' => 'menu_local_action',
           '#link' => array(
             'title' => 'Plugin ID 2',
-            'route_name' => 'test_route_2',
-            'route_parameters' => array('test2'),
+            'url' => Url::fromRoute('test_route_2', ['test2']),
             'localized_options' => '',
           ),
           '#access' => FALSE,
diff --git a/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
index 9d6ea001331f3a6f1657335d7254171975910b40..6de1785bf1863bc0286c55b6465ba8b20499cecf 100644
--- a/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
@@ -146,17 +146,13 @@ public function testToString(array $urls) {
    * @covers ::toArray
    */
   public function testToArray(array $urls) {
-    $expected = array(
-      'path' => $this->unroutedExternal,
-      'options' => array('external' => TRUE),
-    );
-    $this->assertSame($expected, $urls[0]->toArray());
-
-    $expected = array(
-      'path' => $this->unroutedInternal,
-      'options' => array(),
-    );
-    $this->assertSame($expected, $urls[1]->toArray());
+    $expected = Url::fromUri($this->unroutedExternal, ['external' => TRUE]);
+    $expected->setUnroutedUrlAssembler(\Drupal::service('unrouted_url_assembler'));
+    $this->assertEquals($expected, $urls[0]);
+
+    $expected = Url::fromUri($this->unroutedInternal);
+    $expected->setUnroutedUrlAssembler(\Drupal::service('unrouted_url_assembler'));
+    $this->assertEquals($expected, $urls[1]);
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php
index cf0496f694f39bdf6fad662010c87bc84cf9f058..404702e2a55f2496f5a848e19d6c3a00f6663741 100644
--- a/core/tests/Drupal/Tests/Core/UrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UrlTest.php
@@ -102,7 +102,8 @@ public function testUrlFromRequest() {
     foreach ($this->map as $index => $values) {
       $path = array_pop($values);
       $url = Url::createFromRequest(Request::create("$path"));
-      $this->assertSame($values, array_values($url->toArray()));
+      $expected = Url::fromRoute($values[0], $values[1], $values[2]);
+      $this->assertEquals($expected, $url);
       $urls[$index] = $url;
     }
     return $urls;
@@ -245,12 +246,9 @@ public function testToString($urls) {
    */
   public function testToArray($urls) {
     foreach ($urls as $index => $url) {
-      $expected = array(
-        'route_name' => $this->map[$index][0],
-        'route_parameters' => $this->map[$index][1],
-        'options' => $this->map[$index][2],
-      );
-      $this->assertSame($expected, $url->toArray());
+      $expected = Url::fromRoute($this->map[$index][0], $this->map[$index][1], $this->map[$index][2]);
+      $expected->setUrlGenerator(\Drupal::urlGenerator());
+      $this->assertEquals($expected, $url);
     }
   }
 
@@ -350,9 +348,7 @@ public function testAccess($access) {
    */
   public function testRenderAccess($access) {
     $element = array(
-      '#route_name' => 'entity.node.canonical',
-      '#route_parameters' => ['node' => 3],
-      '#options' => [],
+      '#url' => Url::fromRoute('entity.node.canonical', ['node' => 3]),
     );
     $this->container->set('current_user', $this->getMock('Drupal\Core\Session\AccountInterface'));
     $this->container->set('access_manager', $this->getMockAccessManager($access));