diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md
index 35c6d01c5904289b77fc7f1de9419ef91a1510e9..3629e0ca3695000863d8c254516f64bf59a7bf60 100644
--- a/.gitlab/merge_request_templates/Default.md
+++ b/.gitlab/merge_request_templates/Default.md
@@ -28,6 +28,7 @@ guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md)
 - [ ] Up-to-date CHANGELOG.md (or not necessary)
 - [ ] Up-to-date JSON schema (or not necessary)
 - [ ] Appropriate user and developer documentation (or not necessary)
+  - Update / write published documentation (`make doc`).
   - How do I use the software?  Assume "stupid" users.
   - How do I develop or debug the software?  Assume novice developers.
 - [ ] Annotations in code (Gitlab comments)
@@ -41,7 +42,8 @@ guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md)
 - [ ] I understand the intent of this MR
 - [ ] All automated tests pass
 - [ ] Up-to-date CHANGELOG.md (or not necessary)
-- [ ] Appropriate user and developer documentation (or not necessary)
+- [ ] Appropriate user and developer documentation (or not necessary), also in published
+      documentation.
 - [ ] The test environment setup works and the intended behavior is reproducible in the test
   environment
 - [ ] In-code documentation and comments are up-to-date.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c1f5481af947c60e4f3192a7935183537ff3a05..be47de54cd33c917b8997eebaa55c31edfe71543 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.11.0] 2023-10-13 ##
+
+### Added ###
+
+* Configuration options `REST_RESPONSE_LOG_FORMAT` and
+  `GRPC_RESPONSE_LOG_FORMAT` which control the format and information included
+  in the log message of any response of the respective API. See
+  `conf/core/server.conf` for more information.
+* REST API: Permanent redirect from "FileSystem" to "FileSystem/".
+
+### Fixed ###
+
+* Wrong url returned by FileSystem resource behind proxy.
+* `NullPointerException` in GRPC API converters when executing SELECT query on
+  NULL values.
+
 ## [0.10.0] - 2023-06-02 ##
 (Florian Spreckelsen)
 
diff --git a/CITATION.cff b/CITATION.cff
index a45e422c288822d0c34e1f5df2bcb83ceedd8e23..962c0cfce8443708de4094ea83658bd468ada26d 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -23,6 +23,6 @@ authors:
     given-names: Stefan
     orcid: https://orcid.org/0000-0001-7214-8125
 title: "CaosDB - Server"
-version: 0.10.0
+version: 0.11.0
 doi: 10.3390/data4020083
-date-released: 2023-06-02
+date-released: 2023-10-13
diff --git a/conf/core/log4j2-default.properties b/conf/core/log4j2-default.properties
index 5983f8e33d51729c70cd3d685fe299aa13e8f27c..39f5f0ead11bb5ccfa9e39d3007d40ee8fa3077c 100644
--- a/conf/core/log4j2-default.properties
+++ b/conf/core/log4j2-default.properties
@@ -8,6 +8,10 @@ verbose = true
 property.LOG_DIR = log
 property.REQUEST_TIME_LOGGER_LEVEL = OFF
 
+# the root logger has level "WARN" but we want to log the GRPC traffic in INFO level:
+logger.grpc_response_log.name = org.caosdb.server.grpc.LoggingInterceptor
+logger.grpc_response_log.level = INFO
+
 ## appenders
 # stderr
 appender.stderr.type = Console
diff --git a/conf/core/server.conf b/conf/core/server.conf
index c030d6eb63d61ad2d9adc6a174877b1006396537..5df6c40ca04a9c6f22d03f61c937ab5fbeab565f 100644
--- a/conf/core/server.conf
+++ b/conf/core/server.conf
@@ -100,6 +100,21 @@ GRPC_SERVER_PORT_HTTPS=8443
 # HTTP port of the grpc end-point
 GRPC_SERVER_PORT_HTTP=
 
+# --------------------------------------------------
+# Response Log formatting (this cannot be configured by the logging frame work
+# and thus has to be configured here).
+# --------------------------------------------------
+
+# Logging format of the GRPC API.
+# Known keys: user-agent, local-address, remote-address, method.
+# 'OFF' turns off the logging.
+GRPC_RESPONSE_LOG_FORMAT={method} {local-address} {remote-address} {user-agent}
+# Logging format of the REST API.
+# Known keys: see column "Variable name" at https://javadocs.restlet.talend.com/2.4/jse/api/index.html?org/restlet/util/Resolver.html
+# 'OFF' turns off the logging.
+# Leaving this empty means using restlet's default settings.
+REST_RESPONSE_LOG_FORMAT=
+
 # --------------------------------------------------
 # HTTPS options
 # --------------------------------------------------
@@ -229,4 +244,4 @@ ENTITY_VERSIONING_ENABLED=true
 
 
 # Enabling the state machine extension
-# EXT_STATE_ENTITY=ENABLE
\ No newline at end of file
+# EXT_STATE_ENTITY=ENABLE
diff --git a/pom.xml b/pom.xml
index 835de2d3eebef06cb10e9efd2e4aaf6890287357..05d1aa4b7d8e01b5cd3794b6fabbd01ea81a24ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.caosdb</groupId>
   <artifactId>caosdb-server</artifactId>
-  <version>0.10.0</version>
+  <version>0.11.0</version>
   <packaging>jar</packaging>
   <name>CaosDB Server</name>
   <scm>
diff --git a/src/doc/conf.py b/src/doc/conf.py
index 3c062b94cff76b69ac234ab7512a02c0b6d27247..434ed4623fbacd240d35142f855c1fd4301de915 100644
--- a/src/doc/conf.py
+++ b/src/doc/conf.py
@@ -26,9 +26,9 @@ copyright = '2023, IndiScale GmbH'
 author = 'Daniel Hornung, Timm Fitschen'
 
 # The short X.Y version
-version = '0.10.0'
+version = '0.11.0'
 # The full version, including alpha/beta/rc tags
-release = '0.10.0'
+release = '0.11.0'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java
index 9daabaed65aa39a9be868567581588625aba7a53..2a115c5cfa06ed0e3db85847c27fb4c8424eadaf 100644
--- a/src/main/java/org/caosdb/server/CaosDBServer.java
+++ b/src/main/java/org/caosdb/server/CaosDBServer.java
@@ -108,6 +108,7 @@ import org.restlet.data.Reference;
 import org.restlet.data.ServerInfo;
 import org.restlet.data.Status;
 import org.restlet.engine.Engine;
+import org.restlet.routing.Redirector;
 import org.restlet.routing.Route;
 import org.restlet.routing.Router;
 import org.restlet.routing.Template;
@@ -223,7 +224,6 @@ public class CaosDBServer extends Application {
             public boolean notifyObserver(String e, Observable sender) {
               if (e.equals(ServerProperties.KEY_TIMEZONE)) {
 
-                String[] availableIDs = TimeZone.getAvailableIDs();
                 TimeZone newZoneId =
                     TimeZone.getTimeZone(getServerProperty(ServerProperties.KEY_TIMEZONE));
                 TimeZone.setDefault(newZoneId);
@@ -709,6 +709,9 @@ public class CaosDBServer extends Application {
     protectedRouter.attach("/EntityPermissions/", EntityPermissionsResource.class);
     protectedRouter.attach("/EntityPermissions/{specifier}", EntityPermissionsResource.class);
     protectedRouter.attach("/Owner/{specifier}", EntityOwnerResource.class);
+    protectedRouter.attach(
+        "/FileSystem",
+        new Redirector(getContext(), "/FileSystem/", Redirector.MODE_CLIENT_PERMANENT));
     protectedRouter.attach("/FileSystem/", FileSystemResource.class);
     // FileSystem etc. needs to accept parameters which contain slashes and would otherwise be
     // split at the first separator
@@ -894,6 +897,13 @@ class CaosDBComponent extends Component {
 
   public CaosDBComponent() {
     super();
+    String responseLogFormat =
+        CaosDBServer.getServerProperty(ServerProperties.KEY_REST_RESPONSE_LOG_FORMAT);
+    if ("OFF".equalsIgnoreCase(responseLogFormat)) {
+      getLogService().setEnabled(false);
+    } else if (responseLogFormat != null && !responseLogFormat.isEmpty()) {
+      getLogService().setResponseLogFormat(responseLogFormat);
+    }
     setName(CaosDBServer.getServerProperty(ServerProperties.KEY_SERVER_NAME));
     setOwner(CaosDBServer.getServerProperty(ServerProperties.KEY_SERVER_OWNER));
   }
diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java
index d58250242f691498288d765dd252986eda7fb09f..93a0c7473be1c31565e4b159ba55e01d557687e7 100644
--- a/src/main/java/org/caosdb/server/ServerProperties.java
+++ b/src/main/java/org/caosdb/server/ServerProperties.java
@@ -150,6 +150,8 @@ public class ServerProperties extends Properties implements Observable {
 
   public static final String KEY_PASSWORD_STRENGTH_REGEX = "PASSWORD_VALID_REGEX";
   public static final String KEY_PASSWORD_WEAK_MESSAGE = "PASSWORD_INVALID_MESSAGE";
+  public static final String KEY_REST_RESPONSE_LOG_FORMAT = "REST_RESPONSE_LOG_FORMAT";
+  public static final String KEY_GRPC_RESPONSE_LOG_FORMAT = "GRPC_RESPONSE_LOG_FORMAT";
 
   /**
    * Read the config files and initialize the server properties.
diff --git a/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java b/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java
index 854fc07f9ed93eda28e8aca99c1ea1983c73f4fe..075036921aba51acc05ff1b76df01763485c53c2 100644
--- a/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java
+++ b/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java
@@ -26,12 +26,9 @@ import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class ACMPermissions implements Comparable<ACMPermissions> {
 
-  private static Logger LOGGER = LoggerFactory.getLogger(ACMPermissions.class);
   public static final String USER_PARAMETER = "?USER?";
   public static final String REALM_PARAMETER = "?REALM?";
   public static final String ROLE_PARAMETER = "?ROLE?";
diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java
index 5addf3f3f3e34a8e3e6a9152b9511046fe4b6a23..05ea26c255ae5b2bd1e75df7f3fdc4d94fca5e5a 100644
--- a/src/main/java/org/caosdb/server/database/BackendTransaction.java
+++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java
@@ -47,7 +47,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLIsSubType;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLListRoles;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLListUsers;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLLogUserVisit;
-import org.caosdb.server.database.backend.implementation.MySQL.MySQLRegisterSubDomain;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveAll;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveAllUncheckedFiles;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveDatatypes;
@@ -106,7 +105,6 @@ import org.caosdb.server.database.backend.interfaces.IsSubTypeImpl;
 import org.caosdb.server.database.backend.interfaces.ListRolesImpl;
 import org.caosdb.server.database.backend.interfaces.ListUsersImpl;
 import org.caosdb.server.database.backend.interfaces.LogUserVisitImpl;
-import org.caosdb.server.database.backend.interfaces.RegisterSubDomainImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveAllImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveAllUncheckedFilesImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl;
@@ -177,7 +175,6 @@ public abstract class BackendTransaction implements Undoable {
       setImpl(IsSubTypeImpl.class, MySQLIsSubType.class);
       setImpl(UpdateSparseEntityImpl.class, MySQLUpdateSparseEntity.class);
       setImpl(RetrieveAllImpl.class, MySQLRetrieveAll.class);
-      setImpl(RegisterSubDomainImpl.class, MySQLRegisterSubDomain.class);
       setImpl(RetrieveDatatypesImpl.class, MySQLRetrieveDatatypes.class);
       setImpl(RetrieveUserImpl.class, MySQLRetrieveUser.class);
       setImpl(RetrieveParentsImpl.class, MySQLRetrieveParents.class);
diff --git a/src/main/java/org/caosdb/server/database/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/DatabaseUtils.java
deleted file mode 100644
index 5280de17d91982db0481105af8c9ca760db1384f..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/DatabaseUtils.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.database;
-
-import com.google.common.base.Objects;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.caosdb.server.database.proto.FlatProperty;
-import org.caosdb.server.database.proto.ProtoProperty;
-import org.caosdb.server.database.proto.SparseEntity;
-import org.caosdb.server.database.proto.VerySparseEntity;
-import org.caosdb.server.datatype.AbstractCollectionDatatype;
-import org.caosdb.server.datatype.CollectionValue;
-import org.caosdb.server.datatype.IndexedSingleValue;
-import org.caosdb.server.datatype.ReferenceValue;
-import org.caosdb.server.datatype.SingleValue;
-import org.caosdb.server.entity.EntityID;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.Message;
-import org.caosdb.server.entity.RetrieveEntity;
-import org.caosdb.server.entity.Role;
-import org.caosdb.server.entity.StatementStatus;
-import org.caosdb.server.entity.wrapper.Domain;
-import org.caosdb.server.entity.wrapper.Parent;
-import org.caosdb.server.entity.wrapper.Property;
-
-public class DatabaseUtils {
-
-  public static final String bytes2UTF8(final byte[] bytes) {
-    if (bytes == null) {
-      return null;
-    } else {
-      return new String(bytes);
-    }
-  }
-
-  private static Domain makeReplacement(final EntityInterface p) {
-    // add a new helper domain.
-    final Domain d =
-        new Domain(p.getProperties(), p.getDatatype(), p.getValue(), p.getStatementStatus());
-    d.setDescOverride(p.isDescOverride());
-    d.setNameOverride(p.isNameOverride());
-    d.setDatatypeOverride(p.isDatatypeOverride());
-    d.setName(p.getName());
-    d.setDescription(p.getDescription());
-    p.setReplacement(d);
-    return d;
-  }
-
-  public static void deriveStage1Inserts(
-      final List<EntityInterface> stage1Inserts, final EntityInterface e) {
-    // entity value
-    if (e.hasValue()) {
-      final Property p = new Property(e);
-      p.setStatementStatus(StatementStatus.FIX);
-      p.setPIdx(0);
-      p.setDatatype(e.getDatatype());
-      p.setValue(e.getValue());
-      processPropertiesStage1(stage1Inserts, p, e);
-    }
-
-    for (final EntityInterface p : e.getProperties()) {
-      processPropertiesStage1(stage1Inserts, p, e);
-    }
-  }
-
-  private static void processPropertiesStage1(
-      final List<EntityInterface> stage1Inserts, final EntityInterface p, final EntityInterface e) {
-    stage1Inserts.add(p);
-    if (!p.isDescOverride()
-        && !p.isNameOverride()
-        && !p.isDatatypeOverride()
-        && (!p.hasProperties() || hasUniquePropertyId(p, e))
-        && !(p.getDatatype() instanceof AbstractCollectionDatatype)) {
-      // this property can be represented without any replacement. We explicitly
-      // setReplacement(null) because there is a corner case (related to the inheritance of
-      // properties) where there is a replacement present which belongs to the parent entity, see
-      // https://gitlab.com/caosdb/caosdb-server/-/issues/216.
-      p.setReplacement(null);
-    } else {
-      stage1Inserts.add(makeReplacement(p));
-    }
-    processSubPropertiesStage1(stage1Inserts, p);
-  }
-
-  public static int deriveStage2Inserts(
-      final List<EntityInterface> stage2Inserts, final List<EntityInterface> stage1Inserts) {
-    int domainCount = 0;
-    for (final EntityInterface p : stage1Inserts) {
-      if (p.hasRole() && p.getRole() == Role.Domain) {
-        domainCount++;
-      }
-      if (!p.hasReplacement() && p.hasProperties()) {
-        stage2Inserts.addAll(p.getProperties());
-      }
-    }
-    return domainCount;
-  }
-
-  private static boolean hasUniquePropertyId(final EntityInterface p, final EntityInterface e) {
-    final EntityID id = p.getId();
-    for (final EntityInterface p2 : e.getProperties()) {
-      if (Objects.equal(p2.getId(), id) && p2 != p) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private static void processSubPropertiesStage1(
-      final List<EntityInterface> stage1Inserts, final EntityInterface p) {
-    for (final EntityInterface subP : p.getProperties()) {
-      if (subP.hasProperties()) {
-        stage1Inserts.add(makeReplacement(subP));
-        processSubPropertiesStage1(stage1Inserts, subP);
-      }
-    }
-  }
-
-  public static void parseFromSparseEntities(final EntityInterface e, final SparseEntity spe) {
-    if (spe != null) {
-      e.parseSparseEntity(spe);
-    }
-  }
-
-  public static void parseOverrides(final List<FlatProperty> properties, final ResultSet rs)
-      throws SQLException {
-    while (rs.next()) {
-      final int property_id = rs.getInt("property_id");
-      for (final FlatProperty p : properties) {
-        if (p.id == property_id) {
-          final String name = bytes2UTF8(rs.getBytes("name_override"));
-          if (name != null) {
-            p.name = name;
-          }
-          final String desc = bytes2UTF8(rs.getBytes("desc_override"));
-          if (desc != null) {
-            p.desc = desc;
-          }
-          final String type = bytes2UTF8(rs.getBytes("type_override"));
-          if (type != null) {
-            p.type = type;
-          }
-          final String coll = bytes2UTF8(rs.getBytes("collection_override"));
-          if (coll != null) {
-            p.collection = coll;
-          }
-        }
-      }
-    }
-  }
-
-  public static List<FlatProperty> parsePropertyResultset(final ResultSet rs) throws SQLException {
-    final ArrayList<FlatProperty> ret = new ArrayList<FlatProperty>();
-    while (rs.next()) {
-      final FlatProperty fp = new FlatProperty();
-      fp.id = rs.getInt("PropertyID");
-      fp.value = bytes2UTF8(rs.getBytes("PropertyValue"));
-      fp.status = bytes2UTF8(rs.getBytes("PropertyStatus"));
-      fp.idx = rs.getInt("PropertyIndex");
-      ret.add(fp);
-    }
-    return ret;
-  }
-
-  /**
-   * Helper function for parsing MySQL results.
-   *
-   * <p>This function creates SparseEntities and parses only the name, the role and the acl of an
-   * entity.
-   *
-   * <p>Never returns null.
-   */
-  public static SparseEntity parseNameRoleACL(final ResultSet rs) throws SQLException {
-    final SparseEntity ret = new SparseEntity();
-    ret.role = bytes2UTF8(rs.getBytes("EntityRole"));
-    ret.name = bytes2UTF8(rs.getBytes("EntityName"));
-    ret.acl = bytes2UTF8(rs.getBytes("ACL"));
-    return ret;
-  }
-
-  /**
-   * Helper function for parsing MySQL results.
-   *
-   * <p>This function creates SparseEntities and parses all fields which belong to a SparseEntity:
-   * id, name, role, acl, description, datatype, and the file properties.
-   *
-   * <p>Never returns null.
-   */
-  public static SparseEntity parseEntityResultSet(final ResultSet rs) throws SQLException {
-    final SparseEntity ret = parseNameRoleACL(rs);
-    ret.id = rs.getInt("EntityID");
-    ret.description = bytes2UTF8(rs.getBytes("EntityDesc"));
-    ret.datatype = bytes2UTF8(rs.getBytes("Datatype"));
-    ret.collection = bytes2UTF8(rs.getBytes("Collection"));
-
-    ret.filePath = bytes2UTF8(rs.getBytes("FilePath"));
-    ret.fileSize = rs.getLong("FileSize");
-    ret.fileHash = bytes2UTF8(rs.getBytes("FileHash"));
-
-    ret.versionId = bytes2UTF8(rs.getBytes("Version"));
-    return ret;
-  }
-
-  public static ArrayList<VerySparseEntity> parseParentResultSet(final ResultSet rs)
-      throws SQLException {
-    final ArrayList<VerySparseEntity> ret = new ArrayList<VerySparseEntity>();
-    while (rs.next()) {
-      final VerySparseEntity vsp = new VerySparseEntity();
-      vsp.id = rs.getInt("ParentID");
-      vsp.name = bytes2UTF8(rs.getBytes("ParentName"));
-      vsp.description = bytes2UTF8(rs.getBytes("ParentDescription"));
-      vsp.role = bytes2UTF8(rs.getBytes("ParentRole"));
-      vsp.acl = bytes2UTF8(rs.getBytes("ACL"));
-      ret.add(vsp);
-    }
-    return ret;
-  }
-
-  public static <K extends EntityInterface> K parseEntityFromVerySparseEntity(
-      final K entity, final VerySparseEntity vse) {
-    entity.setId(new EntityID(vse.id));
-    entity.setName(vse.name);
-    entity.setRole(vse.role);
-    entity.setDescription(vse.description);
-    return entity;
-  }
-
-  public static void parseParentsFromVerySparseEntity(
-      final EntityInterface entity, final List<VerySparseEntity> pars) {
-    for (final VerySparseEntity vsp : pars) {
-      final Parent p = new Parent(new RetrieveEntity(new EntityID(vsp.id)));
-      p.setName(vsp.name);
-      p.setDescription(vsp.description);
-      entity.addParent(p);
-    }
-  }
-
-  private static void replace(
-      final Property p, final Map<EntityID, Property> domainMap, final boolean isHead) {
-    // ... find the corresponding domain and replace it
-    ReferenceValue ref;
-    try {
-      ref = ReferenceValue.parseReference(p.getValue());
-    } catch (final Message e) {
-      throw new RuntimeException("This should never happen.");
-    }
-    final EntityInterface replacement = domainMap.get(ref.getId());
-    if (replacement == null) {
-      if (isHead) {
-        throw new NullPointerException("Replacement was null");
-      }
-      // entity has been deleted (we are processing properties of an old entity version)
-      p.setValue(null);
-      p.addWarning(
-          new Message(
-              "The referenced entity has been deleted in the mean time and is not longer available."));
-      return;
-    }
-    if (replacement.isDescOverride()) {
-      p.setDescOverride(true);
-      p.setDescription(replacement.getDescription());
-    }
-    if (replacement.isNameOverride()) {
-      p.setNameOverride(true);
-      p.setName(replacement.getName());
-    }
-    if (replacement.isDatatypeOverride()) {
-      p.setDatatypeOverride(true);
-      p.setDatatype(replacement.getDatatype());
-    }
-    p.setProperties(replacement.getProperties());
-    p.setStatementStatus(replacement.getStatementStatus());
-    p.setValue(replacement.getValue());
-  }
-
-  public static void transformToDeepPropertyTree(
-      final EntityInterface e, final List<Property> protoProperties) {
-    // here we will store every domain we can find and we will index it by
-    // its id.
-    final Map<EntityID, Property> domainMap = new HashMap<>();
-
-    // loop over all properties and collect the domains
-    for (final Property p : protoProperties) {
-      // if this is a domain it represents a deeper branch of the property
-      // tree.
-      if (p.getRole() == Role.Domain) {
-        if (domainMap.containsKey(p.getId())) {
-          // aggregate the multiple values.
-          final Property domain = domainMap.get(p.getId());
-          if (!(domain.getValue() instanceof CollectionValue)) {
-            final SingleValue singleValue = (SingleValue) domain.getValue();
-            final CollectionValue vals = new CollectionValue();
-            final IndexedSingleValue iSingleValue =
-                new IndexedSingleValue(domain.getPIdx(), singleValue);
-            vals.add(iSingleValue);
-            domain.setValue(vals);
-          }
-          ((CollectionValue) domain.getValue()).add(p.getPIdx(), p.getValue());
-        } else {
-          domainMap.put(p.getId(), p);
-        }
-      }
-    }
-
-    // loop over all properties
-    final boolean isHead =
-        e.getVersion().getSuccessors() == null || e.getVersion().getSuccessors().isEmpty();
-    for (final Property p : protoProperties) {
-
-      // if this is a replacement
-      if (p.getStatementStatus() == StatementStatus.REPLACEMENT) {
-        replace(p, domainMap, isHead);
-      }
-
-      for (final Property subP : p.getProperties()) {
-        if (subP.getStatementStatus() == StatementStatus.REPLACEMENT) {
-          replace(subP, domainMap, isHead);
-        }
-      }
-
-      if (p.getId().equals(e.getId())) {
-        // this is the value of an abstract property.
-        e.setValue(p.getValue());
-      } else if (p.getRole() != Role.Domain) {
-        // if this is not a domain it is to be added to the properties
-        // of e.
-        e.addProperty(p);
-      }
-    }
-  }
-
-  public static ArrayList<Property> parseFromProtoProperties(final List<ProtoProperty> protos) {
-    final ArrayList<Property> ret = new ArrayList<Property>();
-    for (final ProtoProperty pp : protos) {
-      final Property property = parseFlatProperty(pp.property);
-      parseFromFlatProperties(property.getProperties(), pp.subProperties);
-      ret.add(property);
-    }
-    return ret;
-  }
-
-  private static void parseFromFlatProperties(
-      final List<Property> properties, final List<FlatProperty> props) {
-    for (final FlatProperty fp : props) {
-      final Property property = parseFlatProperty(fp);
-      properties.add(property);
-    }
-  }
-
-  private static Property parseFlatProperty(final FlatProperty fp) {
-    final Property property = new Property(new RetrieveEntity());
-    property.parseFlatProperty(fp);
-    return property;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7d364ae6376524ef892de3353d699547417717e
--- /dev/null
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java
@@ -0,0 +1,365 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2018 Research Group Biomedical Physics,
+ * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package org.caosdb.server.database.backend.implementation.MySQL;
+
+import com.google.common.base.Objects;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.caosdb.server.database.proto.FlatProperty;
+import org.caosdb.server.database.proto.ProtoProperty;
+import org.caosdb.server.database.proto.SparseEntity;
+import org.caosdb.server.database.proto.VerySparseEntity;
+import org.caosdb.server.datatype.AbstractCollectionDatatype;
+import org.caosdb.server.datatype.CollectionValue;
+import org.caosdb.server.datatype.GenericValue;
+import org.caosdb.server.entity.EntityID;
+import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.StatementStatus;
+import org.caosdb.server.entity.wrapper.Property;
+
+public class DatabaseUtils {
+
+  private static final String DELETED_REFERENCE_IN_PREVIOUS_VERSION =
+      "DELETED_REFERENCE_IN_PREVIOUS_VERSION";
+
+  public static final String bytes2UTF8(final byte[] bytes) {
+    if (bytes == null) {
+      return null;
+    } else {
+      return new String(bytes);
+    }
+  }
+
+  public static int deriveStage1Inserts(
+      final List<Property> stage1Inserts, final EntityInterface e) {
+    // entity value
+    if (e.hasValue()) {
+      final Property p = new Property(e);
+      p.setStatementStatus(StatementStatus.FIX);
+      p.setDatatype(e.getDatatype());
+      p.setValue(e.getValue());
+      p.setPIdx(0);
+      processPropertiesStage1(stage1Inserts, p, e);
+    }
+
+    for (final Property p : e.getProperties()) {
+      processPropertiesStage1(stage1Inserts, p, e);
+    }
+    int replacementCount = 0;
+    for (EntityInterface pp : stage1Inserts) {
+      if (pp instanceof Replacement) {
+        replacementCount++;
+      }
+    }
+    return replacementCount;
+  }
+
+  private static void processPropertiesStage1(
+      final List<Property> stage1Inserts, final Property p, final EntityInterface e) {
+
+    if (!p.isDescOverride()
+        && !p.isNameOverride()
+        && !p.isDatatypeOverride()
+        && (!p.hasProperties() || hasUniquePropertyId(p, e))
+        && !(p.getDatatype() instanceof AbstractCollectionDatatype)) {
+      stage1Inserts.add(p);
+    } else {
+      Replacement r = new Replacement(p);
+      stage1Inserts.add(r);
+      stage1Inserts.add(r.replacement);
+    }
+    processSubPropertiesStage1(stage1Inserts, p);
+  }
+
+  public static void deriveStage2Inserts(
+      final List<Property> stage2Inserts,
+      final List<Property> stage1Inserts,
+      Deque<EntityID> replacementIds,
+      EntityInterface entity) {
+    for (final Property p : stage1Inserts) {
+      if (p instanceof Replacement) {
+
+        EntityID replacementId = replacementIds.pop();
+        ((Replacement) p).setReplacementId(replacementId);
+
+        if (p.hasProperties()) {
+          for (Property subP : p.getProperties()) {
+            subP.setDomain(p);
+            stage2Inserts.add(subP);
+          }
+        }
+      } else {
+        if (p.hasProperties()) {
+          for (Property subP : p.getProperties()) {
+            stage2Inserts.add(subP);
+          }
+        }
+      }
+    }
+  }
+
+  private static boolean hasUniquePropertyId(final EntityInterface p, final EntityInterface e) {
+    final EntityID id = p.getId();
+    for (final EntityInterface p2 : e.getProperties()) {
+      if (Objects.equal(p2.getId(), id) && p2 != p) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private static void processSubPropertiesStage1(
+      final List<Property> stage1Inserts, final EntityInterface p) {
+    for (final Property subP : p.getProperties()) {
+      if (subP.hasProperties()) {
+        stage1Inserts.add(new Replacement(subP));
+        processSubPropertiesStage1(stage1Inserts, subP);
+      }
+    }
+  }
+
+  public static void parseOverrides(final List<ProtoProperty> properties, final ResultSet rs)
+      throws SQLException {
+    while (rs.next()) {
+      final int property_id = rs.getInt("property_id");
+      for (final FlatProperty p : properties) {
+        if (p.id == property_id) {
+          final String name = bytes2UTF8(rs.getBytes("name_override"));
+          if (name != null) {
+            p.name = name;
+          }
+          final String desc = bytes2UTF8(rs.getBytes("desc_override"));
+          if (desc != null) {
+            p.desc = desc;
+          }
+          final String type = bytes2UTF8(rs.getBytes("type_override"));
+          if (type != null) {
+            p.type = type;
+          }
+          final String coll = bytes2UTF8(rs.getBytes("collection_override"));
+          if (coll != null) {
+            p.collection = coll;
+          }
+        }
+      }
+    }
+  }
+
+  public static List<ProtoProperty> parsePropertyResultset(final ResultSet rs) throws SQLException {
+    final List<ProtoProperty> ret = new LinkedList<>();
+    while (rs.next()) {
+      ProtoProperty pp = new ProtoProperty();
+      pp.id = rs.getInt("PropertyID");
+      pp.value = bytes2UTF8(rs.getBytes("PropertyValue"));
+      pp.status = bytes2UTF8(rs.getBytes("PropertyStatus"));
+      pp.idx = rs.getInt("PropertyIndex");
+      ret.add(pp);
+    }
+    return ret;
+  }
+
+  /**
+   * Helper function for parsing MySQL results.
+   *
+   * <p>This function creates SparseEntities and parses only the name, the role and the acl of an
+   * entity.
+   *
+   * <p>Never returns null.
+   */
+  public static SparseEntity parseNameRoleACL(final ResultSet rs) throws SQLException {
+    final SparseEntity ret = new SparseEntity();
+    ret.role = bytes2UTF8(rs.getBytes("EntityRole"));
+    ret.name = bytes2UTF8(rs.getBytes("EntityName"));
+    ret.acl = bytes2UTF8(rs.getBytes("ACL"));
+    return ret;
+  }
+
+  /**
+   * Helper function for parsing MySQL results.
+   *
+   * <p>This function creates SparseEntities and parses all fields which belong to a SparseEntity:
+   * id, name, role, acl, description, datatype, and the file properties.
+   *
+   * <p>Never returns null.
+   */
+  public static SparseEntity parseEntityResultSet(final ResultSet rs) throws SQLException {
+    final SparseEntity ret = parseNameRoleACL(rs);
+    ret.id = rs.getInt("EntityID");
+    ret.description = bytes2UTF8(rs.getBytes("EntityDesc"));
+    ret.datatype = bytes2UTF8(rs.getBytes("Datatype"));
+    ret.collection = bytes2UTF8(rs.getBytes("Collection"));
+
+    ret.filePath = bytes2UTF8(rs.getBytes("FilePath"));
+    ret.fileSize = rs.getLong("FileSize");
+    ret.fileHash = bytes2UTF8(rs.getBytes("FileHash"));
+
+    ret.versionId = bytes2UTF8(rs.getBytes("Version"));
+    return ret;
+  }
+
+  public static LinkedList<VerySparseEntity> parseParentResultSet(final ResultSet rs)
+      throws SQLException {
+    final LinkedList<VerySparseEntity> ret = new LinkedList<>();
+    while (rs.next()) {
+      final VerySparseEntity vsp = new VerySparseEntity();
+      vsp.id = rs.getInt("ParentID");
+      vsp.name = bytes2UTF8(rs.getBytes("ParentName"));
+      vsp.description = bytes2UTF8(rs.getBytes("ParentDescription"));
+      vsp.role = bytes2UTF8(rs.getBytes("ParentRole"));
+      vsp.acl = bytes2UTF8(rs.getBytes("ACL"));
+      ret.add(vsp);
+    }
+    return ret;
+  }
+
+  public static ArrayList<Property> parseFromProtoProperties(
+      EntityInterface entity, List<ProtoProperty> protos) {
+    final ArrayList<Property> ret = new ArrayList<Property>();
+    for (final ProtoProperty pp : protos) {
+      if (pp.id.equals(entity.getId().toInteger())) {
+        if (pp.value != null) {
+          entity.setValue(new GenericValue(pp.value));
+        }
+        if (pp.collValues != null) {
+          CollectionValue value = new CollectionValue();
+          for (Object next : pp.collValues) {
+            if (next == null) {
+              value.add(null);
+            } else {
+              value.add(new GenericValue(next.toString()));
+            }
+          }
+          entity.setValue(value);
+        }
+
+      } else {
+        Message warning = null;
+        if (pp.status == DELETED_REFERENCE_IN_PREVIOUS_VERSION) {
+          pp.status = StatementStatus.FIX.name();
+          warning =
+              new Message(
+                  "The referenced entity has been deleted in the mean time and is not longer available.");
+        }
+        final Property property = parseFlatProperty(pp);
+        if (warning != null) {
+          property.addWarning(warning);
+        }
+        ret.add(property);
+      }
+    }
+    return ret;
+  }
+
+  private static Property parseFlatProperty(final ProtoProperty fp) {
+    final Property property = new Property(new RetrieveEntity());
+    property.parseProtoProperty(fp);
+    return property;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static LinkedList<ProtoProperty> transformToDeepPropertyTree(
+      List<ProtoProperty> properties, boolean isHead) {
+    LinkedList<ProtoProperty> result = new LinkedList<>();
+    Map<String, ProtoProperty> replacements = new HashMap<>();
+    for (ProtoProperty pp : properties) {
+      if (pp.status.equals(ReplacementStatus.REPLACEMENT.name())) {
+        replacements.put(pp.value, null);
+      }
+    }
+    Iterator<ProtoProperty> iterator = properties.iterator();
+    while (iterator.hasNext()) {
+      ProtoProperty pp = iterator.next();
+      if (replacements.containsKey(pp.id.toString())) {
+        ProtoProperty other = replacements.get(pp.id.toString());
+        // handle collection values
+        if (other != null) {
+          if (other.collValues == null) {
+            other.collValues = new LinkedList<>();
+            Entry<Integer, String> obj = new SimpleEntry<>(other.idx, other.value);
+            other.collValues.add(obj);
+            other.value = null;
+          }
+          other.collValues.add(new SimpleEntry<>(pp.idx, pp.value));
+        } else {
+          replacements.put(pp.id.toString(), pp);
+        }
+        iterator.remove();
+      }
+    }
+    for (ProtoProperty pp : properties) {
+      if (pp.status.equals(ReplacementStatus.REPLACEMENT.name())) {
+        replace(pp, replacements.get(pp.value), isHead);
+      }
+      if (pp.collValues != null) {
+        // sort
+        pp.collValues.sort(
+            (x, y) -> {
+              return ((Entry<Integer, String>) x).getKey() - ((Entry<Integer, String>) y).getKey();
+            });
+        pp.collValues =
+            pp.collValues
+                .stream()
+                .map((x) -> (Object) ((Entry<Integer, String>) x).getValue())
+                .collect(Collectors.toList());
+      }
+      result.add(pp);
+    }
+    return result;
+  }
+
+  /*
+   * This replace function is used during the retrieval of properties. It is
+   * basically the opposite of the Replacement class. It copies the information
+   * from the replacement object back into the Property.
+   */
+  private static void replace(ProtoProperty pp, ProtoProperty replacement, boolean isHead) {
+    if (replacement == null) {
+      if (isHead) {
+        throw new NullPointerException("Replacement was null");
+      }
+      // entity has been deleted (we are processing properties of an old entity version)
+      pp.value = null;
+      pp.status = DELETED_REFERENCE_IN_PREVIOUS_VERSION;
+      return;
+    }
+    pp.desc = replacement.desc;
+    pp.name = replacement.name;
+    pp.type = replacement.type;
+    pp.collection = replacement.collection;
+    pp.value = replacement.value;
+    pp.collValues = replacement.collValues;
+    pp.status = replacement.status;
+    pp.subProperties = replacement.subProperties;
+  }
+}
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java
index 09cae6886c2daa94d93ab76539a3e1343f5aecbe..474048f1a72331c3a2e35b38c5cb4c9b3e4d6543 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java
@@ -5,7 +5,6 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.List;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.GetAllNamesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java
index 7c3c5a33e160f3da6a8c2b89fe0487e0317c99da..becb56a2397fffa323f005507463a586bdecc0cf 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java
@@ -25,15 +25,28 @@ package org.caosdb.server.database.backend.implementation.MySQL;
 import static java.sql.Types.BIGINT;
 
 import java.sql.PreparedStatement;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLIntegrityConstraintViolationException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.InsertEntityPropertiesImpl;
 import org.caosdb.server.database.exceptions.IntegrityException;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.FlatProperty;
+import org.caosdb.server.datatype.AbstractCollectionDatatype;
 import org.caosdb.server.datatype.AbstractDatatype.Table;
+import org.caosdb.server.datatype.CollectionValue;
+import org.caosdb.server.datatype.IndexedSingleValue;
+import org.caosdb.server.datatype.ReferenceValue;
+import org.caosdb.server.datatype.SingleValue;
 import org.caosdb.server.entity.EntityID;
+import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.wrapper.Property;
 
 public class MySQLInsertEntityProperties extends MySQLTransaction
     implements InsertEntityPropertiesImpl {
@@ -46,7 +59,27 @@ public class MySQLInsertEntityProperties extends MySQLTransaction
   }
 
   @Override
-  public void execute(
+  public void execute(EntityInterface entity) {
+    final List<Property> stage1Inserts = new LinkedList<>();
+    final List<Property> stage2Inserts = new LinkedList<>();
+
+    final int domainCount = DatabaseUtils.deriveStage1Inserts(stage1Inserts, entity);
+
+    try {
+      final Deque<EntityID> replacementIds = registerReplacementId(domainCount);
+
+      DatabaseUtils.deriveStage2Inserts(stage2Inserts, stage1Inserts, replacementIds, entity);
+
+      insertStages(stage1Inserts, entity.getDomain(), entity.getId());
+      insertStages(stage2Inserts, entity.getId(), null);
+    } catch (final SQLIntegrityConstraintViolationException exc) {
+      throw new IntegrityException(exc);
+    } catch (SQLException | ConnectionException e) {
+      throw new TransactionException(e);
+    }
+  }
+
+  private void insertFlatProperty(
       final EntityID domain,
       final EntityID entity,
       final FlatProperty fp,
@@ -76,10 +109,124 @@ public class MySQLInsertEntityProperties extends MySQLTransaction
       stmt.execute();
     } catch (final SQLIntegrityConstraintViolationException exc) {
       throw new IntegrityException(exc);
-    } catch (final SQLException exc) {
-      throw new TransactionException(exc);
-    } catch (final ConnectionException exc) {
+    } catch (final SQLException | ConnectionException exc) {
       throw new TransactionException(exc);
     }
   }
+
+  private void insertStages(
+      final List<Property> inserts, final EntityID domain, final EntityID entity)
+      throws TransactionException {
+
+    for (final Property property : inserts) {
+
+      // prepare flat property
+      final FlatProperty fp = new FlatProperty();
+      Table table = Table.null_data;
+      Long unit_sig = null;
+      fp.id = property.getId().toInteger();
+      fp.idx = property.getPIdx();
+      fp.status = property.getStatementStatus().name();
+
+      if (property.getStatementStatus() == ReplacementStatus.REPLACEMENT) {
+        // special treatment: swap value and id. This is part of the back-end specification for the
+        // representation of replacement. The reason why this happens here (and
+        // not in the replacement class for instance) is that the original
+        // Property must not be changed for this. Otherwise we would have to
+        // change it back after the insertion or internally used replacement ids
+        // would be leaked.
+
+        // value is to be the id of the property which is being replaced
+        fp.value = fp.id.toString();
+
+        // id is to be the replacement id (an internally used/private id)
+        fp.id = ((ReferenceValue) property.getValue()).getId().toInteger();
+        table = Table.reference_data;
+      } else {
+
+        if (property.hasUnit()) {
+          unit_sig = property.getUnit().getSignature();
+        }
+        if (property.hasValue()) {
+          if (property.getValue() instanceof CollectionValue) {
+            // insert collection of values
+            final CollectionValue v = (CollectionValue) property.getValue();
+            final Iterator<IndexedSingleValue> iterator = v.iterator();
+            final SingleValue firstValue = iterator.next();
+
+            // insert 2nd to nth item
+            for (int i = 1; i < v.size(); i++) {
+              final SingleValue vi = iterator.next();
+              fp.idx = i;
+              if (vi == null) {
+                fp.value = null;
+                table = Table.null_data;
+              } else {
+                fp.value = vi.toDatabaseString();
+                table = vi.getTable();
+              }
+              insertFlatProperty(
+                  domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig);
+            }
+
+            // insert first item
+            fp.idx = 0;
+            if (firstValue == null) {
+              fp.value = null;
+              table = Table.null_data;
+            } else {
+              fp.value = firstValue.toDatabaseString();
+              table = firstValue.getTable();
+            }
+
+          } else {
+            // insert single value
+            fp.value = ((SingleValue) property.getValue()).toDatabaseString();
+            if (property instanceof Property && ((Property) property).isName()) {
+              table = Table.name_data;
+            } else {
+              table = ((SingleValue) property.getValue()).getTable();
+            }
+          }
+        }
+        if (property.isNameOverride()) {
+          fp.name = property.getName();
+        }
+        if (property.isDescOverride()) {
+          fp.desc = property.getDescription();
+        }
+        if (property.isDatatypeOverride()) {
+          if (property.getDatatype() instanceof AbstractCollectionDatatype) {
+            fp.type =
+                ((AbstractCollectionDatatype) property.getDatatype())
+                    .getDatatype()
+                    .getId()
+                    .toString();
+            fp.collection =
+                ((AbstractCollectionDatatype) property.getDatatype()).getCollectionName();
+          } else {
+            fp.type = property.getDatatype().getId().toString();
+          }
+        }
+      }
+
+      insertFlatProperty(
+          domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig);
+    }
+  }
+
+  public static final String STMT_REGISTER_SUBDOMAIN = "call registerSubdomain(?)";
+
+  public Deque<EntityID> registerReplacementId(final int domainCount)
+      throws SQLException, ConnectionException {
+    final PreparedStatement stmt = prepareStatement(STMT_REGISTER_SUBDOMAIN);
+    stmt.setInt(1, domainCount);
+    try (final ResultSet rs = stmt.executeQuery()) {
+      final Deque<EntityID> ret = new ArrayDeque<>();
+      while (rs.next()) {
+        ret.add(new EntityID(rs.getInt(1)));
+      }
+      return ret;
+    }
+  }
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java
index 1b945acf566db938f12847f758636dbfddeac3d1..27227926156fc9b35ab75473e1efa2cce55f152e 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java
@@ -26,7 +26,6 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLIntegrityConstraintViolationException;
 import java.sql.Types;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.InsertSparseEntityImpl;
 import org.caosdb.server.database.exceptions.IntegrityException;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRegisterSubDomain.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRegisterSubDomain.java
deleted file mode 100644
index 1e14600e71023dff544a09910fabe0dc8b17ca42..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRegisterSubDomain.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.database.backend.implementation.MySQL;
-
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.interfaces.RegisterSubDomainImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.entity.EntityID;
-
-public class MySQLRegisterSubDomain extends MySQLTransaction implements RegisterSubDomainImpl {
-
-  public MySQLRegisterSubDomain(final Access access) {
-    super(access);
-  }
-
-  public static final String STMT_REGISTER_SUBDOMAIN = "call registerSubdomain(?)";
-
-  @Override
-  public Deque<EntityID> execute(final int domainCount) throws TransactionException {
-    try {
-      final PreparedStatement stmt = prepareStatement(STMT_REGISTER_SUBDOMAIN);
-      stmt.setInt(1, domainCount);
-      final ResultSet rs = stmt.executeQuery();
-      try {
-        final Deque<EntityID> ret = new ArrayDeque<>();
-        while (rs.next()) {
-          ret.add(new EntityID(rs.getInt(1)));
-        }
-        return ret;
-      } finally {
-        rs.close();
-      }
-    } catch (final SQLException e) {
-      throw new TransactionException(e);
-    } catch (final ConnectionException e) {
-      throw new TransactionException(e);
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
index f847bf63e9f72b4632cecf55ee4a132f141a2cd1..9a2ea81629853e3baf9de7757e1abe6c8f01fe28 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
@@ -27,7 +27,6 @@ import java.sql.SQLException;
 import java.util.LinkedList;
 import java.util.List;
 import org.apache.shiro.SecurityUtils;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.RetrieveAllImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java
index e085f0510a5c0a95f1e0a3bc2f702b4e345d706e..685249417ca9c1664cc1a30af3fc3a597788c29f 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java
@@ -25,8 +25,7 @@ package org.caosdb.server.database.backend.implementation.MySQL;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.ArrayList;
-import org.caosdb.server.database.DatabaseUtils;
+import java.util.List;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
@@ -53,7 +52,7 @@ public class MySQLRetrieveDatatypes extends MySQLTransaction implements Retrieve
           + "FROM entities AS e WHERE e.role='DATATYPE'";
 
   @Override
-  public ArrayList<VerySparseEntity> execute() throws TransactionException {
+  public List<VerySparseEntity> execute() throws TransactionException {
     try {
       final PreparedStatement stmt = prepareStatement(STMT_GET_DATATYPE);
 
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java
index 64ce7e9023bd18506a4b13610033923fa7320ca8..1e2a6621d2fb85a5a5260b1a607670e970f54701 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java
@@ -26,8 +26,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.ArrayList;
-import org.caosdb.server.database.DatabaseUtils;
+import java.util.LinkedList;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.RetrieveParentsImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
@@ -43,7 +42,7 @@ public class MySQLRetrieveParents extends MySQLTransaction implements RetrievePa
   private static final String stmtStr = "call retrieveEntityParents(?, ?)";
 
   @Override
-  public ArrayList<VerySparseEntity> execute(final EntityID id, final String version)
+  public LinkedList<VerySparseEntity> execute(final EntityID id, final String version)
       throws TransactionException {
     try {
       ResultSet rs = null;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java
index 1ff7dd7afd0b468a90d9e3b82faca1adc5cae84f..9fd7e0a8e441b3ede2a3447ec364c24e16898fb5 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.database.backend.implementation.MySQL;
 
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.security.NoSuchAlgorithmException;
 import java.sql.PreparedStatement;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
index 6bcfeac05cd3631f05615d26741c47f4c5c1bf63..100df4ce72ebae75d3022866b345bacc9752ad91 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
@@ -26,13 +26,11 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.RetrievePropertiesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.FlatProperty;
 import org.caosdb.server.database.proto.ProtoProperty;
 import org.caosdb.server.entity.EntityID;
 
@@ -46,27 +44,20 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
   private static final String stmtStr2 = "call retrieveOverrides(?,?,?)";
 
   @Override
-  public ArrayList<ProtoProperty> execute(final EntityID entity, final String version)
-      throws TransactionException {
+  public LinkedList<ProtoProperty> execute(
+      final EntityID entity, final String version, boolean isHead) throws TransactionException {
     try {
       final PreparedStatement prepareStatement = prepareStatement(stmtStr);
 
-      final List<FlatProperty> props =
+      final List<ProtoProperty> propsStage1 =
           retrieveFlatPropertiesStage1(0, entity.toInteger(), version, prepareStatement);
 
-      final ArrayList<ProtoProperty> protos = new ArrayList<ProtoProperty>();
-      for (final FlatProperty p : props) {
-        final ProtoProperty proto = new ProtoProperty();
-        proto.property = p;
+      for (final ProtoProperty p : propsStage1) {
 
-        final List<FlatProperty> subProps =
+        p.subProperties =
             retrieveFlatPropertiesStage1(entity.toInteger(), p.id, version, prepareStatement);
-
-        proto.subProperties = subProps;
-
-        protos.add(proto);
       }
-      return protos;
+      return DatabaseUtils.transformToDeepPropertyTree(propsStage1, isHead);
     } catch (final SQLException e) {
       throw new TransactionException(e);
     } catch (final ConnectionException e) {
@@ -74,43 +65,38 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
     }
   }
 
-  private List<FlatProperty> retrieveFlatPropertiesStage1(
+  private List<ProtoProperty> retrieveFlatPropertiesStage1(
       final Integer domain,
       final Integer entity,
       final String version,
       final PreparedStatement stmt)
       throws SQLException, ConnectionException {
-    ResultSet rs = null;
-    try {
-      if (domain != null && domain >= 0) {
-        stmt.setInt(1, domain);
-      } else {
-        stmt.setInt(1, 0);
-      }
+    if (domain != null && domain >= 0) {
+      stmt.setInt(1, domain);
+    } else {
+      stmt.setInt(1, 0);
+    }
 
-      stmt.setInt(2, entity);
-      if (version == null) {
-        stmt.setNull(3, Types.VARBINARY);
-      } else {
-        stmt.setString(3, version);
-      }
+    stmt.setInt(2, entity);
+    if (version == null) {
+      stmt.setNull(3, Types.VARBINARY);
+    } else {
+      stmt.setString(3, version);
+    }
 
-      final long t1 = System.currentTimeMillis();
-      rs = stmt.executeQuery();
+    final long t1 = System.currentTimeMillis();
+
+    try (ResultSet rs = stmt.executeQuery()) {
       final long t2 = System.currentTimeMillis();
       addMeasurement(this.getClass().getSimpleName() + ".retrieveFlatPropertiesStage1", t2 - t1);
 
-      final List<FlatProperty> props = DatabaseUtils.parsePropertyResultset(rs);
+      final List<ProtoProperty> properties = DatabaseUtils.parsePropertyResultset(rs);
 
-      final PreparedStatement stmt2 = prepareStatement(stmtStr2);
+      final PreparedStatement retrieveOverrides = prepareStatement(stmtStr2);
 
-      retrieveOverrides(domain, entity, version, stmt2, props);
+      retrieveOverrides(domain, entity, version, retrieveOverrides, properties);
 
-      return props;
-    } finally {
-      if (rs != null && !rs.isClosed()) {
-        rs.close();
-      }
+      return properties;
     }
   }
 
@@ -118,24 +104,25 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
       final Integer domain,
       final Integer entity,
       final String version,
-      final PreparedStatement stmt2,
-      final List<FlatProperty> props)
+      final PreparedStatement retrieveOverrides,
+      final List<ProtoProperty> props)
       throws SQLException {
 
     ResultSet rs = null;
     try {
-      stmt2.setInt(1, domain);
-      stmt2.setInt(2, entity);
+      retrieveOverrides.setInt(1, domain);
+      retrieveOverrides.setInt(2, entity);
       if (version == null) {
-        stmt2.setNull(3, Types.VARBINARY);
+        retrieveOverrides.setNull(3, Types.VARBINARY);
       } else {
-        stmt2.setString(3, version);
+        retrieveOverrides.setString(3, version);
       }
       final long t1 = System.currentTimeMillis();
-      rs = stmt2.executeQuery();
+      rs = retrieveOverrides.executeQuery();
       final long t2 = System.currentTimeMillis();
       addMeasurement(this.getClass().getSimpleName() + ".retrieveOverrides", t2 - t1);
       DatabaseUtils.parseOverrides(props, rs);
+
     } finally {
       if (rs != null && !rs.isClosed()) {
         rs.close();
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java
index ca3bd078daba4c72e7f516ce64b1da1515a1a6eb..48cd84b3869262ba7f1d8129fdde2335bed6cd8e 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java
@@ -26,7 +26,6 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java
index bde1878db5d49b2da201b7d589c1ee8b8d73f656..40f036f1d63753c0be9e6153f741db7d4f9d04ad 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.database.backend.implementation.MySQL;
 
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java
index 585be49afe93dc94c6ba2bc4f058c5d46fcf40f6..6c4c2629a5effee25128f478bef20f5558a027ba 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java
@@ -27,7 +27,6 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.LinkedList;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java
index 1c8039253b4c5e74a8054dfa937fec23c55440e8..b2d0fb9e3b7615e7e95243a2a7bde2fa99e98ca2 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java
@@ -27,7 +27,6 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLIntegrityConstraintViolationException;
 import java.sql.Types;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.UpdateSparseEntityImpl;
 import org.caosdb.server.database.exceptions.IntegrityException;
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/Replacement.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/Replacement.java
new file mode 100644
index 0000000000000000000000000000000000000000..61e431bab60b9973f6aa1f0b824ffc3e3b60744e
--- /dev/null
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/Replacement.java
@@ -0,0 +1,63 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package org.caosdb.server.database.backend.implementation.MySQL;
+
+import org.caosdb.server.datatype.ReferenceValue;
+import org.caosdb.server.entity.EntityID;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.StatementStatusInterface;
+import org.caosdb.server.entity.wrapper.Property;
+
+enum ReplacementStatus implements StatementStatusInterface {
+  REPLACEMENT
+}
+/**
+ * A wrapper of {@link Property} objects used by the back-end implementation for the MySQL/MariaDB
+ * back-end.
+ *
+ * <p>This class helps to transform deeply nested properties, properties with overridden data types,
+ * and much more to the flat (row-like) representation used internally by the back-end (which is an
+ * implementation detail of the back-end or part of the non-public API of the back-end from that
+ * point of view).
+ */
+public class Replacement extends Property {
+
+  public Property replacement;
+
+  @Override
+  public EntityID getId() {
+    return replacement.getId();
+  }
+
+  public void setReplacementId(EntityID id) {
+    replacement.getId().link(id);
+  }
+
+  public Replacement(Property p) {
+    super(p);
+    replacement = new Property(new RetrieveEntity());
+
+    replacement.setDomain(p.getDomainEntity());
+    replacement.setId(new EntityID());
+    replacement.setStatementStatus(ReplacementStatus.REPLACEMENT);
+    replacement.setValue(new ReferenceValue(p.getId()));
+    replacement.setPIdx(0);
+  }
+}
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java
index 2406d4895487bdec5f271b41bf46207a48c7375d..f17643fd6ff0e980aef122a318fd9fc2668e2b79 100644
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java
+++ b/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java
@@ -22,15 +22,10 @@
  */
 package org.caosdb.server.database.backend.interfaces;
 
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.FlatProperty;
-import org.caosdb.server.datatype.AbstractDatatype.Table;
-import org.caosdb.server.entity.EntityID;
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.utils.Undoable;
 
 public interface InsertEntityPropertiesImpl extends BackendTransactionImpl, Undoable {
 
-  public abstract void execute(
-      EntityID domain, EntityID entity, FlatProperty fp, Table table, Long unit_sig)
-      throws TransactionException;
+  public abstract void execute(EntityInterface entity);
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java
index 91c68dc56a31fe520d6b29c4b6780afe433f9039..0886ff4374e846acfd14e37b8d2414be912d6e3d 100644
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java
+++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java
@@ -22,11 +22,11 @@
  */
 package org.caosdb.server.database.backend.interfaces;
 
-import java.util.ArrayList;
+import java.util.List;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.VerySparseEntity;
 
 public interface RetrieveDatatypesImpl extends BackendTransactionImpl {
 
-  public abstract ArrayList<VerySparseEntity> execute() throws TransactionException;
+  public abstract List<VerySparseEntity> execute() throws TransactionException;
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java
index 9bfddba10a8b548d1259adb22aac392a127a8b5e..9f7eba821e8038befa47f35b31e017aa042bb28f 100644
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java
+++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java
@@ -22,13 +22,13 @@
  */
 package org.caosdb.server.database.backend.interfaces;
 
-import java.util.ArrayList;
+import java.util.LinkedList;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.VerySparseEntity;
 import org.caosdb.server.entity.EntityID;
 
 public interface RetrieveParentsImpl extends BackendTransactionImpl {
 
-  public ArrayList<VerySparseEntity> execute(EntityID id, String version)
+  public LinkedList<VerySparseEntity> execute(EntityID id, String version)
       throws TransactionException;
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java
index dc1d5aa09653c9a4905dcc456a7037f4009bb838..6bf2051ab1ada80bd09ca074b4b50b6af3abf574 100644
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java
+++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java
@@ -22,12 +22,13 @@
  */
 package org.caosdb.server.database.backend.interfaces;
 
-import java.util.ArrayList;
+import java.util.LinkedList;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.ProtoProperty;
 import org.caosdb.server.entity.EntityID;
 
 public interface RetrievePropertiesImpl extends BackendTransactionImpl {
 
-  public ArrayList<ProtoProperty> execute(EntityID id, String version) throws TransactionException;
+  public LinkedList<ProtoProperty> execute(EntityID id, String version, boolean isHead)
+      throws TransactionException;
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java
index f9af6a482efaccf896a1a85fc65d0e1217bd6a53..8de8afc084ae16dea370a519a4fde3524fa8c7c9 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java
@@ -22,25 +22,10 @@
  */
 package org.caosdb.server.database.backend.transaction;
 
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.List;
 import org.caosdb.server.database.BackendTransaction;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.backend.interfaces.InsertEntityPropertiesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.FlatProperty;
-import org.caosdb.server.datatype.AbstractCollectionDatatype;
-import org.caosdb.server.datatype.AbstractDatatype.Table;
-import org.caosdb.server.datatype.CollectionValue;
-import org.caosdb.server.datatype.IndexedSingleValue;
-import org.caosdb.server.datatype.SingleValue;
-import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.Role;
-import org.caosdb.server.entity.StatementStatus;
-import org.caosdb.server.entity.wrapper.Property;
 
 public class InsertEntityProperties extends BackendTransaction {
 
@@ -54,126 +39,6 @@ public class InsertEntityProperties extends BackendTransaction {
   public void execute() throws TransactionException {
 
     final InsertEntityPropertiesImpl t = getImplementation(InsertEntityPropertiesImpl.class);
-
-    final ArrayList<EntityInterface> stage1Inserts = new ArrayList<EntityInterface>();
-    final ArrayList<EntityInterface> stage2Inserts = new ArrayList<EntityInterface>();
-
-    DatabaseUtils.deriveStage1Inserts(stage1Inserts, this.entity);
-
-    final int domainCount = DatabaseUtils.deriveStage2Inserts(stage2Inserts, stage1Inserts);
-
-    final Deque<EntityID> domainIds = execute(new RegisterSubDomain(domainCount)).getDomains();
-
-    insertStages(t, domainIds, stage1Inserts, this.entity.getDomain(), this.entity.getId());
-    insertStages(t, domainIds, stage2Inserts, this.entity.getId(), null);
-  }
-
-  private void insertStages(
-      final InsertEntityPropertiesImpl t,
-      final Deque<EntityID> domainIds,
-      final List<EntityInterface> stage1Inserts,
-      final EntityID domain,
-      final EntityID entity)
-      throws TransactionException {
-
-    for (final EntityInterface property : stage1Inserts) {
-      if (property.hasRole() && property.getRole() == Role.Domain && !property.hasId()) {
-        property.setId(domainIds.removeFirst());
-      }
-      int pIdx;
-      if (property instanceof Property) {
-        // this is a normal property
-        pIdx = ((Property) property).getPIdx();
-      } else {
-        // this is a replacement
-        pIdx = 0;
-      }
-
-      // prepare flat property
-      final FlatProperty fp = new FlatProperty();
-      Table table = Table.null_data;
-      Long unit_sig = null;
-      fp.id = property.getId().toInteger();
-      fp.idx = pIdx;
-
-      if (property.hasReplacement()) {
-        if (!property.getReplacement().hasId()) {
-          property.getReplacement().setId(domainIds.removeFirst());
-        }
-
-        fp.value = property.getReplacement().getId().toString();
-        fp.status = StatementStatus.REPLACEMENT.toString();
-
-        table = Table.reference_data;
-      } else {
-        if (property.hasUnit()) {
-          unit_sig = property.getUnit().getSignature();
-        }
-        fp.status = property.getStatementStatus().toString();
-        if (property.hasValue()) {
-          if (property.getValue() instanceof CollectionValue) {
-            // insert collection of values
-            final CollectionValue v = (CollectionValue) property.getValue();
-            final Iterator<IndexedSingleValue> iterator = v.iterator();
-            final SingleValue firstValue = iterator.next();
-
-            // insert 2nd to nth item
-            for (int i = 1; i < v.size(); i++) {
-              final SingleValue vi = iterator.next();
-              fp.idx = i;
-              if (vi == null) {
-                fp.value = null;
-                table = Table.null_data;
-              } else {
-                fp.value = vi.toDatabaseString();
-                table = vi.getTable();
-              }
-              t.execute(
-                  domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig);
-            }
-
-            // insert first item
-            fp.idx = 0;
-            if (firstValue == null) {
-              fp.value = null;
-              table = Table.null_data;
-            } else {
-              fp.value = firstValue.toDatabaseString();
-              table = firstValue.getTable();
-            }
-
-          } else {
-            // insert single value
-            fp.value = ((SingleValue) property.getValue()).toDatabaseString();
-            if (property instanceof Property && ((Property) property).isName()) {
-              table = Table.name_data;
-            } else {
-              table = ((SingleValue) property.getValue()).getTable();
-            }
-          }
-        }
-        if (property.isNameOverride()) {
-          fp.name = property.getName();
-        }
-        if (property.isDescOverride()) {
-          fp.desc = property.getDescription();
-        }
-        if (property.isDatatypeOverride()) {
-          if (property.getDatatype() instanceof AbstractCollectionDatatype) {
-            fp.type =
-                ((AbstractCollectionDatatype) property.getDatatype())
-                    .getDatatype()
-                    .getId()
-                    .toString();
-            fp.collection =
-                ((AbstractCollectionDatatype) property.getDatatype()).getCollectionName();
-          } else {
-            fp.type = property.getDatatype().getId().toString();
-          }
-        }
-      }
-
-      t.execute(domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig);
-    }
+    t.execute(this.entity);
   }
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
index 5871c97e8ff172fe58b37627cc93824e96c7b32e..2846fa64d56ea696d4ecb0412a88de449d0483a1 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
@@ -54,7 +54,9 @@ public class InsertEntityTransaction extends BackendTransaction {
         if (newEntity.hasFileProperties()) {
           execute(new InsertFile(newEntity));
         }
-        execute(new InsertEntityProperties(newEntity));
+        if (newEntity.hasProperties() || newEntity.hasValue()) {
+          execute(new InsertEntityProperties(newEntity));
+        }
         execute(new InsertParents(newEntity));
 
         if (newEntity.getEntityStatus() == EntityStatus.QUALIFIED) {
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RegisterSubDomain.java b/src/main/java/org/caosdb/server/database/backend/transaction/RegisterSubDomain.java
deleted file mode 100644
index 0321a621a82a9ae2841d31b55b05a46743f963bd..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RegisterSubDomain.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.database.backend.transaction;
-
-import java.util.Deque;
-import org.caosdb.server.database.BackendTransaction;
-import org.caosdb.server.database.backend.interfaces.RegisterSubDomainImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.entity.EntityID;
-
-public class RegisterSubDomain extends BackendTransaction {
-
-  private final int domainCount;
-  private Deque<EntityID> list;
-
-  public RegisterSubDomain(final int domainCount) {
-    this.domainCount = domainCount;
-  }
-
-  @Override
-  public void execute() throws TransactionException {
-    final RegisterSubDomainImpl t = getImplementation(RegisterSubDomainImpl.class);
-    this.list = t.execute(this.domainCount);
-  }
-
-  public Deque<EntityID> getDomains() {
-    return this.list;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java
index b4a726da8d7b352f69df23a54371feef2d2201c5..1ca9f60e964f1e18ca49b749dde21f8e8785aaa6 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.database.backend.transaction;
 
-import java.util.ArrayList;
+import java.util.List;
 import org.caosdb.server.database.BackendTransaction;
 import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
@@ -35,7 +35,7 @@ import org.caosdb.server.entity.container.Container;
 
 public class RetrieveDatatypes extends BackendTransaction {
 
-  private ArrayList<VerySparseEntity> list;
+  private List<VerySparseEntity> list;
 
   @Override
   public void execute() throws TransactionException {
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java
index b551430a26ed6a254789258aca688521f5a57ae4..262ee271acf5b6d3652a0177b0818cbf1e9f74ee 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java
@@ -24,15 +24,18 @@
  */
 package org.caosdb.server.database.backend.transaction;
 
-import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
 import org.apache.commons.jcs.access.behavior.ICacheAccess;
 import org.caosdb.server.caching.Cache;
 import org.caosdb.server.database.CacheableBackendTransaction;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.backend.interfaces.RetrieveParentsImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.VerySparseEntity;
+import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.wrapper.Parent;
 
 // TODO Problem with the caching.
 // When an old entity version has a parent which is deleted, the name is
@@ -47,9 +50,9 @@ import org.caosdb.server.entity.EntityInterface;
 // See also a failing test in caosdb-pyinttest:
 // tests/test_version.py::test_bug_cached_parent_name_in_old_version
 public class RetrieveParents
-    extends CacheableBackendTransaction<String, ArrayList<VerySparseEntity>> {
+    extends CacheableBackendTransaction<String, LinkedList<VerySparseEntity>> {
 
-  private static final ICacheAccess<String, ArrayList<VerySparseEntity>> cache =
+  private static final ICacheAccess<String, LinkedList<VerySparseEntity>> cache =
       Cache.getCache("BACKEND_EntityParents");
 
   /**
@@ -71,16 +74,26 @@ public class RetrieveParents
   }
 
   @Override
-  public ArrayList<VerySparseEntity> executeNoCache() throws TransactionException {
+  public LinkedList<VerySparseEntity> executeNoCache() throws TransactionException {
     final RetrieveParentsImpl t = getImplementation(RetrieveParentsImpl.class);
     return t.execute(this.entity.getId(), this.entity.getVersion().getId());
   }
 
   @Override
-  protected void process(final ArrayList<VerySparseEntity> t) throws TransactionException {
+  protected void process(final LinkedList<VerySparseEntity> t) throws TransactionException {
     this.entity.getParents().clear();
 
-    DatabaseUtils.parseParentsFromVerySparseEntity(this.entity, t);
+    parseParentsFromVerySparseEntity(this.entity, t);
+  }
+
+  private void parseParentsFromVerySparseEntity(
+      final EntityInterface entity, final List<VerySparseEntity> pars) {
+    for (final VerySparseEntity vsp : pars) {
+      final Parent p = new Parent(new RetrieveEntity(new EntityID(vsp.id)));
+      p.setName(vsp.name);
+      p.setDescription(vsp.description);
+      entity.addParent(p);
+    }
   }
 
   @Override
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java
index 726382f28c59a45a647098c3d227288fa1fabff6..f9c11303b31ac81696873876b532f9db95f3dbac 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java
@@ -24,11 +24,12 @@
  */
 package org.caosdb.server.database.backend.transaction;
 
-import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
 import org.apache.commons.jcs.access.behavior.ICacheAccess;
 import org.caosdb.server.caching.Cache;
 import org.caosdb.server.database.CacheableBackendTransaction;
-import org.caosdb.server.database.DatabaseUtils;
+import org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils;
 import org.caosdb.server.database.backend.interfaces.RetrievePropertiesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.ProtoProperty;
@@ -37,11 +38,11 @@ import org.caosdb.server.entity.Role;
 import org.caosdb.server.entity.wrapper.Property;
 
 public class RetrieveProperties
-    extends CacheableBackendTransaction<String, ArrayList<ProtoProperty>> {
+    extends CacheableBackendTransaction<String, LinkedList<ProtoProperty>> {
 
   private final EntityInterface entity;
   public static final String CACHE_REGION = "BACKEND_EntityProperties";
-  private static final ICacheAccess<String, ArrayList<ProtoProperty>> cache =
+  private static final ICacheAccess<String, LinkedList<ProtoProperty>> cache =
       Cache.getCache(CACHE_REGION);
 
   /**
@@ -61,40 +62,46 @@ public class RetrieveProperties
   }
 
   @Override
-  public ArrayList<ProtoProperty> executeNoCache() throws TransactionException {
+  public LinkedList<ProtoProperty> executeNoCache() throws TransactionException {
     final RetrievePropertiesImpl t = getImplementation(RetrievePropertiesImpl.class);
-    return t.execute(this.entity.getId(), this.entity.getVersion().getId());
+    return t.execute(
+        this.entity.getId(), this.entity.getVersion().getId(), this.entity.getVersion().isHead());
   }
 
   @Override
-  protected void process(final ArrayList<ProtoProperty> t) throws TransactionException {
+  protected void process(final LinkedList<ProtoProperty> t) throws TransactionException {
     this.entity.getProperties().clear();
 
-    final ArrayList<Property> props = DatabaseUtils.parseFromProtoProperties(t);
+    final List<Property> props = DatabaseUtils.parseFromProtoProperties(this.entity, t);
+    this.entity.getProperties().addAll(props);
 
-    for (final Property p : props) {
+    retrieveSparseSubproperties(props);
+  }
+
+  private void retrieveSparseSubproperties(List<Property> properties) {
+    for (final Property p : properties) {
       // retrieve sparse properties stage 1
+
+      // handle corner case where a record type is used as a property with an overridden name.
+      boolean isNameOverride = p.isNameOverride();
+      String overrideName = p.getName();
+      p.setNameOverride(false);
+
       final RetrieveSparseEntity t1 = new RetrieveSparseEntity(p);
       execute(t1);
+      String originalName = p.getName();
+      if (isNameOverride) {
+        p.setNameOverride(isNameOverride);
+        p.setName(overrideName);
+      }
 
-      // add default data type for record types
       if (!p.hasDatatype() && p.getRole() == Role.RecordType) {
-        p.setDatatype(p.getName());
+        p.setDatatype(originalName);
       }
 
       // retrieve sparse properties stage 2
-      for (final EntityInterface subP : p.getProperties()) {
-        final RetrieveSparseEntity t2 = new RetrieveSparseEntity(subP);
-        execute(t2);
-
-        // add default data type for record types
-        if (!subP.hasDatatype() && subP.getRole() == Role.RecordType) {
-          subP.setDatatype(subP.getName());
-        }
-      }
+      retrieveSparseSubproperties(p.getProperties());
     }
-
-    DatabaseUtils.transformToDeepPropertyTree(this.entity, props);
   }
 
   @Override
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java
index aabd490b0fb8a6b5e801f6132113726a9361d258..f1ba8354f98fb0f6716fcfe0a742a597b69dd0a3 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java
@@ -27,7 +27,6 @@ package org.caosdb.server.database.backend.transaction;
 import org.apache.commons.jcs.access.behavior.ICacheAccess;
 import org.caosdb.server.caching.Cache;
 import org.caosdb.server.database.CacheableBackendTransaction;
-import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.SparseEntity;
@@ -81,8 +80,10 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<String, Sp
 
   @Override
   protected void process(final SparseEntity t) throws TransactionException {
-    DatabaseUtils.parseFromSparseEntities(this.entity, t);
-    this.entity.setEntityStatus(EntityStatus.VALID);
+    if (t != null) {
+      this.entity.parseSparseEntity(t);
+      this.entity.setEntityStatus(EntityStatus.VALID);
+    }
   }
 
   @Override
diff --git a/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java b/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java
index 44acaa024513d3f4b8d89c3f59bda9baf9de3044..7a11cd4daf6369649a0205a5637d3e86fbafbffe 100644
--- a/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java
+++ b/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java
@@ -25,9 +25,9 @@ package org.caosdb.server.database.proto;
 import java.io.Serializable;
 import java.util.List;
 
-public class ProtoProperty implements Serializable {
-
+public class ProtoProperty extends FlatProperty implements Serializable {
   private static final long serialVersionUID = 7731301985162924975L;
-  public List<FlatProperty> subProperties = null;
-  public FlatProperty property = null;
+
+  public List<ProtoProperty> subProperties = null;
+  public List<Object> collValues = null;
 }
diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java
index 48367508112c2c3267abd771fa84eb21ea16a534..614397c50526606176fc50b06e2f07e76cbfb9ff 100644
--- a/src/main/java/org/caosdb/server/entity/Entity.java
+++ b/src/main/java/org/caosdb/server/entity/Entity.java
@@ -47,7 +47,6 @@ import org.caosdb.server.datatype.Value;
 import org.caosdb.server.entity.Message.MessageType;
 import org.caosdb.server.entity.container.ParentContainer;
 import org.caosdb.server.entity.container.PropertyContainer;
-import org.caosdb.server.entity.wrapper.Domain;
 import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.entity.xml.EntityToElementStrategy;
@@ -422,7 +421,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa
     }
   }
 
-  private StatementStatus statementStatus = null;
+  private StatementStatusInterface statementStatus = null;
 
   /**
    * statementStatus getter.
@@ -430,7 +429,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa
    * @return
    */
   @Override
-  public StatementStatus getStatementStatus() {
+  public StatementStatusInterface getStatementStatus() {
     return this.statementStatus;
   }
 
@@ -440,7 +439,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa
    * @return
    */
   @Override
-  public void setStatementStatus(final StatementStatus statementStatus) {
+  public void setStatementStatus(final StatementStatusInterface statementStatus) {
     this.statementStatus = statementStatus;
   }
 
@@ -609,10 +608,11 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa
 
   @Override
   public void parseValue() throws Message {
-    if (!this.isParsed) {
-      this.isParsed = true;
-      setValue(getDatatype().parseValue(getValue()));
+    if (this.isParsed) {
+      return;
     }
+    this.isParsed = true;
+    if (getValue() != null) setValue(getDatatype().parseValue(getValue()));
   }
 
   @Override
@@ -848,23 +848,6 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa
     }
   }
 
-  @Override
-  public void setReplacement(final Domain d) {
-    this.replacement = d;
-  }
-
-  @Override
-  public boolean hasReplacement() {
-    return this.replacement != null;
-  }
-
-  @Override
-  public Domain getReplacement() {
-    return this.replacement;
-  }
-
-  private Domain replacement = null;
-
   private final HashMap<String, String> flags = new HashMap<String, String>();
 
   @Override
diff --git a/src/main/java/org/caosdb/server/entity/EntityInterface.java b/src/main/java/org/caosdb/server/entity/EntityInterface.java
index f967b307a1ea13a16d7e9225cb038a15f6883727..ba77b5c4ac8b5c833d2046dec506655531ff62cb 100644
--- a/src/main/java/org/caosdb/server/entity/EntityInterface.java
+++ b/src/main/java/org/caosdb/server/entity/EntityInterface.java
@@ -31,7 +31,6 @@ import org.caosdb.server.datatype.AbstractDatatype;
 import org.caosdb.server.datatype.Value;
 import org.caosdb.server.entity.container.ParentContainer;
 import org.caosdb.server.entity.container.PropertyContainer;
-import org.caosdb.server.entity.wrapper.Domain;
 import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.entity.xml.SerializeFieldStrategy;
@@ -58,9 +57,9 @@ public interface EntityInterface
 
   public abstract boolean hasId();
 
-  public abstract StatementStatus getStatementStatus();
+  public abstract StatementStatusInterface getStatementStatus();
 
-  public abstract void setStatementStatus(StatementStatus statementStatus);
+  public abstract void setStatementStatus(StatementStatusInterface statementStatus);
 
   public abstract void setStatementStatus(String statementStatus);
 
@@ -127,12 +126,6 @@ public interface EntityInterface
 
   public abstract void setProperties(PropertyContainer properties);
 
-  public abstract void setReplacement(Domain d);
-
-  public abstract boolean hasReplacement();
-
-  public abstract Domain getReplacement();
-
   public abstract EntityInterface setDescOverride(boolean b);
 
   public abstract boolean isDescOverride();
diff --git a/src/main/java/org/caosdb/server/entity/StatementStatus.java b/src/main/java/org/caosdb/server/entity/StatementStatus.java
index 0a7971751bffcdfc0323835433fc006bf5269ec9..83d0c57e4ece787e53efa2e149ff0910e1850ed5 100644
--- a/src/main/java/org/caosdb/server/entity/StatementStatus.java
+++ b/src/main/java/org/caosdb/server/entity/StatementStatus.java
@@ -33,17 +33,11 @@ package org.caosdb.server.entity;
  * href="../../../../../specification/RecordType.html">the documentation of RecordTypes</a> for more
  * information.
  *
- * <p>2. Marking an entity as a ``REPLACEMENT`` which is needed for flat representation of deeply
- * nested properties. This constant is only used for internal processes and has no meaning in the
- * API. That is also the reason why this enum is not called "Importance". Apart from that, in most
- * cases its meaning is identical to the importance of an entity.
- *
  * @author Timm Fitschen (t.fitschen@indiscale.com)
  */
-public enum StatementStatus {
+public enum StatementStatus implements StatementStatusInterface {
   OBLIGATORY,
   RECOMMENDED,
   SUGGESTED,
   FIX,
-  REPLACEMENT
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RegisterSubDomainImpl.java b/src/main/java/org/caosdb/server/entity/StatementStatusInterface.java
similarity index 58%
rename from src/main/java/org/caosdb/server/database/backend/interfaces/RegisterSubDomainImpl.java
rename to src/main/java/org/caosdb/server/entity/StatementStatusInterface.java
index 87ea0c85db6a698f49c103506b520342deff2fb0..d1596afda95a4836dcd84fc79b67cf2e0b4f12a4 100644
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RegisterSubDomainImpl.java
+++ b/src/main/java/org/caosdb/server/entity/StatementStatusInterface.java
@@ -1,9 +1,8 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -17,16 +16,14 @@
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
  */
-package org.caosdb.server.database.backend.interfaces;
-
-import java.util.Deque;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.entity.EntityID;
+package org.caosdb.server.entity;
 
-public interface RegisterSubDomainImpl extends BackendTransactionImpl {
-
-  public abstract Deque<EntityID> execute(int domainCount) throws TransactionException;
+/**
+ * Interface for the StatementStatus. This may be used by back-end implementations to use back-end
+ * specific implementations (e.g.
+ * org.caosdb.server.database.backend.implementation.MySQL.ReplacementStatus).
+ */
+public interface StatementStatusInterface {
+  public String name();
 }
diff --git a/src/main/java/org/caosdb/server/entity/wrapper/Domain.java b/src/main/java/org/caosdb/server/entity/wrapper/Domain.java
deleted file mode 100644
index 146706efd2caa74ff1399568bf499f37683bfdbe..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/entity/wrapper/Domain.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.entity.wrapper;
-
-import org.caosdb.server.datatype.AbstractDatatype;
-import org.caosdb.server.datatype.Value;
-import org.caosdb.server.entity.Entity;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.Role;
-import org.caosdb.server.entity.container.PropertyContainer;
-
-public class Domain extends Entity {
-
-  private boolean descO;
-
-  public Domain(
-      final PropertyContainer properties,
-      final AbstractDatatype datatype,
-      final Value value,
-      final org.caosdb.server.entity.StatementStatus statementStatus) {
-
-    setRole(Role.Domain);
-    setProperties(properties);
-    setDatatype(datatype);
-    setValue(value);
-    setStatementStatus(statementStatus);
-  }
-
-  @Override
-  public EntityInterface setDescOverride(final boolean b) {
-    this.descO = b;
-    return this;
-  }
-
-  @Override
-  public boolean isDescOverride() {
-    return this.descO;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java
index 7da945738ac966114a020098171c049a6fa387a3..b294a17e5ea68ee02e529735ec39a748bc4f2c83 100644
--- a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java
+++ b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java
@@ -38,7 +38,7 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.FileProperties;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Role;
-import org.caosdb.server.entity.StatementStatus;
+import org.caosdb.server.entity.StatementStatusInterface;
 import org.caosdb.server.entity.Version;
 import org.caosdb.server.entity.container.ParentContainer;
 import org.caosdb.server.entity.container.PropertyContainer;
@@ -112,13 +112,13 @@ public abstract class EntityWrapper implements EntityInterface {
   }
 
   @Override
-  public StatementStatus getStatementStatus() {
+  public StatementStatusInterface getStatementStatus() {
     return this.entity.getStatementStatus();
   }
 
   @Override
-  public void setStatementStatus(final StatementStatus statementStatus) {
-    this.entity.setStatementStatus(statementStatus);
+  public void setStatementStatus(final StatementStatusInterface replacement) {
+    this.entity.setStatementStatus(replacement);
   }
 
   @Override
@@ -376,21 +376,6 @@ public abstract class EntityWrapper implements EntityInterface {
     this.entity.setProperties(properties);
   }
 
-  @Override
-  public void setReplacement(final Domain d) {
-    this.entity.setReplacement(d);
-  }
-
-  @Override
-  public boolean hasReplacement() {
-    return this.entity.hasReplacement();
-  }
-
-  @Override
-  public Domain getReplacement() {
-    return this.entity.getReplacement();
-  }
-
   @Override
   public Map<String, String> getFlags() {
     return this.entity.getFlags();
diff --git a/src/main/java/org/caosdb/server/entity/wrapper/Property.java b/src/main/java/org/caosdb/server/entity/wrapper/Property.java
index efa1a6857a8dec0b3d7dcc45ec689d5b3bc0f745..9268d7f3cb327a70a4cca872657d72e2779fc344 100644
--- a/src/main/java/org/caosdb/server/entity/wrapper/Property.java
+++ b/src/main/java/org/caosdb/server/entity/wrapper/Property.java
@@ -25,11 +25,13 @@ package org.caosdb.server.entity.wrapper;
 import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.authz.Permission;
 import org.apache.shiro.subject.Subject;
-import org.caosdb.server.database.proto.FlatProperty;
+import org.caosdb.server.database.proto.ProtoProperty;
 import org.caosdb.server.datatype.AbstractCollectionDatatype;
+import org.caosdb.server.datatype.CollectionValue;
 import org.caosdb.server.datatype.GenericValue;
 import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.RetrieveEntity;
 import org.caosdb.server.entity.xml.PropertyToElementStrategy;
 
 public class Property extends EntityWrapper {
@@ -72,29 +74,45 @@ public class Property extends EntityWrapper {
     return this;
   }
 
-  public Property parseFlatProperty(final FlatProperty fp) {
-    setId(new EntityID(fp.id));
-    setStatementStatus(fp.status);
-    setPIdx(fp.idx);
-    if (fp.name != null) {
-      setName(fp.name);
+  public Property parseProtoProperty(final ProtoProperty pp) {
+    setId(new EntityID(pp.id));
+    setStatementStatus(pp.status);
+    setPIdx(pp.idx);
+    if (pp.name != null) {
+      setName(pp.name);
       setNameOverride(true);
     }
-    if (fp.desc != null) {
-      setDescription(fp.desc);
+    if (pp.desc != null) {
+      setDescription(pp.desc);
       setDescOverride(true);
     }
-    if (fp.type != null) {
-      if (fp.collection != null) {
+    if (pp.type != null) {
+      if (pp.collection != null) {
         this.setDatatype(
-            AbstractCollectionDatatype.collectionDatatypeFactory(fp.collection, fp.type));
+            AbstractCollectionDatatype.collectionDatatypeFactory(pp.collection, pp.type));
       } else {
-        this.setDatatype(fp.type);
+        this.setDatatype(pp.type);
       }
       setDatatypeOverride(true);
     }
-    if (fp.value != null) {
-      setValue(new GenericValue(fp.value));
+    if (pp.value != null) {
+      setValue(new GenericValue(pp.value));
+    }
+    if (pp.collValues != null) {
+      CollectionValue value = new CollectionValue();
+      for (Object next : pp.collValues) {
+        if (next == null) {
+          value.add(null);
+        } else {
+          value.add(new GenericValue(next.toString()));
+        }
+      }
+      setValue(value);
+    }
+    if (pp.subProperties != null) {
+      for (ProtoProperty subP : pp.subProperties) {
+        this.addProperty(new Property(new RetrieveEntity()).parseProtoProperty(subP));
+      }
     }
     return this;
   }
diff --git a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java
index 7f6e6dcae9152091b854007149b0793cbff45542..35d94aa79fd62a04f8b0dae1a17829a1106c2d97 100644
--- a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java
+++ b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java
@@ -85,9 +85,7 @@ public class EntityToElementStrategy implements ToElementStrategy {
 
     // @review Florian Spreckelsen 2022-03-22
 
-    if (entity.getEntityACL() != null) {
-      element.addContent(entity.getEntityACL().getPermissionsFor(SecurityUtils.getSubject()));
-    }
+    addPermissions(entity, element);
     if (serializeFieldStrategy.isToBeSet("id") && entity.hasId()) {
       element.setAttribute("id", entity.getId().toString());
     }
@@ -135,6 +133,12 @@ public class EntityToElementStrategy implements ToElementStrategy {
     }
   }
 
+  protected void addPermissions(EntityInterface entity, Element element) {
+    if (entity.getEntityACL() != null) {
+      element.addContent(entity.getEntityACL().getPermissionsFor(SecurityUtils.getSubject()));
+    }
+  }
+
   /**
    * Set the value of the entity.
    *
diff --git a/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java
index 23fd8a553e66df23349c20e8539e3154f3e2ac0a..7109a0d8d5312dcb7cbdecc587dc28fe0a4146ba 100644
--- a/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java
+++ b/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java
@@ -35,6 +35,11 @@ public class PropertyToElementStrategy extends EntityToElementStrategy {
     super("Property");
   }
 
+  @Override
+  protected void addPermissions(EntityInterface entity, Element element) {
+    // don't
+  }
+
   @Override
   public Element addToElement(
       final EntityInterface entity,
diff --git a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java
index 6fef590746bbf2c3ff802baec73385e4b061ec95..8ab8b0579bd6d0a32976ac6e2e28e776f00d4850 100644
--- a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java
+++ b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java
@@ -75,6 +75,7 @@ import org.caosdb.server.entity.MagicTypes;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Role;
 import org.caosdb.server.entity.StatementStatus;
+import org.caosdb.server.entity.StatementStatusInterface;
 import org.caosdb.server.entity.container.ParentContainer;
 import org.caosdb.server.entity.container.RetrieveContainer;
 import org.caosdb.server.entity.wrapper.Property;
@@ -298,6 +299,8 @@ public class CaosDBToGrpcConverters {
 
     } else if (value instanceof GenericValue) {
       return convertGenericValue((GenericValue) value);
+    } else if (value == null) {
+      return ScalarValue.newBuilder().setSpecialValue(SpecialValue.SPECIAL_VALUE_UNSPECIFIED);
     }
     return null;
   }
@@ -357,8 +360,8 @@ public class CaosDBToGrpcConverters {
     return convertScalarValue(v.getWrapped()).build();
   }
 
-  private Importance convert(final StatementStatus statementStatus) {
-    switch (statementStatus) {
+  private Importance convert(final StatementStatusInterface statementStatus) {
+    switch ((StatementStatus) statementStatus) {
       case FIX:
         return Importance.IMPORTANCE_FIX;
       case OBLIGATORY:
@@ -704,7 +707,7 @@ public class CaosDBToGrpcConverters {
     return null;
   }
 
-  private org.caosdb.api.entity.v1.Value.Builder getSelectedValue(Selection s, EntityInterface e) {
+  org.caosdb.api.entity.v1.Value.Builder getSelectedValue(Selection s, EntityInterface e) {
     org.caosdb.api.entity.v1.Value.Builder result = null;
     String selector = s.getSelector();
     result = handleSpecialSelectors(selector, e);
@@ -735,8 +738,9 @@ public class CaosDBToGrpcConverters {
                 result = getSelectedValue(s.getSubselection(), referenced_entity);
               } else if (v instanceof CollectionValue) {
                 for (Value i : (CollectionValue) v) {
-                  if (i instanceof ReferenceValue) {
-                    EntityInterface referenced_entity = ((ReferenceValue) i).getEntity();
+                  Value wrapped = ((IndexedSingleValue) i).getWrapped();
+                  if (wrapped instanceof ReferenceValue) {
+                    EntityInterface referenced_entity = ((ReferenceValue) wrapped).getEntity();
                     result = getSelectedValue(s.getSubselection(), referenced_entity);
                     results.add(result);
                   } else {
diff --git a/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java b/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java
index 9edd35090659e908ecf7f3fb502d34c36ac7e829..4e52485d4af4357b90a560baee6095c3b14683ae 100644
--- a/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java
+++ b/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java
@@ -1,5 +1,6 @@
 package org.caosdb.server.grpc;
 
+import io.grpc.Attributes.Key;
 import io.grpc.Context;
 import io.grpc.Contexts;
 import io.grpc.Metadata;
@@ -7,18 +8,70 @@ import io.grpc.ServerCall;
 import io.grpc.ServerCall.Listener;
 import io.grpc.ServerCallHandler;
 import io.grpc.ServerInterceptor;
+import java.net.SocketAddress;
+import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.ServerProperties;
+import org.restlet.routing.Template;
+import org.restlet.util.Resolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+class CallResolver<ReqT, RespT> extends Resolver<String> {
+
+  private ServerCall<ReqT, RespT> call;
+  private Metadata headers;
+
+  public static final Metadata.Key<String> KEY_USER_AGENT =
+      Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER);
+  public static final Key<SocketAddress> KEY_LOCAL_ADDRESS = io.grpc.Grpc.TRANSPORT_ATTR_LOCAL_ADDR;
+  public static final Key<SocketAddress> KEY_REMOTE_ADDRESS =
+      io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR;
+
+  public CallResolver(
+      ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
+    this.call = call;
+    this.headers = headers;
+  }
+
+  @Override
+  public String resolve(String name) {
+    switch (name) {
+      case "user-agent":
+        return headers.get(KEY_USER_AGENT);
+      case "remote-address":
+        return call.getAttributes().get(KEY_REMOTE_ADDRESS).toString();
+      case "local-address":
+        return call.getAttributes().get(KEY_LOCAL_ADDRESS).toString();
+      case "method":
+        return call.getMethodDescriptor().getFullMethodName();
+      default:
+        break;
+    }
+    return null;
+  }
+}
+
 public class LoggingInterceptor implements ServerInterceptor {
 
+  private Template template;
+
+  public LoggingInterceptor() {
+    String format = CaosDBServer.getServerProperty(ServerProperties.KEY_GRPC_RESPONSE_LOG_FORMAT);
+    if ("OFF".equalsIgnoreCase(format)) {
+      this.template = null;
+    } else if (format != null) {
+      this.template = new Template(format);
+    }
+  }
+
   private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class.getName());
 
   @Override
   public <ReqT, RespT> Listener<ReqT> interceptCall(
       ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
-
-    logger.info(call.getMethodDescriptor().getFullMethodName() + " - " + call.getAttributes());
+    if (template != null) {
+      logger.info(template.format(new CallResolver<ReqT, RespT>(call, headers, next)));
+    }
     return Contexts.interceptCall(Context.current(), call, headers, next);
   }
 }
diff --git a/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java b/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java
index 62722317f882dffae4168808514a6d972ae8bfe6..4416227f9672a9b3be3ffb34306e78de04a20a2f 100644
--- a/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java
+++ b/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java
@@ -54,7 +54,7 @@ public class CustomConfigurationFactory extends PropertiesConfigurationFactory {
     LOGGER.debug("Reconfiguration is done by {}", getClass().toString());
 
     List<String> sources = getConfigFiles();
-    if (sources.size() > 1) {
+    if (sources.size() > 0) {
       final List<AbstractConfiguration> configs = new ArrayList<>();
       for (final String sourceLocation : sources) {
         LOGGER.debug("Reconfigure with {}", sourceLocation);
diff --git a/src/main/java/org/caosdb/server/query/Backreference.java b/src/main/java/org/caosdb/server/query/Backreference.java
index 7d551fd45e761a54d2c2508dcbb1b98ffc72891c..b801ffaf396a3ba9f98531ca06fc274b05c70b96 100644
--- a/src/main/java/org/caosdb/server/query/Backreference.java
+++ b/src/main/java/org/caosdb/server/query/Backreference.java
@@ -23,7 +23,7 @@
 package org.caosdb.server.query;
 
 import static java.sql.Types.VARCHAR;
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.sql.CallableStatement;
 import java.sql.Connection;
diff --git a/src/main/java/org/caosdb/server/query/Conjunction.java b/src/main/java/org/caosdb/server/query/Conjunction.java
index 03b331242e239a26f108973ca7a37624ab80ea64..59dfd57b20ed8bce443e670e767dc42bb91ac6ac 100644
--- a/src/main/java/org/caosdb/server/query/Conjunction.java
+++ b/src/main/java/org/caosdb/server/query/Conjunction.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.query;
 
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.sql.CallableStatement;
 import java.sql.Connection;
diff --git a/src/main/java/org/caosdb/server/query/Disjunction.java b/src/main/java/org/caosdb/server/query/Disjunction.java
index edd2e6daf243cd25ea112fdbfb946f2b80ee80b1..925ef85952e2d31783a6f7447074d0384e508a98 100644
--- a/src/main/java/org/caosdb/server/query/Disjunction.java
+++ b/src/main/java/org/caosdb/server/query/Disjunction.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.query;
 
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.sql.CallableStatement;
 import java.sql.Connection;
diff --git a/src/main/java/org/caosdb/server/query/Negation.java b/src/main/java/org/caosdb/server/query/Negation.java
index cfdfd05e286030f525b8d4f3c291e2bdfb27b100..431124df7b995faf6aa493363a51095e7028b9b8 100644
--- a/src/main/java/org/caosdb/server/query/Negation.java
+++ b/src/main/java/org/caosdb/server/query/Negation.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.query;
 
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.sql.CallableStatement;
 import java.sql.Connection;
diff --git a/src/main/java/org/caosdb/server/query/POV.java b/src/main/java/org/caosdb/server/query/POV.java
index 0de160cdb6249ffaea10f4eeeafdf4884323a186..ebc35a16b1495accf8cd4a114dd688c2b2529c1b 100644
--- a/src/main/java/org/caosdb/server/query/POV.java
+++ b/src/main/java/org/caosdb/server/query/POV.java
@@ -25,7 +25,7 @@ package org.caosdb.server.query;
 import static java.sql.Types.DOUBLE;
 import static java.sql.Types.INTEGER;
 import static java.sql.Types.VARCHAR;
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import de.timmfitschen.easyunits.parser.ParserException;
 import java.sql.CallableStatement;
diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java
index c4a458630456d057ccddf26c48def0e72ccf3789..abaca4365e54c21ee2ba8cf8bf7727fe746c7277 100644
--- a/src/main/java/org/caosdb/server/query/Query.java
+++ b/src/main/java/org/caosdb/server/query/Query.java
@@ -21,7 +21,7 @@
  */
 package org.caosdb.server.query;
 
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.io.Serializable;
 import java.sql.CallableStatement;
diff --git a/src/main/java/org/caosdb/server/query/SubProperty.java b/src/main/java/org/caosdb/server/query/SubProperty.java
index 8ff167eac1e045875576a75f3d922f5c6461c68e..d5ce923449b45cb45dab77e4c30b5d8b343e01e3 100644
--- a/src/main/java/org/caosdb/server/query/SubProperty.java
+++ b/src/main/java/org/caosdb/server/query/SubProperty.java
@@ -23,7 +23,7 @@
 package org.caosdb.server.query;
 
 import static java.sql.Types.VARCHAR;
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.sql.CallableStatement;
 import java.sql.Connection;
diff --git a/src/main/java/org/caosdb/server/resource/FileSystemResource.java b/src/main/java/org/caosdb/server/resource/FileSystemResource.java
index 1e601cb2116c92ae3aa31aaca4be01b429f9e826..9c0d615833148f706b190d7dfbc1aafdca61a75f 100644
--- a/src/main/java/org/caosdb/server/resource/FileSystemResource.java
+++ b/src/main/java/org/caosdb/server/resource/FileSystemResource.java
@@ -86,14 +86,16 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
     }
 
     if (file.isDirectory()) {
-      String referenceString = getReference().toString();
-      if (!referenceString.endsWith("/")) {
-        referenceString = referenceString + "/";
-      }
+      String path = (specifier.endsWith("/") ? specifier : specifier + "/");
+      String url =
+          getUtils().getServerRootURI()
+              + "/FileSystem"
+              + (path.startsWith("/") ? path : ("/" + path));
+
       final Element folder = new Element("dir");
-      folder.setAttribute("path", (specifier.endsWith("/") ? specifier : specifier + "/"));
+      folder.setAttribute("path", path);
       folder.setAttribute("name", file.getName());
-      folder.setAttribute("url", referenceString);
+      folder.setAttribute("url", url);
 
       final boolean thumbnailsExist =
           new File(file.getAbsolutePath() + File.separator + ".thumbnails").exists();
@@ -110,7 +112,7 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
         }
 
         if (thumbnailsExist) {
-          final Attribute thumbnailAttribute = getThumbnailAttribute(file, child, referenceString);
+          final Attribute thumbnailAttribute = getThumbnailAttribute(file, child, url);
           if (thumbnailAttribute != null) {
             celem.setAttribute(thumbnailAttribute);
           }
diff --git a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java b/src/main/java/org/caosdb/server/utils/ResultSetIterator.java
index 0912d278c7158c9b7b75a585c2827d851951f29b..71429f1a704299555ab1d86be0695e3104be828d 100644
--- a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java
+++ b/src/main/java/org/caosdb/server/utils/ResultSetIterator.java
@@ -1,6 +1,6 @@
 package org.caosdb.server.utils;
 
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8;
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
diff --git a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java
index 375d6b5e7bbced84cec5bdcdee26a9e436f0215a..6c583fccce77471ae4a4272f35d94ec2c886afd3 100644
--- a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java
+++ b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java
@@ -61,6 +61,23 @@ public class WebinterfaceUtils {
   private long buildNumberDate;
   private static final Map<String, WebinterfaceUtils> instances = new HashMap<>();
 
+  public static String getForwardedProto(Request request) {
+    String scheme = null;
+    String forwarded = request.getHeaders().getFirstValue("Forwarded", true);
+    if (forwarded != null) {
+      for (String directive : forwarded.split(";")) {
+        String[] s = directive.split("=", 2);
+        if (s.length == 2 && "proto".equalsIgnoreCase(s[0])) {
+          scheme = s[1];
+        }
+      }
+    }
+    if (scheme == null) {
+      scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true);
+    }
+    return scheme;
+  }
+
   /**
    * Retrieve an instance of {@link WebinterfaceUtils} for the request. The instance can be shared
    * with other callers.
@@ -70,7 +87,7 @@ public class WebinterfaceUtils {
    */
   public static WebinterfaceUtils getInstance(Request request) {
     String hostStr = request.getHostRef().getHostIdentifier();
-    String scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true);
+    String scheme = getForwardedProto(request);
     if (scheme != null) {
       hostStr = hostStr.replaceFirst("^" + request.getHostRef().getScheme(), scheme);
     }
diff --git a/src/test/java/org/caosdb/server/database/InsertTest.java b/src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java
similarity index 55%
rename from src/test/java/org/caosdb/server/database/InsertTest.java
rename to src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java
index 524e7addd20581db349082f91206605a97bdee8e..03bd185c8884822f89f8ff323a1c32125b66f88b 100644
--- a/src/test/java/org/caosdb/server/database/InsertTest.java
+++ b/src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java
@@ -20,21 +20,24 @@
  *
  * ** end header
  */
-package org.caosdb.server.database;
+package org.caosdb.server.database.backend.implementation.MySQL;
 
-import static org.caosdb.server.database.DatabaseUtils.deriveStage1Inserts;
-import static org.caosdb.server.database.DatabaseUtils.deriveStage2Inserts;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.deriveStage1Inserts;
+import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.deriveStage2Inserts;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.LinkedList;
+import java.util.List;
 import org.caosdb.server.datatype.CollectionValue;
 import org.caosdb.server.datatype.GenericValue;
+import org.caosdb.server.datatype.ReferenceValue;
 import org.caosdb.server.datatype.SingleValue;
 import org.caosdb.server.entity.Entity;
 import org.caosdb.server.entity.EntityID;
-import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.InsertEntity;
 import org.caosdb.server.entity.RetrieveEntity;
 import org.caosdb.server.entity.Role;
@@ -44,6 +47,14 @@ import org.junit.jupiter.api.Test;
 
 public class InsertTest {
 
+  private Deque<EntityID> registerReplacementIds(int count) {
+    Deque<EntityID> replacementIds = new ArrayDeque<>();
+    for (int i = 1; i < count + 1; i++) {
+      replacementIds.add(new EntityID(-i));
+    }
+    return replacementIds;
+  }
+
   /**
    * Record with very deep property tree.
    *
@@ -122,79 +133,67 @@ public class InsertTest {
     p10.setStatementStatus(StatementStatus.OBLIGATORY);
     p9.addProperty(p10);
 
-    final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>();
-    final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>();
-    new LinkedList<EntityInterface>();
-    deriveStage1Inserts(stage1Inserts, r);
+    final LinkedList<Property> stage1Inserts = new LinkedList<>();
+    final LinkedList<Property> stage2Inserts = new LinkedList<>();
+
+    int replacementCount = deriveStage1Inserts(stage1Inserts, r);
+    assertEquals(3, replacementCount);
+
+    deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(replacementCount), r);
 
     assertEquals(7, stage1Inserts.size());
-    assertEquals(Role.Property, stage1Inserts.get(0).getRole());
-    assertEquals(new EntityID(1), stage1Inserts.get(0).getId());
+    assertFalse(stage1Inserts.get(0) instanceof Replacement);
+    assertEquals(1, stage1Inserts.get(0).getId().toInteger());
     assertEquals("V1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(0).hasReplacement());
 
-    assertEquals(Role.Property, stage1Inserts.get(1).getRole());
-    assertEquals(new EntityID(2), stage1Inserts.get(1).getId());
+    assertFalse(stage1Inserts.get(1) instanceof Replacement);
+    assertEquals(2, stage1Inserts.get(1).getId().toInteger());
     assertEquals("V2", ((SingleValue) stage1Inserts.get(1).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(1).hasReplacement());
 
-    assertEquals(Role.Property, stage1Inserts.get(2).getRole());
-    assertEquals(new EntityID(4), stage1Inserts.get(2).getId());
+    assertFalse(stage1Inserts.get(2) instanceof Replacement);
+    assertEquals(4, stage1Inserts.get(2).getId().toInteger());
     assertEquals("V4", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(2).hasReplacement());
 
-    assertEquals(Role.Domain, stage1Inserts.get(3).getRole());
+    assertTrue(stage1Inserts.get(3) instanceof Replacement);
+    assertEquals(-1, stage1Inserts.get(3).getId().toInteger());
     assertEquals("V5", ((SingleValue) stage1Inserts.get(3).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(3).hasReplacement());
 
-    assertEquals(Role.Property, stage1Inserts.get(4).getRole());
-    assertEquals(new EntityID(7), stage1Inserts.get(4).getId());
+    assertFalse(stage1Inserts.get(4) instanceof Replacement);
+    assertEquals(7, stage1Inserts.get(4).getId().toInteger());
     assertEquals("V7", ((SingleValue) stage1Inserts.get(4).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(4).hasReplacement());
 
-    assertEquals(Role.Domain, stage1Inserts.get(5).getRole());
+    assertTrue(stage1Inserts.get(5) instanceof Replacement);
+    assertEquals(-2, stage1Inserts.get(5).getId().toInteger());
     assertEquals("V8", ((SingleValue) stage1Inserts.get(5).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(5).hasReplacement());
 
-    assertEquals(Role.Domain, stage1Inserts.get(6).getRole());
+    assertTrue(stage1Inserts.get(6) instanceof Replacement);
+    assertEquals(-3, stage1Inserts.get(6).getId().toInteger());
     assertEquals("V9", ((SingleValue) stage1Inserts.get(6).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(6).hasReplacement());
-
-    deriveStage2Inserts(stage2Inserts, stage1Inserts);
 
     assertEquals(6, stage2Inserts.size());
-    assertEquals(Role.Property, stage2Inserts.get(0).getRole());
     assertEquals(new EntityID(3), stage2Inserts.get(0).getId());
     assertEquals("V3", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString());
-    assertFalse(stage2Inserts.get(0).hasReplacement());
+    assertEquals(new EntityID(2), stage2Inserts.get(0).getDomain());
 
-    assertEquals(Role.Property, stage2Inserts.get(1).getRole());
     assertEquals(new EntityID(5), stage2Inserts.get(1).getId());
     assertEquals("V5", ((SingleValue) stage2Inserts.get(1).getValue()).toDatabaseString());
-    assertTrue(stage2Inserts.get(1).hasReplacement());
-    assertEquals(stage1Inserts.get(3), stage2Inserts.get(1).getReplacement());
+    assertEquals(new EntityID(4), stage2Inserts.get(1).getDomain());
 
-    assertEquals(Role.Property, stage2Inserts.get(2).getRole());
     assertEquals(new EntityID(6), stage2Inserts.get(2).getId());
     assertEquals("V6", ((SingleValue) stage2Inserts.get(2).getValue()).toDatabaseString());
-    assertFalse(stage2Inserts.get(2).hasReplacement());
+    assertEquals(new EntityID(-1), stage2Inserts.get(2).getDomain());
 
-    assertEquals(Role.Property, stage2Inserts.get(3).getRole());
     assertEquals(new EntityID(8), stage2Inserts.get(3).getId());
     assertEquals("V8", ((SingleValue) stage2Inserts.get(3).getValue()).toDatabaseString());
-    assertTrue(stage2Inserts.get(3).hasReplacement());
-    assertEquals(stage1Inserts.get(5), stage2Inserts.get(3).getReplacement());
+    assertEquals(new EntityID(7), stage2Inserts.get(3).getDomain());
 
-    assertEquals(Role.Property, stage2Inserts.get(4).getRole());
     assertEquals(new EntityID(9), stage2Inserts.get(4).getId());
     assertEquals("V9", ((SingleValue) stage2Inserts.get(4).getValue()).toDatabaseString());
-    assertTrue(stage2Inserts.get(4).hasReplacement());
-    assertEquals(stage1Inserts.get(6), stage2Inserts.get(4).getReplacement());
+    assertEquals(new EntityID(-2), stage2Inserts.get(4).getDomain());
 
-    assertEquals(Role.Property, stage2Inserts.get(5).getRole());
     assertEquals(new EntityID(10), stage2Inserts.get(5).getId());
     assertEquals("V10", ((SingleValue) stage2Inserts.get(5).getValue()).toDatabaseString());
-    assertFalse(stage2Inserts.get(5).hasReplacement());
+    assertEquals(new EntityID(-3), stage2Inserts.get(5).getDomain());
   }
 
   /**
@@ -227,34 +226,31 @@ public class InsertTest {
     subp.setStatementStatus(StatementStatus.FIX);
     p2.addProperty(subp);
 
-    final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>();
-    final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>();
+    final LinkedList<Property> stage1Inserts = new LinkedList<>();
+    final LinkedList<Property> stage2Inserts = new LinkedList<>();
 
-    deriveStage1Inserts(stage1Inserts, r);
+    int replacementCount = deriveStage1Inserts(stage1Inserts, r);
+    deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(replacementCount), r);
 
     assertEquals(3, stage1Inserts.size());
-    assertEquals(Role.Property, stage1Inserts.get(0).getRole());
+
+    assertFalse(stage1Inserts.get(0) instanceof Replacement);
     assertEquals(new EntityID(1), stage1Inserts.get(0).getId());
     assertEquals("V1-1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(0).hasReplacement());
 
-    assertEquals(Role.Property, stage1Inserts.get(1).getRole());
+    assertTrue(stage1Inserts.get(1) instanceof Replacement);
     assertEquals("V1-2", ((SingleValue) stage1Inserts.get(1).getValue()).toDatabaseString());
-    assertEquals(new EntityID(1), stage1Inserts.get(1).getId());
-    assertTrue(stage1Inserts.get(1).hasReplacement());
-    assertEquals(stage1Inserts.get(2), stage1Inserts.get(1).getReplacement());
+    assertEquals(new EntityID(-1), stage1Inserts.get(1).getId());
 
-    assertEquals(Role.Domain, stage1Inserts.get(2).getRole());
-    assertEquals("V1-2", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString());
-    assertFalse(stage1Inserts.get(2).hasReplacement());
-
-    deriveStage2Inserts(stage2Inserts, stage1Inserts);
+    assertFalse(stage1Inserts.get(2) instanceof Replacement);
+    assertEquals(new EntityID(-1), stage1Inserts.get(2).getId());
+    assertEquals("1", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString());
 
     assertEquals(1, stage2Inserts.size());
-    assertEquals(Role.Property, stage2Inserts.get(0).getRole());
+
+    assertFalse(stage2Inserts.get(0) instanceof Replacement);
     assertEquals(new EntityID(2), stage2Inserts.get(0).getId());
     assertEquals("V2", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString());
-    assertFalse(stage2Inserts.get(0).hasReplacement());
   }
 
   /**
@@ -291,39 +287,33 @@ public class InsertTest {
     sub1.setStatementStatus(StatementStatus.FIX);
     p1.addProperty(sub1);
 
-    final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>();
-    final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>();
+    final LinkedList<Property> stage1Inserts = new LinkedList<>();
+    final LinkedList<Property> stage2Inserts = new LinkedList<>();
 
-    deriveStage1Inserts(stage1Inserts, r);
+    int count = deriveStage1Inserts(stage1Inserts, r);
+    deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(count), r);
 
     assertEquals(4, stage1Inserts.size());
-    assertEquals(Role.Property, stage1Inserts.get(0).getRole());
-    assertEquals(new EntityID(1), stage1Inserts.get(0).getId());
-    assertTrue(stage1Inserts.get(0).hasReplacement());
-    assertEquals(stage1Inserts.get(1), stage1Inserts.get(0).getReplacement());
+    assertTrue(stage1Inserts.get(0) instanceof Replacement);
+    assertEquals(new EntityID(-1), stage1Inserts.get(0).getId());
     assertEquals(null, stage1Inserts.get(0).getValue());
 
-    assertEquals(Role.Domain, stage1Inserts.get(1).getRole());
-    assertFalse(stage1Inserts.get(1).hasReplacement());
-    assertEquals(null, stage1Inserts.get(1).getValue());
+    assertFalse(stage1Inserts.get(1) instanceof Replacement);
+    assertEquals(new EntityID(-1), stage1Inserts.get(1).getId());
+    assertEquals(1, ((ReferenceValue) stage1Inserts.get(1).getValue()).getId().toInteger());
 
-    assertEquals(Role.Property, stage1Inserts.get(2).getRole());
+    assertFalse(stage1Inserts.get(2) instanceof Replacement);
     assertEquals(new EntityID(1), stage1Inserts.get(2).getId());
-    assertFalse(stage1Inserts.get(2).hasReplacement());
     assertEquals(null, stage1Inserts.get(2).getValue());
 
-    assertEquals(Role.Property, stage1Inserts.get(3).getRole());
+    assertFalse(stage1Inserts.get(3) instanceof Replacement);
     assertEquals(new EntityID(1), stage1Inserts.get(3).getId());
-    assertFalse(stage1Inserts.get(3).hasReplacement());
     assertEquals(null, stage1Inserts.get(3).getValue());
 
-    deriveStage2Inserts(stage2Inserts, stage1Inserts);
-
     assertEquals(1, stage2Inserts.size());
-    assertEquals(Role.Property, stage2Inserts.get(0).getRole());
     assertEquals(new EntityID(2), stage2Inserts.get(0).getId());
     assertEquals("V1", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString());
-    assertFalse(stage2Inserts.get(0).hasReplacement());
+    assertEquals(new EntityID(-1), stage2Inserts.get(0).getDomain());
   }
 
   /**
@@ -360,32 +350,154 @@ public class InsertTest {
     p3.setStatementStatus(StatementStatus.FIX);
     r.addProperty(p3);
 
-    final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>();
-    final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>();
+    final LinkedList<Property> stage1Inserts = new LinkedList<>();
+    final LinkedList<Property> stage2Inserts = new LinkedList<>();
 
-    deriveStage1Inserts(stage1Inserts, r);
+    int count = deriveStage1Inserts(stage1Inserts, r);
+    deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(count), r);
 
     assertEquals(4, stage1Inserts.size());
-    assertEquals(Role.Property, stage1Inserts.get(0).getRole());
+
+    assertFalse(stage1Inserts.get(0) instanceof Replacement);
     assertEquals(new EntityID(1), stage1Inserts.get(0).getId());
-    assertFalse(stage1Inserts.get(0).hasReplacement());
     assertEquals("V1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString());
 
-    assertEquals(Role.Property, stage1Inserts.get(1).getRole());
-    assertEquals(new EntityID(2), stage1Inserts.get(1).getId());
-    assertTrue(stage1Inserts.get(1).hasReplacement());
-    assertEquals(stage1Inserts.get(2), stage1Inserts.get(1).getReplacement());
+    assertTrue(stage1Inserts.get(1) instanceof Replacement);
+    assertEquals(new EntityID(-1), stage1Inserts.get(1).getId());
     assertTrue(stage1Inserts.get(1).getValue() instanceof CollectionValue);
 
-    assertEquals(Role.Domain, stage1Inserts.get(2).getRole());
-    assertFalse(stage1Inserts.get(2).hasReplacement());
-    assertTrue(stage1Inserts.get(2).getValue() instanceof CollectionValue);
+    assertFalse(stage1Inserts.get(2) instanceof Replacement);
+    assertEquals(new EntityID(-1), stage1Inserts.get(2).getId());
+    assertEquals(2, ((ReferenceValue) stage1Inserts.get(2).getValue()).getId().toInteger());
 
-    assertEquals(Role.Property, stage1Inserts.get(3).getRole());
+    assertFalse(stage1Inserts.get(3) instanceof Replacement);
     assertEquals(new EntityID(3), stage1Inserts.get(3).getId());
-    assertFalse(stage1Inserts.get(3).hasReplacement());
     assertEquals("V3", ((SingleValue) stage1Inserts.get(3).getValue()).toDatabaseString());
 
-    deriveStage2Inserts(stage2Inserts, stage1Inserts);
+    assertEquals(0, stage2Inserts.size());
+  }
+
+  /** Deeply nested properties without any values, with overridden name and description */
+  @Test
+  public void testTransformation5() {
+    final Entity r = new InsertEntity("Test", Role.RecordType);
+    final Property p1 = new Property(new RetrieveEntity(new EntityID(1)));
+    p1.setRole("Property");
+    p1.setDatatype("TEXT");
+    p1.setDescription("desc1");
+    p1.setDescOverride(true);
+    p1.setName("P1");
+    p1.setNameOverride(true);
+    p1.setStatementStatus(StatementStatus.RECOMMENDED);
+    r.addProperty(p1);
+
+    final Property p2 = new Property(new RetrieveEntity(new EntityID(2)));
+    p2.setRole("Property");
+    p2.setDatatype("TEXT");
+    p2.setDescription("desc2");
+    p2.setDescOverride(true);
+    p2.setName("P2");
+    p2.setNameOverride(true);
+    p2.setStatementStatus(StatementStatus.RECOMMENDED);
+    r.addProperty(p2);
+
+    final Property p21 = new Property(new RetrieveEntity(new EntityID(1)));
+    p21.setRole("Property");
+    p21.setDatatype("TEXT");
+    p21.setDescription("desc21");
+    p21.setDescOverride(true);
+    p21.setName("P21");
+    p21.setNameOverride(true);
+    p21.setStatementStatus(StatementStatus.FIX);
+    p2.addProperty(p21);
+
+    final Property p22 = new Property(new RetrieveEntity(new EntityID(2)));
+    p22.setRole("Property");
+    p22.setDatatype("TEXT");
+    p22.setDescription("desc22");
+    p22.setDescOverride(true);
+    p22.setName("P22");
+    p22.setNameOverride(true);
+    p22.setStatementStatus(StatementStatus.FIX);
+    p2.addProperty(p22);
+
+    final Property p3 = new Property(new RetrieveEntity(new EntityID(3)));
+    p3.setRole("Property");
+    p3.setDatatype("TEXT");
+    p3.setDescription("desc3");
+    p3.setDescOverride(true);
+    p3.setName("P3");
+    p3.setNameOverride(true);
+    p3.setStatementStatus(StatementStatus.RECOMMENDED);
+    r.addProperty(p3);
+
+    final Property p31 = new Property(new RetrieveEntity(new EntityID(1)));
+    p31.setRole("Property");
+    p31.setDatatype("TEXT");
+    p31.setDescription("desc31");
+    p31.setDescOverride(true);
+    p31.setName("P31");
+    p31.setNameOverride(true);
+    p31.setStatementStatus(StatementStatus.FIX);
+    p3.addProperty(p31);
+
+    final Property p32 = new Property(new RetrieveEntity(new EntityID(2)));
+    p32.setRole("Property");
+    p32.setDatatype("TEXT");
+    p32.setDescription("desc32");
+    p32.setDescOverride(true);
+    p32.setName("P32");
+    p32.setNameOverride(true);
+    p32.setStatementStatus(StatementStatus.FIX);
+    p3.addProperty(p32);
+
+    final Property p321 = new Property(new RetrieveEntity(new EntityID(1)));
+    p321.setRole("Property");
+    p321.setDatatype("TEXT");
+    p321.setDescription("desc321");
+    p321.setDescOverride(true);
+    p321.setName("P321");
+    p321.setNameOverride(true);
+    p321.setStatementStatus(StatementStatus.FIX);
+    p32.addProperty(p321);
+
+    final Property p322 = new Property(new RetrieveEntity(new EntityID(2)));
+    p322.setRole("Property");
+    p322.setDatatype("TEXT");
+    p322.setDescription("desc322");
+    p322.setDescOverride(true);
+    p322.setName("P322");
+    p322.setNameOverride(true);
+    p322.setStatementStatus(StatementStatus.FIX);
+    p32.addProperty(p322);
+
+    final Property p323 = new Property(new RetrieveEntity(new EntityID(3)));
+    p323.setRole("Property");
+    p323.setDatatype("TEXT");
+    p323.setDescription("desc323");
+    p323.setDescOverride(true);
+    p323.setName("P323");
+    p323.setNameOverride(true);
+    p323.setStatementStatus(StatementStatus.FIX);
+    p32.addProperty(p323);
+
+    final Property p33 = new Property(new RetrieveEntity(new EntityID(3)));
+    p33.setRole("Property");
+    p33.setDatatype("TEXT");
+    p33.setDescription("desc33");
+    p33.setDescOverride(true);
+    p33.setName("P33");
+    p33.setNameOverride(true);
+    p33.setStatementStatus(StatementStatus.FIX);
+    p3.addProperty(p33);
+
+    List<Property> stage1Inserts = new LinkedList<>();
+    List<Property> stage2Inserts = new LinkedList<>();
+    int c = DatabaseUtils.deriveStage1Inserts(stage1Inserts, r);
+    DatabaseUtils.deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(c), r);
+
+    assertEquals(4, c);
+    assertEquals(7, stage1Inserts.size());
+    assertEquals(8, stage2Inserts.size());
   }
 }
diff --git a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java
index d39a803f9cfe3ce0a4cf23265683d121b3bc688d..d46c187324b76e179ef8d562cd5a150ab7968a45 100644
--- a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java
+++ b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java
@@ -1,12 +1,14 @@
 package org.caosdb.server.grpc;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.TimeZone;
+import org.caosdb.api.entity.v1.Value.Builder;
 import org.caosdb.datetime.DateTimeFactory2;
+import org.caosdb.server.datatype.CollectionValue;
 import org.caosdb.server.datatype.GenericValue;
+import org.caosdb.server.datatype.ReferenceValue;
 import org.caosdb.server.datatype.Value;
 import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.entity.FileProperties;
@@ -17,6 +19,7 @@ import org.caosdb.server.entity.StatementStatus;
 import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.entity.xml.IdAndServerMessagesOnlyStrategy;
+import org.caosdb.server.query.Query.Selection;
 import org.junit.jupiter.api.Test;
 
 public class CaosDBToGrpcConvertersTest {
@@ -27,7 +30,9 @@ public class CaosDBToGrpcConvertersTest {
     DateTimeFactory2 factory = new DateTimeFactory2(timeZone);
     CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(timeZone);
     Value value = null;
-    assertNull(converters.convertScalarValue(value));
+    assertEquals(
+        converters.convertScalarValue(value).toString(),
+        "special_value: SPECIAL_VALUE_UNSPECIFIED\n");
     value = factory.parse("2022");
     assertEquals(converters.convertScalarValue(value).toString(), "string_value: \"2022\"\n");
     value = factory.parse("2022-12");
@@ -101,4 +106,68 @@ public class CaosDBToGrpcConvertersTest {
         converters.convert(entity).toString(),
         "entity {\n  id: \"1234\"\n}\nerrors {\n  code: 1\n  description: \"error\"\n}\nwarnings {\n  code: 1\n  description: \"warning\"\n}\ninfos {\n  code: 1\n  description: \"info\"\n}\n");
   }
+
+  @Test
+  public void testGetSelectedValueWithNullValue() {
+    Property p = new Property(new RetrieveEntity());
+    p.setName("p0");
+    p.setDatatype("DOUBLE");
+    RetrieveEntity entity = new RetrieveEntity();
+    entity.addProperty(p);
+
+    CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(null);
+    Builder value = converters.getSelectedValue(new Selection("p0"), entity);
+    assertEquals(
+        "scalar_value {\n" + "  special_value: SPECIAL_VALUE_UNSPECIFIED\n" + "}\n",
+        value.toString());
+  }
+
+  @Test
+  public void testGetSelectedValueWithListOfReferenceValue() {
+    CollectionValue col = new CollectionValue();
+    Property p = new Property(new RetrieveEntity());
+    p.setName("Person");
+    p.setDatatype("List<Person>");
+
+    Property fullName1 = new Property(new RetrieveEntity());
+    fullName1.setName("full name");
+    fullName1.setDatatype("TEXT");
+    fullName1.setValue(new GenericValue("Harry Belafonte"));
+
+    RetrieveEntity person1 = new RetrieveEntity();
+    person1.addProperty(fullName1);
+    ReferenceValue val1 = new ReferenceValue(new EntityID(1234));
+    val1.setEntity(person1, false);
+    col.add(val1);
+
+    Property fullName2 = new Property(new RetrieveEntity());
+    fullName2.setName("full name");
+    fullName2.setDatatype("TEXT");
+    fullName2.setValue(new GenericValue("Louis Prima"));
+
+    RetrieveEntity person2 = new RetrieveEntity();
+    person2.addProperty(fullName2);
+    ReferenceValue val2 = new ReferenceValue(new EntityID(1234));
+    val2.setEntity(person2, false);
+    col.add(val2);
+    p.setValue(col);
+
+    RetrieveEntity entity = new RetrieveEntity();
+    entity.addProperty(p);
+
+    CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(null);
+    Builder value =
+        converters.getSelectedValue(
+            new Selection("Person").setSubSelection(new Selection("full name")), entity);
+    assertEquals(
+        "list_values {\n"
+            + "  values {\n"
+            + "    string_value: \"Harry Belafonte\"\n"
+            + "  }\n"
+            + "  values {\n"
+            + "    string_value: \"Louis Prima\"\n"
+            + "  }\n"
+            + "}\n",
+        value.toString());
+  }
 }