Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/WriteTest.java
===================================================================
--- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/WriteTest.java	(revision 1195489)
+++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/WriteTest.java	(working copy)
@@ -504,4 +504,30 @@
             // success
         }
     }
+    
+    // https://issues.apache.org/jira/browse/JCR-3131
+    public void testEmptySaveNoRootAccess() throws RepositoryException, NotExecutableException {
+
+        Session s = getTestSession();
+        s.save();
+
+        Privilege[] read = privilegesFromName(Privilege.JCR_READ);
+
+        try {
+            JackrabbitAccessControlList tmpl = getPolicy(acMgr, "/", testUser.getPrincipal());
+            tmpl.addEntry(testUser.getPrincipal(), read, false, getRestrictions(superuser, path));
+            acMgr.setPolicy(tmpl.getPath(), tmpl);
+            superuser.save();
+
+            // empty save operation
+            s.save();
+        }
+        finally {
+            // undo revocation of read privilege
+            JackrabbitAccessControlList tmpl = getPolicy(acMgr, "/", testUser.getPrincipal());
+            tmpl.addEntry(testUser.getPrincipal(), read, true, getRestrictions(superuser, path));
+            acMgr.setPolicy(tmpl.getPath(), tmpl);
+            superuser.save();
+        }
+    }
 }
\ No newline at end of file
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java	(revision 1195447)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java	(working copy)
@@ -45,23 +45,28 @@
         } else {
             id = context.getItemStateManager().getIdOfRootTransientNodeState();
         }
-        if (LOG.isDebugEnabled()) {
-            String path;
-            try {
-                NodeId transientRoot = context.getItemStateManager().getIdOfRootTransientNodeState();
-                ItemImpl item = context.getItemManager().getItem(transientRoot);
-                path = item.getPath();
-            } catch (Exception e) {
-                LOG.warn("Could not get the path", e);
-                path = "?";
+        if (id != null) {
+            // id == null -> nothing to save; (JCR-3131)
+            if (LOG.isDebugEnabled()) {
+                String path;
+                try {
+                    NodeId transientRoot = context.getItemStateManager()
+                            .getIdOfRootTransientNodeState();
+                    ItemImpl item = context.getItemManager().getItem(
+                            transientRoot);
+                    path = item.getPath();
+                } catch (Exception e) {
+                    LOG.warn("Could not get the path", e);
+                    path = "?";
+                }
+                if (LOG_WITH_STACKTRACE) {
+                    LOG.debug("Saving changes under " + path, new Exception());
+                } else {
+                    LOG.debug("Saving changes under " + path);
+                }
             }
-            if (LOG_WITH_STACKTRACE) {
-                LOG.debug("Saving changes under " + path, new Exception());
-            } else {
-                LOG.debug("Saving changes under " + path);
-            }
+            context.getItemManager().getItem(id).save();
         }
-        context.getItemManager().getItem(id).save();
         return this;
     }
 
