diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 7e6e12f..0e9dfe1 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -452,11 +452,18 @@ function _batch_finished() {
     if ($_batch['form_state']->getRedirect() === NULL) {
       $redirect = $_batch['batch_redirect'] ?: $_batch['source_url'];
       $options = UrlHelper::parse($redirect);
-      if (!UrlHelper::isExternal($options['path'])) {
-        $options['path'] = $GLOBALS['base_url'] . '/' . $options['path'];
+      // Any path with a scheme does not correspond to a route.
+      if (parse_url($options['path'], PHP_URL_SCHEME)) {
+        $redirect = Url::unrouted($options['path'], $options);
+      }
+      else {
+        $redirect = \Drupal::pathValidator()->getUrlIfValid($options['path']);
+        if (!$redirect) {
+          // Stay on the same page if the redirect was invalid.
+          $redirect = Url::routed('<current>');
+        }
+        $redirect->setOptions($options);
       }
-      $redirect = Url::createFromPath($options['path']);
-      $redirect->setOptions($options);
       $_batch['form_state']->setRedirectUrl($redirect);
     }
 
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 5a6859d..c958c60 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -543,7 +543,10 @@ function install_run_task($task, &$install_state) {
       }
       // Process the batch. For progressive batches, this will redirect.
       // Otherwise, the batch will complete.
-      $response = batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
+      // install_redirect_url() returns core/install.php, so let's ensure to
+      // drop it from it and use base:// as batch_process() is using the
+      // unrouted url assembler, which requires base://.
+      $response = batch_process(preg_replace('@^core/@', 'base://', install_redirect_url($install_state)), install_full_redirect_url($install_state));
       if ($response instanceof Response) {
         // Save $_SESSION data from batch.
         \Drupal::service('session_manager')->save();
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkBase.php b/core/lib/Drupal/Core/Menu/MenuLinkBase.php
index b09bfa9..326a775 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkBase.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkBase.php
@@ -141,9 +141,7 @@ public function getUrlObject($title_attribute = TRUE) {
       return new Url($this->pluginDefinition['route_name'], $this->pluginDefinition['route_parameters'], $options);
     }
     else {
-      $url = Url::createFromPath($this->pluginDefinition['url']);
-      $url->setOptions($options);
-      return $url;
+      return Url::unrouted($this->pluginDefinition['url'], $options);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Path/PathValidator.php b/core/lib/Drupal/Core/Path/PathValidator.php
index 1b519c7..d7506c6 100644
--- a/core/lib/Drupal/Core/Path/PathValidator.php
+++ b/core/lib/Drupal/Core/Path/PathValidator.php
@@ -99,7 +99,7 @@ public function getUrlIfValid($path) {
       if (empty($parsed_url['path'])) {
         return FALSE;
       }
-      return Url::createFromPath($path);
+      return Url::unrouted($path);
     }
 
     $path = ltrim($path, '/');
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index d97f017..123cc7a 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -7,10 +7,10 @@
 
 namespace Drupal\Core;
 
-use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -28,6 +28,13 @@ class Url {
   protected $urlGenerator;
 
   /**
+   * The unrouted URL assembler.
+   *
+   * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
+   */
+  protected $urlAssembler;
+
+  /**
    * The access manager
    *
    * @var \Drupal\Core\Access\AccessManagerInterface
@@ -56,24 +63,35 @@ class Url {
   protected $options = array();
 
   /**
-   * Indicates whether this URL is external.
+   * Indicates whether this object contains an external URL.
    *
    * @var bool
    */
   protected $external = FALSE;
 
   /**
-   * The external path.
+   * Indicates whether this URL is for a URI without a Drupal route.
+   *
+   * @var bool
+   */
+  protected $unrouted = FALSE;
+
+  /**
+   * The non-route URI.
    *
-   * Only used if self::$external is TRUE.
+   * Only used if self::$unrouted is TRUE.
    *
    * @var string
    */
-  protected $path;
+  protected $uri;
 
   /**
    * Constructs a new Url object.
    *
+   * In most cases, use Url::routed() or Url::unrouted rather than constructing
+   * Url objects directly in order to avoid ambiguity and make your code more
+   * self-documenting.
+   *
    * @param string $route_name
    *   The name of the route
    * @param array $route_parameters
@@ -95,6 +113,12 @@ class Url {
    *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
    *     respectively. if mixed mode sessions are permitted, TRUE enforces HTTPS
    *     and FALSE enforces HTTP.
+   *
+   * @see static::routed()
+   * @see static::unrouted()
+   *
+   * @todo Update this documentation for non-routed URIs in
+   *   https://www.drupal.org/node/2346787
    */
   public function __construct($route_name, $route_parameters = array(), $options = array()) {
     $this->routeName = $route_name;
@@ -103,44 +127,93 @@ public function __construct($route_name, $route_parameters = array(), $options =
   }
 
   /**
-   * Returns the Url object matching a path. READ THE FOLLOWING SECURITY NOTE.
+   * Creates a new Url object for a URL that has a Drupal route.
    *
-   * SECURITY NOTE: The path is not checked to be valid and accessible by the
-   * current user to allow storing and reusing Url objects by different users.
-   * The 'path.validator' service getUrlIfValid() method should be used instead
-   * of this one if validation and access check is desired. Otherwise,
-   * 'access_manager' service checkNamedRoute() method should be used on the
-   * router name and parameters stored in the Url object returned by this
-   * method.
+   * This method is for URLs that have Drupal routes (that is, most pages
+   * generated by Drupal). For non-routed local URIs relative to the base
+   * path (like robots.txt) use Url::unrouted() with the base:// scheme.
    *
-   * @param string $path
-   *   A path (e.g. 'node/1', 'http://drupal.org').
+   * @param string $route_name
+   *   The name of the route
+   * @param array $route_parameters
+   *   (optional) An associative array of route parameter names and values.
+   * @param array $options
+   *   (optional) An associative array of additional URL options, with the
+   *   following elements:
+   *   - 'query': An array of query key/value-pairs (without any URL-encoding)
+   *     to append to the URL. Merged with the parameters array.
+   *   - 'fragment': A fragment identifier (named anchor) to append to the URL.
+   *     Do not include the leading '#' character.
+   *   - 'absolute': Defaults to FALSE. Whether to force the output to be an
+   *     absolute link (beginning with http:). Useful for links that will be
+   *     displayed outside the site, such as in an RSS feed.
+   *   - 'language': An optional language object used to look up the alias
+   *     for the URL. If $options['language'] is omitted, it defaults to the
+   *     current language for the language type LanguageInterface::TYPE_URL.
+   *   - 'https': Whether this URL should point to a secure location. If not
+   *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
+   *     respectively. if mixed mode sessions are permitted, TRUE enforces HTTPS
+   *     and FALSE enforces HTTP.
    *
-   * @return static
-   *   An Url object. Warning: the object is created even if the current user
-   *   can not access the path.
+   * @return \Drupal\Core\Url
+   *   A new Url object for a routed (internal to Drupal) URL.
    *
-   * @throws \Drupal\Core\Routing\MatchingRouteNotFoundException
-   *   Thrown when the path cannot be matched.
+   * @see static::unrouted()
    */
-  public static function createFromPath($path) {
-    if (UrlHelper::isExternal($path)) {
-      $url = new static($path);
-      $url->setExternal();
-      return $url;
-    }
+  public static function routed($route_name, $route_parameters = array(), $options = array()) {
+    return new static($route_name, $route_parameters, $options);
+  }
 
-    // Special case the front page route.
-    if ($path == '<front>') {
-      return new static($path);
-    }
-    else {
-      return static::createFromRequest(Request::create("/$path"));
-    }
+  /**
+   * Creates a new Url object for a URI that does not have a Drupal route.
+   *
+   * This method is for generating URLs for URIs that do not have Drupal
+   * routes, both external URLs and unrouted local URIs like
+   * base://robots.txt. For URLs that have Drupal routes (that is, most pages
+   * generated by Drupal), use Url::routed().
+   *
+   * @param string $uri
+   *   The URI of the external resource including the scheme. For Drupal paths
+   *   that are not handled by the routing system, use base:// for the scheme.
+   * @param array $options
+   *   (optional) An associative array of additional URL options, with the
+   *   following elements:
+   *   - 'query': An array of query key/value-pairs (without any URL-encoding)
+   *     to append to the URL. Merged with the parameters array.
+   *   - 'fragment': A fragment identifier (named anchor) to append to the URL.
+   *     Do not include the leading '#' character.
+   *   - 'absolute': Defaults to FALSE. Whether to force the output to be an
+   *     absolute link (beginning with http:). Useful for links that will be
+   *     displayed outside the site, such as in an RSS feed.
+   *   - 'language': An optional language object used to look up the alias
+   *     for the URL. If $options['language'] is omitted, it defaults to the
+   *     current language for the language type LanguageInterface::TYPE_URL.
+   *   - 'https': Whether this URL should point to a secure location. If not
+   *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
+   *     respectively. if mixed mode sessions are permitted, TRUE enforces HTTPS
+   *     and FALSE enforces HTTP.
+   *
+   * @return \Drupal\Core\Url
+   *   A new Url object for an unrouted (non-Drupal) URL.
+   *
+   * @see static::routed()
+   */
+  public static function unrouted($uri, $options = array()) {
+    $url = new static($uri, array(), $options);
+    $url->setUnrouted();
+    return $url;
   }
 
   /**
-   * Returns the Url object matching a request. READ THE SECURITY NOTE ON createFromPath().
+   * Returns the Url object matching a request.
+   *
+   * SECURITY NOTE: The request path is not checked to be valid and accessible
+   * by the current user to allow storing and reusing Url objects by different
+   * users. The 'path.validator' service getUrlIfValid() method should be used
+   * instead of this one if validation and access check is desired. Otherwise,
+   * 'access_manager' service checkNamedRoute() method should be used on the
+   * router name and parameters stored in the Url object returned by this
+   * method.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   A request object.
@@ -163,23 +236,23 @@ public static function createFromRequest(Request $request) {
   }
 
   /**
-   * Sets this Url to be external.
+   * Sets this Url to encapsulate an unrouted URI.
    *
    * @return $this
    */
-  protected function setExternal() {
-    $this->external = TRUE;
-
-    // What was passed in as the route name is actually the path.
-    $this->path = $this->routeName;
-
+  protected function setUnrouted() {
+    $this->unrouted = TRUE;
+    // What was passed in as the route name is actually the URI.
+    // @todo Consider fixing this in https://www.drupal.org/node/2346787.
+    $this->uri = $this->routeName;
     // Set empty route name and parameters.
     $this->routeName = NULL;
     $this->routeParameters = array();
-    // Flag the path as external so the UrlGenerator does not need to check.
-    $this->options['external'] = TRUE;
-
-    return $this;
+    // @todo Add a method for the check below in
+    // https://www.drupal.org/node/2346859.
+    if ($this->external = strpos($this->uri, 'base://') !== 0) {
+      $this->options['external'] = TRUE;
+    }
   }
 
   /**
@@ -192,15 +265,24 @@ public function isExternal() {
   }
 
   /**
+   * Indicates if this Url has a Drupal route.
+   *
+   * @return bool
+   */
+  public function isRouted() {
+    return !$this->unrouted;
+  }
+
+  /**
    * Returns the route name.
    *
    * @return string
    *
    * @throws \UnexpectedValueException.
-   *   If this is an external URL with no corresponding route.
+   *   If this is a URI with no corresponding route.
    */
   public function getRouteName() {
-    if ($this->isExternal()) {
+    if ($this->unrouted) {
       throw new \UnexpectedValueException('External URLs do not have an internal route name.');
     }
 
@@ -213,10 +295,10 @@ public function getRouteName() {
    * @return array
    *
    * @throws \UnexpectedValueException.
-   *   If this is an external URL with no corresponding route.
+   *   If this is a URI with no corresponding route.
    */
   public function getRouteParameters() {
-    if ($this->isExternal()) {
+    if ($this->unrouted) {
       throw new \UnexpectedValueException('External URLs do not have internal route parameters.');
     }
 
@@ -230,10 +312,13 @@ public function getRouteParameters() {
    *   The array of parameters.
    *
    * @return $this
+   *
+   * @throws \UnexpectedValueException.
+   *   If this is a URI with no corresponding route.
    */
   public function setRouteParameters($parameters) {
-    if ($this->isExternal()) {
-      throw new \Exception('External URLs do not have route parameters.');
+    if ($this->unrouted) {
+      throw new \UnexpectedValueException('External URLs do not have route parameters.');
     }
     $this->routeParameters = $parameters;
     return $this;
@@ -248,10 +333,13 @@ public function setRouteParameters($parameters) {
    *   The route parameter.
    *
    * @return $this
+   *
+   * @throws \UnexpectedValueException.
+   *   If this is a URI with no corresponding route.
    */
   public function setRouteParameter($key, $value) {
-    if ($this->isExternal()) {
-      throw new \Exception('External URLs do not have route parameters.');
+    if ($this->unrouted) {
+      throw new \UnexpectedValueException('External URLs do not have route parameters.');
     }
     $this->routeParameters[$key] = $value;
     return $this;
@@ -312,22 +400,22 @@ public function setOption($name, $value) {
   }
 
   /**
-   * Returns the external path of the URL.
+   * Returns the URI of the URL.
    *
-   * Only to be used if self::$external is TRUE.
+   * Only to be used if self::$unrouted is TRUE.
    *
    * @return string
-   *   The external path.
+   *   A URI not connected to a route. May be an external URL.
    *
    * @throws \UnexpectedValueException
-   *   Thrown when the path was requested for an internal URL.
+   *   Thrown when the URI was requested for a routed URL.
    */
-  public function getPath() {
-    if (!$this->isExternal()) {
-      throw new \UnexpectedValueException('Internal URLs do not have external paths.');
+  public function getUri() {
+    if (!$this->unrouted) {
+      throw new \UnexpectedValueException('This URL has a Drupal route, so the canonical form is not a URI.');
     }
 
-    return $this->path;
+    return $this->uri;
   }
 
   /**
@@ -344,11 +432,11 @@ public function setAbsolute($absolute = TRUE) {
   }
 
   /**
-   * Generates the path for this Url object.
+   * Generates the URI for this Url object.
    */
   public function toString() {
-    if ($this->isExternal()) {
-      return $this->urlGenerator()->generateFromPath($this->getPath(), $this->getOptions());
+    if ($this->unrouted) {
+      return $this->unroutedUrlAssembler()->assemble($this->getUri(), $this->getOptions());
     }
 
     return $this->urlGenerator()->generateFromRoute($this->getRouteName(), $this->getRouteParameters(), $this->getOptions());
@@ -361,9 +449,9 @@ public function toString() {
    *   An associative array containing all the properties of the route.
    */
   public function toArray() {
-    if ($this->isExternal()) {
+    if ($this->unrouted) {
       return array(
-        'path' => $this->getPath(),
+        'path' => $this->getUri(),
         'options' => $this->getOptions(),
       );
     }
@@ -383,9 +471,9 @@ public function toArray() {
    *   An associative array suitable for a render array.
    */
   public function toRenderArray() {
-    if ($this->isExternal()) {
+    if ($this->unrouted) {
       return array(
-        '#href' => $this->getPath(),
+        '#href' => $this->getUri(),
         '#options' => $this->getOptions(),
       );
     }
@@ -408,14 +496,14 @@ public function toRenderArray() {
    *   The internal path for this route.
    *
    * @throws \UnexpectedValueException.
-   *   If this is an external URL with no corresponding system path.
+   *   If this is a URI with no corresponding system path.
    *
    * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
    *   System paths should not be used - use route names and parameters.
    */
   public function getInternalPath() {
-    if ($this->isExternal()) {
-      throw new \UnexpectedValueException('External URLs do not have internal representations.');
+    if ($this->unrouted) {
+      throw new \UnexpectedValueException('Unrouted URIs do not have internal representations.');
     }
     return $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters());
   }
@@ -473,6 +561,19 @@ protected function urlGenerator() {
   }
 
   /**
+   * Gets the unrouted URL assembler for non-Drupal URLs.
+   *
+   * @return \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
+   *   The unrouted URL assembler.
+   */
+  protected function unroutedUrlAssembler() {
+    if (!$this->urlAssembler) {
+      $this->urlAssembler = \Drupal::service('unrouted_url_assembler');
+    }
+    return $this->urlAssembler;
+  }
+
+  /**
    * Sets the URL generator.
    *
    * @param \Drupal\Core\Routing\UrlGeneratorInterface
@@ -485,4 +586,17 @@ public function setUrlGenerator(UrlGeneratorInterface $url_generator) {
     return $this;
   }
 
+  /**
+   * Sets the unrouted URL assembler.
+   *
+   * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
+   *   The unrouted URL assembler.
+   *
+   * @return $this
+   */
+  public function setUnroutedUrlAssembler(UnroutedUrlAssemblerInterface $url_assembler) {
+    $this->urlAssembler = $url_assembler;
+    return $this;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php b/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php
index cba87fe..36fca11 100644
--- a/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php
+++ b/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php
@@ -12,23 +12,24 @@
 interface UnroutedUrlAssemblerInterface {
 
   /**
-   * Builds a domain-local or external URL from a path or URL.
+   * Builds a domain-local or external URL from a URI.
    *
    * For actual implementations the logic probably has to be split up between
-   * domain-local and external URLs.
+   * domain-local URIs and external URLs.
    *
    * @param string $uri
-   *   A path on the same domain or external URL being linked to, such as "foo"
+   *   A local URI or an external URL being linked to, such as "base://foo"
    *    or "http://example.com/foo".
    *   - If you provide a full URL, it will be considered an external URL as
    *     long as it has an allowed protocol.
-   *   - If you provide only a path (e.g. "foo"), it will be
-   *     considered a URL local to the same domain. Additional query
-   *     arguments for local paths must be supplied in $options['query'], not
-   *     included in $path.
+   *   - If you provide only a local URI (e.g. "base://foo"), it will be
+   *     considered a path local to Drupal, but not handled by the routing
+   *     system.  The base path (the subdirectory where the front controller
+   *     is found) will be added to the path. Additional query arguments for
+   *     local paths must be supplied in $options['query'], not part of $uri.
    *   - If your external URL contains a query (e.g. http://example.com/foo?a=b),
    *     then you can either URL encode the query keys and values yourself and
-   *     include them in $path, or use $options['query'] to let this method
+   *     include them in $uri, or use $options['query'] to let this method
    *     URL encode them.
    *
    * @param array $options
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index aecd844..5e85078 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -26,6 +26,7 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) {
       $output .= '<p>' . t('The Aggregator module is an on-site syndicator and news reader that gathers and displays fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines in feeds, using a number of standardized XML-based formats. For more information, see the <a href="!aggregator-module">online documentation for the Aggregator module</a>.', array('!aggregator-module' => 'https://drupal.org/documentation/modules/aggregator')) . '</p>';
       $output .= '<h3>' . t('Uses') . '</h3>';
       $output .= '<dl>';
+      // Check if the aggregator sources View is enabled.
       if ($url = $path_validator->getUrlIfValid('aggregator/sources')) {
         $output .= '<dt>' . t('Viewing feeds') . '</dt>';
         $output .= '<dd>' . t('Users view feed content in the <a href="!aggregator">main aggregator display</a>, or by <a href="!aggregator-sources">their source</a> (usually via an RSS feed reader). The most recent content in a feed can be displayed as a block through the <a href="!admin-block">Blocks administration page</a>.', array('!aggregator' => \Drupal::url('aggregator.page_last'), '!aggregator-sources' => $url->toString(), '!admin-block' => \Drupal::url('block.admin_display'))) . '</dd>';
@@ -33,6 +34,7 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) {
       $output .= '<dt>' . t('Adding, editing, and deleting feeds') . '</dt>';
       $output .= '<dd>' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the <a href="!feededit">Feed aggregator administration page</a>.', array('!feededit' => \Drupal::url('aggregator.admin_overview'))) . '</dd>';
       $output .= '<dt>' . t('<abbr title="Outline Processor Markup Language">OPML</abbr> integration') . '</dt>';
+      // Check if the aggregator opml View is enabled.
       if ($url = $path_validator->getUrlIfValid('aggregator/opml')) {
         $output .= '<dd>' . t('A <a href="!aggregator-opml">machine-readable OPML file</a> of all feeds is available. OPML is an XML-based file format used to share outline-structured information such as a list of RSS feeds. Feeds can also be <a href="!import-opml">imported via an OPML file</a>.', array('!aggregator-opml' => $url->toString(), '!import-opml' => \Drupal::url('aggregator.opml_add'))) . '</dd>';
       }
diff --git a/core/modules/aggregator/src/Entity/Item.php b/core/modules/aggregator/src/Entity/Item.php
index 493e4cb..d760594 100644
--- a/core/modules/aggregator/src/Entity/Item.php
+++ b/core/modules/aggregator/src/Entity/Item.php
@@ -244,7 +244,7 @@ public function getListCacheTags() {
    * Entity URI callback.
    */
   public static function buildUri(ItemInterface $item) {
-    return Url::createFromPath($item->getLink());
+    return Url::unrouted($item->getLink());
   }
 
 }
diff --git a/core/modules/field_ui/src/FieldUI.php b/core/modules/field_ui/src/FieldUI.php
index 77ec651..be74fff 100644
--- a/core/modules/field_ui/src/FieldUI.php
+++ b/core/modules/field_ui/src/FieldUI.php
@@ -51,15 +51,15 @@ public static function getNextDestination(array $destinations) {
       $next_destination += array(
         'route_parameters' => array(),
       );
-      $next_destination = new Url($next_destination['route_name'], $next_destination['route_parameters'], $next_destination['options']);
+      $next_destination = Url::routed($next_destination['route_name'], $next_destination['route_parameters'], $next_destination['options']);
     }
     else {
       $options = UrlHelper::parse($next_destination);
       if ($destinations) {
         $options['query']['destinations'] = $destinations;
       }
-      $next_destination = Url::createFromPath($options['path']);
-      $next_destination->setOptions($options);
+      // Redirect to any given path within the same domain.
+      $next_destination = Url::unrouted('base://' . $options['path']);
     }
     return $next_destination;
   }
diff --git a/core/modules/link/link.module b/core/modules/link/link.module
index d33181e..c9837a3 100644
--- a/core/modules/link/link.module
+++ b/core/modules/link/link.module
@@ -69,6 +69,6 @@ function template_preprocess_link_formatter_link_separate(&$variables) {
     $variables['link'] = \Drupal::linkGenerator()->generateFromUrl($variables['url_title'], $variables['url']);
   }
   else {
-    $variables['link'] = l($variables['url_title'], $variables['url']->getPath(), $variables['url']->getOptions());
+    $variables['link'] = l($variables['url_title'], $variables['url']->getUri(), $variables['url']->getOptions());
   }
 }
diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
index 05caf97..2d9cc98 100644
--- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
+++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Drupal\link\LinkItemInterface;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Plugin implementation of the 'link' formatter.
@@ -163,7 +164,7 @@ public function viewElements(FieldItemListInterface $items) {
           '#options' => $url->getOptions(),
         );
         if ($url->isExternal()) {
-          $element[$delta]['#href'] = $url->getPath();
+          $element[$delta]['#href'] = $url->getUri();
         }
         else {
           $element[$delta]['#route_name'] = $url->getRouteName();
@@ -206,11 +207,10 @@ protected function buildUrl(LinkItemInterface $item) {
     }
 
     if ($item->isExternal()) {
-      $url = Url::createFromPath($item->url);
-      $url->setOptions($options);
+      $url = Url::unrouted($item->url, $options);
     }
     else {
-      $url = new Url($item->route_name, (array) $item->route_parameters, (array) $options);
+      $url = Url::routed($item->route_name, (array) $item->route_parameters, (array) $options);
     }
 
     return $url;
diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
index 55f3489..150cc72 100644
--- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
@@ -107,7 +107,7 @@ public function getUrlObject() {
     else {
       $path = $this->getUrl();
       if (isset($path)) {
-        $url = Url::createFromPath($path);
+        $url = Url::unrouted($path);
       }
       else {
         $url = new Url('<front>');
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
index 2d37566..4fb93b2 100644
--- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -198,7 +198,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state)
 
     if ($extracted) {
       if ($extracted->isExternal()) {
-        $new_definition['url'] = $extracted->getPath();
+        $new_definition['url'] = $extracted->getUri();
       }
       else {
         $new_definition['route_name'] = $extracted->getRouteName();
diff --git a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
index 8c23c07..8478bae 100644
--- a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
+++ b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
@@ -75,7 +75,8 @@ public function testAllFormattersInternal() {
     // Set up test values.
     $this->testValue = 'admin';
     $this->entity = entity_create('entity_test', array());
-    $this->entity->{$this->fieldName}->url = $this->testValue;
+    $this->entity->{$this->fieldName}->route_name = 'system.admin';
+    $this->entity->{$this->fieldName}->url = 'admin';
 
     // Set up the expected result.
     // AssertFormatterRdfa looks for a full path.
@@ -94,7 +95,8 @@ public function testAllFormattersFront() {
     // Set up test values.
     $this->testValue = '<front>';
     $this->entity = entity_create('entity_test', array());
-    $this->entity->{$this->fieldName}->url = $this->testValue;
+    $this->entity->{$this->fieldName}->route_name = $this->testValue;
+    $this->entity->{$this->fieldName}->url = '<front>';
 
     // Set up the expected result.
     $expected_rdf = array(
diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php
index df96d6f..4bd041e 100644
--- a/core/modules/shortcut/src/Entity/Shortcut.php
+++ b/core/modules/shortcut/src/Entity/Shortcut.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Url;
 use Drupal\shortcut\ShortcutInterface;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Defines the shortcut entity class.
@@ -134,7 +135,15 @@ public static function preCreate(EntityStorageInterface $storage, array &$values
   public function preSave(EntityStorageInterface $storage) {
     parent::preSave($storage);
 
-    $url = Url::createFromPath($this->path->value);
+    // @todo fix PathValidatorInterface::getUrlIfValid() so we can use it
+    //   here. The problem is that we need an exception, not a FALSE
+    //   return value. https://www.drupal.org/node/2346695
+    if ($this->path->value == '<front>') {
+      $url = new Url($this->path->value);
+    }
+    else {
+      $url = Url::createFromRequest(Request::create("/{$this->path->value}"));
+    }
     $this->setRouteName($url->getRouteName());
     $this->setRouteParams($url->getRouteParameters());
   }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index b478fb4..f74ecde 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -457,7 +457,7 @@ function system_authorized_get_url(array $options = array()) {
   // Force HTTPS if available, regardless of what the caller specifies.
   $options['https'] = TRUE;
   // Prefix with $base_url so url() treats it as an external link.
-  $url = Url::createFromPath($base_url . '/core/authorize.php');
+  $url = Url::unrouted('base://core/authorize.php');
   $url_options = $url->getOptions();
   $url->setOptions($options + $url_options);
   return $url;
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 7fc352e..3eb9cc4 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,7 +54,7 @@ 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')) {
-        $form_state->setRedirectUrl(Url::createFromPath($GLOBALS['base_url'] . '/' . $form_state->getValue('destination')));
+        $form_state->setRedirectUrl(Url::unrouted('base://' . $form_state->getValue('destination')));
       }
     }
     else {
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index b9174bc..815d1cf 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -298,7 +298,7 @@ function toolbar_menu_navigation_links(array $tree) {
     $url = $link->getUrlObject();
     if ($url->isExternal()) {
       // This is an unusual case, so just get a distinct, safe string.
-      $id = substr(Crypt::hashBase64($url->getPath()), 0, 16);
+      $id = substr(Crypt::hashBase64($url->getUri()), 0, 16);
     }
     else {
       $id = str_replace(array('.', '<', '>'), array('-', '', ''), $url->getRouteName());
diff --git a/core/modules/update/update.report.inc b/core/modules/update/update.report.inc
index 9ee4dbf..b5215e2 100644
--- a/core/modules/update/update.report.inc
+++ b/core/modules/update/update.report.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Template\Attribute;
+use Drupal\Core\Url;
 
 /**
  * Prepares variables for project status report templates.
@@ -135,7 +136,7 @@ function template_preprocess_update_project_status(&$variables) {
 
   // Set the project title and URL.
   $variables['title'] = (isset($project['title'])) ? $project['title'] : $project['name'];
-  $variables['url'] = (isset($project['link'])) ? url($project['link']) : NULL;
+  $variables['url'] = (isset($project['link'])) ? Url::unrouted($project['link'])->toString() : NULL;
 
   $variables['install_type'] = $project['install_type'];
   if ($project['install_type'] == 'dev' && !empty($project['datestamp'])) {
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index e33cd28..6a51027 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -576,6 +576,7 @@ function template_preprocess_username(&$variables) {
   $variables['name'] = String::checkPlain($name);
   $variables['profile_access'] = \Drupal::currentUser()->hasPermission('access user profiles');
 
+  $external = FALSE;
   // Populate link path and attributes if appropriate.
   if ($variables['uid'] && $variables['profile_access']) {
     // We are linking to a local user.
@@ -589,10 +590,19 @@ function template_preprocess_username(&$variables) {
     $variables['attributes']['rel'] = 'nofollow';
     $variables['link_path'] = $account->homepage;
     $variables['homepage'] = $account->homepage;
+    $external = TRUE;
   }
-  // We have a link path, so we should generate a link using url().
+  // We have a link path, so we should generate a URL.
   if (isset($variables['link_path'])) {
-    $variables['attributes']['href'] = url($variables['link_path'], $variables['link_options']);
+    if ($external) {
+      $variables['attributes']['href'] = Url::unrouted($variables['link_path'], $variables['link_options'])
+        ->toString();
+    }
+    else {
+      $variables['attributes']['href'] = Url::routed('entity.user.canonical', array(
+        'user' => $variables['uid'],
+      ))->toString();
+    }
   }
 }
 
diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php
index 12083cf..4112cce 100644
--- a/core/modules/views_ui/src/ViewEditForm.php
+++ b/core/modules/views_ui/src/ViewEditForm.php
@@ -20,6 +20,7 @@
 use Drupal\Core\Url;
 use Drupal\user\TempStoreFactory;
 use Drupal\views\Views;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -322,13 +323,11 @@ public function save(array $form, FormStateInterface $form_state) {
           $query->remove('destination');
         }
       }
-      if (!UrlHelper::isExternal($destination)) {
-        $destination = $GLOBALS['base_url'] . '/' . $destination;
-      }
-      $form_state->setRedirectUrl(Url::createFromPath($destination));
+      $form_state->setRedirectUrl(Url::unrouted("base://$destination"));
     }
 
     $view->save();
+
     drupal_set_message($this->t('The view %name has been saved.', array('%name' => $view->label())));
 
     // Remove this view from cache so we can edit it properly.
diff --git a/core/tests/Drupal/Tests/Core/ExternalUrlTest.php b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
similarity index 54%
rename from core/tests/Drupal/Tests/Core/ExternalUrlTest.php
rename to core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
index 57a51d4..472dc64 100644
--- a/core/tests/Drupal/Tests/Core/ExternalUrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UnroutedUrlTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\Tests\Core\ExternalUrlTest.
+ * Contains \Drupal\Tests\Core\UnroutedUrlTest.
  */
 
 namespace Drupal\Tests\Core;
@@ -15,16 +15,16 @@
 
 /**
  * @coversDefaultClass \Drupal\Core\Url
- * @group ExternalUrlTest
+ * @group UrlTest
  */
-class ExternalUrlTest extends UnitTestCase {
+class UnroutedUrlTest extends UnitTestCase {
 
   /**
-   * The URL generator
+   * The URL assembler
    *
-   * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $urlGenerator;
+  protected $urlAssembler;
 
   /**
    * The router.
@@ -34,11 +34,18 @@ class ExternalUrlTest extends UnitTestCase {
   protected $router;
 
   /**
-   * An external URL to test.
+   * An unrouted, external URL to test.
    *
    * @var string
    */
-  protected $path = 'http://drupal.org';
+  protected $unroutedExternal = 'http://drupal.org';
+
+  /**
+   * An unrouted, internal URL to test.
+   *
+   * @var string
+   */
+  protected $unroutedInternal = 'base://robots.txt';
 
   /**
    * {@inheritdoc}
@@ -46,15 +53,15 @@ class ExternalUrlTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
-    $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
-    $this->urlGenerator->expects($this->any())
-      ->method('generateFromPath')
+    $this->urlAssembler = $this->getMock('Drupal\Core\Utility\UnroutedUrlAssemblerInterface');
+    $this->urlAssembler->expects($this->any())
+      ->method('assemble')
       ->will($this->returnArgument(0));
 
     $this->router = $this->getMock('Drupal\Tests\Core\Routing\TestRouterInterface');
     $container = new ContainerBuilder();
     $container->set('router.no_access_checks', $this->router);
-    $container->set('url_generator', $this->urlGenerator);
+    $container->set('unrouted_url_assembler', $this->urlAssembler);
     \Drupal::setContainer($container);
   }
 
@@ -62,13 +69,20 @@ protected function setUp() {
    * Tests the createFromPath method.
    *
    * @covers ::createFromPath()
-   * @covers ::setExternal()
    */
   public function testCreateFromPath() {
-    $url = Url::createFromPath($this->path);
-    $this->assertInstanceOf('Drupal\Core\Url', $url);
-    $this->assertTrue($url->isExternal());
-    return $url;
+    $urls = [
+      Url::unrouted($this->unroutedExternal),
+      Url::unrouted($this->unroutedInternal)
+    ];
+
+    $this->assertInstanceOf('Drupal\Core\Url', $urls[0]);
+    $this->assertTrue($urls[0]->isExternal());
+
+    $this->assertInstanceOf('Drupal\Core\Url', $urls[1]);
+    $this->assertFalse($urls[1]->isExternal());
+
+    return $urls;
   }
 
   /**
@@ -96,8 +110,9 @@ public function testCreateFromRequest() {
    *
    * @covers ::isExternal()
    */
-  public function testIsExternal(Url $url) {
-    $this->assertTrue($url->isExternal());
+  public function testIsExternal(array $urls) {
+    $this->assertTrue($urls[0]->isExternal());
+    $this->assertFalse($urls[1]->isExternal());
   }
 
   /**
@@ -107,8 +122,9 @@ public function testIsExternal(Url $url) {
    *
    * @covers ::toString()
    */
-  public function testToString(Url $url) {
-    $this->assertSame($this->path, $url->toString());
+  public function testToString(array $urls) {
+    $this->assertSame($this->unroutedExternal, $urls[0]->toString());
+    $this->assertSame($this->unroutedInternal, $urls[1]->toString());
   }
 
   /**
@@ -118,12 +134,18 @@ public function testToString(Url $url) {
    *
    * @covers ::toArray()
    */
-  public function testToArray(Url $url) {
+  public function testToArray(array $urls) {
     $expected = array(
-      'path' => $this->path,
+      'path' => $this->unroutedExternal,
       'options' => array('external' => TRUE),
     );
-    $this->assertSame($expected, $url->toArray());
+    $this->assertSame($expected, $urls[0]->toArray());
+
+    $expected = array(
+      'path' => $this->unroutedInternal,
+      'options' => array(),
+    );
+    $this->assertSame($expected, $urls[1]->toArray());
   }
 
   /**
@@ -135,8 +157,8 @@ public function testToArray(Url $url) {
    *
    * @covers ::getRouteName()
    */
-  public function testGetRouteName(Url $url) {
-    $url->getRouteName();
+  public function testGetRouteName(array $urls) {
+    $urls[0]->getRouteName();
   }
 
   /**
@@ -148,8 +170,8 @@ public function testGetRouteName(Url $url) {
    *
    * @covers ::getRouteParameters()
    */
-  public function testGetRouteParameters(Url $url) {
-    $url->getRouteParameters();
+  public function testGetRouteParameters(array $urls) {
+    $urls[0]->getRouteParameters();
   }
 
   /**
@@ -161,8 +183,8 @@ public function testGetRouteParameters(Url $url) {
    *
    * @expectedException \Exception
    */
-  public function testGetInternalPath(Url $url) {
-    $this->assertNull($url->getInternalPath());
+  public function testGetInternalPath(array $urls) {
+    $this->assertNull($urls[0]->getInternalPath());
   }
 
   /**
@@ -170,10 +192,11 @@ public function testGetInternalPath(Url $url) {
    *
    * @depends testCreateFromPath
    *
-   * @covers ::getPath()
+   * @covers ::getUri()
    */
-  public function testGetPath(Url $url) {
-    $this->assertNotNull($url->getPath());
+  public function testGetUri(array $urls) {
+    $this->assertNotNull($urls[0]->getUri());
+    $this->assertNotNull($urls[1]->getUri());
   }
 
   /**
@@ -183,8 +206,9 @@ public function testGetPath(Url $url) {
    *
    * @covers ::getOptions()
    */
-  public function testGetOptions(Url $url) {
-    $this->assertInternalType('array', $url->getOptions());
+  public function testGetOptions(array $urls) {
+    $this->assertInternalType('array', $urls[0]->getOptions());
+    $this->assertInternalType('array', $urls[1]->getOptions());
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php
index 133a73c..7cc514a 100644
--- a/core/tests/Drupal/Tests/Core/UrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UrlTest.php
@@ -102,8 +102,8 @@ public function testCreateFromPath() {
 
     $urls = array();
     foreach ($this->map as $index => $values) {
-      $path = trim(array_pop($values), '/');
-      $url = Url::createFromPath($path);
+      $path = array_pop($values);
+      $url = Url::createFromRequest(Request::create("$path"));
       $this->assertSame($values, array_values($url->toArray()));
       $urls[$index] = $url;
     }
@@ -131,7 +131,7 @@ protected function getRequestConstraint($path) {
    * @covers ::createFromPath()
    */
   public function testCreateFromPathFront() {
-    $url = Url::createFromPath('<front>');
+    $url = Url::routed('<front>');
     $this->assertSame('<front>', $url->getRouteName());
   }
 
@@ -148,7 +148,7 @@ public function testCreateFromPathInvalid() {
       ->with($this->getRequestConstraint('/non-existent'))
       ->will($this->throwException(new ResourceNotFoundException()));
 
-    $this->assertNull(Url::createFromPath('non-existent'));
+    $this->assertNull(Url::createFromRequest(Request::create('/non-existent')));
   }
 
   /**
@@ -209,6 +209,9 @@ public function testIsExternal($urls) {
   /**
    * Tests the getPath() method for internal URLs.
    *
+   * @param \Drupal\Core\Url[] $urls
+   *   Array of URL objects.
+   *
    * @depends testCreateFromPath
    *
    * @expectedException \UnexpectedValueException
@@ -217,7 +220,7 @@ public function testIsExternal($urls) {
    */
   public function testGetPathForInternalUrl($urls) {
     foreach ($urls as $url) {
-      $url->getPath();
+      $url->getUri();
     }
   }
 
@@ -227,8 +230,8 @@ public function testGetPathForInternalUrl($urls) {
    * @covers ::getPath
    */
   public function testGetPathForExternalUrl() {
-    $url = Url::createFromPath('http://example.com/test');
-    $this->assertEquals('http://example.com/test', $url->getPath());
+    $url = Url::unrouted('http://example.com/test');
+    $this->assertEquals('http://example.com/test', $url->getUri());
   }
 
   /**
@@ -292,7 +295,7 @@ public function testGetRouteName($urls) {
    * @expectedException \UnexpectedValueException
    */
   public function testGetRouteNameWithExternalUrl() {
-    $url = Url::createFromPath('http://example.com');
+    $url = Url::unrouted('http://example.com');
     $url->getRouteName();
   }
 
@@ -319,7 +322,7 @@ public function testGetRouteParameters($urls) {
    * @expectedException \UnexpectedValueException
    */
   public function testGetRouteParametersWithExternalUrl() {
-    $url = Url::createFromPath('http://example.com');
+    $url = Url::unrouted('http://example.com');
     $url->getRouteParameters();
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
index 7926b2f..8c38e75 100644
--- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
@@ -40,6 +40,13 @@ class LinkGeneratorTest extends UnitTestCase {
   protected $moduleHandler;
 
   /**
+   * The mocked URL Assembler service.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Utility\UnroutedUrlAssemblerInterface
+   */
+  protected $urlAssembler;
+
+  /**
    * Contains the LinkGenerator default options.
    */
   protected $defaultOptions = array(
@@ -60,6 +67,7 @@ protected function setUp() {
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 
     $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler);
+    $this->urlAssembler = $this->getMock('\Drupal\Core\Utility\UnroutedUrlAssemblerInterface');
   }
 
   /**
@@ -142,17 +150,18 @@ public function testGenerateFromUrl() {
    * @covers ::generateFromUrl()
    */
   public function testGenerateFromUrlExternal() {
-    $this->urlGenerator->expects($this->once())
-      ->method('generateFromPath')
-      ->with('http://drupal.org', array('set_active_class' => TRUE, 'external' => TRUE) + $this->defaultOptions)
-      ->will($this->returnArgument(0));
-
     $this->moduleHandler->expects($this->once())
       ->method('alter')
       ->with('link', $this->isType('array'));
 
-    $url = Url::createFromPath('http://drupal.org');
+    $this->urlAssembler->expects($this->once())
+      ->method('assemble')
+      ->with('http://drupal.org', array('set_active_class' => TRUE, 'external' => TRUE) + $this->defaultOptions)
+      ->willReturnArgument(0);
+
+    $url = Url::unrouted('http://drupal.org');
     $url->setUrlGenerator($this->urlGenerator);
+    $url->setUnroutedUrlAssembler($this->urlAssembler);
     $url->setOption('set_active_class', TRUE);
 
     $result = $this->linkGenerator->generateFromUrl('Drupal', $url);
