diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
index 892f069..9259c93 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 a8f2614..a10e929 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\x80al", 'Drup�al'),
     );
   }
 
