diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 0958c3a..77fd3a2 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -47,14 +47,7 @@ function _batch_page(Request $request) {
       return new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE]));
     }
   }
-  // Restore safe strings from previous batches.
-  // This is safe because we are passing through the known safe values from
-  // SafeMarkup::getAll(). See _batch_shutdown().
-  // @todo Ensure we are not storing an excessively large string list in:
-  //   https://www.drupal.org/node/2295823
-  if (!empty($batch['safe_strings'])) {
-    SafeMarkup::setMultiple($batch['safe_strings']);
-  }
+
   // Register database update for the end of processing.
   drupal_register_shutdown_function('_batch_shutdown');
 
@@ -521,10 +514,6 @@ function _batch_finished() {
  */
 function _batch_shutdown() {
   if ($batch = batch_get()) {
-    // Update safe strings.
-    // @todo Ensure we are not storing an excessively large string list in:
-    //   https://www.drupal.org/node/2295823
-    $batch['safe_strings'] = SafeMarkup::getAll();
     \Drupal::service('batch.storage')->update($batch);
   }
 }
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 16353d8..9feb1ac 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -854,11 +854,6 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
         $request->query->remove('destination');
       }
 
-      // Store safe strings.
-      // @todo Ensure we are not storing an excessively large string list in:
-      //   https://www.drupal.org/node/2295823
-      $batch['safe_strings'] = SafeMarkup::getAll();
-
       // Store the batch.
       \Drupal::service('batch.storage')->create($batch);
 
diff --git a/core/lib/Drupal/Component/Utility/EscapedString.php b/core/lib/Drupal/Component/Utility/EscapedString.php
new file mode 100644
index 0000000..559350a
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/EscapedString.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Component\Utility\EscapedString.
+ */
+
+namespace Drupal\Component\Utility;
+
+/**
+ * Escapes a string for HTML display.
+ *
+ * @ingroup sanitization
+ *
+ * @deprecated Will be removed before Drupal 9.0.0. Rely on Twig's
+ *   auto-escaping feature, or use the @link theme_render #plain_text @endlink
+ *   key when constructing a render array that contains plain text in order to
+ *   use the renderer's auto-escaping feature. If neither of these are
+ *   possible, \Drupal\Component\Utility\Html::escape() can be used in places
+ *   where explicit escaping is needed.
+ */
+class EscapedString implements SafeStringInterface {
+  use SafeStringTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return Html::escape($this->string);
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index a8c9576..d4ecdf8 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -31,19 +31,6 @@
  * @see theme_render
  */
 class SafeMarkup {
-  use PlaceholderTrait;
-
-  /**
-   * The list of safe strings.
-   *
-   * Strings in this list are marked as secure for the entire page render, not
-   * just the code or element that set it. Therefore, only valid HTML should be
-   * marked as safe (never partial markup). For example, you should never mark
-   * string such as '<' or '<script>' safe.
-   *
-   * @var array
-   */
-  protected static $safeStrings = array();
 
   /**
    * Checks if a string is safe to output.
@@ -51,82 +38,19 @@ class SafeMarkup {
    * @param string|\Drupal\Component\Utility\SafeStringInterface $string
    *   The content to be checked.
    * @param string $strategy
-   *   The escaping strategy. Defaults to 'html'. Two escaping strategies are
-   *   supported by default:
-   *   - 'html': (default) The string is safe for use in HTML code.
-   *   - 'all': The string is safe for all use cases.
-   *   See the
-   *   @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
-   *   for more information on escaping strategies in Twig.
+   *   (optional) This value is ignored.
    *
    * @return bool
    *   TRUE if the string has been marked secure, FALSE otherwise.
+   *
+   * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
+   *   Check if an object implements
+   *   \Drupal\Component\Utility\SafeStringInterface instead.
    */
   public static function isSafe($string, $strategy = 'html') {
     // Do the instanceof checks first to save unnecessarily casting the object
     // to a string.
-    return $string instanceOf SafeStringInterface || isset(static::$safeStrings[(string) $string][$strategy]) ||
-      isset(static::$safeStrings[(string) $string]['all']);
-  }
-
-  /**
-   * Adds previously retrieved known safe strings to the safe string list.
-   *
-   * This method is for internal use. Do not use it to prevent escaping of
-   * markup; instead, use the appropriate
-   * @link sanitization sanitization functions @endlink or the
-   * @link theme_render theme and render systems @endlink so that the output
-   * can be themed, escaped, and altered properly.
-   *
-   * This marks strings as secure for the entire page render, not just the code
-   * or element that set it. Therefore, only valid HTML should be
-   * marked as safe (never partial markup). For example, you should never do:
-   * @code
-   *   SafeMarkup::setMultiple(['<' => ['html' => TRUE]]);
-   * @endcode
-   * or:
-   * @code
-   *   SafeMarkup::setMultiple(['<script>' => ['all' => TRUE]]);
-   * @endcode
-
-   * @param array $safe_strings
-   *   A list of safe strings as previously retrieved by self::getAll().
-   *   Every string in this list will be represented by a multidimensional
-   *   array in which the keys are the string and the escaping strategy used for
-   *   this string, and in which the value is the boolean TRUE.
-   *   See self::isSafe() for the list of supported escaping strategies.
-   *
-   * @throws \UnexpectedValueException
-   *
-   * @internal This is called by FormCache, StringTranslation and the Batch API.
-   *   It should not be used anywhere else.
-   */
-  public static function setMultiple(array $safe_strings) {
-    foreach ($safe_strings as $string => $strategies) {
-      foreach ($strategies as $strategy => $value) {
-        $string = (string) $string;
-        if ($value === TRUE) {
-          static::$safeStrings[$string][$strategy] = TRUE;
-        }
-        else {
-          // Danger - something is very wrong.
-          throw new \UnexpectedValueException('Only the value TRUE is accepted for safe strings');
-        }
-      }
-    }
-  }
-
-  /**
-  * Gets all strings currently marked as safe.
-  *
-  * This is useful for the batch and form APIs, where it is important to
-  * preserve the safe markup state across page requests.
-  *
-  * @return array
-  *   An array of strings currently marked safe.
-  */
-  public static function getAll() {
-    return static::$safeStrings;
+    return $string instanceOf SafeStringInterface;
   }
 
   /**
@@ -138,13 +62,12 @@ public static function getAll() {
    * @param string $text
    *   The text to be checked or processed.
    *
-   * @return string
-   *   An HTML safe version of $text, or an empty string if $text is not valid
-   *   UTF-8.
+   * @return \Drupal\Component\Utility\EscapedString
+   *   A SafeStringInterface object that escapes when rendered to string.
    *
    * @ingroup sanitization
    *
-   * @deprecated Will be removed before Drupal 8.0.0. Rely on Twig's
+   * @deprecated Will be removed before Drupal 9.0.0. Rely on Twig's
    *   auto-escaping feature, or use the @link theme_render #plain_text @endlink
    *   key when constructing a render array that contains plain text in order to
    *   use the renderer's auto-escaping feature. If neither of these are
@@ -154,9 +77,7 @@ public static function getAll() {
    * @see drupal_validate_utf8()
    */
   public static function checkPlain($text) {
-    $string = Html::escape($text);
-    static::$safeStrings[$string]['html'] = TRUE;
-    return $string;
+    return EscapedString::create($text);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Form/FormCache.php b/core/lib/Drupal/Core/Form/FormCache.php
index 5c8c14f..d83d0f9 100644
--- a/core/lib/Drupal/Core/Form/FormCache.php
+++ b/core/lib/Drupal/Core/Form/FormCache.php
@@ -169,13 +169,6 @@ protected function loadCachedFormState($form_build_id, FormStateInterface $form_
           require_once $this->root . '/' . $file;
         }
       }
-      // Retrieve the list of safe strings and store it for this request. The
-      // safety of these strings has already been determined in ::setCache().
-      // @todo Ensure we are not storing an excessively large string list
-      //   in: https://www.drupal.org/node/2295823
-      $build_info += ['safe_strings' => []];
-      SafeMarkup::setMultiple($build_info['safe_strings']);
-      unset($build_info['safe_strings']);
       $form_state->setBuildInfo($build_info);
     }
   }
@@ -206,11 +199,6 @@ public function setCache($form_build_id, $form, FormStateInterface $form_state)
       $this->keyValueExpirableFactory->get('form')->setWithExpire($form_build_id, $form, $expire);
     }
 
-    // Store the known list of safe strings for form re-use.
-    // @todo Ensure we are not storing an excessively large string list in:
-    //   https://www.drupal.org/node/2295823
-    $form_state->addBuildInfo('safe_strings', SafeMarkup::getAll());
-
     if ($data = $form_state->getCacheableArray()) {
       $this->keyValueExpirableFactory->get('form_state')->setWithExpire($form_build_id, $data, $expire);
     }
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
index 2e496b8..31169cd 100644
--- a/core/phpunit.xml.dist
+++ b/core/phpunit.xml.dist
@@ -46,8 +46,6 @@
   <listeners>
     <listener class="\Drupal\Tests\Listeners\DrupalStandardsListener">
     </listener>
-    <listener class="\Drupal\Tests\Listeners\SafeMarkupSideEffects">
-    </listener>
   </listeners>
   <!-- Filter for coverage reports. -->
   <filter>
diff --git a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
index 12f2012..91f0394 100644
--- a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
@@ -30,85 +30,6 @@ protected function tearDown() {
     UrlHelper::setAllowedProtocols(['http', 'https']);
   }
 
-
-  /**
-   * Helper function to add a string to the safe list for testing.
-   *
-   * @param string $string
-   *   The content to be marked as secure.
-   * @param string $strategy
-   *   The escaping strategy used for this string. Two values are supported
-   *   by default:
-   *   - 'html': (default) The string is safe for use in HTML code.
-   *   - 'all': The string is safe for all use cases.
-   *   See the
-   *   @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
-   *   for more information on escaping strategies in Twig.
-   *
-   * @return string
-   *   The input string that was marked as safe.
-   */
-  protected function safeMarkupSet($string, $strategy = 'html') {
-    $reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
-    $reflected_property = $reflected_class->getProperty('safeStrings');
-    $reflected_property->setAccessible(true);
-    $current_value = $reflected_property->getValue();
-    $current_value[$string][$strategy] = TRUE;
-    $reflected_property->setValue($current_value);
-    return $string;
-  }
-
-  /**
-   * Tests SafeMarkup::isSafe() with different providers.
-   *
-   * @covers ::isSafe
-   */
-  public function testStrategy() {
-    $returned = $this->safeMarkupSet('string0', 'html');
-    $this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "html" provider is safe for default (html)');
-    $returned = $this->safeMarkupSet('string1', 'all');
-    $this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "all" provider is safe for default (html)');
-    $returned = $this->safeMarkupSet('string2', 'css');
-    $this->assertFalse(SafeMarkup::isSafe($returned), 'String set with "css" provider is not safe for default (html)');
-    $returned = $this->safeMarkupSet('string3');
-    $this->assertFalse(SafeMarkup::isSafe($returned, 'all'), 'String set with "html" provider is not safe for "all"');
-  }
-
-  /**
-   * Data provider for testSet().
-   */
-  public function providerSet() {
-    // Checks that invalid multi-byte sequences are escaped.
-    $tests[] = array(
-      'Foo�barbaz',
-      'SafeMarkup::setMarkup() functions with valid sequence "Foo�barbaz"',
-      TRUE
-    );
-    $tests[] = array(
-      "Fooÿñ",
-      'SafeMarkup::setMarkup() functions with valid sequence "Fooÿñ"'
-    );
-    $tests[] = array("<div>", 'SafeMarkup::setMultiple() does not escape HTML');
-
-    return $tests;
-  }
-
-  /**
-   * Tests SafeMarkup::setMultiple().
-   * @dataProvider providerSet
-   *
-   * @param string $text
-   *   The text or object to provide to SafeMarkup::setMultiple().
-   * @param string $message
-   *   The message to provide as output for the test.
-   *
-   * @covers ::setMultiple
-   */
-  public function testSet($text, $message) {
-    SafeMarkup::setMultiple([$text => ['html' => TRUE]]);
-    $this->assertTrue(SafeMarkup::isSafe($text), $message);
-  }
-
   /**
    * Tests SafeMarkup::isSafe() with different objects.
    *
@@ -122,38 +43,6 @@ public function testIsSafe() {
   }
 
   /**
-   * Tests SafeMarkup::setMultiple().
-   *
-   * @covers ::setMultiple
-   */
-  public function testSetMultiple() {
-    $texts = array(
-      'multistring0' => array('html' => TRUE),
-      'multistring1' => array('all' => TRUE),
-    );
-    SafeMarkup::setMultiple($texts);
-    foreach ($texts as $string => $providers) {
-      $this->assertTrue(SafeMarkup::isSafe($string), 'The value has been marked as safe for html');
-    }
-  }
-
-  /**
-   * Tests SafeMarkup::setMultiple().
-   *
-   * Only TRUE may be passed in as the value.
-   *
-   * @covers ::setMultiple
-   *
-   * @expectedException \UnexpectedValueException
-   */
-  public function testInvalidSetMultiple() {
-    $texts = array(
-      'invalidstring0' => array('html' => 1),
-    );
-    SafeMarkup::setMultiple($texts);
-  }
-
-  /**
    * Tests SafeMarkup::checkPlain().
    *
    * @dataProvider providerCheckPlain
@@ -270,8 +159,7 @@ public function __toString() {
 /**
  * Marks text as safe.
  *
- * SafeMarkupTestSafeString is used to mark text as safe because
- * SafeMarkup::$safeStrings is a global static that affects all tests.
+ * SafeMarkupTestSafeString is used to mark text as safe.
  */
 class SafeMarkupTestSafeString implements SafeStringInterface {
   use SafeStringTrait;
diff --git a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
index 156a125..c6032f0 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
@@ -322,34 +322,6 @@ public function testLoadCachedFormStateWithFiles() {
   }
 
   /**
-   * @covers ::loadCachedFormState
-   */
-  public function testLoadCachedFormStateWithSafeStrings() {
-    $this->assertEmpty(SafeMarkup::getAll());
-    $form_build_id = 'the_form_build_id';
-    $form_state = new FormState();
-    $cached_form = ['#cache_token' => NULL];
-
-    $this->formCacheStore->expects($this->once())
-      ->method('get')
-      ->with($form_build_id)
-      ->willReturn($cached_form);
-    $this->account->expects($this->once())
-      ->method('isAnonymous')
-      ->willReturn(TRUE);
-
-    $cached_form_state = ['build_info' => ['safe_strings' => [
-      'a_safe_string' => ['html' => TRUE],
-    ]]];
-    $this->formStateCacheStore->expects($this->once())
-      ->method('get')
-      ->with($form_build_id)
-      ->willReturn($cached_form_state);
-
-    $this->formCache->getCache($form_build_id, $form_state);
-  }
-
-  /**
    * @covers ::setCache
    */
   public function testSetCacheWithForm() {
@@ -364,7 +336,6 @@ public function testSetCacheWithForm() {
       ->with($form_build_id, $form, $this->isType('int'));
 
     $form_state_data = $form_state->getCacheableArray();
-    $form_state_data['build_info']['safe_strings'] = [];
     $this->formStateCacheStore->expects($this->once())
       ->method('setWithExpire')
       ->with($form_build_id, $form_state_data, $this->isType('int'));
@@ -384,7 +355,6 @@ public function testSetCacheWithoutForm() {
       ->method('setWithExpire');
 
     $form_state_data = $form_state->getCacheableArray();
-    $form_state_data['build_info']['safe_strings'] = [];
     $this->formStateCacheStore->expects($this->once())
       ->method('setWithExpire')
       ->with($form_build_id, $form_state_data, $this->isType('int'));
@@ -408,7 +378,6 @@ public function testSetCacheAuthUser() {
       ->with($form_build_id, $form_data, $this->isType('int'));
 
     $form_state_data = $form_state->getCacheableArray();
-    $form_state_data['build_info']['safe_strings'] = [];
     $this->formStateCacheStore->expects($this->once())
       ->method('setWithExpire')
       ->with($form_build_id, $form_state_data, $this->isType('int'));
@@ -426,34 +395,6 @@ public function testSetCacheAuthUser() {
   /**
    * @covers ::setCache
    */
-  public function testSetCacheWithSafeStrings() {
-    SafeMarkup::setMultiple([
-      'a_safe_string' => ['html' => TRUE],
-    ]);
-    $form_build_id = 'the_form_build_id';
-    $form = [
-      '#form_id' => 'the_form_id'
-    ];
-    $form_state = new FormState();
-
-    $this->formCacheStore->expects($this->once())
-      ->method('setWithExpire')
-      ->with($form_build_id, $form, $this->isType('int'));
-
-    $form_state_data = $form_state->getCacheableArray();
-    $form_state_data['build_info']['safe_strings'] = [
-      'a_safe_string' => ['html' => TRUE],
-    ];
-    $this->formStateCacheStore->expects($this->once())
-      ->method('setWithExpire')
-      ->with($form_build_id, $form_state_data, $this->isType('int'));
-
-    $this->formCache->setCache($form_build_id, $form, $form_state);
-  }
-
-  /**
-   * @covers ::setCache
-   */
   public function testSetCacheBuildIdMismatch() {
     $form_build_id = 'the_form_build_id';
     $form = [
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 4d0dec5..c39e045 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -36,18 +36,6 @@ class RendererTest extends RendererTestBase {
   ];
 
   /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    // Reset the static list of SafeStrings to prevent bleeding between tests.
-    $reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
-    $reflected_property = $reflected_class->getProperty('safeStrings');
-    $reflected_property->setAccessible(true);
-    $reflected_property->setValue([]);
-  }
-
-  /**
    * @covers ::render
    * @covers ::doRender
    *
diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
index 3e78e91..6d9fa27 100644
--- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
+++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Render\RenderableInterface;
 use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Render\SafeString;
 use Drupal\Core\Template\TwigEnvironment;
 use Drupal\Core\Template\TwigExtension;
 use Drupal\Tests\UnitTestCase;
@@ -139,14 +140,10 @@ public function testSafeJoin() {
     $twig_extension = new TwigExtension($renderer);
     $twig_environment = $this->prophesize(TwigEnvironment::class)->reveal();
 
-
-    // Simulate t().
-    $string = '<em>will be markup</em>';
-    SafeMarkup::setMultiple([$string => ['html' => TRUE]]);
-
     $items = [
       '<em>will be escaped</em>',
-      $string,
+      // Simulate t().
+      SafeString::create('<em>will be markup</em>'),
       ['#markup' => '<strong>will be rendered</strong>']
     ];
     $result = $twig_extension->safeJoin($twig_environment, $items, '<br/>');
diff --git a/core/tests/Drupal/Tests/Listeners/SafeMarkupSideEffects.php b/core/tests/Drupal/Tests/Listeners/SafeMarkupSideEffects.php
deleted file mode 100644
index 54939cb..0000000
--- a/core/tests/Drupal/Tests/Listeners/SafeMarkupSideEffects.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Tests\Listeners\SafeMarkupSideEffects.
- *
- * Listener for PHPUnit tests, to enforce that data providers don't add to the
- * SafeMarkup static safe string list.
- */
-
-namespace Drupal\Tests\Listeners;
-
-use Drupal\Component\Utility\SafeMarkup;
-
-/**
- * Listens for PHPUnit tests and fails those with SafeMarkup side effects.
- */
-class SafeMarkupSideEffects extends \PHPUnit_Framework_BaseTestListener {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) {
-    // Use a static so we only do this test once after all the data providers
-    // have run.
-    static $tested = FALSE;
-    if ($suite->getName() !== '' && !$tested) {
-      $tested = TRUE;
-      if (!empty(SafeMarkup::getAll())) {
-        throw new \RuntimeException('SafeMarkup string list polluted by data providers');
-      }
-    }
-  }
-
-}
diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php
index 9d805be..38045f9 100644
--- a/core/tests/Drupal/Tests/UnitTestCase.php
+++ b/core/tests/Drupal/Tests/UnitTestCase.php
@@ -54,12 +54,6 @@ protected function setUp() {
     FileCacheFactory::setConfiguration(['default' => ['class' => '\Drupal\Component\FileCache\NullFileCache']]);
 
     $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
-
-    // Reset the static list of SafeStrings to prevent bleeding between tests.
-    $reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
-    $reflected_property = $reflected_class->getProperty('safeStrings');
-    $reflected_property->setAccessible(true);
-    $reflected_property->setValue([]);
   }
 
   /**
