diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
index 892f0691ae97d889bb72915d88b6d6400b6c8c98..64709439b86dc4bb815d03059d31f6536b4627ce 100644
--- a/core/lib/Drupal/Component/Utility/Html.php
+++ b/core/lib/Drupal/Component/Utility/Html.php
@@ -366,7 +366,8 @@ public static function decodeEntities($text) {
    * - < (less than) becomes &lt;
    * - > (greater than) becomes &gt;
    * Special characters that have already been escaped will be double-escaped
-   * (for example, "&lt;" becomes "&amp;lt;").
+   * (for example, "&lt;" becomes "&amp;lt;"), and invalid UTF-8 encoding
+   * will be converted to the Unicode replacement character ("�").
    *
    * This method is not the opposite of Html::decodeEntities(). For example,
    * this method will not encode "é" to "&eacute;", whereas
@@ -385,7 +386,7 @@ public static function decodeEntities($text) {
    * @ingroup sanitization
    */
   public static function escape($text) {
-    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
+    return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
index a8f261404c13ad8bb7a31e274ef7943b0adf9d5d..178a662568487802af9d5b624124ba689013d53c 100644
--- a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
@@ -288,6 +288,7 @@ public function providerEscape() {
       array('→', '→'),
       array('➼', '➼'),
       array('€', '€'),
+      array('Drup�al', "Drup\x80al"),
     );
   }
 
diff --git a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
index bfa4cb3733a2db3219a1224b8dd82fb35d6c9892..2d8e0ff3d267190a1a5dbf0ea316a313ef1fe13a 100644
--- a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
@@ -46,11 +46,11 @@ public function testSet($text, $message) {
    * @see testSet()
    */
   public function providerSet() {
-    // Checks that invalid multi-byte sequences are rejected.
-    $tests[] = array("Foo\xC0barbaz", '', 'SafeMarkup::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE);
-    $tests[] = array("Fooÿñ", 'SafeMarkup::set() accepts valid sequence "Fooÿñ"');
-    $tests[] = array(new TextWrapper("Fooÿñ"), 'SafeMarkup::set() accepts valid sequence "Fooÿñ" in an object implementing __toString()');
-    $tests[] = array("<div>", 'SafeMarkup::set() accepts HTML');
+    // Checks that invalid multi-byte sequences are escaped.
+    $tests[] = array("Foo\xC0barbaz", 'Foo�barbaz', 'Invalid sequence "Foo\xC0barbaz" is escaped', TRUE);
+    $tests[] = array("Fooÿñ", 'SafeMarkup::set() does not escape valid sequence "Fooÿñ"');
+    $tests[] = array(new TextWrapper("Fooÿñ"), 'SafeMarkup::set() does not escape valid sequence "Fooÿñ" in an object implementing __toString()');
+    $tests[] = array("<div>", 'SafeMarkup::set() does not escape HTML');
 
     return $tests;
   }
@@ -141,10 +141,10 @@ function testCheckPlain($text, $expected, $message, $ignorewarnings = FALSE) {
    * @see testCheckPlain()
    */
   function providerCheckPlain() {
-    // Checks that invalid multi-byte sequences are rejected.
-    $tests[] = array("Foo\xC0barbaz", '', 'SafeMarkup::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE);
-    $tests[] = array("\xc2\"", '', 'SafeMarkup::checkPlain() rejects invalid sequence "\xc2\""', TRUE);
-    $tests[] = array("Fooÿñ", "Fooÿñ", 'SafeMarkup::checkPlain() accepts valid sequence "Fooÿñ"');
+    // Checks that invalid multi-byte sequences are escaped.
+    $tests[] = array("Foo\xC0barbaz", 'Foo�barbaz', 'SafeMarkup::checkPlain() escapes invalid sequence "Foo\xC0barbaz"', TRUE);
+    $tests[] = array("\xc2\"", '�&quot;', 'SafeMarkup::checkPlain() escapes invalid sequence "\xc2\""', TRUE);
+    $tests[] = array("Fooÿñ", "Fooÿñ", 'SafeMarkup::checkPlain() does not escape valid sequence "Fooÿñ"');
 
     // Checks that special characters are escaped.
     $tests[] = array("<script>", '&lt;script&gt;', 'SafeMarkup::checkPlain() escapes &lt;script&gt;');
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityListBuilderTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityListBuilderTest.php
index 8dc339eb0ea9db3c0cc7fb8b163a5b680986467e..b76ce384a38242fa64de3e40b234bf9bdb04fe7a 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityListBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityListBuilderTest.php
@@ -182,10 +182,10 @@ public function testBuildRow($input, $expected, $message, $ignorewarnings = FALS
    */
   public function providerTestBuildRow() {
     $tests = array();
-    // Checks that invalid multi-byte sequences are rejected.
-    $tests[] = array("Foo\xC0barbaz", '', 'EntityTestListBuilder::buildRow() rejects invalid sequence "Foo\xC0barbaz"', TRUE);
-    $tests[] = array("\xc2\"", '', 'EntityTestListBuilder::buildRow() rejects invalid sequence "\xc2\""', TRUE);
-    $tests[] = array("Fooÿñ", "Fooÿñ", 'EntityTestListBuilder::buildRow() accepts valid sequence "Fooÿñ"');
+    // Checks that invalid multi-byte sequences are escaped.
+    $tests[] = array("Foo\xC0barbaz", 'Foo�barbaz', 'EntityTestListBuilder::buildRow() escapes invalid sequence "Foo\xC0barbaz"', TRUE);
+    $tests[] = array("\xc2\"", '�&quot;', 'EntityTestListBuilder::buildRow escapes invalid sequence "\xc2\""', TRUE);
+    $tests[] = array("Fooÿñ", "Fooÿñ", 'EntityTestListBuilder::buildR does not escape valid sequence "Fooÿñ"');
 
     // Checks that special characters are escaped.
     $tests[] = array("<script>", '&lt;script&gt;', 'EntityTestListBuilder::buildRow() escapes &lt;script&gt;');