diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
index 6ceaaed..85aa445 100644
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
 import java.net.UnknownHostException;
 import java.util.EnumMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
@@ -40,6 +41,7 @@ import com.google.common.cache.RemovalCause;
 import com.google.common.cache.RemovalListener;
 import com.google.common.cache.RemovalNotification;
 import com.google.common.cache.Weigher;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.mongodb.DB;
@@ -462,8 +464,7 @@ public class DocumentMK {
     }
 
     private void parseAddNode(Commit commit, JsopReader t, String path) {
-        DocumentNodeState n = new DocumentNodeState(nodeStore, path,
-                new RevisionVector(commit.getRevision()));
+        List<PropertyState> props = Lists.newArrayList();
         if (!t.matches('}')) {
             do {
                 String key = t.readString();
@@ -473,11 +474,13 @@ public class DocumentMK {
                     parseAddNode(commit, t, childPath);
                 } else {
                     String value = t.readRawValue().trim();
-                    n.setProperty(key, value);
+                    props.add(nodeStore.createPropertyState(key, value));
                 }
             } while (t.matches(','));
             t.read('}');
         }
+        DocumentNodeState n = new DocumentNodeState(nodeStore, path,
+                new RevisionVector(commit.getRevision()), props, false, null);
         commit.addNode(n);
     }
 
@@ -505,10 +508,8 @@ public class DocumentMK {
                                 String targetPath,
                                 Commit commit) {
         RevisionVector destRevision = commit.getBaseRevision().update(commit.getRevision());
-        DocumentNodeState newNode = new DocumentNodeState(nodeStore, targetPath, destRevision);
-        for (PropertyState p : source.getProperties()) {
-            newNode.setProperty(p);
-        }
+        DocumentNodeState newNode = new DocumentNodeState(nodeStore, targetPath, destRevision,
+                source.getProperties(), false, null);
 
         commit.addNode(newNode);
         if (move) {
diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
index 078bedb..078da5b 100644
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.NoSuchElementException;
@@ -29,6 +30,8 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.cache.CacheValue;
 import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
@@ -70,12 +73,12 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach
      */
     static final int MAX_FETCH_SIZE = INITIAL_FETCH_SIZE << 4;
 
-    final String path;
-    RevisionVector lastRevision;
-    final RevisionVector rootRevision;
-    final boolean fromExternalChange;
-    final Map<String, PropertyState> properties;
-    final boolean hasChildren;
+    private final String path;
+    private final RevisionVector lastRevision;
+    private final RevisionVector rootRevision;
+    private final boolean fromExternalChange;
+    private final Map<String, PropertyState> properties;
+    private final boolean hasChildren;
 
     private final DocumentNodeStore store;
 
@@ -84,13 +87,16 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach
     DocumentNodeState(@Nonnull DocumentNodeStore store,
                       @Nonnull String path,
                       @Nonnull RevisionVector rootRevision) {
-        this(store, path, rootRevision, false);
+        this(store, path, rootRevision, Collections.<PropertyState>emptyList(), false, null);
     }
 
     DocumentNodeState(@Nonnull DocumentNodeStore store, @Nonnull String path,
-                      @Nonnull RevisionVector rootRevision, boolean hasChildren) {
-        this(store, path, rootRevision, new HashMap<String, PropertyState>(),
-                hasChildren, null, false);
+                      @Nonnull RevisionVector rootRevision,
+                      Iterable<? extends PropertyState> properties,
+                      boolean hasChildren,
+                      @Nullable RevisionVector lastRevision) {
+        this(store, path, rootRevision, asMap(properties),
+                hasChildren, lastRevision, false);
     }
 
     private DocumentNodeState(@Nonnull DocumentNodeStore store,
@@ -301,19 +307,6 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach
         }
     }
 
-    void setProperty(String propertyName, String value) {
-        if (value == null) {
-            properties.remove(propertyName);
-        } else {
-            properties.put(propertyName,
-                    new DocumentPropertyState(store, propertyName, value));
-        }
-    }
-
-    void setProperty(PropertyState property) {
-        properties.put(property.getName(), property);
-    }
-
     String getPropertyAsString(String propertyName) {
         PropertyState prop = properties.get(propertyName);
         if (prop == null) {
@@ -375,10 +368,6 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach
         return path + "@" + lastRevision;
     }
 
-    void setLastRevision(RevisionVector lastRevision) {
-        this.lastRevision = lastRevision;
-    }
-
     @Override
     public int getMemory() {
         int size = 40 // shallow
@@ -471,6 +460,14 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach
         });
     }
 
+    private static Map<String, PropertyState> asMap(Iterable<? extends PropertyState> props){
+        ImmutableMap.Builder<String, PropertyState> builder = ImmutableMap.builder();
+        for (PropertyState ps : props){
+            builder.put(ps.getName(), ps);
+        }
+        return builder.build();
+    }
+
     public String asString() {
         JsopWriter json = new JsopBuilder();
         json.key("path").value(path);
@@ -528,12 +525,16 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach
             }
             json.read(',');
         }
-        state = new DocumentNodeState(store, path, rootRev, hasChildren);
-        state.setLastRevision(lastRev);
+
+        List<PropertyState> props = Lists.newArrayList();
         for (Entry<String, String> e : map.entrySet()) {
-            state.setProperty(e.getKey(), e.getValue());
+            String value = e.getValue();
+            if (value != null) {
+                props.add(store.createPropertyState(e.getKey(), value));
+            }
         }
-        return state;
+
+        return new DocumentNodeState(store, path, rootRev, props, hasChildren, lastRev);
     }
 
     /**
diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
index 65e541f..b8eafce 100644
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
@@ -77,6 +77,7 @@ import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.UncheckedExecutionException;
 
 import org.apache.jackrabbit.api.stats.TimeSeries;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.commons.IOUtils;
 import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
 import org.apache.jackrabbit.oak.plugins.blob.BlobStoreBlob;
@@ -853,6 +854,10 @@ public final class DocumentNodeStore
         return nodeStateCache.getDocumentNodeState(path, rootRevision, rev);
     }
 
+    PropertyState createPropertyState(String name, String value){
+        return new DocumentPropertyState(this, name, checkNotNull(value));
+    }
+
     /**
      * Get the node for the given path and revision. The returned object might
      * not be modified directly.
diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
index f366a74..5a9095a 100644
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
@@ -42,6 +42,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Queues;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.cache.CacheValue;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
@@ -955,7 +956,8 @@ public final class NodeDocument extends Document implements CachedNodeDocument{
             return null;
         }
         String path = getPath();
-        DocumentNodeState n = new DocumentNodeState(nodeStore, path, readRevision, hasChildren());
+        List<PropertyState> props = Lists.newArrayList();
+
         for (String key : keySet()) {
             if (!Utils.isPropertyName(key)) {
                 continue;
@@ -990,7 +992,9 @@ public final class NodeDocument extends Document implements CachedNodeDocument{
             }
             String propertyName = Utils.unescapePropertyName(key);
             String v = value != null ? value.value : null;
-            n.setProperty(propertyName, v);
+            if (v != null){
+                props.add(nodeStore.createPropertyState(propertyName, v));
+            }
         }
 
         // when was this node last modified?
@@ -1041,13 +1045,12 @@ public final class NodeDocument extends Document implements CachedNodeDocument{
                 lastRevision = lastRevision.update(r);
             }
         }
-        n.setLastRevision(lastRevision);
 
         if (store instanceof RevisionListener) {
             ((RevisionListener) store).updateAccessedRevision(lastRevision);
         }
 
-        return n;
+        return new DocumentNodeState(nodeStore, path, readRevision, props, hasChildren(), lastRevision);
     }
 
     /**
diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java
index 6d1fc20..3f4f988 100644
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java
@@ -19,12 +19,18 @@ package org.apache.jackrabbit.oak.plugins.document;
 import static org.apache.jackrabbit.oak.plugins.document.DocumentNodeState.Children;
 import static org.junit.Assert.fail;
 
+import java.util.Collections;
 import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Callable;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.mongodb.BasicDBObject;
 
 import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey;
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 import org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState;
@@ -238,21 +244,26 @@ public class MeasureMemory {
     }
 
     static DocumentNodeState generateNode(int propertyCount) {
-        DocumentNodeState n = new DocumentNodeState(STORE, new String("/hello/world"),
-                new RevisionVector(new Revision(1, 2, 3)));
+        return generateNode(propertyCount, Collections.<PropertyState>emptyList());
+    }
+
+    static DocumentNodeState generateNode(int propertyCount, List<PropertyState> extraProps) {
+        List<PropertyState> props = Lists.newArrayList();
+        props.addAll(extraProps);
         for (int i = 0; i < propertyCount; i++) {
-            n.setProperty("property" + i, "\"values " + i + "\"");
+            String key = "property" + i;
+            props.add(STORE.createPropertyState(key, "\"values " + i + "\""));
         }
-        n.setLastRevision(new RevisionVector(new Revision(1, 2, 3)));
-        return n;
+        return new DocumentNodeState(STORE, new String("/hello/world"),
+                new RevisionVector(new Revision(1, 2, 3)), props, false, new RevisionVector(new Revision(1, 2, 3)));
     }
 
     static DocumentNodeState generateNodeWithBinaryProperties(int propertyCount) {
-        DocumentNodeState n = generateNode(0);
+        List<PropertyState> props = Lists.newArrayList();
         for (int i = 0; i < propertyCount; i++) {
-            n.setProperty("binary" + i, new String(BLOB_VALUE));
+            props.add(STORE.createPropertyState("binary" + i, new String(BLOB_VALUE)));
         }
-        return n;
+        return generateNode(0, props);
     }
 
     static BasicDBObject generateBasicObject(int propertyCount) {
diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java
index bd038e9..eac2fdb 100644
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java
@@ -26,8 +26,12 @@ import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Random;
 
+import com.google.common.collect.ImmutableMap;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
@@ -79,8 +83,8 @@ public class SimpleTest {
         DocumentStore s = mk.getDocumentStore();
         DocumentNodeStore ns = mk.getNodeStore();
         RevisionVector rev = RevisionVector.fromString(mk.getHeadRevision());
-        DocumentNodeState n = new DocumentNodeState(ns, "/test", rev);
-        n.setProperty("name", "\"Hello\"");
+        DocumentNodeState n = new DocumentNodeState(ns, "/test", rev,
+                Collections.singleton(ns.createPropertyState("name", "\"Hello\"")), false, null);
         UpdateOp op = n.asOperation(rev.getRevision(ns.getClusterId()));
         // mark as commit root
         NodeDocument.setRevision(op, rev.getRevision(ns.getClusterId()), "c");
