diff --git a/conf/core/jobs.csv b/conf/core/jobs.csv
new file mode 100644
index 0000000000000000000000000000000000000000..a604e63b736ee5af04889d8ec0af356f76b7a119
--- /dev/null
+++ b/conf/core/jobs.csv
@@ -0,0 +1,112 @@
+#
+# 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) 2021 Timm Fitsche <t.fitschen@indiscale.com>
+# Copyright (C) 2021 IndiScale GmbH <info@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/>.
+#
+
+# Five columns:
+# DOMAIN_ID, ENTITY_ID, TRANSACTION, JOB, MODE
+
+# general rules
+0,0,INSERT,CheckPropValid,MUST
+0,0,INSERT,CheckParValid,MUST
+0,0,INSERT,CheckParOblPropPresent,MUST
+0,0,INSERT,CheckValueParsable,MUST
+0,0,UPDATE,CheckPropValid,MUST
+0,0,UPDATE,CheckParValid,MUST
+0,0,UPDATE,CheckParOblPropPresent,MUST
+0,0,UPDATE,CheckValueParsable,MUST
+0,0,DELETE,CheckReferenceDependencyExistent,MUST
+0,0,DELETE,CheckChildDependencyExistent,MUST
+
+
+# role specific rules
+## recordtype rules
+0,1,INSERT,CheckNamePresent,MUST
+0,1,INSERT,CheckPropPresent,SHOULD
+0,1,INSERT,SetImpToRecByDefault,MUST
+
+0,1,UPDATE,CheckNamePresent,MUST
+0,1,UPDATE,CheckPropPresent,SHOULD
+0,1,UPDATE,SetImpToRecByDefault,MUST
+
+## record rules
+0,2,INSERT,CheckNamePresent,SHOULD
+0,2,INSERT,CheckPropPresent,SHOULD
+0,2,INSERT,CheckParPresent,MUST
+0,2,INSERT,SetImpToFix,MUST
+
+0,2,UPDATE,CheckNamePresent,SHOULD
+0,2,UPDATE,CheckPropPresent,SHOULD
+0,2,UPDATE,CheckParPresent,MUST
+0,2,UPDATE,SetImpToFix,MUST
+
+## file rules
+0,3,INSERT,CheckNamePresent,SHOULD
+0,3,INSERT,MatchFileProp,MUST
+0,3,INSERT,CheckTargetPathValid,MUST
+0,3,INSERT,SetImpToFix,MUST
+
+0,3,UPDATE,CheckNamePresent,SHOULD
+0,3,UPDATE,MatchFileProp,MUST
+0,3,UPDATE,CheckTargetPathValid,MUST
+0,3,UPDATE,SetImpToFix,MUST
+
+## property rules
+0,4,INSERT,CheckDatatypePresent,MUST
+0,4,UPDATE,CheckDatatypePresent,MUST
+0,4,INSERT,CheckNamePresent,MUST
+0,4,UPDATE,CheckNamePresent,MUST
+0,4,INSERT,SetImpToFix,MUST
+0,4,UPDATE,SetImpToFix,MUST
+
+## query template rules
+0,8,UPDATE,CheckQueryTemplate,MUST
+0,8,INSERT,CheckQueryTemplate,MUST
+
+# data type specific rules
+## reference rules
+0,11,INSERT,CheckRefidPresent,SHOULD
+0,11,INSERT,CheckRefidValid,MUST
+0,11,INSERT,CheckRefidIsaParRefid,SHOULD
+
+0,11,UPDATE,CheckRefidPresent,SHOULD
+0,11,UPDATE,CheckRefidValid,MUST
+0,11,UPDATE,CheckRefidIsaParRefid,SHOULD
+
+## integer rules
+0,12,INSERT,CheckUnitPresent,SHOULD
+0,12,INSERT,ParseUnit,SHOULD
+
+0,12,UPDATE,CheckUnitPresent,SHOULD
+0,12,UPDATE,ParseUnit,SHOULD
+
+## double rules
+0,13,INSERT,CheckUnitPresent,SHOULD
+0,13,INSERT,ParseUnit,SHOULD
+
+0,13,UPDATE,CheckUnitPresent,SHOULD
+0,13,UPDATE,ParseUnit,SHOULD
+
+## filereference rules
+0,17,INSERT,CheckRefidValid,MUST
+0,17,INSERT,CheckRefidIsaParRefid,MUST
+
+0,17,UPDATE,CheckRefidValid,MUST
+0,17,UPDATE,CheckRefidIsaParRefid,MUST
diff --git a/conf/core/server.conf b/conf/core/server.conf
index 76ed6030523f24f977f41a510c703fe075f30042..2e91664904c06178008a89009fdd1e73eaa5c82a 100644
--- a/conf/core/server.conf
+++ b/conf/core/server.conf
@@ -124,6 +124,9 @@ ONE_TIME_TOKEN_EXPIRES_MS=604800000
 # Path to config file for one time tokens, see authtoken.example.yml.
 AUTHTOKEN_CONFIG=
 
+# Path to config file for the job rules (e.g. which job is loaded for which kind of entity)
+JOB_RULES_CONFIG=conf/core/jobs.csv
+
 # Timeout after which a one-time token expires once it has been first consumed,
 # regardless of the maximum of replays that are allowed for that token. This is
 # only a default value. The actual timeout of tokens can be configured
@@ -190,9 +193,5 @@ GLOBAL_ENTITY_PERMISSIONS_FILE=./conf/core/global_entity_permissions.xml
 ENTITY_VERSIONING_ENABLED=true
 
 
-# --------------------------------------------------
-# Extension settings
-# --------------------------------------------------
-
 # Enabling the state machine extension
 # EXT_STATE_ENTITY=ENABLE
diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java
index 3172a3e88e7790580af1b7865c024fa2b40c017b..176492d691cc63c422e5996bb055763f8d05a751 100644
--- a/src/main/java/org/caosdb/server/ServerProperties.java
+++ b/src/main/java/org/caosdb/server/ServerProperties.java
@@ -133,6 +133,7 @@ public class ServerProperties extends Properties {
   public static final String KEY_WEBUI_HTTP_HEADER_CACHE_MAX_AGE =
       "WEBUI_HTTP_HEADER_CACHE_MAX_AGE";
   public static final String KEY_AUTHTOKEN_CONFIG = "AUTHTOKEN_CONFIG";
+  public static final String KEY_JOB_RULES_CONFIG = "JOB_RULES_CONFIG";
 
   /**
    * Read the config files and initialize the server properties.
@@ -150,11 +151,11 @@ public class ServerProperties extends Properties {
     loadConfigFile(serverProperties, new File(basepath + "/conf/ext/server.conf"));
 
     // load ext/server.conf.d/ (ordering is determined by system collation)
-    File confDir = new File(basepath + "/conf/ext/server.conf.d");
+    final File confDir = new File(basepath + "/conf/ext/server.conf.d");
     if (confDir.exists() && confDir.isDirectory()) {
-      String[] confFiles = confDir.list();
+      final String[] confFiles = confDir.list();
       Arrays.sort(confFiles, Comparator.naturalOrder());
-      for (String confFile : confFiles) {
+      for (final String confFile : confFiles) {
         // prevent backup files from being read
         if (confFile.endsWith(".conf")) {
           loadConfigFile(serverProperties, new File(confDir, confFile));
@@ -163,7 +164,7 @@ public class ServerProperties extends Properties {
     }
 
     // load env vars
-    for (java.util.Map.Entry<String, String> envvar : System.getenv().entrySet()) {
+    for (final java.util.Map.Entry<String, String> envvar : System.getenv().entrySet()) {
       if (envvar.getKey().startsWith("CAOSDB_CONFIG_") && envvar.getKey().length() > 14) {
         serverProperties.setProperty(envvar.getKey().substring(14), envvar.getValue());
       }
@@ -171,11 +172,11 @@ public class ServerProperties extends Properties {
 
     // log configuration alphabetically
     if (logger.isInfoEnabled()) {
-      ArrayList<String> names = new ArrayList<>(serverProperties.stringPropertyNames());
+      final ArrayList<String> names = new ArrayList<>(serverProperties.stringPropertyNames());
       Collections.sort(names);
-      for (String name : names) {
-        String val =
-            (name.contains("PASSW") || name.contains("SECRET"))
+      for (final String name : names) {
+        final String val =
+            name.contains("PASSW") || name.contains("SECRET")
                 ? "****"
                 : serverProperties.getProperty(name);
         logger.info(name + "=" + val);
@@ -184,7 +185,7 @@ public class ServerProperties extends Properties {
     return serverProperties;
   }
 
-  private static void loadConfigFile(Properties serverProperties, File confFile)
+  private static void loadConfigFile(final Properties serverProperties, final File confFile)
       throws IOException {
     if (confFile.exists() && confFile.isFile()) {
       logger.info("Reading configuration from " + confFile.getAbsolutePath());
diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java
index 965e7181d1d010528b341f6cbf34d4c0c67a9e5c..8accff648bd97da90d7d4138ea4463ea6b5c98be 100644
--- a/src/main/java/org/caosdb/server/database/BackendTransaction.java
+++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java
@@ -61,7 +61,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveRole
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveSparseEntity;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveUser;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveVersionHistory;
-import org.caosdb.server.database.backend.implementation.MySQL.MySQLRuleLoader;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetFileCheckedTimestampImpl;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetFileChecksum;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetPassword;
@@ -117,7 +116,6 @@ import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveUserImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl;
-import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl;
 import org.caosdb.server.database.backend.interfaces.SetFileCheckedTimestampImpl;
 import org.caosdb.server.database.backend.interfaces.SetFileChecksumImpl;
 import org.caosdb.server.database.backend.interfaces.SetPasswordImpl;
@@ -129,8 +127,6 @@ import org.caosdb.server.database.backend.interfaces.UpdateUserImpl;
 import org.caosdb.server.database.backend.interfaces.UpdateUserRolesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.misc.TransactionBenchmark;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.RetrieveEntity;
 import org.caosdb.server.utils.UndoHandler;
 import org.caosdb.server.utils.Undoable;
 
@@ -183,7 +179,6 @@ public abstract class BackendTransaction implements Undoable {
       setImpl(GetFileRecordByPathImpl.class, MySQLGetFileRecordByPath.class);
       setImpl(RetrievePropertiesImpl.class, MySQLRetrieveProperties.class);
       setImpl(RetrieveSparseEntityImpl.class, MySQLRetrieveSparseEntity.class);
-      setImpl(RuleLoaderImpl.class, MySQLRuleLoader.class);
       setImpl(SyncStatsImpl.class, MySQLSyncStats.class);
       setImpl(FileExists.class, UnixFileSystemFileExists.class);
       setImpl(FileWasModifiedAfter.class, UnixFileSystemFileWasModifiedAfter.class);
@@ -251,7 +246,7 @@ public abstract class BackendTransaction implements Undoable {
   protected <T extends BackendTransactionImpl> T getImplementation(final Class<T> clz) {
     init();
     try {
-      Class<?> implclz = impl.get(clz);
+      final Class<?> implclz = impl.get(clz);
       final T ret = (T) implclz.getConstructor(Access.class).newInstance(this.access);
       if (ret instanceof Undoable) {
         this.undoHandler.append((Undoable) ret);
@@ -289,18 +284,13 @@ public abstract class BackendTransaction implements Undoable {
   }
 
   /** Set the benchmark object for this AbstractTransaction. */
-  public void setTransactionBenchmark(TransactionBenchmark b) {
+  public void setTransactionBenchmark(final TransactionBenchmark b) {
     this.benchmark = b;
   }
 
-  public void addMeasurement(Object o, long time) {
+  public void addMeasurement(final Object o, final long time) {
     if (this.benchmark != null) {
       this.benchmark.addMeasurement(o, time);
     }
   }
-
-  /** Return the type of transaction of an entity, e.g. "Retrieve" for a {@link RetrieveEntity}. */
-  public String getTransactionType(EntityInterface e) {
-    return e.getClass().getSimpleName().replace("Entity", "");
-  }
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java
index 525c3973e5267d336727e723a383aff575dd8493..8b34fe2aee9a28a3fdeb56c7c7cbc03e53233982 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java
@@ -1,80 +1,81 @@
-/*
- * ** 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 static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
-
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.Rule;
-import org.caosdb.server.jobs.core.Mode;
-
-public class MySQLRuleLoader extends MySQLTransaction implements RuleLoaderImpl {
-
-  public MySQLRuleLoader(final Access access) {
-    super(access);
-  }
-
-  public static final String STMT_GET_RULES =
-      "SELECT rules.transaction, rules.criterion, rules.modus from rules where rules.domain_id=? AND rules.entity_id=? AND rules.transaction=?";
-
-  @Override
-  public ArrayList<Rule> executeNoCache(
-      final Integer domain, final Integer entity, final String transaction)
-      throws TransactionException {
-    try {
-      final PreparedStatement stmt = prepareStatement(STMT_GET_RULES);
-
-      stmt.setInt(1, domain);
-      stmt.setInt(2, entity);
-      stmt.setString(3, transaction);
-      stmt.execute();
-
-      final ResultSet rs = stmt.executeQuery();
-      try {
-        final ArrayList<Rule> ret = new ArrayList<Rule>();
-        while (rs.next()) {
-          final Rule r = new Rule();
-          r.mode = Mode.valueOf(bytes2UTF8(rs.getBytes("modus")));
-          r.job = bytes2UTF8(rs.getBytes("criterion"));
-          r.domain = domain;
-          r.entity = entity;
-          r.transaction = transaction;
-          ret.add(r);
-        }
-        return ret;
-      } finally {
-        rs.close();
-      }
-    } catch (final SQLException e) {
-      throw new TransactionException(e);
-    } catch (final ConnectionException e) {
-      throw new TransactionException(e);
-    }
-  }
-}
+/// *
+// * ** 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 static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+//
+// import java.sql.PreparedStatement;
+// import java.sql.ResultSet;
+// import java.sql.SQLException;
+// import java.util.ArrayList;
+// import org.caosdb.server.database.access.Access;
+// import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl;
+// import org.caosdb.server.database.exceptions.TransactionException;
+// import org.caosdb.server.database.proto.Rule;
+// import org.caosdb.server.jobs.core.Mode;
+//
+// public class MySQLRuleLoader extends MySQLTransaction implements RuleLoaderImpl {
+//
+//  public MySQLRuleLoader(final Access access) {
+//    super(access);
+//  }
+//
+//  public static final String STMT_GET_RULES =
+//      "SELECT rules.transaction, rules.criterion, rules.modus from rules where rules.domain_id=?
+// AND rules.entity_id=? AND rules.transaction=?";
+//
+//  @Override
+//  public ArrayList<Rule> executeNoCache(
+//      final Integer domain, final Integer entity, final String transaction)
+//      throws TransactionException {
+//    try {
+//      final PreparedStatement stmt = prepareStatement(STMT_GET_RULES);
+//
+//      stmt.setInt(1, domain);
+//      stmt.setInt(2, entity);
+//      stmt.setString(3, transaction);
+//      stmt.execute();
+//
+//      final ResultSet rs = stmt.executeQuery();
+//      try {
+//        final ArrayList<Rule> ret = new ArrayList<Rule>();
+//        while (rs.next()) {
+//          final Rule r = new Rule();
+//          r.mode = Mode.valueOf(bytes2UTF8(rs.getBytes("modus")));
+//          r.job = bytes2UTF8(rs.getBytes("criterion"));
+//          r.domain = domain;
+//          r.entity = entity;
+//          r.transaction = transaction;
+//          ret.add(r);
+//        }
+//        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/interfaces/RuleLoaderImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java
index 151e3d9d21d02d953cffeec8a55b4e01377fc023..66fd1abd9286d34964fc0f00096a17eba8dd56e3 100644
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java
+++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java
@@ -1,33 +1,33 @@
-/*
- * ** 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.interfaces;
-
-import java.util.ArrayList;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.Rule;
-
-public interface RuleLoaderImpl extends BackendTransactionImpl {
-
-  public ArrayList<Rule> executeNoCache(Integer domain, Integer entity, String transaction)
-      throws TransactionException;
-}
+/// *
+// * ** 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.interfaces;
+//
+// import java.util.ArrayList;
+// import org.caosdb.server.database.exceptions.TransactionException;
+// import org.caosdb.server.database.proto.Rule;
+//
+// public interface RuleLoaderImpl extends BackendTransactionImpl {
+//
+//  public ArrayList<Rule> executeNoCache(Integer domain, Integer entity, String transaction)
+//      throws TransactionException;
+// }
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java
index 9b798de4279de8034168c4ecfbc1738c70370a65..eb345f85ebdd4372feed664c9db12944870b1078 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java
@@ -34,7 +34,9 @@ import org.caosdb.server.utils.EntityStatus;
  * Record the current transaction in the entities transaction history.
  *
  * @author Timm Fitschen (t.fitschen@indiscale.com)
+ * @deprecated To be replaced by versioning history.
  */
+@Deprecated
 public class InsertTransactionHistory extends BackendTransaction {
 
   private final TransactionContainer container;
@@ -62,7 +64,7 @@ public class InsertTransactionHistory extends BackendTransaction {
           || e.getEntityStatus() == EntityStatus.VALID) {
 
         t.execute(
-            getTransactionType(e),
+            e.getClass().getSimpleName().replace("Entity", ""),
             this.realm,
             this.user,
             this.datetime.getUTCSeconds(),
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java b/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
index c73023bd4429db19ca0a968aa99b657f0d0cbea4..753265f0fb6007de47c0b03e0b8bb5f6a7f47a5c 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
@@ -1,96 +1,96 @@
-/*
- * ** 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) 2019 IndiScale GmbH
- * Copyright (C) 2019 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/>.
- *
- * ** end header
- */
-package org.caosdb.server.database.backend.transaction;
-
-import java.util.ArrayList;
-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.backend.interfaces.RuleLoaderImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.Rule;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.entity.wrapper.Property;
-import org.caosdb.server.jobs.Job;
-import org.caosdb.server.transaction.Transaction;
-
-/**
- * Load transaction rules (e.g. "Referenced entities must always exist") and their configuration
- * from the back-end.
- *
- * @author Timm Fitschen (t.fitschen@indiscale.com)
- */
-public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Rule>> {
-
-  private static final ICacheAccess<String, ArrayList<Rule>> cache =
-      Cache.getCache("BACKEND_JobRules");
-  private final Transaction<? extends TransactionContainer> transaction;
-  private final EntityInterface e;
-  private final Integer entity;
-  private final Integer domain;
-  private ArrayList<Job> jobs;
-  private String transactionType;
-
-  public RuleLoader(
-      final Integer domain,
-      final Integer entity,
-      final EntityInterface e,
-      final Transaction<? extends TransactionContainer> transaction) {
-    super(cache);
-    this.domain = domain;
-    this.entity = entity;
-    this.e = e;
-    if (e instanceof Property) {
-      this.transactionType = getTransactionType(((Property) e).getDomainEntity());
-    } else {
-      this.transactionType = getTransactionType(e);
-    }
-    this.transaction = transaction;
-  }
-
-  @Override
-  protected String getKey() {
-    return "<" + this.domain + "," + this.entity + "," + this.transactionType + ">";
-  }
-
-  public ArrayList<Job> getJobs() {
-    return this.jobs;
-  }
-
-  @Override
-  public ArrayList<Rule> executeNoCache() throws TransactionException {
-    final RuleLoaderImpl t = getImplementation(RuleLoaderImpl.class);
-    return t.executeNoCache(this.domain, this.entity, this.transactionType);
-  }
-
-  @Override
-  protected void process(final ArrayList<Rule> t) throws TransactionException {
-    this.jobs = new ArrayList<Job>();
-    for (final Rule r : t) {
-      this.jobs.add(Job.getJob(r.job, r.mode, this.e, this.transaction));
-    }
-  }
-}
+/// *
+// * ** 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) 2019 IndiScale GmbH
+// * Copyright (C) 2019 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/>.
+// *
+// * ** end header
+// */
+// package org.caosdb.server.database.backend.transaction;
+//
+// import java.util.ArrayList;
+// 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.backend.interfaces.RuleLoaderImpl;
+// import org.caosdb.server.database.exceptions.TransactionException;
+// import org.caosdb.server.database.proto.Rule;
+// import org.caosdb.server.entity.EntityInterface;
+// import org.caosdb.server.entity.container.TransactionContainer;
+// import org.caosdb.server.entity.wrapper.Property;
+// import org.caosdb.server.jobs.Job;
+// import org.caosdb.server.transaction.Transaction;
+//
+/// **
+// * Load transaction rules (e.g. "Referenced entities must always exist") and their configuration
+// * from the back-end.
+// *
+// * @author Timm Fitschen (t.fitschen@indiscale.com)
+// */
+// public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Rule>> {
+//
+//  private static final ICacheAccess<String, ArrayList<Rule>> cache =
+//      Cache.getCache("BACKEND_JobRules");
+//  private final Transaction<? extends TransactionContainer> transaction;
+//  private final EntityInterface e;
+//  private final Integer entity;
+//  private final Integer domain;
+//  private ArrayList<Job> jobs;
+//  private String transactionType;
+//
+//  public RuleLoader(
+//      final Integer domain,
+//      final Integer entity,
+//      final EntityInterface e,
+//      final Transaction<? extends TransactionContainer> transaction) {
+//    super(cache);
+//    this.domain = domain;
+//    this.entity = entity;
+//    this.e = e;
+//    if (e instanceof Property) {
+//      this.transactionType = getTransactionType(((Property) e).getDomainEntity());
+//    } else {
+//      this.transactionType = getTransactionType(e);
+//    }
+//    this.transaction = transaction;
+//  }
+//
+//  @Override
+//  protected String getKey() {
+//    return "<" + this.domain + "," + this.entity + "," + this.transactionType + ">";
+//  }
+//
+//  public ArrayList<Job> getJobs() {
+//    return this.jobs;
+//  }
+//
+//  @Override
+//  public ArrayList<Rule> executeNoCache() throws TransactionException {
+//    final RuleLoaderImpl t = getImplementation(RuleLoaderImpl.class);
+//    return t.executeNoCache(this.domain, this.entity, this.transactionType);
+//  }
+//
+//  @Override
+//  protected void process(final ArrayList<Rule> t) throws TransactionException {
+//    this.jobs = new ArrayList<Job>();
+//    for (final Rule r : t) {
+//      this.jobs.add(Job.getJob(r.job, r.mode, this.e, this.transaction));
+//    }
+//  }
+// }
diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java
index 9432fc3916606cddbf64922850d7f2f9ff5284c2..0f0875edbe9e183b10909b61296da9c8cae9f920 100644
--- a/src/main/java/org/caosdb/server/jobs/Job.java
+++ b/src/main/java/org/caosdb/server/jobs/Job.java
@@ -40,14 +40,16 @@ import org.caosdb.server.database.backend.transaction.IsSubType;
 import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
 import org.caosdb.server.database.backend.transaction.RetrieveParents;
 import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity;
-import org.caosdb.server.database.backend.transaction.RuleLoader;
+import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.datatype.AbstractCollectionDatatype;
 import org.caosdb.server.datatype.AbstractDatatype;
 import org.caosdb.server.datatype.ReferenceDatatype2;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.container.ParentContainer;
 import org.caosdb.server.entity.container.TransactionContainer;
+import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.jobs.core.Mode;
 import org.caosdb.server.transaction.Transaction;
 import org.caosdb.server.utils.EntityStatus;
@@ -63,6 +65,8 @@ public abstract class Job {
   /** All known Job classes, by name (actually lowercase getSimpleName()). */
   static HashMap<String, Class<? extends Job>> allClasses = null;
 
+  private final JobConfig jobConfig = JobConfig.getInstance();
+
   private static List<Class<? extends Job>> loadAlways;
 
   private Transaction<? extends TransactionContainer> transaction = null;
@@ -149,10 +153,68 @@ public abstract class Job {
     return this.mode;
   }
 
+  protected final void setMode(final Mode mode) {
+    this.mode = mode;
+  }
+
   TransactionContainer getContainer() {
     return getTransaction().getContainer();
   }
 
+  /**
+   * This assumes that the parent has either an id (persistent or temporary) or a name.
+   *
+   * @param child
+   * @param parent
+   * @return
+   * @throws Message
+   */
+  protected final boolean isSubType(final EntityInterface child, final EntityInterface parent)
+      throws EntityWasNotUniqueException {
+    if (parent.hasId()) {
+      if (parent.getId().equals(child.getId())) {
+        return true;
+      }
+
+    } else if (parent.hasName()) {
+      if (parent.getName().equals(child.getName())) {
+        return true;
+      }
+    }
+
+    // check direct parents of child
+    ParentContainer directParents;
+    if (child.hasParents()) {
+      directParents = child.getParents();
+    } else {
+      directParents = resolve(child).getParents();
+    }
+    for (final Parent directParent : directParents) {
+      EntityInterface resolvedDirectParent = null;
+      if (directParent.hasId()) {
+        resolvedDirectParent = getEntityById(directParent.getId());
+      } else if (directParent.hasName()) {
+        resolvedDirectParent = getEntityByName(directParent.getName());
+      }
+
+      if (resolvedDirectParent != null) {
+        if (isSubType(resolvedDirectParent, parent)) {
+          return true;
+        }
+      } else if (directParent.hasId()) {
+        if (isValidSubType(directParent.getId(), parent.getId())) {
+          return true;
+        }
+      } else if (directParent.hasName()) {
+        if (isValidSubType(resolve(directParent).getId(), parent.getId())) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
   protected final boolean isValidSubType(final int child, final int parent) {
     if (!"false".equals(getContainer().getFlags().get("cache"))) {
       final HashMap<String, Boolean> isSubTypeCache = getCache("isSubType");
@@ -187,17 +249,17 @@ public abstract class Job {
       // the targeted entity version is the entity after the transaction or the
       // entity without a specific version. Thus we have to fetch the entity
       // from the container if possible.
-      EntityInterface ret = getEntityById(id);
+      final EntityInterface ret = getEntityById(id);
       if (ret != null) {
         return ret;
       }
     } else if (version.startsWith("HEAD~")) {
-      EntityInterface entById = getEntityById(id);
+      final EntityInterface entById = getEntityById(id);
       if (entById != null && entById.getEntityStatus() != EntityStatus.VALID) {
         // if version is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is is to be
         // updated, the actual offset has to be reduced by 1. HEAD always denotes the entity@HEAD
         // *after* the successful transaction, so that it is consistent with subsequent retrieves.
-        int offset = Integer.parseInt(version.substring(5)) - 1;
+        final int offset = Integer.parseInt(version.substring(5)) - 1;
         if (offset == 0) {
           // special case HEAD~1
           resulting_version = "HEAD";
@@ -215,7 +277,7 @@ public abstract class Job {
     return ret;
   }
 
-  protected final EntityInterface retrieveValidEntity(Integer id) {
+  protected final EntityInterface retrieveValidEntity(final Integer id) {
     return execute(new RetrieveFullEntityTransaction(id)).getContainer().get(0);
   }
 
@@ -316,14 +378,12 @@ public abstract class Job {
       return new ArrayList<Job>();
     }
     if (dt instanceof ReferenceDatatype2) {
-      final RuleLoader t = new RuleLoader(0, 17, entity, transaction);
-      return execute(t).getJobs();
+      return jobConfig.getConfiguredJobs(0, 17, entity, transaction);
     } else if (dt instanceof AbstractCollectionDatatype) {
       final AbstractDatatype datatype = ((AbstractCollectionDatatype) dt).getDatatype();
       return loadDataTypeSpecificJobs(datatype, entity, transaction);
     } else if (dt.getId() != null) {
-      final RuleLoader t = new RuleLoader(0, dt.getId(), entity, transaction);
-      return execute(t).getJobs();
+      return jobConfig.getConfiguredJobs(0, dt.getId(), entity, transaction);
     } else {
       return null;
     }
@@ -334,7 +394,7 @@ public abstract class Job {
 
     final ArrayList<Job> jobs = new ArrayList<>();
     // load permanent jobs
-    for (Class<? extends Job> j : loadAlways) {
+    for (final Class<? extends Job> j : loadAlways) {
       if (EntityJob.class.isAssignableFrom(j)
           && j.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) {
         jobs.add(getJob(j, Mode.MUST, entity, transaction));
@@ -342,28 +402,34 @@ public abstract class Job {
     }
 
     // load general rules
-    {
-      final RuleLoader t = new RuleLoader(0, 0, entity, transaction);
-      jobs.addAll(execute(t).getJobs());
+    final List<Job> generalRules = jobConfig.getConfiguredJobs(0, 0, entity, transaction);
+    if (generalRules != null) {
+      jobs.addAll(generalRules);
     }
 
     // load Role specific rules
     if (entity.hasRole()) {
-      final RuleLoader t = new RuleLoader(0, entity.getRole().getId(), entity, transaction);
-      jobs.addAll(execute(t).getJobs());
+      final List<Job> roleRules =
+          jobConfig.getConfiguredJobs(0, entity.getRole().getId(), entity, transaction);
+      if (roleRules != null) {
+        jobs.addAll(roleRules);
+      }
     }
 
     // load data type specific rules
-    jobs.addAll(loadDataTypeSpecificJobs(entity, transaction));
+    final List<Job> datatypeRules = loadDataTypeSpecificJobs(entity, transaction);
+    if (datatypeRules != null) {
+      jobs.addAll(datatypeRules);
+    }
 
     return jobs;
   }
 
   private static Job getJob(
-      Class<? extends Job> jobClass,
-      Mode mode,
-      EntityInterface entity,
-      Transaction<? extends TransactionContainer> transaction) {
+      final Class<? extends Job> jobClass,
+      final Mode mode,
+      final EntityInterface entity,
+      final Transaction<? extends TransactionContainer> transaction) {
     Job ret;
     try {
 
@@ -472,15 +538,38 @@ public abstract class Job {
     return this.stage;
   }
 
+  protected EntityInterface resolve(final EntityInterface entity)
+      throws EntityWasNotUniqueException {
+    EntityInterface resolvedParent = null;
+    // if the parent has an id>0, it is to be a valid entity.
+    if (!entity.hasId() && entity.hasName()) {
+      resolvedParent = getEntityByName(entity.getName());
+      if (resolvedParent == null) {
+        final Integer eid = retrieveValidIDByName(entity.getName());
+        entity.setId(eid);
+      }
+    }
+
+    if (entity.hasId()) {
+      // get parent from container
+      resolvedParent = getEntityById(entity.getId());
+      if (resolvedParent == null) {
+        resolvedParent = retrieveValidEntity(entity.getId());
+      }
+    }
+
+    return resolvedParent;
+  }
+
   /**
    * Return those matching jobs which are annotated with the "loadAlways" attribute.
    *
    * @return A list with the jobs.
    */
-  public static List<Job> loadPermanentContainerJobs(Transaction<?> transaction) {
+  public static List<Job> loadPermanentContainerJobs(final Transaction<?> transaction) {
     final ArrayList<Job> jobs = new ArrayList<>();
     // load permanent jobs: ContainerJob classes with the correct transaction
-    for (Class<? extends Job> j : loadAlways) {
+    for (final Class<? extends Job> j : loadAlways) {
       if (ContainerJob.class.isAssignableFrom(j)
           && j.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) {
         jobs.add(getJob(j, Mode.MUST, null, transaction));
diff --git a/src/main/java/org/caosdb/server/jobs/JobConfig.java b/src/main/java/org/caosdb/server/jobs/JobConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cceeae3bffb545cbfa82af7540fe2e9c602ba6c
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/JobConfig.java
@@ -0,0 +1,134 @@
+package org.caosdb.server.jobs;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.ServerProperties;
+import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.container.TransactionContainer;
+import org.caosdb.server.entity.wrapper.Property;
+import org.caosdb.server.jobs.core.Mode;
+import org.caosdb.server.transaction.Transaction;
+import org.caosdb.server.utils.ConfigurationException;
+
+public class JobConfig {
+
+  private static JobConfig instance;
+
+  static {
+    try {
+      instance = new JobConfig();
+    } catch (final Exception e) {
+      e.printStackTrace();
+      // CaosDB Server cannot work without a proper job configuration.
+      System.exit(1);
+    }
+  }
+
+  private final Map<String, List<Object[]>> rules;
+
+  public JobConfig() throws FileNotFoundException, IOException {
+    this(new File(CaosDBServer.getServerProperty(ServerProperties.KEY_JOB_RULES_CONFIG)));
+  }
+
+  public JobConfig(final File config) throws FileNotFoundException, IOException {
+    this.rules = getRulesFromCSV(config);
+  }
+
+  private Map<String, List<Object[]>> getRulesFromCSV(final File file)
+      throws FileNotFoundException, IOException {
+    try (final BufferedReader reader = new BufferedReader(new FileReader(file))) {
+      final Map<String, List<Object[]>> result = new HashMap<>();
+      reader
+          .lines()
+          .forEach(
+              line -> {
+                // cut away comments, trim, then split into columns
+                final String[] row = line.replaceFirst("#.*$", "").strip().split("\\s*,\\s*");
+                if (row.length == 1 && row[0].isBlank()) {
+                  // comment or empty line
+                  // ignore
+                  return;
+                }
+                addRule(result, row);
+              });
+      return result;
+    }
+  }
+
+  private String getKey(final Integer domain, final Integer entity, final String transaction) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append("<");
+    sb.append(domain);
+    sb.append(",");
+    sb.append(transaction.toLowerCase());
+    sb.append(",");
+    sb.append(entity);
+    sb.append(">");
+    return sb.toString();
+  }
+
+  private void addRule(final Map<String, List<Object[]>> result, final String[] row) {
+    if (row.length != 5) {
+      throw new ConfigurationException(
+          "Could not parse the job rules. Lines of four comma-separated values expected");
+    }
+    try {
+      final Integer domain = Integer.parseInt(row[0]);
+      final Integer entity = Integer.parseInt(row[1]);
+      final String transaction = row[2];
+      final String job = row[3];
+      final Mode mode = Mode.valueOf(row[4]);
+      final String key = getKey(domain, entity, transaction);
+      if (!result.containsKey(key)) {
+        result.put(key, new ArrayList<>());
+      }
+      result.get(key).add(new Object[] {job, mode});
+
+    } catch (final Exception e) {
+      throw new ConfigurationException("Could not parse the job rules.", e);
+    }
+  }
+
+  public List<Job> getConfiguredJobs(
+      final Integer domain,
+      final Integer entity,
+      final EntityInterface target,
+      final Transaction<? extends TransactionContainer> transaction) {
+    String transactionType;
+    if (target instanceof Property) {
+      transactionType = getTransactionType(((Property) target).getDomainEntity());
+    } else {
+      transactionType = getTransactionType(target);
+    }
+    final String key = getKey(domain, entity, transactionType);
+    final List<Object[]> jobRules = rules.get(key);
+    if (jobRules != null) {
+      final List<Job> result = new LinkedList<>();
+      jobRules.forEach(
+          config -> {
+            result.add(Job.getJob((String) config[0], (Mode) config[1], target, transaction));
+          });
+      return result;
+    }
+    return null;
+  }
+
+  public static JobConfig getInstance() {
+    return instance;
+  }
+
+  /** Return the type of transaction of an entity, e.g. "Retrieve" for a {@link RetrieveEntity}. */
+  String getTransactionType(final EntityInterface e) {
+    return e.getClass().getSimpleName().replace("Entity", "");
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
index f46013d278f268b95b1b105130e1eabd74f3a9f4..f9c7704c8bd0ff00214b41d5793c8fdb15bcc61a 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
@@ -82,9 +82,12 @@ public final class CheckDatatypePresent extends EntityJob {
 
         // run jobsreturn this.entities;
         final List<Job> datatypeJobs = loadDataTypeSpecificJobs();
-        List<ScheduledJob> scheduledJobs = getTransaction().getSchedule().addAll(datatypeJobs);
-        for (final ScheduledJob job : scheduledJobs) {
-          getTransaction().getSchedule().runJob(job);
+        if (datatypeJobs != null) {
+          final List<ScheduledJob> scheduledJobs =
+              getTransaction().getSchedule().addAll(datatypeJobs);
+          for (final ScheduledJob job : scheduledJobs) {
+            getTransaction().getSchedule().runJob(job);
+          }
         }
       } else {
 
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
index 7aca2bac3dd3709006497f9ce8f556f267c4ca81..fc557f8907c22799dc1400a17c5377fb677306e5 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
@@ -22,9 +22,11 @@
  */
 package org.caosdb.server.jobs.core;
 
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
 import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.StatementStatus;
+import org.caosdb.server.entity.wrapper.Parent;
+import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -35,93 +37,114 @@ import org.caosdb.server.utils.ServerMessages;
  * @author tf
  */
 public class CheckParOblPropPresent extends EntityJob {
+
+  public static final Message ENTITY_NOT_UNIQUE =
+      new Message("Error", 0, "Could not check importance. Parent was not uniquely resolvable.");
+
+  public static final String OBL_IMPORTANCE_FLAG_KEY = "force-missing-obligatory";
+
   @Override
   public final void run() {
+    if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED
+        || getEntity() instanceof Property
+        || getEntity() instanceof Parent) {
+      return;
+    }
+    if (getEntity().hasParents()) {
 
-    // This test needs the valid/qualified ids of all properties.
-    // Thus, run a CheckPropValid first.
-    runJobFromSchedule(getEntity(), CheckPropValid.class);
+      handleImportanceFlags();
+      if (getMode() == Mode.IGNORE) {
+        // importance is to be ignored
+        return;
+      }
 
-    // This test needs the valid/qualified parents. If a parent is only
-    // referenced by name it has to be translated into a (hopefully) unique
-    // id.
-    runJobFromSchedule(getEntity(), CheckParValid.class);
+      // inherit properties
+      runJobFromSchedule(getEntity(), Inheritance.class);
 
-    if (getEntity().hasParents()) {
-      execute(new RetrieveFullEntityTransaction(getEntity().getParents()));
+      // get importance-flags
+
+      // loop over all parents
+      for (final EntityInterface parent : getEntity().getParents()) {
+        final EntityInterface resolvedParent = resolve(parent);
+        checkObligatoryPropertiesMissing(resolvedParent);
+      }
     }
+  }
 
-    // inherit properties
-    runJobFromSchedule(getEntity(), Inheritance.class);
+  /**
+   * Check if all obligatory properties of the given parent are present. Add a warning,
+   *
+   * @param parent
+   * @throws Message
+   */
+  private void checkObligatoryPropertiesMissing(final EntityInterface parent) {
+    // does this parent have properties?
+    if (parent.hasProperties()) {
 
-    // loop over all parents
-    if (getEntity().hasParents()) {
-      for (final EntityInterface parent : getEntity().getParents()) {
+      // loop over all properties of this parent
+      outerLoop:
+      for (final EntityInterface parentProperty : parent.getProperties()) {
 
-        // if the parent has an id>0, it is to be a valid entity.
-        if (parent.hasId() && parent.getId() > 0) {
-          if (parent.getEntityStatus() == EntityStatus.NONEXISTENT) {
-            continue;
-          }
+        // only obligatory properties are of interest.
+        if (parentProperty.hasStatementStatus()
+            && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) {
 
-          // does this parent have properties?
-          if (parent.hasProperties()) {
-
-            // loop over all properties of this parent
-            outerLoop:
-            for (final EntityInterface parentProperty : parent.getProperties()) {
-
-              // only obligatory properties are of interest.
-              if (parentProperty.hasStatementStatus()
-                  && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) {
-
-                // loop over all properties of the entity
-                innerLoop:
-                for (final EntityInterface entityProperty : getEntity().getProperties()) {
-
-                  if (!entityProperty.hasId()) {
-                    // continue inner loop means that this
-                    // is not the entityProperty in
-                    // question, continue search for an
-                    // entityProperty.
-                    continue innerLoop;
-                  }
-
-                  // TODO sub type instead of equals!!!
-                  if (parentProperty.getId().equals(entityProperty.getId())) {
-                    // continue outer loop means that we
-                    // found an entityProperty that
-                    // implements the obligatory
-                    // parentProperty, continue with the
-                    // other parentProperties.
-                    continue outerLoop;
-                  }
-                }
-
-                // No entityProperty has been found which
-                // implements this parentProperty. Add the
-                // respective messages.
-                switch (getMode()) {
-                  case MUST:
-                    // TODO add information WHICH property is
-                    // missing.
-                    getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
-                    break;
-                  case SHOULD:
-                    // TODO add information WHICH property is
-                    // missing.
-                    getEntity().addWarning(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
-                    break;
-                  default:
-                    break;
-                }
-              }
+          // loop over all properties of the entity
+          for (final EntityInterface entityProperty : getEntity().getProperties()) {
+
+            if (isSubType(entityProperty, parentProperty)) {
+              // continue outer loop means that we
+              // found an entityProperty that
+              // implements the obligatory
+              // parentProperty, continue with the
+              // other parentProperties.
+              continue outerLoop;
             }
           }
-        } else {
-          // TODO add processing of new parents, new properties
+
+          // No entityProperty has been found which
+          // implements this parentProperty. Add the
+          // respective messages.
+          switch (getMode()) {
+            case MUST:
+              // TODO add information WHICH property is
+              // missing.
+              getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
+              break;
+            case SHOULD:
+              // TODO add information WHICH property is
+              // missing.
+              getEntity().addWarning(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
+              break;
+            default:
+              // should never be reached, but ignoring is the way to go anyway
+              break;
+          }
         }
       }
     }
   }
+
+  private void handleImportanceFlags() {
+    final String globalFlag =
+        getTransaction().getContainer().getFlags().get(OBL_IMPORTANCE_FLAG_KEY);
+    final String entityFlag =
+        getEntity().getFlags().getOrDefault(OBL_IMPORTANCE_FLAG_KEY, globalFlag);
+    if (entityFlag != null) {
+      switch (entityFlag) {
+        case "error":
+          setMode(Mode.MUST);
+          break;
+        case "warn":
+          setMode(Mode.SHOULD);
+          break;
+        case "ignore":
+          setMode(Mode.IGNORE);
+          break;
+        default:
+          // do nothing
+          break;
+      }
+    }
+  }
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
index 39b66e35330a485bbd8eb4c40724ab3b250ccc45..89ea4dcc17d143720c55a971e9eb8fcafbbfb30c 100644
--- a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
+++ b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
@@ -1,32 +1,37 @@
 package org.caosdb.server.jobs.core;
 
 import java.util.List;
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
-import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.JobAnnotation;
 import org.caosdb.server.jobs.TransactionStage;
+import org.caosdb.server.utils.EntityStatus;
 
 @JobAnnotation(stage = TransactionStage.CHECK)
 public class InheritInitialState extends EntityStateJob {
 
   @Override
   protected void run() {
+    if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED) {
+      return;
+    }
     try {
-      State parentState = getFirstParentState();
+      final State parentState = getFirstParentState();
       if (parentState != null) {
         getEntity().addMessage(parentState);
         parentState.getStateEntity();
         getEntity().setEntityACL(parentState.getStateACL());
       }
-    } catch (Message e) {
+    } catch (final Message e) {
       getEntity().addError(e);
     }
   }
 
   private State getFirstParentState() throws Message {
-    for (Parent par : getEntity().getParents()) {
-      List<Property> stateProperties = getStateProperties(par, false);
+    for (EntityInterface par : getEntity().getParents()) {
+      par = resolve(par);
+      final List<Property> stateProperties = getStateProperties(par, false);
       if (stateProperties.size() > 0) {
         return createState(stateProperties.get(0));
       }
diff --git a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
index 107c5768a5c15fbb05ea6d68b0cb956cbb235687..8b26ba196ce8d4d32f03acda8b38b6524217fcc1 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
@@ -65,7 +65,7 @@ public class Inheritance extends EntityJob {
   @Override
   protected void run() {
     if (getEntity() instanceof InsertEntity || getEntity() instanceof UpdateEntity) {
-      if (getEntity().hasParents()) {
+      if (getEntity().getEntityStatus() == EntityStatus.QUALIFIED && getEntity().hasParents()) {
         final ArrayList<Property> transfer = new ArrayList<>();
         parentLoop:
         for (final EntityInterface parent : getEntity().getParents()) {
@@ -108,7 +108,7 @@ public class Inheritance extends EntityJob {
             }
           }
           // prop's Datatype might need to be resolved.
-          ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
+          final ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
           getTransaction().getSchedule().runJob(job);
 
           getEntity().addProperty(new Property(prop.getWrapped()));
@@ -133,12 +133,13 @@ public class Inheritance extends EntityJob {
               break propertyLoop;
             }
 
-            runJobFromSchedule(getEntity(), CheckPropValid.class);
-
             EntityInterface validProperty = new Entity(property.getId());
             if (getEntity().hasParents()) {
               outer:
-              for (final EntityInterface par : getEntity().getParents()) {
+              for (EntityInterface par : getEntity().getParents()) {
+                if (!par.hasProperties()) {
+                  par = resolve(par);
+                }
                 for (final EntityInterface prop : par.getProperties()) {
                   if (validProperty.hasId() && validProperty.getId().equals(prop.getId())) {
                     validProperty = prop;
@@ -166,7 +167,7 @@ public class Inheritance extends EntityJob {
               }
             }
             // prop's Datatype might need to be resolved.
-            ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
+            final ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
             getTransaction().getSchedule().runJob(job);
 
             property.addProperty(new Property(prop.getWrapped()));
@@ -187,7 +188,9 @@ public class Inheritance extends EntityJob {
    * @param inheritance
    */
   private void collectInheritedProperties(
-      List<Property> transfer, EntityInterface from, INHERITANCE_MODE inheritance) {
+      final List<Property> transfer,
+      final EntityInterface from,
+      final INHERITANCE_MODE inheritance) {
     if (from.hasProperties()) {
       for (final Property propProperty : from.getProperties()) {
         switch (inheritance) {
diff --git a/src/main/java/org/caosdb/server/jobs/core/Mode.java b/src/main/java/org/caosdb/server/jobs/core/Mode.java
index 561a4b0c5fcb4e2c8c66a2b9f9a02ea91c44c119..e786c7fe5eb2f7d1e7da3f4ea1f831d37b3f4755 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Mode.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Mode.java
@@ -25,6 +25,5 @@ package org.caosdb.server.jobs.core;
 public enum Mode {
   MUST,
   SHOULD,
-  SHOULDNT,
-  MUSTNOT
+  IGNORE,
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java
index 300e32bf1662e4990f52bda0570a65c05e5d60b8..2836ccf742ebbd1bbda5a8ed3ae95ac0d69c2fbb 100644
--- a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java
+++ b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java
@@ -27,10 +27,11 @@ import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
-@JobAnnotation(flag = "uniquename")
+@JobAnnotation(flag = "uniquename", stage = TransactionStage.PRE_CHECK)
 public class UniqueName extends FlagJob {
 
   private void doCheck(final EntityInterface entity) {
diff --git a/src/main/java/org/caosdb/server/utils/ConfigurationException.java b/src/main/java/org/caosdb/server/utils/ConfigurationException.java
index 517c11677c73d9624b93df18fb5196e2f4578024..df04ac169f3c8113443a69795840a50774bef0b1 100644
--- a/src/main/java/org/caosdb/server/utils/ConfigurationException.java
+++ b/src/main/java/org/caosdb/server/utils/ConfigurationException.java
@@ -26,7 +26,11 @@ public class ConfigurationException extends RuntimeException {
 
   private static final long serialVersionUID = -8973694831724941007L;
 
-  public ConfigurationException(String reason) {
+  public ConfigurationException(final String reason) {
     super(reason);
   }
+
+  public ConfigurationException(final String reason, final Throwable cause) {
+    super(reason, cause);
+  }
 }
diff --git a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java b/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java
deleted file mode 100644
index 93402bf0d06c487451d9219b4487f301eb855f49..0000000000000000000000000000000000000000
--- a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.caosdb.server.database.backend.transaction;
-
-import static org.junit.Assert.assertEquals;
-
-import org.caosdb.server.database.BackendTransaction;
-import org.caosdb.server.entity.DeleteEntity;
-import org.caosdb.server.entity.InsertEntity;
-import org.caosdb.server.entity.RetrieveEntity;
-import org.caosdb.server.entity.Role;
-import org.caosdb.server.entity.UpdateEntity;
-import org.jdom2.Element;
-import org.junit.Test;
-
-public class BackendTransactionTest {
-
-  @Test
-  public void testGetTransactionType() {
-    BackendTransaction transaction = new InsertTransactionHistory(null, null, null, null);
-    assertEquals("Retrieve", transaction.getTransactionType(new RetrieveEntity("test")));
-    assertEquals("Insert", transaction.getTransactionType(new InsertEntity("test", Role.Record)));
-    assertEquals("Delete", transaction.getTransactionType(new DeleteEntity(1234)));
-    assertEquals("Update", transaction.getTransactionType(new UpdateEntity(new Element("Record"))));
-  }
-}
diff --git a/src/test/java/org/caosdb/server/jobs/JobConfigTest.java b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bdaa63f5bf0b781e19fc5b7ea8388816f425812f
--- /dev/null
+++ b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java
@@ -0,0 +1,23 @@
+package org.caosdb.server.jobs;
+
+import static org.junit.Assert.assertEquals;
+
+import org.caosdb.server.entity.DeleteEntity;
+import org.caosdb.server.entity.InsertEntity;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.Role;
+import org.caosdb.server.entity.UpdateEntity;
+import org.jdom2.Element;
+import org.junit.Test;
+
+public class JobConfigTest {
+
+  @Test
+  public void testGetTransactionType() {
+    final JobConfig jobConfig = JobConfig.getInstance();
+    assertEquals("Retrieve", jobConfig.getTransactionType(new RetrieveEntity("test")));
+    assertEquals("Insert", jobConfig.getTransactionType(new InsertEntity("test", Role.Record)));
+    assertEquals("Delete", jobConfig.getTransactionType(new DeleteEntity(1234)));
+    assertEquals("Update", jobConfig.getTransactionType(new UpdateEntity(new Element("Record"))));
+  }
+}