diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index 37550a4..b2e2b86 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core;
 
 use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
@@ -240,7 +241,7 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
    *
    * @see static::fromRoute()
    */
-  public static function fromUri($uri, $options = array()) {
+  public static function fromUri($uri, $options = []) {
     $uri_parts = parse_url($uri);
     if ($uri_parts === FALSE) {
       throw new \InvalidArgumentException(String::format('The URI "@uri" is malformed.', ['@uri' => $uri]));
@@ -248,14 +249,24 @@ public static function fromUri($uri, $options = array()) {
     if (empty($uri_parts['scheme'])) {
       throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base: for items like a static file that needs the base path. Use user-path: for user input without a scheme. Use entity: for referencing the canonical route of a content entity.', ['@uri' => $uri]));
     }
+    $uri_parts += ['path' => '', 'fragment' => ''];
+    // Extract query parameters and fragment and merge them into $options.
+    $options += ['fragment' => $uri_parts['fragment']];
+    if (!empty($uri_parts['query'])) {
+      $uri_query = [];
+      parse_str($uri_parts['query'], $uri_query);
+      $options['query'] += $uri_query;
+    }
 
-    $uri_parts += ['path' => ''];
     if ($uri_parts['scheme'] === 'entity') {
-      $url = static::fromEntityUri($uri);
+      $url = static::fromEntityUri($uri, $options);
     }
     elseif ($uri_parts['scheme'] === 'user-path') {
       $url = static::fromUserPathUri($uri, $options);
     }
+    elseif ($uri_parts['scheme'] === 'route') {
+      $url = static::fromRouteUri($uri, $options);
+    }
     else {
       $url = new static($uri, array(), $options);
       if ($uri_parts['scheme'] !== 'base') {
@@ -273,6 +284,8 @@ public static function fromUri($uri, $options = array()) {
    *
    * @param string $uri
    *   An URI of the form entity:{entity_type}/{entity_id}.
+   * @param array $options
+   *   An array of options, see static::fromUri() for details.
    *
    * @return \Drupal\Core\Url
    *   A new Url object for an entity's canonical route.
@@ -280,14 +293,14 @@ public static function fromUri($uri, $options = array()) {
    * @throws \InvalidArgumentException
    *   Thrown if the entity URI is invalid.
    */
-  protected static function fromEntityUri($uri) {
+  protected static function fromEntityUri($uri, $options) {
     $uri_parts = parse_url($uri);
     list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
     if ($uri_parts['scheme'] != 'entity' || $entity_id === '') {
       throw new \InvalidArgumentException(String::format('The entity URI "@uri" is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.', ['@uri' => $uri]));
     }
 
-    return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id]);
+    return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options);
   }
 
   /**
@@ -302,16 +315,41 @@ protected static function fromEntityUri($uri) {
    *   A new Url object for a 'user-path:' URI.
    */
   protected static function fromUserPathUri($uri, array $options) {
-    $uri_reference = explode(':', $uri, 2)[1];
+    $uri_parts = parse_url($uri);
     $url = \Drupal::pathValidator()
-      ->getUrlIfValidWithoutAccessCheck($uri_reference) ?: static::fromUri('base:' . $uri_reference);
-    // Allow to specify additional or override options from the path.
+      ->getUrlIfValidWithoutAccessCheck($uri_parts['path']) ?: static::fromUri('base:' . $uri_parts['path'], $options);
+    // Allow specifying additional options.
     $url->setOptions($options + $url->getOptions());
 
     return $url;
   }
 
   /**
+   * Creates a new Url object for 'route:' URIs.
+   *
+   * @param string $uri
+   *   An URI of the form route:{path}. Where path is the route name optionally
+   *   followed by a ";" followed by query parameters in key=value format
+   *   with & separators.
+   * @param array $options
+   *   An array of options, see static::fromUri() for details.
+   *
+   * @return \Drupal\Core\Url
+   *   A new Url object for a 'route:' URI.
+   */
+  protected static function fromRouteUri($uri, array $options) {
+    $uri_parts = parse_url($uri);
+    $route_parts = explode(';', $uri_parts['path'], 2);
+    $route_name = $route_parts[0];
+    $route_parameters = [];
+    if (!empty($route_parts[1])) {
+      parse_str($route_parts[1], $route_parameters);
+    }
+
+    return new static($route_name, $route_parameters. $options);
+  }
+
+  /**
    * Returns the Url object matching a request.
    *
    * SECURITY NOTE: The request path is not checked to be valid and accessible
@@ -358,6 +396,25 @@ protected function setUnrouted() {
   }
 
   /**
+   * @return string
+   *   A URI representation of the Url object data.
+   */
+  public function toUriString() {
+    if ($this->isRouted()) {
+      $uri = 'route:' . $this->routeName;
+      if ($this->routeParameters) {
+        $uri .= ';' . UrlHelper::buildQuery($this->routeParameters);
+      }
+    }
+    else {
+      $uri = $this->uri;
+    }
+    $query = !empty($this->options['query']) ? ('?' . UrlHelper::buildQuery($options['query'])) : '';
+    $fragment = !empty($this->options['fragment']) ? '#' . $this->options['fragment'] : '';
+    return $uri . $query . $fragment;
+  }
+
+  /**
    * Indicates if this Url is external.
    *
    * @return bool
diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php
index 9d883d7..a6a9a6c 100644
--- a/core/tests/Drupal/Tests/Core/UrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UrlTest.php
@@ -489,6 +489,18 @@ public function testEntityUris() {
     $url = Url::fromUri('entity:test_entity/1');
     $this->assertSame('entity.test_entity.canonical', $url->getRouteName());
     $this->assertEquals(['test_entity' => '1'], $url->getRouteParameters());
+    // Ensure the options are parsed out.
+    $url = Url::fromUri('entity:test_entity/2?page=1&foo=bar#bottom');
+    $this->assertSame('entity.test_entity.canonical', $url->getRouteName());
+    $this->assertEquals(['test_entity' => '2'], $url->getRouteParameters());
+    $this->assertEquals($url->getOption('query'), ['page' => '1', 'foo' => 'bar']);
+    $this->assertSame($url->getOption('fragment'), 'bottom');
+    // Ensure the options are parsed out and merged with $options.
+    $url = Url::fromUri('entity:test_entity/2?page=1&foo=bar#bottom', ['fragment' => 'top', 'query' => ['foo' => 'yes', 'focus' => 'no']]);
+    $this->assertSame('entity.test_entity.canonical', $url->getRouteName());
+    $this->assertEquals(['test_entity' => '2'], $url->getRouteParameters());
+    $this->assertEquals($url->getOption('query'), ['page' => '1', 'foo' => 'yes', 'focus' => 'no']);
+    $this->assertSame($url->getOption('fragment'), 'top');
   }
 
   /**
@@ -508,6 +520,16 @@ public function testInvalidEntityUriParameter() {
   }
 
   /**
+   * Tests the fromUri() method with an entity: URI.
+   *
+   * @covers ::fromUri
+   */
+  public function testToUriString() {
+    $url = Url::fromUri('entity:test_entity/1');
+    $this->assertSame($url->toUriString(), 'route:entity.test_entity.canonical;test_entity=1');
+  }
+
+  /**
    * Creates a mock access manager for the access tests.
    *
    * @param bool $access
