diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 76b8fd2..2e50a01 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -765,13 +765,20 @@ public function __set($name, $value) {
     if ($value instanceof TypedDataInterface && !$value instanceof EntityInterface) {
       $value = $value->getValue();
     }
-    // If this is an entity field, handle it accordingly. We first check whether
-    // a field object has been already created. If not, we create one.
-    if (isset($this->fields[$name][$this->activeLangcode])) {
-      $this->fields[$name][$this->activeLangcode]->setValue($value);
-    }
-    elseif ($this->hasField($name)) {
-      $this->getTranslatedField($name, $this->activeLangcode)->setValue($value);
+
+    // Handle fields.
+    if ($this->hasField($name)) {
+      if ($value === NULL) {
+        throw new \LogicException(String::format('Fields cannot receive the NULL value (field name: @field_name).', array('@field_name' => $name)));
+      }
+      // First check whether a field object has been already created. If not, we
+      // create one.
+      if (isset($this->fields[$name][$this->activeLangcode])) {
+        $this->fields[$name][$this->activeLangcode]->setValue($value);
+      }
+      else {
+        $this->getTranslatedField($name, $this->activeLangcode)->setValue($value);
+      }
     }
     // The translations array is unset when cloning the entity object, we just
     // need to restore it.
@@ -789,24 +796,26 @@ public function __set($name, $value) {
    * Implements the magic method for isset().
    */
   public function __isset($name) {
+    // "Official" Field API fields are always set.
     if ($this->hasField($name)) {
-      return $this->get($name)->getValue() !== NULL;
+      return TRUE;
     }
+    // For non-field properties, check the internal values.
     else {
       return isset($this->values[$name]);
     }
   }
 
   /**
-   * Implements the magic method for unset.
+   * Implements the magic method for unset().
    */
   public function __unset($name) {
+    // Fields are always set and cannot be unset, they can only be emptied.
     if ($this->hasField($name)) {
-      $this->get($name)->setValue(NULL);
-    }
-    else {
-      unset($this->values[$name]);
+      throw new \LogicException(String::format('Fields cannot be unset (field name: @field_name).', array('@field_name' => $name)));
     }
+    // For non-field properties, unset the internal value.
+    unset($this->values[$name]);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index 56579e4..9c92647 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -127,7 +127,7 @@ public function getValue($include_computed = FALSE) {
    */
   public function setValue($values, $notify = TRUE) {
     if (!isset($values) || $values === array()) {
-      $this->list = $values;
+      $this->list = array();
     }
     else {
       // Support passing in only the value of the first item.
diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
index 6a24454..2422a9e 100644
--- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
+++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
@@ -144,15 +144,6 @@ public function denormalize($data, $class, $format = NULL, array $context = arra
 
     $entity = $this->entityManager->getStorage($typed_data_ids['entity_type'])->create($values);
 
-    // Special handling for PATCH: destroy all possible default values that
-    // might have been set on entity creation. We want an "empty" entity that
-    // will only get filled with fields from the data array.
-    if (isset($context['request_method']) && $context['request_method'] == 'patch') {
-      foreach ($entity as $field_name => $field) {
-        $entity->set($field_name, NULL);
-      }
-    }
-
     // Remove links from data array.
     unset($data['_links']);
     // Get embedded resources and remove from data array.
@@ -171,6 +162,12 @@ public function denormalize($data, $class, $format = NULL, array $context = arra
       }
     }
 
+    // Special handling for PATCH: pass the names of the fields whose values
+    // should be merged.
+    if (isset($context['request_method']) && $context['request_method'] == 'patch') {
+      $entity->_restPatchFields = array_keys($data);
+    }
+
     // Iterate through remaining items in data array. These should all
     // correspond to fields.
     foreach ($data as $field_name => $field_data) {
diff --git a/core/modules/hal/src/Tests/DenormalizeTest.php b/core/modules/hal/src/Tests/DenormalizeTest.php
index 8b5711c..6931d1c 100644
--- a/core/modules/hal/src/Tests/DenormalizeTest.php
+++ b/core/modules/hal/src/Tests/DenormalizeTest.php
@@ -176,7 +176,7 @@ public function testBasicFieldDenormalization() {
   }
 
   /**
-   * Verifies that only specified properties get populated in the PATCH context.
+   * Verifies that the denormalized entity is correct in the PATCH context.
    */
   public function testPatchDenormailzation() {
     $data = array(
@@ -195,15 +195,7 @@ public function testPatchDenormailzation() {
     $denormalized = $this->serializer->denormalize($data, $this->entityClass, $this->format, array('request_method' => 'patch'));
     // Check that the one field got populated as expected.
     $this->assertEqual($data['field_test_text'], $denormalized->get('field_test_text')->getValue());
-    // Unset that field so that now all fields are NULL.
-    $denormalized->set('field_test_text', NULL);
-    // Assert that all fields are NULL and not set to default values. Example:
-    // the UUID field is NULL and not initialized as usual.
-    foreach ($denormalized as $field_name => $field) {
-      // The 'langcode' field always has a value.
-      if ($field_name != 'langcode') {
-        $this->assertFalse(isset($denormalized->$field_name), "$field_name is not set.");
-      }
-    }
+    // Check the custom property that contains the list of fields to merge.
+    $this->assertEqual($denormalized->_restPatchFields, ['field_test_text']);
   }
 }
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 7e0a854..feef9d7 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -60,7 +60,7 @@ protected function prepareEntity() {
 
     if (!$node->isNew()) {
       // Remove the revision log message from the original node entity.
-      $node->revision_log = NULL;
+      $node->revision_log = array();
     }
   }
 
diff --git a/core/modules/quickedit/src/Form/QuickEditFieldForm.php b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
index a1516fe..6d9d2c3 100644
--- a/core/modules/quickedit/src/Form/QuickEditFieldForm.php
+++ b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
@@ -187,7 +187,7 @@ protected function buildEntity(array $form, FormStateInterface $form_state) {
 
     // @todo Refine automated log messages and abstract them to all entity
     //   types: http://drupal.org/node/1678002.
-    if ($entity->getEntityTypeId() == 'node' && $entity->isNewRevision() && !isset($entity->revision_log)) {
+    if ($entity->getEntityTypeId() == 'node' && $entity->isNewRevision() && $entity->revision_log->isEmpty()) {
       $entity->revision_log = t('Updated the %field-name field through in-place editing.', array('%field-name' => $entity->get($field_name)->getFieldDefinition()->getLabel()));
     }
 
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 7b3e49e..25b75ac 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -133,22 +133,21 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity
     }
 
     // Overwrite the received properties.
-    foreach ($entity as $field_name => $field) {
-      if (isset($entity->{$field_name})) {
-        // It is not possible to set the language to NULL as it is automatically
-        // re-initialized. As it must not be empty, skip it if it is.
-        // @todo: Use the langcode entity key when available. See
-        //   https://drupal.org/node/2143729.
-        if ($field_name == 'langcode' && $field->isEmpty()) {
-          continue;
-        }
-        if ($field->isEmpty() && !$original_entity->get($field_name)->access('delete')) {
-          throw new AccessDeniedHttpException(t('Access denied on deleting field @field.', array('@field' => $field_name)));
-        }
-        $original_entity->set($field_name, $field->getValue());
-        if (!$original_entity->get($field_name)->access('update')) {
-          throw new AccessDeniedHttpException(t('Access denied on updating field @field.', array('@field' => $field_name)));
-        }
+    foreach ($entity->_restPatchFields as $field_name) {
+      $field = $entity->get($field_name);
+      // It is not possible to set the language to NULL as it is automatically
+      // re-initialized. As it must not be empty, skip it if it is.
+      // @todo: Use the langcode entity key when available. See
+      //   https://drupal.org/node/2143729.
+      if ($field_name == 'langcode' && $field->isEmpty()) {
+        continue;
+      }
+      if ($field->isEmpty() && !$original_entity->get($field_name)->access('delete')) {
+        throw new AccessDeniedHttpException(t('Access denied on deleting field @field.', array('@field' => $field_name)));
+      }
+      $original_entity->set($field_name, $field->getValue());
+      if (!$original_entity->get($field_name)->access('update')) {
+        throw new AccessDeniedHttpException(t('Access denied on updating field @field.', array('@field' => $field_name)));
       }
     }
 
diff --git a/core/modules/system/src/Tests/Entity/EntityFieldTest.php b/core/modules/system/src/Tests/Entity/EntityFieldTest.php
index 369da19..22e6b11 100644
--- a/core/modules/system/src/Tests/Entity/EntityFieldTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityFieldTest.php
@@ -141,7 +141,7 @@ protected function doTestReadWrite($entity_type) {
     $this->assertEqual($new_user->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type)));
     $this->assertEqual($new_user->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated username value can be read.', array('%entity_type' => $entity_type)));
 
-    // Try unsetting a field.
+    // Try unsetting a field property.
     $entity->name->value = NULL;
     $entity->user_id->target_id = NULL;
     $this->assertNull($entity->name->value, format_string('%entity_type: Name field is not set.', array('%entity_type' => $entity_type)));
@@ -171,25 +171,30 @@ protected function doTestReadWrite($entity_type) {
     $this->assertFalse(isset($entity->name[0]->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
     $this->assertFalse(isset($entity->name->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
 
+    // Test emptying a field.
     $entity->name = array();
     $this->assertTrue(isset($entity->name), 'Name field is set.');
+    $this->assertIdentical(count($entity->name), 0, format_string('%entity_type: Name field contains no items.', array('%entity_type' => $entity_type)));
+    $this->assertIdentical($entity->name->getValue(), array(), format_string('%entity_type: Name field value is an empty array.', array('%entity_type' => $entity_type)));
     $this->assertFalse(isset($entity->name[0]), 'Name field item is not set.');
     $this->assertFalse(isset($entity->name[0]->value), 'First name item value is not set.');
     $this->assertFalse(isset($entity->name->value), 'Name value is not set.');
 
-    $entity->name = NULL;
-    $this->assertFalse(isset($entity->name), 'Name field is not set.');
-    $this->assertFalse(isset($entity->name[0]), 'Name field item is not set.');
-    $this->assertFalse(isset($entity->name[0]->value), 'First name item value is not set.');
-    $this->assertFalse(isset($entity->name->value), 'Name value is not set.');
-
-    $entity->name->value = 'a value';
-    $this->assertTrue(isset($entity->name->value), format_string('%entity_type: Name is set.', array('%entity_type' => $entity_type)));
-    unset($entity->name);
-    $this->assertFalse(isset($entity->name), format_string('%entity_type: Name field is not set.', array('%entity_type' => $entity_type)));
-    $this->assertFalse(isset($entity->name[0]), format_string('%entity_type: Name field item is not set.', array('%entity_type' => $entity_type)));
-    $this->assertFalse(isset($entity->name[0]->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
-    $this->assertFalse(isset($entity->name->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
+    // Test unsetting a field.
+    try {
+      unset($entity->name);
+      $this->fail('Cannot unset a field.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Cannot unset a field.');
+    }
+    try {
+      $entity->name = NULL;
+      $this->fail('Cannot assign the NULL value to a field.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Cannot assign the NULL value to a field.');
+    }
 
     // Access the language field.
     $this->assertEqual($langcode, $entity->langcode->value, format_string('%entity_type: Language code can be read.', array('%entity_type' => $entity_type)));
@@ -265,18 +270,6 @@ protected function doTestReadWrite($entity_type) {
     $this->assertEqual(count(iterator_to_array($entity->name->getIterator())), count($entity->name), format_string('%entity_type: Count matches iterator count.', array('%entity_type' => $entity_type)));
     $this->assertTrue($entity->name->getValue() === array(0 => array('value' => NULL)), format_string('%entity_type: Name field value contains a NULL value.', array('%entity_type' => $entity_type)));
 
-    // Test removing all list items by assigning an empty array.
-    $entity->name = array();
-    $this->assertIdentical(count($entity->name), 0, format_string('%entity_type: Name field contains no items.', array('%entity_type' => $entity_type)));
-    $this->assertIdentical($entity->name->getValue(), array(), format_string('%entity_type: Name field value is an empty array.', array('%entity_type' => $entity_type)));
-
-    $entity->name->value = 'foo';
-    $this->assertEqual($entity->name->value, 'foo', format_string('%entity_type: Name field set.', array('%entity_type' => $entity_type)));
-    // Test removing all list items by setting it to NULL.
-    $entity->name = NULL;
-    $this->assertIdentical(count($entity->name), 0, format_string('%entity_type: Name field contains no items.', array('%entity_type' => $entity_type)));
-    $this->assertNull($entity->name->getValue(), format_string('%entity_type: Name field value is an empty array.', array('%entity_type' => $entity_type)));
-
     // Test get and set field values.
     $entity->name = 'foo';
     $this->assertEqual($entity->name[0]->toArray(), array('value' => 'foo'), format_string('%entity_type: Field value has been retrieved via toArray()', array('%entity_type' => $entity_type)));
