diff --git a/core/core.services.yml b/core/core.services.yml
index db5240658f91dd22c4b2bd6b17bba326adce1cf9..05d80695e9fff262a1fb7ec214f8ae5fc55d8487 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -861,6 +861,23 @@ services:
   plugin.manager.element_info:
     class: Drupal\Core\Render\ElementInfoManager
     parent: default_plugin_manager
+  stream_wrapper_manager:
+    class: Drupal\Core\StreamWrapper\StreamWrapperManager
+    arguments: ['@module_handler']
+    calls:
+      - [setContainer, ['@service_container']]
+  stream_wrapper.public:
+    class: Drupal\Core\StreamWrapper\PublicStream
+    tags:
+      - { name: stream_wrapper, scheme: public }
+  stream_wrapper.private:
+    class: Drupal\Core\StreamWrapper\PrivateStream
+    tags:
+      - { name: stream_wrapper, scheme: private }
+  stream_wrapper.temporary:
+    class: Drupal\Core\StreamWrapper\TemporaryStream
+    tags:
+      - { name: stream_wrapper, scheme: temporary }
   kernel_destruct_subscriber:
     class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
     tags:
diff --git a/core/includes/file.inc b/core/includes/file.inc
index 6030f14339764aff7f9ce6b13396329e9bbe643d..f543c77d5658cf1f399a544464d81192392e5425 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -11,39 +11,7 @@
 use Drupal\Component\Utility\String;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PublicStream;
-
-/**
- * Stream wrapper bit flags that are the basis for composite types.
- *
- * Note that 0x0002 is skipped, because it was the value of a constant that has
- * since been removed.
- */
-
-/**
- * Stream wrapper bit flag -- a filter that matches all wrappers.
- */
-const STREAM_WRAPPERS_ALL = 0x0000;
-
-/**
- * Stream wrapper bit flag -- refers to a local file system location.
- */
-const STREAM_WRAPPERS_LOCAL = 0x0001;
-
-/**
- * Stream wrapper bit flag -- wrapper is readable (almost always true).
- */
-const STREAM_WRAPPERS_READ = 0x0004;
-
-/**
- * Stream wrapper bit flag -- wrapper is writeable.
- */
-const STREAM_WRAPPERS_WRITE = 0x0008;
-
-/**
- * Stream wrapper bit flag -- exposed in the UI and potentially web accessible.
- */
-const STREAM_WRAPPERS_VISIBLE = 0x0010;
-
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 
 /**
  * Default mode for new directories. See drupal_chmod().
@@ -55,45 +23,6 @@
  */
 const FILE_CHMOD_FILE = 0664;
 
-/**
- * Composite stream wrapper bit flags that are usually used as the types.
- */
-
-/**
- * Stream wrapper type flag -- not visible in the UI or accessible via web,
- * but readable and writable. E.g. the temporary directory for uploads.
- */
-define('STREAM_WRAPPERS_HIDDEN', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE);
-
-/**
- * Stream wrapper type flag -- hidden, readable and writeable using local files.
- */
-define('STREAM_WRAPPERS_LOCAL_HIDDEN', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_HIDDEN);
-
-/**
- * Stream wrapper type flag -- visible, readable and writeable.
- */
-define('STREAM_WRAPPERS_WRITE_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE | STREAM_WRAPPERS_VISIBLE);
-
-/**
- * Stream wrapper type flag -- visible and read-only.
- */
-define('STREAM_WRAPPERS_READ_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_VISIBLE);
-
-/**
- * Stream wrapper type flag -- the default when 'type' is omitted from
- * hook_stream_wrappers(). This does not include STREAM_WRAPPERS_LOCAL,
- * because PHP grants a greater trust level to local files (for example, they
- * can be used in an "include" statement, regardless of the "allow_url_include"
- * setting), so stream wrappers need to explicitly opt-in to this.
- */
-define('STREAM_WRAPPERS_NORMAL', STREAM_WRAPPERS_WRITE_VISIBLE);
-
-/**
- * Stream wrapper type flag -- visible, readable and writeable using local files.
- */
-define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_NORMAL);
-
 /**
  * @defgroup file File interface
  * @{
@@ -155,122 +84,46 @@
 /**
  * Provides Drupal stream wrapper registry.
  *
- * A stream wrapper is an abstraction of a file system that allows Drupal to
- * use the same set of methods to access both local files and remote resources.
- *
- * Provide a facility for managing and querying user-defined stream wrappers
- * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
- * registered to handle a stream, which we need to be able to find the handler
- * for class instantiation.
- *
- * If a module registers a scheme that is already registered with PHP, the
- * existing scheme will be unregistered and replaced with the specified class.
- *
- * A stream is referenced as "scheme://target".
- *
- * The optional $filter parameter can be used to retrieve only the stream
- * wrappers that are appropriate for particular usage. For example, this returns
- * only stream wrappers that use local file storage:
- * @code
- *   $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
- * @endcode
- *
- * The $filter parameter can only filter to types containing a particular flag.
- * In some cases, you may want to filter to types that do not contain a
- * particular flag. For example, you may want to retrieve all stream wrappers
- * that are not writable, or all stream wrappers that are not local. PHP's
- * array_diff_key() function can be used to help with this. For example, this
- * returns only stream wrappers that do not use local file storage:
- * @code
- *   $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL));
- * @endcode
- *
- * @param $filter
+ * @param int $filter
  *   (Optional) Filters out all types except those with an on bit for each on
- *   bit in $filter. For example, if $filter is STREAM_WRAPPERS_WRITE_VISIBLE,
- *   which is equal to (STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE |
- *   STREAM_WRAPPERS_VISIBLE), then only stream wrappers with all three of these
- *   bits set are returned. Defaults to STREAM_WRAPPERS_ALL, which returns all
- *   registered stream wrappers.
- *
- * @return
+ *   bit in $filter. For example, if $filter is
+ *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
+ *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
+ *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all three
+ *   of these bits set are returned. Defaults to StreamWrapperInterface::ALL,
+ *   which returns all registered stream wrappers.
+ *
+ * @return array
  *   An array keyed by scheme, with values containing an array of information
  *   about the stream wrapper, as returned by hook_stream_wrappers(). If $filter
- *   is omitted or set to STREAM_WRAPPERS_ALL, the entire Drupal stream wrapper
- *   registry is returned. Otherwise only the stream wrappers whose 'type'
- *   bitmask has an on bit for each bit specified in $filter are returned.
+ *   is omitted or set to StreamWrapperInterface::ALL, the entire Drupal stream
+ *   wrapper registry is returned. Otherwise only the stream wrappers whose
+ *   'type' bitmask has an on bit for each bit specified in $filter are
+ *   returned.
  *
- * @see hook_stream_wrappers()
  * @see hook_stream_wrappers_alter()
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('stream_wrapper_manager')->getWrappers().
  */
-function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
-  $wrappers_storage = &drupal_static(__FUNCTION__, array());
-
-  if (empty($wrappers_storage)) {
-    // Initialize $wrappers_storage, so that we are not calling this method
-    // repeatedly if no stream wrappers exist.
-    $wrappers_storage[STREAM_WRAPPERS_ALL] = array();
-    $wrappers = array();
-    if (\Drupal::hasService('module_handler')) {
-      $wrappers = \Drupal::moduleHandler()->invokeAll('stream_wrappers');
-      foreach ($wrappers as $scheme => $info) {
-        // Add defaults.
-        $wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
-      }
-      \Drupal::moduleHandler()->alter('stream_wrappers', $wrappers);
-    }
-    $existing = stream_get_wrappers();
-    foreach ($wrappers as $scheme => $info) {
-      // We only register classes that implement our interface.
-      if (in_array('Drupal\Core\StreamWrapper\StreamWrapperInterface', class_implements($info['class']), TRUE)) {
-        // Record whether we are overriding an existing scheme.
-        if (in_array($scheme, $existing, TRUE)) {
-          $wrappers[$scheme]['override'] = TRUE;
-          stream_wrapper_unregister($scheme);
-        }
-        else {
-          $wrappers[$scheme]['override'] = FALSE;
-        }
-        if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
-          stream_wrapper_register($scheme, $info['class']);
-        }
-        else {
-          stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL);
-        }
-      }
-      // Pre-populate the static cache with the filters most typically used.
-      $wrappers_storage[STREAM_WRAPPERS_ALL][$scheme] = $wrappers[$scheme];
-      if (($info['type'] & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) {
-        $wrappers_storage[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[$scheme];
-      }
-    }
-  }
-
-  if (!isset($wrappers_storage[$filter])) {
-    $wrappers_storage[$filter] = array();
-    foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) {
-      // Bit-wise filter.
-      if (($info['type'] & $filter) == $filter) {
-        $wrappers_storage[$filter][$scheme] = $info;
-      }
-    }
-  }
-
-  return $wrappers_storage[$filter];
+function file_get_stream_wrappers($filter = StreamWrapperInterface::ALL) {
+  return \Drupal::service('stream_wrapper_manager')->getWrappers($filter);
 }
 
 /**
  * Returns the stream wrapper class name for a given scheme.
  *
- * @param $scheme
+ * @param string $scheme
  *   Stream scheme.
  *
- * @return
+ * @return string|bool
  *   Return string if a scheme has a registered handler, or FALSE.
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('stream_wrapper_manager')->getClass().
  */
 function file_stream_wrapper_get_class($scheme) {
-  $wrappers = file_get_stream_wrappers();
-  return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class'];
+  return \Drupal::service('stream_wrapper_manager')->getClass($scheme);
 }
 
 /**
@@ -301,10 +154,10 @@ function file_uri_scheme($uri) {
  * and that it is callable. This is useful if you want to confirm a valid
  * scheme without creating a new instance of the registered handler.
  *
- * @param $scheme
+ * @param string $scheme
  *   A URI scheme, a stream is referenced as "scheme://target".
  *
- * @return
+ * @return bool
  *   Returns TRUE if the string is the name of a validated stream,
  *   or FALSE if the scheme does not have a registered handler.
  */
@@ -338,7 +191,7 @@ function file_uri_target($uri) {
 /**
  * Gets the default file stream implementation.
  *
- * @return
+ * @return string
  *   'public', 'private' or any other file scheme defined as the default.
  */
 function file_default_scheme() {
@@ -354,10 +207,10 @@ function file_default_scheme() {
  * - Remove trailing slashes from target
  * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
  *
- * @param $uri
+ * @param string $uri
  *   String reference containing the URI to normalize.
  *
- * @return
+ * @return string
  *   The normalized URI.
  */
 function file_stream_wrapper_uri_normalize($uri) {
@@ -380,25 +233,20 @@ function file_stream_wrapper_uri_normalize($uri) {
  * The scheme determines the stream wrapper class that should be
  * used by consulting the stream wrapper registry.
  *
- * @param $uri
+ * @param string $uri
  *   A stream, referenced as "scheme://target".
  *
- * @return
+ * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
  *   Returns a new stream wrapper object appropriate for the given URI or FALSE
  *   if no registered handler could be found. For example, a URI of
  *   "private://example.txt" would return a new private stream wrapper object
  *   (Drupal\Core\StreamWrapper\PrivateStream).
+ *
+ * * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('stream_wrapper_manager')->getViaUri().
  */
 function file_stream_wrapper_get_instance_by_uri($uri) {
-  if ($scheme = file_uri_scheme($uri)) {
-    $class = file_stream_wrapper_get_class($scheme);
-    if (class_exists($class)) {
-      $instance = new $class();
-      $instance->setUri($uri);
-      return $instance;
-    }
-  }
-  return FALSE;
+  return \Drupal::service('stream_wrapper_manager')->getViaUri($uri);
 }
 
 /**
@@ -412,25 +260,20 @@ function file_stream_wrapper_get_instance_by_uri($uri) {
  * Note: the instance URI will be initialized to "scheme://" so that you can
  * make the customary method calls as if you had retrieved an instance by URI.
  *
- * @param $scheme
+ * @param string $scheme
  *   If the stream was "public://target", "public" would be the scheme.
  *
- * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface
+ * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
  *   Returns a new stream wrapper object appropriate for the given $scheme.
  *   For example, for the public scheme a stream wrapper object
  *   (Drupal\Core\StreamWrapper\PublicStream).
  *   FALSE is returned if no registered handler could be found.
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ *   Use \Drupal::service('stream_wrapper_manager')->getViaScheme().
  */
 function file_stream_wrapper_get_instance_by_scheme($scheme) {
-  $class = file_stream_wrapper_get_class($scheme);
-  if (class_exists($class)) {
-    $instance = new $class();
-    $instance->setUri($scheme . '://');
-    return $instance;
-  }
-  else {
-    return FALSE;
-  }
+  return \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
 }
 
 /**
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index adb7d81abb846daa98bf8f79a321560a73df3346..1550ac0405a6f8fbaf8b563257499ac81825a805 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -325,6 +325,12 @@ function install_begin_request(&$install_state) {
     ->register('path.matcher', 'Drupal\Core\Path\PathMatcher')
     ->addArgument(new Reference('config.factory'));
 
+  // Register the stream wrapper manager.
+  $container
+    ->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager')
+    ->addArgument(new Reference('module_handler'))
+    ->addMethodCall('setContainer', array(new Reference('service_container')));
+
   \Drupal::setContainer($container);
 
   // Determine whether base system services are ready to operate.
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 9515b4f13a17c4fa6f6831b9018c6d7d72660c4b..bcca67ce3e5c43f53055530a66ae2a4b5e79a5a9 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -652,16 +652,6 @@ function drupal_install_system($install_state) {
   $kernel->getContainer()->get('module_handler')->install(array('system'), FALSE);
   \Drupal::service('router.builder')->rebuild();
 
-  // DrupalKernel::prepareLegacyRequest() above calls into
-  // DrupalKernel::bootCode(), which primes file_get_stream_wrappers()'s static
-  // list of custom stream wrappers that are based on the currently enabled
-  // list of modules (none).
-  // @todo Custom stream wrappers of a new module have to be registered as soon
-  //   as the module is installed/enabled. Fix either ModuleHandler::install()
-  //   and/or DrupalKernel::updateModules().
-  // @see https://drupal.org/node/2028109
-  drupal_static_reset('file_get_stream_wrappers');
-
   // Ensure default language is saved.
   if (isset($install_state['parameters']['langcode'])) {
     \Drupal::config('system.site')
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 8393903fb650645061f670467052a91d125b7e2b..db54f9133ebede9a1140c12b61cd0d8185d05612 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Cache\ListCacheBinsPass;
 use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
 use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass;
 use Drupal\Core\DependencyInjection\ServiceProviderInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
@@ -55,6 +56,7 @@ public function register(ContainerBuilder $container) {
 
     // Collect tagged handler services as method calls on consumer services.
     $container->addCompilerPass(new TaggedHandlersPass());
+    $container->addCompilerPass(new RegisterStreamWrappersPass());
 
     // Add a compiler pass for registering event subscribers.
     $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterStreamWrappersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterStreamWrappersPass.php
new file mode 100644
index 0000000000000000000000000000000000000000..01193d3d802bb34b81cebef721332a3a8ff6bf1c
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterStreamWrappersPass.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Adds services tagged 'stream_wrapper' to the stream_wrapper_manager service.
+ */
+class RegisterStreamWrappersPass implements CompilerPassInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('stream_wrapper_manager')) {
+      return;
+    }
+
+    $stream_wrapper_manager = $container->getDefinition('stream_wrapper_manager');
+
+    foreach ($container->findTaggedServiceIds('stream_wrapper') as $id => $attributes) {
+      $class = $container->getDefinition($id)->getClass();
+      $scheme = $attributes[0]['scheme'];
+
+      $stream_wrapper_manager->addMethodCall('addStreamWrapper', array($id, $class, $scheme));
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 6a9b78d8b88f61e77d6a8d3a94bfa8bb3713532c..c014e4ad2541c22351e5045431d9d005d3d4f741 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -389,6 +389,7 @@ public function boot() {
     $seed = unpack("L", Crypt::randomBytes(4));
     mt_srand($seed[1]);
 
+    $this->container->get('stream_wrapper_manager')->register();
     $this->booted = TRUE;
 
     return $this;
@@ -401,6 +402,7 @@ public function shutdown() {
     if (FALSE === $this->booted) {
       return;
     }
+    $this->container->get('stream_wrapper_manager')->unregister();
     $this->booted = FALSE;
     $this->container = NULL;
     $this->moduleList = NULL;
@@ -430,9 +432,6 @@ public function preHandle(Request $request) {
     // Put the request on the stack.
     $this->container->get('request_stack')->push($request);
 
-    // Make sure all stream wrappers are registered.
-    file_get_stream_wrappers();
-
     // Set the allowed protocols once we have the config available.
     $allowed_protocols = $this->container->get('config.factory')->get('system.filter')->get('protocols');
     if (!isset($allowed_protocols)) {
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index fe6c0809ffb2af55a5d6d08948ece83b539e1ab1..035e75facf4853f0eeed18c8abd7f1c69e344b04 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -865,8 +865,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         // via Drush, as the 'translations' stream wrapper is provided by
         // Interface Translation module and is later used to import
         // translations.
-        drupal_static_reset('file_get_stream_wrappers');
-        file_get_stream_wrappers();
+        \Drupal::service('stream_wrapper_manager')->register();
 
         // Update the theme registry to include it.
         drupal_theme_rebuild();
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 937953f2f49bc8e2eb8ac08d8eab94acfc0bd2e1..0111747462def17a58d057704e5f23ccc9aa335b 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -42,6 +42,13 @@ abstract class LocalStream implements StreamWrapperInterface {
    */
   protected $uri;
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function getType() {
+    return StreamWrapperInterface::NORMAL;
+  }
+
   /**
    * Gets the path that the wrapper is responsible for.
    *
diff --git a/core/lib/Drupal/Core/StreamWrapper/PrivateStream.php b/core/lib/Drupal/Core/StreamWrapper/PrivateStream.php
index ce076567e25f33b9423e13bcbbf78f92e36af1c2..84aa2c174aaf2eea2503711579492bce2226be28 100644
--- a/core/lib/Drupal/Core/StreamWrapper/PrivateStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/PrivateStream.php
@@ -19,6 +19,27 @@ class PrivateStream extends LocalStream {
 
   use UrlGeneratorTrait;
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function getType() {
+    return StreamWrapperInterface::LOCAL_NORMAL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Private files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Private local files served by Drupal.');
+  }
+
   /**
    * Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
    */
diff --git a/core/lib/Drupal/Core/StreamWrapper/PublicStream.php b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php
index 637c9ef0c8bcfbf466df73cbdf067f0ea4f88e62..e2b78ec37ce8233eb026fe1c0b445e7cb952fb27 100644
--- a/core/lib/Drupal/Core/StreamWrapper/PublicStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php
@@ -18,6 +18,27 @@
  */
 class PublicStream extends LocalStream {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function getType() {
+    return StreamWrapperInterface::LOCAL_NORMAL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Public files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Public local files served by the webserver.');
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
index 3f9f5d412dee4a551fd35c7a4dc1f1b325ad4dbe..ac95452b9feb7fb68b6e52183ddf5d33c5b33e55 100644
--- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
@@ -29,6 +29,100 @@
  */
 interface StreamWrapperInterface extends PhpStreamWrapperInterface {
 
+  /**
+   * Stream wrapper bit flags that are the basis for composite types.
+   *
+   * Note that 0x0002 is skipped, because it was the value of a constant that
+   * has since been removed.
+   */
+
+  /**
+   * A filter that matches all wrappers.
+   */
+  const ALL = 0x0000;
+
+  /**
+   * Refers to a local file system location.
+   */
+  const LOCAL = 0x0001;
+
+  /**
+   * Wrapper is readable (almost always true).
+   */
+  const READ = 0x0004;
+
+  /**
+   * Wrapper is writeable.
+   */
+  const WRITE = 0x0008;
+
+  /**
+   * Exposed in the UI and potentially web accessible.
+   */
+  const VISIBLE = 0x0010;
+
+  /**
+   * Composite stream wrapper bit flags that are usually used as the types.
+   */
+
+  /**
+   * Not visible in the UI or accessible via web, but readable and writable.
+   * E.g. the temporary directory for uploads.
+   */
+  const HIDDEN = 0x000C;
+
+  /**
+   * Hidden, readable and writeable using local files.
+   */
+  const LOCAL_HIDDEN = 0x000D;
+
+  /**
+   * Visible, readable and writeable.
+   */
+  const WRITE_VISIBLE = 0x001C;
+
+  /**
+   * Visible and read-only.
+   */
+  const READ_VISIBLE = 0x0014;
+
+  /**
+   * This is the default 'type' falg. This does not include
+   * StreamWrapperInterface::LOCAL, because PHP grants a greater trust level to
+   * local files (for example, they can be used in an "include" statement,
+   * regardless of the "allow_url_include" setting), so stream wrappers need to
+   * explicitly opt-in to this.
+   */
+  const NORMAL = 0x001C;
+
+  /**
+   * Visible, readable and writeable using local files.
+   */
+  const LOCAL_NORMAL = 0x001D;
+
+  /**
+   * Returns the type of stream wrapper.
+   *
+   * @return int
+   */
+  public static function getType();
+
+  /**
+   * Returns the name of the stream wrapper for use in the UI.
+   *
+   * @return string
+   *   The stream wrapper name.
+   */
+  public function getName();
+
+  /**
+   * Returns the description of the stream wrapper for use in the UI.
+   *
+   * @return string
+   *   The stream wrapper description.
+   */
+  public function getDescription();
+
   /**
    * Sets the absolute stream resource URI.
    *
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..43532a0299ded8216ab62f92aceee7eecd582ef1
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php
@@ -0,0 +1,337 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\StreamWrapperManager.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Symfony\Component\DependencyInjection\ContainerAware;
+
+/**
+ * Provides a StreamWrapper manager.
+ *
+ * @see file_get_stream_wrappers()
+ * @see hook_stream_wrappers_alter()
+ * @see system_stream_wrappers()
+ * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface
+ */
+class StreamWrapperManager extends ContainerAware {
+
+  /**
+   * Contains stream wrapper info.
+   *
+   * An associative array where keys are scheme names and values are themselves
+   * associative arrays with the keys class, type and (optionally) service_id,
+   * and string values.
+   *
+   * @var array
+   */
+  protected $info = array();
+
+  /**
+   * Contains collected stream wrappers.
+   *
+   * Keyed by filter, each value is itself an associative array keyed by scheme.
+   * Each of those values is an array representing a stream wrapper, with the
+   * following keys and values:
+   *   - class: stream wrapper class name
+   *   - type: a bitmask corresponding to the type constants in
+   *     StreamWrapperInterface
+   *   - service_id: name of service
+   *
+   * The array on key StreamWrapperInterface::ALL contains representations of
+   * all schemes and corresponding wrappers.
+   *
+   * @var array
+   */
+  protected $wrappers = array();
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a StreamWrapperManager object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * Provides Drupal stream wrapper registry.
+   *
+   * A stream wrapper is an abstraction of a file system that allows Drupal to
+   * use the same set of methods to access both local files and remote
+   * resources.
+   *
+   * Provide a facility for managing and querying user-defined stream wrappers
+   * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
+   * registered to handle a stream, which we need to be able to find the handler
+   * for class instantiation.
+   *
+   * If a module registers a scheme that is already registered with PHP, the
+   * existing scheme will be unregistered and replaced with the specified class.
+   *
+   * A stream is referenced as "scheme://target".
+   *
+   * The optional $filter parameter can be used to retrieve only the stream
+   * wrappers that are appropriate for particular usage. For example, this
+   * returns only stream wrappers that use local file storage:
+   *
+   * @code
+   *   $local_stream_wrappers = file_get_stream_wrappers(StreamWrapperInterface::LOCAL);
+   * @endcode
+   *
+   * The $filter parameter can only filter to types containing a particular
+   * flag. In some cases, you may want to filter to types that do not contain a
+   * particular flag. For example, you may want to retrieve all stream wrappers
+   * that are not writable, or all stream wrappers that are not local. PHP's
+   * array_diff_key() function can be used to help with this. For example, this
+   * returns only stream wrappers that do not use local file storage:
+   * @code
+   *   $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(StreamWrapperInterface::ALL), file_get_stream_wrappers(StreamWrapperInterface::LOCAL));
+   * @endcode
+   *
+   * @param int $filter
+   *   (Optional) Filters out all types except those with an on bit for each on
+   *   bit in $filter. For example, if $filter is
+   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
+   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
+   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
+   *   three of these bits set are returned. Defaults to
+   *   StreamWrapperInterface::ALL, which returns all registered stream
+   *   wrappers.
+   *
+   * @return array
+   *   An array keyed by scheme, with values containing an array of information
+   *   about the stream wrapper, as returned by hook_stream_wrappers(). If
+   *   $filter is omitted or set to StreamWrapperInterface::ALL, the entire
+   *   Drupal stream wrapper registry is returned. Otherwise only the stream
+   *   wrappers whose 'type' bitmask has an on bit for each bit specified in
+   *   $filter are returned.
+   */
+  public function getWrappers($filter = StreamWrapperInterface::ALL) {
+    if (isset($this->wrappers[$filter])) {
+      return $this->wrappers[$filter];
+    }
+    else if (isset($this->wrappers[StreamWrapperInterface::ALL])) {
+      $this->wrappers[$filter] = array();
+      foreach ($this->wrappers[StreamWrapperInterface::ALL] as $scheme => $info) {
+        // Bit-wise filter.
+        if (($info['type'] & $filter) == $filter) {
+          $this->wrappers[$filter][$scheme] = $info;
+        }
+      }
+      return $this->wrappers[$filter];
+    }
+    else {
+      return array();
+    }
+  }
+
+  /**
+   * Returns registered stream wrapper names.
+   *
+   * @param int $filter
+   *   (Optional) Filters out all types except those with an on bit for each on
+   *   bit in $filter. For example, if $filter is
+   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
+   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
+   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
+   *   three of these bits set are returned. Defaults to
+   *   StreamWrapperInterface::ALL, which returns all registered stream
+   *   wrappers.
+   *
+   * @return array
+   *   Stream wrapper names, keyed by scheme.
+   */
+  public function getNames($filter = StreamWrapperInterface::ALL) {
+    $names = array();
+    foreach (array_keys($this->getWrappers($filter)) as $scheme) {
+      $names[$scheme] = $this->getViaScheme($scheme)->getName();
+    }
+
+    return $names;
+  }
+
+  /**
+   * Returns registered stream wrapper descriptions.
+   *
+   * @param int $filter
+   *   (Optional) Filters out all types except those with an on bit for each on
+   *   bit in $filter. For example, if $filter is
+   *   StreamWrapperInterface::WRITE_VISIBLE, which is equal to
+   *   (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE |
+   *   StreamWrapperInterface::VISIBLE), then only stream wrappers with all
+   *   three of these bits set are returned. Defaults to
+   *   StreamWrapperInterface::ALL, which returns all registered stream
+   *   wrappers.
+   *
+   * @return array
+   *   Stream wrapper descriptions, keyed by scheme.
+   */
+  public function getDescriptions($filter = StreamWrapperInterface::ALL) {
+    $descriptions = array();
+    foreach (array_keys($this->getWrappers($filter)) as $scheme) {
+      $descriptions[$scheme] = $this->getViaScheme($scheme)->getDescription();
+    }
+
+    return $descriptions;
+  }
+
+  /**
+   * Returns a stream wrapper via scheme.
+   *
+   * @param string $scheme
+   *   The scheme of the stream wrapper.
+   *
+   * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
+   *   A stream wrapper object, or false if the scheme is not available.
+   */
+  public function getViaScheme($scheme) {
+    return $this->getWrapper($scheme, $scheme . '://');
+  }
+
+  /**
+   * Returns a stream wrapper via URI.
+   *
+   * @param string $uri
+   *   The URI of the stream wrapper.
+   *
+   * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
+   *   A stream wrapper object, or false if the scheme is not available.
+   */
+  public function getViaUri($uri) {
+    $scheme = file_uri_scheme($uri);
+    return $this->getWrapper($scheme, $uri);
+  }
+
+  /**
+   * Returns the stream wrapper class.
+   *
+   * @param string $scheme
+   *   The stream wrapper scheme.
+   *
+   * @return string|bool
+   *   The stream wrapper class, or false if the scheme does not exist.
+   */
+  public function getClass($scheme) {
+    if (isset($this->info[$scheme])) {
+      return $this->info[$scheme]['class'];
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Returns a stream wrapper instance.
+   *
+   * @param string $scheme
+   *   The scheme of the desired stream wrapper.
+   * @param string $uri
+   *   The URI of the stream.
+   *
+   * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool
+   *   A stream wrapper object, or false if the scheme is not available.
+   */
+  protected function getWrapper($scheme, $uri) {
+    if (isset($this->info[$scheme]['service_id'])) {
+      $instance = $this->container->get($this->info[$scheme]['service_id']);
+      $instance->setUri($uri);
+      return $instance;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Adds a stream wrapper.
+   *
+   * Internal use only.
+   *
+   * @param string $service_id
+   *   The service id.
+   * @param string $class
+   *   The stream wrapper class.
+   * @param string $scheme
+   *   The scheme for which the wrapper should be registered.
+   */
+  public function addStreamWrapper($service_id, $class, $scheme) {
+    $this->info[$scheme] = array(
+      'class' => $class,
+      'type' => $class::getType(),
+      'service_id' => $service_id,
+    );
+  }
+
+  /**
+   * Registers the tagged stream wrappers.
+   *
+   * Internal use only.
+   */
+  public function register() {
+    $this->moduleHandler->alter('stream_wrappers', $this->info);
+
+    foreach ($this->info as $scheme => $info) {
+      $this->registerWrapper($scheme, $info['class'], $info['type']);
+    }
+  }
+
+  /**
+   * Unregisters the tagged stream wrappers.
+   *
+   * Internal use only.
+   */
+  public function unregister() {
+    // Normally, there are definitely wrappers set for the ALL filter. However,
+    // in some cases involving many container rebuilds (e.g. WebTestBase),
+    // $this->wrappers may be empty although wrappers are still registered
+    // globally. Thus an isset() check is needed before iterating.
+    if (isset($this->wrappers[StreamWrapperInterface::ALL])) {
+      foreach (array_keys($this->wrappers[StreamWrapperInterface::ALL]) as $scheme) {
+        stream_wrapper_unregister($scheme);
+      }
+    }
+  }
+
+  /**
+   * Registers stream wrapper with PHP.
+   *
+   * @param string $scheme
+   *   The scheme of the stream wrapper.
+   * @param string $class
+   *   The class of the stream wrapper.
+   * @param int $type
+   *   The type of the stream wrapper.
+   */
+  public function registerWrapper($scheme, $class, $type) {
+    if (in_array($scheme, stream_get_wrappers(), TRUE)) {
+      stream_wrapper_unregister($scheme);
+    }
+
+    if (($type & StreamWrapperInterface::LOCAL) == StreamWrapperInterface::LOCAL) {
+      stream_wrapper_register($scheme, $class);
+    }
+    else {
+      stream_wrapper_register($scheme, $class, STREAM_IS_URL);
+    }
+
+    // Pre-populate the static cache with the filters most typically used.
+    $info = array('type' => $type, 'class' => $class);
+    $this->wrappers[StreamWrapperInterface::ALL][$scheme] = $info;
+
+    if (($type & StreamWrapperInterface::WRITE_VISIBLE) == StreamWrapperInterface::WRITE_VISIBLE) {
+      $this->wrappers[StreamWrapperInterface::WRITE_VISIBLE][$scheme] = $info;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/TemporaryStream.php b/core/lib/Drupal/Core/StreamWrapper/TemporaryStream.php
index f04213d6141ad07ac543a19f929434df5306a66d..2f7806000c6d455bd007e1900f5fe466461c1afc 100644
--- a/core/lib/Drupal/Core/StreamWrapper/TemporaryStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/TemporaryStream.php
@@ -15,6 +15,27 @@
  */
 class TemporaryStream extends LocalStream {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function getType() {
+    return StreamWrapperInterface::LOCAL_HIDDEN;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Temporary files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Temporary local files for upload and previews.');
+  }
+
   /**
    * Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
    */
diff --git a/core/lib/Drupal/Core/Test/TestRunnerKernel.php b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
index 50bc112bcc439b3037d51cd69f3eb78a950d44a5..8829113357a2cfe19ad3e7348c0afb689b99db3a 100644
--- a/core/lib/Drupal/Core/Test/TestRunnerKernel.php
+++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
@@ -75,9 +75,7 @@ public function boot() {
 
     simpletest_classloader_register();
 
-    // Register System module stream wrappers and create the build/artifacts
-    // directory if necessary.
-    file_get_stream_wrappers();
+    // Create the build/artifacts directory if necessary.
     if (!is_dir('public://simpletest')) {
       mkdir('public://simpletest', 0777, TRUE);
     }
diff --git a/core/modules/editor/editor.admin.inc b/core/modules/editor/editor.admin.inc
index 2c16f05deed90dfeae7323978e61fab104fced17..85006ebb0c73c7a19014da1967771ea23a2fa03a 100644
--- a/core/modules/editor/editor.admin.inc
+++ b/core/modules/editor/editor.admin.inc
@@ -5,6 +5,7 @@
  * Administration functions for editor.module.
  */
 
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\editor\Entity\Editor;
 
@@ -51,10 +52,7 @@ function editor_image_upload_settings_form(Editor $editor) {
 
   // Any visible, writable wrapper can potentially be used for uploads,
   // including a remote file system that integrates with a CDN.
-  $stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
-  foreach ($stream_wrappers as $scheme => $info) {
-    $options[$scheme] = $info['description'];
-  }
+  $options = \Drupal::service('stream_wrapper_manager')->getDescriptions(StreamWrapperInterface::WRITE_VISIBLE);
   if (!empty($options)) {
     $form['scheme'] = array(
       '#type' => 'radios',
@@ -68,8 +66,8 @@ function editor_image_upload_settings_form(Editor $editor) {
   // Set data- attributes with human-readable names for all possible stream
   // wrappers, so that drupal.ckeditor.drupalimage.admin's summary rendering
   // can use that.
-  foreach ($stream_wrappers as $scheme => $info) {
-    $form['scheme'][$scheme]['#attributes']['data-label'] = t('Storage: @name', array('@name' => $info['name']));
+  foreach (\Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE) as $scheme => $name) {
+    $form['scheme'][$scheme]['#attributes']['data-label'] = t('Storage: @name', array('@name' => $name));
   }
 
   $form['directory'] = array(
diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
index 133998ec021e3fd8ee229eef52a2920c6a4bcf2e..13c861666bb71a3d1799f0b469b60677ff82b36f 100644
--- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
+++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Drupal\Core\TypedData\DataDefinition;
 
 /**
@@ -133,10 +134,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
       ),
     );
 
-    $scheme_options = array();
-    foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
-      $scheme_options[$scheme] = $stream_wrapper['name'];
-    }
+    $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
     $element['uri_scheme'] = array(
       '#type' => 'radios',
       '#title' => t('Upload destination'),
diff --git a/core/modules/file/tests/file_test/file_test.module b/core/modules/file/tests/file_test/file_test.module
index b1bdeb3cf33a04181ab9b59d2473681311f047d0..6ee2ecc32170fccdce9c72c0de05012d02377567 100644
--- a/core/modules/file/tests/file_test/file_test.module
+++ b/core/modules/file/tests/file_test/file_test.module
@@ -13,29 +13,6 @@
 const FILE_URL_TEST_CDN_1 = 'http://cdn1.example.com';
 const FILE_URL_TEST_CDN_2 = 'http://cdn2.example.com';
 
-/**
- * Implements hook_stream_wrappers().
- */
-function file_test_stream_wrappers() {
-  return array(
-    'dummy' => array(
-      'name' => t('Dummy files'),
-      'class' => 'Drupal\file_test\DummyStreamWrapper',
-      'description' => t('Dummy wrapper for simpletest.'),
-    ),
-    'dummy-remote' => array(
-      'name' => t('Dummy files (remote)'),
-      'class' => 'Drupal\file_test\DummyRemoteStreamWrapper',
-      'description' => t('Dummy wrapper for simpletest (remote).'),
-    ),
-    'dummy-readonly' => array(
-      'name' => t('Dummy files (readonly)'),
-      'class' => 'Drupal\file_test\DummyReadOnlyStreamWrapper',
-      'description' => t('Dummy wrapper for simpletest (readonly).'),
-    ),
-  );
-}
-
 /**
  * Reset/initialize the history of calls to the file_* hooks.
  *
diff --git a/core/modules/file/tests/file_test/file_test.services.yml b/core/modules/file/tests/file_test/file_test.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1e05c26bff613ccd1d249442a43402ea8ce108e2
--- /dev/null
+++ b/core/modules/file/tests/file_test/file_test.services.yml
@@ -0,0 +1,13 @@
+services:
+  stream_wrapper.dummy_readonly:
+    class: Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper
+    tags:
+      - { name: stream_wrapper, scheme: dummy-readonly }
+  stream_wrapper.dummy_remote:
+    class: Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper
+    tags:
+      - { name: stream_wrapper, scheme: dummy-remote }
+  stream_wrapper.dummy:
+    class: Drupal\file_test\StreamWrapper\DummyStreamWrapper
+    tags:
+      - { name: stream_wrapper, scheme: dummy }
diff --git a/core/modules/file/tests/file_test/src/DummyReadOnlyStreamWrapper.php b/core/modules/file/tests/file_test/src/StreamWrapper/DummyReadOnlyStreamWrapper.php
similarity index 65%
rename from core/modules/file/tests/file_test/src/DummyReadOnlyStreamWrapper.php
rename to core/modules/file/tests/file_test/src/StreamWrapper/DummyReadOnlyStreamWrapper.php
index d230bc5afa2ab33ed74449936e4d8772fdaeb8bf..99cf3e864f66efd1ba809903a65e575440f51675 100644
--- a/core/modules/file/tests/file_test/src/DummyReadOnlyStreamWrapper.php
+++ b/core/modules/file/tests/file_test/src/StreamWrapper/DummyReadOnlyStreamWrapper.php
@@ -2,10 +2,10 @@
 
 /**
  * @file
- * Definition of Drupal\file_test\DummyReadOnlyStreamWrapper.
+ * Contains \Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper.
  */
 
-namespace Drupal\file_test;
+namespace Drupal\file_test\StreamWrapper;
 
 use Drupal\Core\StreamWrapper\LocalReadOnlyStream;
 
@@ -15,6 +15,21 @@
  * Dummy stream wrapper implementation (dummy-readonly://).
  */
 class DummyReadOnlyStreamWrapper extends LocalReadOnlyStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Dummy files (readonly)');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Dummy wrapper for simpletest (readonly).');
+  }
+
   function getDirectoryPath() {
     return conf_path() . '/files';
   }
diff --git a/core/modules/file/tests/file_test/src/DummyRemoteStreamWrapper.php b/core/modules/file/tests/file_test/src/StreamWrapper/DummyRemoteStreamWrapper.php
similarity index 52%
rename from core/modules/file/tests/file_test/src/DummyRemoteStreamWrapper.php
rename to core/modules/file/tests/file_test/src/StreamWrapper/DummyRemoteStreamWrapper.php
index 274119691af74c36554289c70dfd11bc00a9e355..7e9f089765ea87933d5978e3dc5d303750665cf6 100644
--- a/core/modules/file/tests/file_test/src/DummyRemoteStreamWrapper.php
+++ b/core/modules/file/tests/file_test/src/StreamWrapper/DummyRemoteStreamWrapper.php
@@ -2,10 +2,10 @@
 
 /**
  * @file
- * Definition of Drupal\file_test\DummyRemoteStreamWrapper.
+ * Contains \Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper.
  */
 
-namespace Drupal\file_test;
+namespace Drupal\file_test\StreamWrapper;
 
 use Drupal\Core\StreamWrapper\PublicStream;
 
@@ -17,6 +17,21 @@
  * Basically just the public scheme but not returning a local file for realpath.
  */
 class DummyRemoteStreamWrapper extends PublicStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Dummy files (remote)');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Dummy wrapper for simpletest (remote).');
+  }
+
   function realpath() {
     return FALSE;
   }
diff --git a/core/modules/file/tests/file_test/src/DummyStreamWrapper.php b/core/modules/file/tests/file_test/src/StreamWrapper/DummyStreamWrapper.php
similarity index 65%
rename from core/modules/file/tests/file_test/src/DummyStreamWrapper.php
rename to core/modules/file/tests/file_test/src/StreamWrapper/DummyStreamWrapper.php
index cbea40f039a53b317e6cd137608ffdfa87c3899e..2ed31d0caba251472759684d70d4b2b9a6e0ae44 100644
--- a/core/modules/file/tests/file_test/src/DummyStreamWrapper.php
+++ b/core/modules/file/tests/file_test/src/StreamWrapper/DummyStreamWrapper.php
@@ -2,10 +2,10 @@
 
 /**
  * @file
- * Definition of Drupal\file_test\DummyStreamWrapper.
+ * Contains \Drupal\file_test\StreamWrapper\DummyStreamWrapper.
  */
 
-namespace Drupal\file_test;
+namespace Drupal\file_test\StreamWrapper;
 
 use Drupal\Core\StreamWrapper\LocalStream;
 
@@ -15,6 +15,21 @@
  * Dummy stream wrapper implementation (dummy://).
  */
 class DummyStreamWrapper extends LocalStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Dummy files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Dummy wrapper for simpletest.');
+  }
+
   function getDirectoryPath() {
     return conf_path() . '/files';
   }
diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php
index 0aea62d6fea521f7f317dfe5c468dd487dfa6892..037119b95c79284e67d4d392affcb6c6280c0021 100644
--- a/core/modules/image/src/Entity/ImageStyle.php
+++ b/core/modules/image/src/Entity/ImageStyle.php
@@ -19,6 +19,7 @@
 use Drupal\image\ImageStyleInterface;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
 
 /**
@@ -247,7 +248,7 @@ public function flush($path = NULL) {
     }
 
     // Delete the style directory in each registered wrapper.
-    $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
+    $wrappers = \Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::WRITE_VISIBLE);
     foreach ($wrappers as $wrapper => $wrapper_data) {
       if (file_exists($directory = $wrapper . '://styles/' . $this->id())) {
         file_unmanaged_delete_recursive($directory);
diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
index f92e5494c66f926b1473b0448fe181ba2003cef9..405630895b4757ac5c574f44cad4dbc5c2110934 100644
--- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
+++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Drupal\Core\TypedData\DataDefinition;
 use Drupal\file\Entity\File;
 use Drupal\file\Plugin\Field\FieldType\FileItem;
@@ -168,10 +169,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
     // the field.
     $settings = $this->getFieldDefinition()->getFieldStorageDefinition()->getSettings();
 
-    $scheme_options = array();
-    foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
-      $scheme_options[$scheme] = $stream_wrapper['name'];
-    }
+    $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
     $element['uri_scheme'] = array(
       '#type' => 'radios',
       '#title' => t('Upload destination'),
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 01ef7814837a371dc1757743f937c65a2fc30915..957dc8e2ed7f411fd94193fa162a73b2ae93d356 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -195,21 +195,6 @@ function locale_theme() {
   );
 }
 
-/**
- * Implements hook_stream_wrappers().
- */
-function locale_stream_wrappers() {
-  $wrappers = array(
-    'translations' => array(
-      'name' => new TranslationWrapper('Translation files'),
-      'class' => 'Drupal\locale\TranslationsStream',
-      'description' => new TranslationWrapper('Translation files'),
-      'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
-    ),
-  );
-  return $wrappers;
-}
-
 /**
  * Implements hook_ENTITY_TYPE_insert() for 'configurable_language'.
  */
diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml
index b3cb964bf22e09857d2ad8f72e9264323079e3dd..e25786d135ae7ce9d7a2e9028275816cbf5ac06c 100644
--- a/core/modules/locale/locale.services.yml
+++ b/core/modules/locale/locale.services.yml
@@ -16,3 +16,7 @@ services:
     tags:
       - { name: string_translator }
       - { name: needs_destruction }
+  stream_wrapper.translations:
+    class: Drupal\locale\StreamWrapper\TranslationsStream
+    tags:
+      - { name: stream_wrapper, scheme: translations }
diff --git a/core/modules/locale/src/StreamWrapper/TranslationsStream.php b/core/modules/locale/src/StreamWrapper/TranslationsStream.php
new file mode 100644
index 0000000000000000000000000000000000000000..44c89577220d60a4da3d2884441137f8d2bd1763
--- /dev/null
+++ b/core/modules/locale/src/StreamWrapper/TranslationsStream.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\locale\StreamWrapper\TranslationStream.
+ */
+
+namespace Drupal\locale\StreamWrapper;
+
+use Drupal\Core\Annotation\StreamWrapper;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\StreamWrapper\LocalStream;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+
+/**
+ * Defines a Drupal translations (translations://) stream wrapper class.
+ *
+ * Provides support for storing translation files.
+ */
+class TranslationsStream extends LocalStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getType() {
+    return StreamWrapperInterface::LOCAL_HIDDEN;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Translation files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Translation files');
+  }
+
+  /**
+   * Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
+   */
+  function getDirectoryPath() {
+    return \Drupal::config('locale.settings')->get('translation.path');
+  }
+
+  /**
+   * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl().
+   * @throws \LogicException PO files URL should not be public.
+   */
+  function getExternalUrl() {
+    throw new \LogicException('PO files URL should not be public.');
+  }
+}
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 5534d7e4478768d5769cfbaaec31248f966104df..e281988b08424e76f9311a92606a7808ee5f956e 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -16,6 +16,7 @@
 use Drupal\Core\Language\Language;
 use Drupal\Core\Site\Settings;
 use Symfony\Component\DependencyInjection\Parameter;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -78,13 +79,11 @@ abstract class KernelTestBase extends TestBase {
   protected $keyValueFactory;
 
   /**
-   * A list of stream wrappers that have been registered for this test.
-   *
-   * @see \Drupal\simpletest\KernelTestBase::registerStreamWrapper()
+   * Array of registered stream wrappers.
    *
    * @var array
    */
-  private $streamWrappers = array();
+  protected $streamWrappers = array();
 
   /**
    * {@inheritdoc}
@@ -305,6 +304,12 @@ public function containerBuild(ContainerBuilder $container) {
       $container->getDefinition('password')->setArguments(array(1));
     }
 
+    // Register the stream wrapper manager.
+    $container
+      ->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager')
+      ->addArgument(new Reference('module_handler'))
+      ->addMethodCall('setContainer', array(new Reference('service_container')));
+
     $request = Request::create('/');
     $container->get('request_stack')->push($request);
   }
@@ -495,53 +500,10 @@ protected function disableModules(array $modules) {
    *   The fully qualified class name to register.
    * @param int $type
    *   The Drupal Stream Wrapper API type. Defaults to
-   *   STREAM_WRAPPERS_LOCAL_NORMAL.
+   *   StreamWrapperInterface::NORMAL.
    */
-  protected function registerStreamWrapper($scheme, $class, $type = STREAM_WRAPPERS_LOCAL_NORMAL) {
-    if (isset($this->streamWrappers[$scheme])) {
-      $this->unregisterStreamWrapper($scheme, $this->streamWrappers[$scheme]);
-    }
-    $this->streamWrappers[$scheme] = $type;
-    if (($type & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
-      stream_wrapper_register($scheme, $class);
-    }
-    else {
-      stream_wrapper_register($scheme, $class, STREAM_IS_URL);
-    }
-    // @todo Revamp Drupal's stream wrapper API for D8.
-    // @see https://drupal.org/node/2028109
-    $wrappers = &drupal_static('file_get_stream_wrappers', array());
-    $wrappers[STREAM_WRAPPERS_ALL][$scheme] = array(
-      'type' => $type,
-      'class' => $class,
-    );
-    if (($type & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) {
-      $wrappers[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[STREAM_WRAPPERS_ALL][$scheme];
-    }
-  }
-
-  /**
-   * Unregisters a stream wrapper previously registered by this test.
-   *
-   * KernelTestBase::tearDown() automatically cleans up all registered
-   * stream wrappers, so this usually does not have to be called manually.
-   *
-   * @param string $scheme
-   *   The scheme to unregister.
-   * @param int $type
-   *   The Drupal Stream Wrapper API type of the scheme to unregister.
-   */
-  protected function unregisterStreamWrapper($scheme, $type) {
-    stream_wrapper_unregister($scheme);
-    unset($this->streamWrappers[$scheme]);
-    // @todo Revamp Drupal's stream wrapper API for D8.
-    // @see https://drupal.org/node/2028109
-    $wrappers = &drupal_static('file_get_stream_wrappers', array());
-    foreach ($wrappers as $filter => $schemes) {
-      if (is_int($filter) && (($filter & $type) == $filter)) {
-        unset($wrappers[$filter][$scheme]);
-      }
-    }
+  protected function registerStreamWrapper($scheme, $class, $type = StreamWrapperInterface::NORMAL) {
+    $this->container->get('stream_wrapper_manager')->registerWrapper($scheme, $class, $type);
   }
 
   /**
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index b8bed094f1ee9ffc3748ae54b29c37d4dd8a394b..9475715b4f6f8c6d4f2cdf3d00ea725e953072a8 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -1063,10 +1063,7 @@ private function prepareEnvironment() {
     // - WebTestBase re-initializes Drupal stream wrappers after installation.
     // The original stream wrappers are restored after the test run.
     // @see TestBase::restoreEnvironment()
-    $wrappers = file_get_stream_wrappers();
-    foreach ($wrappers as $scheme => $info) {
-      stream_wrapper_unregister($scheme);
-    }
+    $this->originalContainer->get('stream_wrapper_manager')->unregister();
 
     // Reset statics.
     drupal_static_reset();
@@ -1188,6 +1185,12 @@ private function restoreEnvironment() {
     \Drupal::setContainer($this->originalContainer);
     $GLOBALS['config_directories'] = $this->originalConfigDirectories;
 
+    // Re-initialize original stream wrappers of the parent site.
+    // This must happen after static variables have been reset and the original
+    // container and $config_directories are restored, as simpletest_log_read()
+    // uses the public stream wrapper to locate the error.log.
+    $this->originalContainer->get('stream_wrapper_manager')->register();
+
     if (isset($this->originalPrefix)) {
       drupal_valid_test_ua($this->originalPrefix);
     }
@@ -1196,9 +1199,6 @@ private function restoreEnvironment() {
     }
     conf_path(TRUE, TRUE);
 
-    // Restore stream wrappers of the test runner.
-    file_get_stream_wrappers();
-
     // Restore original shutdown callbacks.
     $callbacks = &drupal_register_shutdown_function();
     $callbacks = $this->originalShutdownCallbacks;
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index bf05b7c6c5e1e2a5d7339268fbc7698c24eac99d..39aeabcfe800e13ccf892387a40604f5b433f677 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -738,7 +738,7 @@ public function getSessionName() {
    *
    * Installs Drupal with the installation profile specified in
    * \Drupal\simpletest\WebTestBase::$profile into the prefixed database.
-
+   *
    * Afterwards, installs any additional modules specified in the static
    * \Drupal\simpletest\WebTestBase::$modules property of each class in the
    * class hierarchy.
@@ -909,13 +909,17 @@ protected function setUp() {
     // Reset/rebuild all data structures after enabling the modules, primarily
     // to synchronize all data structures and caches between the test runner and
     // the child site.
-    // Affects e.g. file_get_stream_wrappers().
     // @see \Drupal\Core\DrupalKernel::bootCode()
     // @todo Test-specific setUp() methods may set up further fixtures; find a
     //   way to execute this after setUp() is done, or to eliminate it entirely.
     $this->resetAll();
     $this->kernel->prepareLegacyRequest($request);
 
+    // Explicitly call register() again on the container registered in \Drupal.
+    // @todo This should already be called through
+    //   DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that
+    //   appears to be calling a different container.
+    $this->container->get('stream_wrapper_manager')->register();
     // Temporary fix so that when running from run-tests.sh we don't get an
     // empty current path which would indicate we're on the home page.
     $path = current_path();
diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php
index 010b9c40a73c6f5d37cdee6bc0683ac709049ea3..7c8db2114b17b749c12e95c7fa422129c51aeca3 100644
--- a/core/modules/system/src/Form/FileSystemForm.php
+++ b/core/modules/system/src/Form/FileSystemForm.php
@@ -13,6 +13,8 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\StreamWrapper\PublicStream;
 use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -27,6 +29,13 @@ class FileSystemForm extends ConfigFormBase {
    */
   protected $dateFormatter;
 
+  /**
+   * The stream wrapper manager.
+   *
+   * @var \Drupal\Core\StreamWrapper\StreamWrapperManager
+   */
+  protected $streamWrapperManager;
+
   /**
    * Constructs a FileSystemForm object.
    *
@@ -34,10 +43,13 @@ class FileSystemForm extends ConfigFormBase {
    *   The factory for configuration objects.
    * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
    *   The date formatter service.
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManager $stream_wrapper_manager
+   *   The stream wrapper manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, DateFormatter $date_formatter) {
+  public function __construct(ConfigFactoryInterface $config_factory, DateFormatter $date_formatter, StreamWrapperManager $stream_wrapper_manager) {
     parent::__construct($config_factory);
     $this->dateFormatter = $date_formatter;
+    $this->streamWrapperManager = $stream_wrapper_manager;
   }
 
   /**
@@ -46,7 +58,8 @@ public function __construct(ConfigFactoryInterface $config_factory, DateFormatte
   public static function create(ContainerInterface $container) {
     return new static (
       $container->get('config.factory'),
-      $container->get('date.formatter')
+      $container->get('date.formatter'),
+      $container->get('stream_wrapper_manager')
     );
   }
 
@@ -89,9 +102,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     );
     // Any visible, writeable wrapper can potentially be used for the files
     // directory, including a remote file system that integrates with a CDN.
-    foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
-      $options[$scheme] = String::checkPlain($info['description']);
-    }
+    $options = $this->streamWrapperManager->getDescriptions(StreamWrapperInterface::WRITE_VISIBLE);
 
     if (!empty($options)) {
       $form['file_default_scheme'] = array(
diff --git a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
index 473972fb15b9a71d98927af2f90544f6461f0210..7d0a28c593ea17e0e517eac39b2c342caaca15ad 100644
--- a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
+++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\ImageToolkit\ImageToolkitBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 
 /**
  * Defines the GD2 toolkit for image manipulation within Drupal.
@@ -165,7 +166,7 @@ public function save($destination) {
     // Work around lack of stream wrapper support in imagejpeg() and imagepng().
     if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
       // If destination is not local, save image to temporary local file.
-      $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
+      $local_wrappers = file_get_stream_wrappers(StreamWrapperInterface::LOCAL);
       if (!isset($local_wrappers[$scheme])) {
         $permanent_destination = $destination;
         $destination = drupal_tempnam('temporary://', 'gd_');
diff --git a/core/modules/system/src/Tests/File/ReadOnlyStreamWrapperTest.php b/core/modules/system/src/Tests/File/ReadOnlyStreamWrapperTest.php
index c9cb68b80efc718a971d9462fb83eaeb7243c9c9..008e4ed5aee5f158ccb5341e1cc29a1b20a838d9 100644
--- a/core/modules/system/src/Tests/File/ReadOnlyStreamWrapperTest.php
+++ b/core/modules/system/src/Tests/File/ReadOnlyStreamWrapperTest.php
@@ -26,7 +26,7 @@ class ReadOnlyStreamWrapperTest extends FileTestBase {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyReadOnlyStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper';
 
   /**
    * Test write functionality of the read-only stream wrapper.
diff --git a/core/modules/system/src/Tests/File/RemoteFileDirectoryTest.php b/core/modules/system/src/Tests/File/RemoteFileDirectoryTest.php
index 5858fb97d1e8d4c926ab941677fb8157b499fd3c..7f9d9129a13bf7a199ee73b92e6fcd886384bf50 100644
--- a/core/modules/system/src/Tests/File/RemoteFileDirectoryTest.php
+++ b/core/modules/system/src/Tests/File/RemoteFileDirectoryTest.php
@@ -33,7 +33,7 @@ class RemoteFileDirectoryTest extends DirectoryTest {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyRemoteStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/src/Tests/File/RemoteFileScanDirectoryTest.php b/core/modules/system/src/Tests/File/RemoteFileScanDirectoryTest.php
index 2b21d32984b7a790f91c2a2547f5e3f2b5747270..cc57f90c37b5581b3603d54314739fedc4199748 100644
--- a/core/modules/system/src/Tests/File/RemoteFileScanDirectoryTest.php
+++ b/core/modules/system/src/Tests/File/RemoteFileScanDirectoryTest.php
@@ -33,7 +33,7 @@ class RemoteFileScanDirectoryTest extends ScanDirectoryTest {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyRemoteStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/src/Tests/File/RemoteFileUnmanagedCopyTest.php b/core/modules/system/src/Tests/File/RemoteFileUnmanagedCopyTest.php
index eadeea5ae75a4cc34e0dfa2acbb197494b437fc0..402196e6056fea30c5bca2480e46998eebad8fd0 100644
--- a/core/modules/system/src/Tests/File/RemoteFileUnmanagedCopyTest.php
+++ b/core/modules/system/src/Tests/File/RemoteFileUnmanagedCopyTest.php
@@ -33,7 +33,7 @@ class RemoteFileUnmanagedCopyTest extends UnmanagedCopyTest {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyRemoteStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteRecursiveTest.php b/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteRecursiveTest.php
index ef7019f8051e48943c83a7d1b1f5d4f4a3e7e7b8..c6bc98243517103e6f054edd39a4849798a0de39 100644
--- a/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteRecursiveTest.php
+++ b/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteRecursiveTest.php
@@ -33,7 +33,7 @@ class RemoteFileUnmanagedDeleteRecursiveTest extends UnmanagedDeleteRecursiveTes
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyRemoteStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteTest.php b/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteTest.php
index 38e6e8a7e3f62e56681d6417bc2f561496c95e50..1cfcd9257c1188194b9f61cf5c985df835f0d7e9 100644
--- a/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteTest.php
+++ b/core/modules/system/src/Tests/File/RemoteFileUnmanagedDeleteTest.php
@@ -33,7 +33,7 @@ class RemoteFileUnmanagedDeleteTest extends UnmanagedDeleteTest {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyRemoteStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/src/Tests/File/RemoteFileUnmanagedMoveTest.php b/core/modules/system/src/Tests/File/RemoteFileUnmanagedMoveTest.php
index a198f7502886a74ce870782cab5c904335de4fd8..ae495b91428e3355709151906548d1fd8e9f974c 100644
--- a/core/modules/system/src/Tests/File/RemoteFileUnmanagedMoveTest.php
+++ b/core/modules/system/src/Tests/File/RemoteFileUnmanagedMoveTest.php
@@ -33,7 +33,7 @@ class RemoteFileUnmanagedMoveTest extends UnmanagedMoveTest {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyRemoteStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/src/Tests/File/RemoteFileUnmanagedSaveDataTest.php b/core/modules/system/src/Tests/File/RemoteFileUnmanagedSaveDataTest.php
index 52d05ef6a91ce6c507e18f7d9dd8fe93383e24cf..73ebf4e984374f4c236a9ac96fb030ef3fb63cbf 100644
--- a/core/modules/system/src/Tests/File/RemoteFileUnmanagedSaveDataTest.php
+++ b/core/modules/system/src/Tests/File/RemoteFileUnmanagedSaveDataTest.php
@@ -33,7 +33,7 @@ class RemoteFileUnmanagedSaveDataTest extends UnmanagedSaveDataTest {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyRemoteStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/src/Tests/File/StreamWrapperTest.php b/core/modules/system/src/Tests/File/StreamWrapperTest.php
index 0c9721423c598a5f48f2f489f0c99378538f1ab6..e678f907ceb8c6a389383a720b559a7cce48e064 100644
--- a/core/modules/system/src/Tests/File/StreamWrapperTest.php
+++ b/core/modules/system/src/Tests/File/StreamWrapperTest.php
@@ -35,7 +35,7 @@ class StreamWrapperTest extends FileTestBase {
    *
    * @var string
    */
-  protected $classname = 'Drupal\file_test\DummyStreamWrapper';
+  protected $classname = 'Drupal\file_test\StreamWrapper\DummyStreamWrapper';
 
   /**
    * Test the getClassName() function.
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index cd2e6cf6ee574f28d4c85f30aee41581dff49c45..d9c44f47934ca0f93b80de0958028646c2e5e163 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -1327,81 +1327,11 @@ function hook_modules_uninstalled($modules) {
   mymodule_cache_rebuild();
 }
 
-/**
- * Registers PHP stream wrapper implementations associated with a module.
- *
- * Provide a facility for managing and querying user-defined stream wrappers
- * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
- * registered to handle a stream, which we need to be able to find the handler
- * for class instantiation.
- *
- * If a module registers a scheme that is already registered with PHP, it will
- * be unregistered and replaced with the specified class.
- *
- * @return
- *   A nested array, keyed first by scheme name ("public" for "public://"),
- *   then keyed by the following values:
- *   - 'name' A short string to name the wrapper.
- *   - 'class' A string specifying the PHP class that implements the
- *     Drupal\Core\StreamWrapper\StreamWrapperInterface interface.
- *   - 'description' A string with a short description of what the wrapper does.
- *   - 'type' (Optional) A bitmask of flags indicating what type of streams this
- *     wrapper will access - local or remote, readable and/or writeable, etc.
- *     Many shortcut constants are defined in file.inc. Defaults to
- *     STREAM_WRAPPERS_NORMAL which includes all of these bit flags:
- *     - STREAM_WRAPPERS_READ
- *     - STREAM_WRAPPERS_WRITE
- *     - STREAM_WRAPPERS_VISIBLE
- *
- * @see file_get_stream_wrappers()
- * @see hook_stream_wrappers_alter()
- * @see system_stream_wrappers()
- */
-function hook_stream_wrappers() {
-  return array(
-    'public' => array(
-      'name' => t('Public files'),
-      'class' => 'Drupal\Core\StreamWrapper\PublicStream',
-      'description' => t('Public local files served by the webserver.'),
-      'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
-    ),
-    'private' => array(
-      'name' => t('Private files'),
-      'class' => 'Drupal\Core\StreamWrapper\PrivateStream',
-      'description' => t('Private local files served by Drupal.'),
-      'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
-    ),
-    'temp' => array(
-      'name' => t('Temporary files'),
-      'class' => 'Drupal\Core\StreamWrapper\TemporaryStream',
-      'description' => t('Temporary local files for upload and previews.'),
-      'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
-    ),
-    'cdn' => array(
-      'name' => t('Content delivery network files'),
-      // @todo: Fix the name of this class when we decide on module PSR-0 usage.
-      'class' => 'MyModuleCDNStream',
-      'description' => t('Files served by a content delivery network.'),
-      // 'type' can be omitted to use the default of STREAM_WRAPPERS_NORMAL
-    ),
-    'youtube' => array(
-      'name' => t('YouTube video'),
-      // @todo: Fix the name of this class when we decide on module PSR-0 usage.
-      'class' => 'MyModuleYouTubeStream',
-      'description' => t('Video streamed from YouTube.'),
-      // A module implementing YouTube integration may decide to support using
-      // the YouTube API for uploading video, but here, we assume that this
-      // particular module only supports playing YouTube video.
-      'type' => STREAM_WRAPPERS_READ_VISIBLE,
-    ),
-  );
-}
-
 /**
  * Alters the list of PHP stream wrapper implementations.
  *
  * @see file_get_stream_wrappers()
- * @see hook_stream_wrappers()
+ * @see \Drupal\Core\StreamWrapper\StreamWrapperManager
  */
 function hook_stream_wrappers_alter(&$wrappers) {
   // Change the name of private files to reflect the performance.
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 26a6e902cad9f5745a2f30d609f94240737cfe94..9376013c362b09b4cd241cd3834aacd0ec3a7912 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -303,35 +303,13 @@ function system_theme_suggestions_field(array $variables) {
 }
 
 /**
- * Implements hook_stream_wrappers().
+ * Implements hook_stream_wrappers_alter().
  */
-function system_stream_wrappers() {
-  $wrappers = array(
-    'public' => array(
-      'name' => new TranslationWrapper('Public files'),
-      'class' => 'Drupal\Core\StreamWrapper\PublicStream',
-      'description' => new TranslationWrapper('Public local files served by the webserver.'),
-      'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
-    ),
-    'temporary' => array(
-      'name' => new TranslationWrapper('Temporary files'),
-      'class' => 'Drupal\Core\StreamWrapper\TemporaryStream',
-      'description' => new TranslationWrapper('Temporary local files for upload and previews.'),
-      'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
-    ),
-  );
-
+function system_stream_wrappers_alter(&$wrappers) {
   // Only register the private file stream wrapper if a file path has been set.
-  if (\Drupal::config('system.file')->get('path.private')) {
-    $wrappers['private'] = array(
-      'name' => new TranslationWrapper('Private files'),
-      'class' => 'Drupal\Core\StreamWrapper\PrivateStream',
-      'description' => new TranslationWrapper('Private local files served by Drupal.'),
-      'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
-    );
+  if (!\Drupal::config('system.file')->get('path.private')) {
+    unset($wrappers['private']);
   }
-
-  return $wrappers;
 }
 
 /**