diff --git a/app/code/Magento/Catalog/Block/Product/View/Attributes.php b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
index fbdda684343b583da89fa7f75869d9d5297e8cdb..32c1c1b6d7a610068ec5b13d4219a318929cb756 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Attributes.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
@@ -81,8 +81,9 @@ class Attributes extends \Magento\Framework\View\Element\Template
         $attributes = $product->getAttributes();
         foreach ($attributes as $attribute) {
             if ($attribute->getIsVisibleOnFront() && !in_array($attribute->getAttributeCode(), $excludeAttr)) {
-                $value = $attribute->getFrontend()->getValue($product);
-
+                if (is_array($value = $attribute->getFrontend()->getValue($product))) {
+                    continue;
+                }
                 if (!$product->hasData($attribute->getAttributeCode())) {
                     $value = __('N/A');
                 } elseif ((string)$value == '') {
@@ -90,7 +91,6 @@ class Attributes extends \Magento\Framework\View\Element\Template
                 } elseif ($attribute->getFrontendInput() == 'price' && is_string($value)) {
                     $value = $this->priceCurrency->convertAndFormat($value);
                 }
-
                 if ($value instanceof Phrase || (is_string($value) && strlen($value))) {
                     $data[$attribute->getAttributeCode()] = [
                         'label' => __($attribute->getStoreLabel()),
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml
index a0041d2e02988122430ba1558f4fdb1523d9f6a9..6ff0e193a774f4b1878a289518a971e698ea3347 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml
@@ -88,7 +88,9 @@ $stores = $block->getStoresSortedBySortOrder();
     $values = [];
     foreach($block->getOptionValues() as $value) {
         $value = $value->getData();
-        $values[] = is_array($value) ? array_map("htmlspecialchars_decode", $value) : $value;
+        $values[] = is_array($value) ? array_map(function($str) {
+            return htmlspecialchars_decode($str, ENT_QUOTES);
+        }, $value) : $value;
     }
     ?>
     <script type="text/x-magento-init">
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml
index 79dc8591fd724f62e3cf57dadcd6e29e9a798e02..11aedc33c2d423a932b8339695663544cbbe55a1 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml
@@ -29,6 +29,7 @@ $class = ($_option->getIsRequire()) ? ' required' : '';
             if ($_option->getMaxCharacters()) {
                 $_textValidate['maxlength'] = $_option->getMaxCharacters();
             }
+            $_textValidate['validate-no-utf8mb4-characters'] = true;
             ?>
             <input type="text"
                    id="options_<?= /* @escapeNotVerified */ $_option->getId() ?>_text"
@@ -47,6 +48,7 @@ $class = ($_option->getIsRequire()) ? ' required' : '';
             if ($_option->getMaxCharacters()) {
                 $_textAreaValidate['maxlength'] = $_option->getMaxCharacters();
             }
+            $_textAreaValidate['validate-no-utf8mb4-characters'] = true;
             ?>
             <textarea id="options_<?= /* @escapeNotVerified */ $_option->getId() ?>_text"
                       class="product-custom-option"
diff --git a/app/code/Magento/Theme/Model/Design/Config/Storage.php b/app/code/Magento/Theme/Model/Design/Config/Storage.php
index a73f70efa0adf7f20716b7d8e73577f174590f6c..c97114f963a09bbf29458264de8e9d0534313c5a 100644
--- a/app/code/Magento/Theme/Model/Design/Config/Storage.php
+++ b/app/code/Magento/Theme/Model/Design/Config/Storage.php
@@ -87,10 +87,13 @@ class Storage
                 $scopeId,
                 $fieldData->getFieldConfig()
             );
-            if ($value !== null) {
-                $fieldData->setValue($value);
+
+            if ($value === null) {
+                $value = '';
             }
+            $fieldData->setValue($value);
         }
+
         return $designConfig;
     }
 
diff --git a/dev/tests/integration/testsuite/Magento/Theme/Model/Design/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Theme/Model/Design/ConfigTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ff43a5fd41cae96de8a7a226c435a1057ff695e
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Theme/Model/Design/ConfigTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Theme\Model\Design;
+
+/**
+ * Test for \Magento\Theme\Model\Design\Config\Storage.
+ */
+class ConfigTest extends \PHPUnit\Framework\TestCase
+{
+    /**
+     * @var \Magento\Theme\Model\Design\Config\Storage
+     */
+    private $storage;
+
+    protected function setUp()
+    {
+        $this->storage = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            \Magento\Theme\Model\Design\Config\Storage::class
+        );
+    }
+
+    /**
+     * Test design/header/welcome if it is saved in db as empty(null) it should be shown on backend as empty.
+     *
+     * @magentoDataFixture Magento/Theme/_files/config_data.php
+     */
+    public function testLoad()
+    {
+        $data = $this->storage->load('stores', 1);
+        foreach ($data->getExtensionAttributes()->getDesignConfigData() as $configData) {
+            if ($configData->getPath() == 'design/header/welcome') {
+                $this->assertSame('', $configData->getValue());
+            }
+        }
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Theme/_files/config_data.php b/dev/tests/integration/testsuite/Magento/Theme/_files/config_data.php
new file mode 100644
index 0000000000000000000000000000000000000000..b8cbebc1f67c1a4409981fc0c68e368c43f81de9
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Theme/_files/config_data.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+use Magento\Config\Model\Config\Factory;
+use Magento\TestFramework\Helper\Bootstrap;
+
+$objectManager = Bootstrap::getObjectManager();
+
+/** @var Factory $configFactory */
+$configFactory = $objectManager->create(Factory::class);
+/** @var \Magento\Config\Model\Config $config */
+$config = $configFactory->create();
+$config->setScope('stores');
+$config->setStore('default');
+$config->setDataByPath('design/header/welcome', null);
+$config->save();
diff --git a/dev/tests/integration/testsuite/Magento/Theme/_files/config_data_rollback.php b/dev/tests/integration/testsuite/Magento/Theme/_files/config_data_rollback.php
new file mode 100644
index 0000000000000000000000000000000000000000..ac02e98b493731ad0e9e927319fa581535ac752d
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Theme/_files/config_data_rollback.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+/** @var \Magento\Framework\App\ResourceConnection $resource */
+$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class);
+$connection = $resource->getConnection();
+$tableName = $resource->getTableName('core_config_data');
+
+$connection->query("DELETE FROM $tableName WHERE path = 'design/header/welcome';");
diff --git a/dev/tests/js/jasmine/tests/lib/mage/validation.test.js b/dev/tests/js/jasmine/tests/lib/mage/validation.test.js
index 50931f940c689e5450cc872f4be2fc19530583ef..12138e5939a7b1bdc487f3a27f7e003930287c48 100644
--- a/dev/tests/js/jasmine/tests/lib/mage/validation.test.js
+++ b/dev/tests/js/jasmine/tests/lib/mage/validation.test.js
@@ -183,4 +183,76 @@ define([
             )).toEqual(true);
         });
     });
+
+    describe('Testing 3 bytes characters only policy (UTF-8)', function () {
+        it('rejects data, if any of the characters cannot be stored using UTF-8 collation', function () {
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '😅😂', null
+            )).toEqual(false);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '😅 test 😂', null
+            )).toEqual(false);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '💩 👻 💀', null
+            )).toEqual(false);
+        });
+
+        it('approves data, if all the characters can be stored using UTF-8 collation', function () {
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '!$-_%ç&#?!', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '1234567890', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '   ', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'test', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'испытание', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'тест', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'ÖƒÕ¸Ö€Õ±Õ¡Ö€Õ¯Õ¸Ö‚Õ´', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'परीक्षण', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'テスト', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '테스트', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '测试', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, '測試', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'ทดสอบ', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'δοκιμή', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'اختبار', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'تست', null
+            )).toEqual(true);
+            expect($.validator.methods['validate-no-utf8mb4-characters'].call(
+                $.validator.prototype, 'מִבְחָן', null
+            )).toEqual(true);
+        });
+    });
+
 });
diff --git a/lib/web/i18n/en_US.csv b/lib/web/i18n/en_US.csv
index 21cfb51d5e3c9ba0ed08853ba7de38486f070bf6..5c63a191420a4d2daeebffef9dcab9e886edbd93 100644
--- a/lib/web/i18n/en_US.csv
+++ b/lib/web/i18n/en_US.csv
@@ -99,6 +99,7 @@ Submit,Submit
 "Password cannot be the same as email address.","Password cannot be the same as email address."
 "Please fix this field.","Please fix this field."
 "Please enter a valid email address.","Please enter a valid email address."
+"Please remove invalid characters: {0}.", "Please remove invalid characters: {0}."
 "Please enter a valid URL.","Please enter a valid URL."
 "Please enter a valid date (ISO).","Please enter a valid date (ISO)."
 "Please enter only digits.","Please enter only digits."
diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js
index 85158c581aec175b50525e5dbfe59ae58cc5c678..fee88826be7eb76adaa906667f48279c64f5452a 100644
--- a/lib/web/mage/validation.js
+++ b/lib/web/mage/validation.js
@@ -396,6 +396,24 @@
             $.mage.__('Please enter at least {0} characters')
         ],
 
+        /* detect chars that would require more than 3 bytes */
+        'validate-no-utf8mb4-characters': [
+            function (value) {
+                var validator = this,
+                    message = $.mage.__('Please remove invalid characters: {0}.'),
+                    matches = value.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF])/g),
+                    result = matches === null;
+
+                if (!result) {
+                    validator.charErrorMessage = message.replace('{0}', matches.join());
+                }
+
+                return result;
+            }, function () {
+                return this.charErrorMessage;
+            }
+        ],
+
         /* eslint-disable max-len */
         'email2': [
             function (value, element) {