diff --git a/CHANGELOG.md b/CHANGELOG.md
index df5b85f849385868b6eb8e1d7daa605bc1b4e8d8..b8362aff3682a130e28f40286fcb6ce2f0f703cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+* Related to #146, a new flag for entities and complete transactions:
+  `force-missing-obligatory=[ignore|warn|error]`. The flag overrides the
+  default behavior of the server (throwing an error when an obligatory property
+  is missing). `ignore` just discards the consistency check, `warn` only issues
+  a warning when obligatory properties are missing and `error` throws an error
+  in that case. The flag can be set for the complete transaction and each
+  single entity, while the entity flag takes precedence.
 * New EntityState plug-in. The plug-in disabled by default and can be enabled
   by setting the server property `EXT_ENTITY_STATE=ENABLED`. See
   [!62](https://gitlab.com/caosdb/caosdb-server/-/merge_requests/62) for more
@@ -31,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+* #146 - Default behavior for missing obligatory properties
 * #131 - CQL Parsing error when white space characters before some units.
 * #134 - CQL Parsing error when multiple white space characters after `FROM`.
 * #130 - Error during `FIND ENTITY` when
diff --git a/conf/core/jobs.csv b/conf/core/jobs.csv
new file mode 100644
index 0000000000000000000000000000000000000000..84795bee8657288473188cd9e7a6af81f6295221
--- /dev/null
+++ b/conf/core/jobs.csv
@@ -0,0 +1,110 @@
+#
+# This file is a part of the CaosDB Project.
+#
+# 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, JOB_FAILURE_SEVERITY
+
+# general rules
+0,0,INSERT,CheckPropValid,ERROR
+0,0,INSERT,CheckParValid,ERROR
+0,0,INSERT,CheckParOblPropPresent,ERROR
+0,0,INSERT,CheckValueParsable,ERROR
+0,0,UPDATE,CheckPropValid,ERROR
+0,0,UPDATE,CheckParValid,ERROR
+0,0,UPDATE,CheckParOblPropPresent,ERROR
+0,0,UPDATE,CheckValueParsable,ERROR
+0,0,DELETE,CheckReferenceDependencyExistent,ERROR
+0,0,DELETE,CheckChildDependencyExistent,ERROR
+
+
+# role specific rules
+## recordtype rules
+0,1,INSERT,CheckNamePresent,ERROR
+0,1,INSERT,CheckPropPresent,WARN
+0,1,INSERT,SetImpToRecByDefault,ERROR
+
+0,1,UPDATE,CheckNamePresent,ERROR
+0,1,UPDATE,CheckPropPresent,WARN
+0,1,UPDATE,SetImpToRecByDefault,ERROR
+
+## record rules
+0,2,INSERT,CheckNamePresent,WARN
+0,2,INSERT,CheckPropPresent,WARN
+0,2,INSERT,CheckParPresent,ERROR
+0,2,INSERT,SetImpToFix,ERROR
+
+0,2,UPDATE,CheckNamePresent,WARN
+0,2,UPDATE,CheckPropPresent,WARN
+0,2,UPDATE,CheckParPresent,ERROR
+0,2,UPDATE,SetImpToFix,ERROR
+
+## file rules
+0,3,INSERT,CheckNamePresent,WARN
+0,3,INSERT,MatchFileProp,ERROR
+0,3,INSERT,CheckTargetPathValid,ERROR
+0,3,INSERT,SetImpToFix,ERROR
+
+0,3,UPDATE,CheckNamePresent,WARN
+0,3,UPDATE,MatchFileProp,ERROR
+0,3,UPDATE,CheckTargetPathValid,ERROR
+0,3,UPDATE,SetImpToFix,ERROR
+
+## property rules
+0,4,INSERT,CheckDatatypePresent,ERROR
+0,4,UPDATE,CheckDatatypePresent,ERROR
+0,4,INSERT,CheckNamePresent,ERROR
+0,4,UPDATE,CheckNamePresent,ERROR
+0,4,INSERT,SetImpToFix,ERROR
+0,4,UPDATE,SetImpToFix,ERROR
+
+## query template rules
+0,8,UPDATE,CheckQueryTemplate,ERROR
+0,8,INSERT,CheckQueryTemplate,ERROR
+
+# data type specific rules
+## reference rules
+0,11,INSERT,CheckRefidPresent,WARN
+0,11,INSERT,CheckRefidValid,ERROR
+0,11,INSERT,CheckRefidIsaParRefid,WARN
+
+0,11,UPDATE,CheckRefidPresent,WARN
+0,11,UPDATE,CheckRefidValid,ERROR
+0,11,UPDATE,CheckRefidIsaParRefid,WARN
+
+## integer rules
+0,12,INSERT,CheckUnitPresent,WARN
+0,12,INSERT,ParseUnit,WARN
+
+0,12,UPDATE,CheckUnitPresent,WARN
+0,12,UPDATE,ParseUnit,WARN
+
+## double rules
+0,13,INSERT,CheckUnitPresent,WARN
+0,13,INSERT,ParseUnit,WARN
+
+0,13,UPDATE,CheckUnitPresent,WARN
+0,13,UPDATE,ParseUnit,WARN
+
+## filereference rules
+0,17,INSERT,CheckRefidValid,ERROR
+0,17,INSERT,CheckRefidIsaParRefid,ERROR
+
+0,17,UPDATE,CheckRefidValid,ERROR
+0,17,UPDATE,CheckRefidIsaParRefid,ERROR
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
deleted file mode 100644
index 525c3973e5267d336727e723a383aff575dd8493..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java
+++ /dev/null
@@ -1,80 +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 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
deleted file mode 100644
index 151e3d9d21d02d953cffeec8a55b4e01377fc023..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java
+++ /dev/null
@@ -1,33 +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.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
deleted file mode 100644
index c73023bd4429db19ca0a968aa99b657f0d0cbea4..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
+++ /dev/null
@@ -1,96 +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
- * 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/database/proto/Rule.java b/src/main/java/org/caosdb/server/database/proto/Rule.java
deleted file mode 100644
index 3d25f7662052b5b59f45255218c1191cf011761e..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/proto/Rule.java
+++ /dev/null
@@ -1,37 +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.proto;
-
-import java.io.Serializable;
-import org.caosdb.server.jobs.core.Mode;
-
-public class Rule implements Serializable {
-
-  private static final long serialVersionUID = 1122097540596265945L;
-
-  public int domain = 0;
-  public int entity = 0;
-  public String job = null;
-  public String transaction = null;
-  public Mode mode = null;
-}
diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java
index 9432fc3916606cddbf64922850d7f2f9ff5284c2..cc943fbd0baf211d7f467cb54a7797616218f608 100644
--- a/src/main/java/org/caosdb/server/jobs/Job.java
+++ b/src/main/java/org/caosdb/server/jobs/Job.java
@@ -40,15 +40,17 @@ 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.jobs.core.Mode;
+import org.caosdb.server.entity.wrapper.Parent;
+import org.caosdb.server.jobs.core.JobFailureSeverity;
 import org.caosdb.server.transaction.Transaction;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -63,10 +65,12 @@ 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;
-  private Mode mode = null;
+  private JobFailureSeverity failureSeverity = null;
   private final TransactionStage stage;
   private EntityInterface entity = null;
 
@@ -95,7 +99,7 @@ public abstract class Job {
   protected ScheduledJob appendJob(final EntityInterface entity, final Class<? extends Job> clazz) {
     try {
       final Job job = clazz.getDeclaredConstructor().newInstance();
-      job.init(getMode(), entity, getTransaction());
+      job.init(getFailureSeverity(), entity, getTransaction());
       return getTransaction().getSchedule().add(job);
     } catch (InstantiationException
         | IllegalAccessException
@@ -124,10 +128,10 @@ public abstract class Job {
   }
 
   public final Job init(
-      final Mode mode,
+      final JobFailureSeverity severity,
       final EntityInterface entity,
       final Transaction<? extends TransactionContainer> transaction) {
-    this.mode = mode;
+    this.failureSeverity = severity;
     this.entity = entity;
     this.transaction = transaction;
     return this;
@@ -145,14 +149,85 @@ public abstract class Job {
     return this.entity;
   }
 
-  protected final Mode getMode() {
-    return this.mode;
+  protected final JobFailureSeverity getFailureSeverity() {
+    return this.failureSeverity;
+  }
+
+  protected final void setFailureSeverity(final JobFailureSeverity severiy) {
+    this.failureSeverity = severiy;
   }
 
   TransactionContainer getContainer() {
     return getTransaction().getContainer();
   }
 
+  /**
+   * Check if an entity ('child') is a direct or indirect child of another entity ('targetParent').
+   *
+   * <p>This assumes that the parent has either an id (persistent or temporary) or a name.
+   *
+   * <p>If the targetParent is not in the set of parents of the child, this method iterates through
+   * the direct parents of the child.
+   *
+   * <p>If both entities are part of this transaction, they are resolved within this transaction.
+   * Otherwise they are fetched from the database backend or the whole evaluation takes place in the
+   * backend (if both have persistent ids and are not part of this transaction).
+   *
+   * <p>If both entities have the same id or name, the return value is true without any further
+   * checks.
+   *
+   * @param child the child entity
+   * @param targetParent the parent entity
+   * @return true iff targetParent is a direct or indirect parent of the child or when the ids or
+   *     names match.
+   * @throws Message
+   */
+  protected final boolean isSubType(final EntityInterface child, final EntityInterface targetParent)
+      throws EntityWasNotUniqueException {
+    if (targetParent.hasId()) {
+      if (targetParent.getId().equals(child.getId())) {
+        return true;
+      }
+
+    } else if (targetParent.hasName()) {
+      if (targetParent.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, targetParent)) {
+          return true;
+        }
+      } else if (directParent.hasId()) {
+        if (isValidSubType(directParent.getId(), targetParent.getId())) {
+          return true;
+        }
+      } else if (directParent.hasName()) {
+        if (isValidSubType(resolve(directParent).getId(), targetParent.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 +262,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 +290,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);
   }
 
@@ -245,7 +320,7 @@ public abstract class Job {
    */
   public static Job getJob(
       final String job,
-      final Mode mode,
+      final JobFailureSeverity severiy,
       final EntityInterface entity,
       final Transaction<? extends TransactionContainer> transaction) {
     // Fill `allClasses` with available subclasses
@@ -253,7 +328,7 @@ public abstract class Job {
 
     // Get matching class for Job and generate it.
     final Class<? extends Job> jobClass = allClasses.get(job.toLowerCase());
-    return getJob(jobClass, mode, entity, transaction);
+    return getJob(jobClass, severiy, entity, transaction);
   }
 
   /**
@@ -316,14 +391,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,42 +407,48 @@ 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));
+        jobs.add(getJob(j, JobFailureSeverity.ERROR, entity, transaction));
       }
     }
 
     // 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 JobFailureSeverity severity,
+      final EntityInterface entity,
+      final Transaction<? extends TransactionContainer> transaction) {
     Job ret;
     try {
 
       if (jobClass != null) {
         ret = jobClass.getDeclaredConstructor().newInstance();
-        ret.init(mode, entity, transaction);
+        ret.init(severity, entity, transaction);
         return ret;
       }
       return null;
@@ -393,7 +472,7 @@ public abstract class Job {
     // load flag jobs
     if (!entity.getFlags().isEmpty()) {
       for (final String key : entity.getFlags().keySet()) {
-        final Job j = getJob(key, Mode.MUST, entity, transaction);
+        final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction);
         if (j != null) {
           if (j instanceof FlagJob) {
             ((FlagJob) j).setValue(entity.getFlag(key));
@@ -408,7 +487,7 @@ public abstract class Job {
       for (final EntityInterface p : entity.getParents()) {
         if (!p.getFlags().isEmpty()) {
           for (final String key : p.getFlags().keySet()) {
-            final Job j = getJob(key, Mode.MUST, entity, transaction);
+            final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction);
             if (j != null) {
               if (j instanceof FlagJob) {
                 ((FlagJob) j).setValue(p.getFlag(key));
@@ -427,7 +506,7 @@ public abstract class Job {
       for (final EntityInterface p : entity.getProperties()) {
         if (!p.getFlags().isEmpty()) {
           for (final String key : p.getFlags().keySet()) {
-            final Job j = getJob(key, Mode.MUST, entity, transaction);
+            final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction);
             if (j != null) {
               if (j instanceof FlagJob) {
                 ((FlagJob) j).setValue(p.getFlag(key));
@@ -460,7 +539,7 @@ public abstract class Job {
         + "-"
         + (getEntity() != null ? getEntity().toString() : "NOENTITY")
         + " #"
-        + getMode().toString()
+        + getFailureSeverity().toString()
         + "]";
   }
 
@@ -472,18 +551,48 @@ public abstract class Job {
     return this.stage;
   }
 
+  /**
+   * Resolve an entity (which might only be specified by it's name or id) to its full representation
+   * (with properties, parents and all).
+   *
+   * @param entity the entity to be resolved.
+   * @return the resolved entity.
+   * @throws EntityWasNotUniqueException if the resolution failed due to ambuiguity of the name.
+   */
+  protected EntityInterface resolve(final EntityInterface entity)
+      throws EntityWasNotUniqueException {
+    EntityInterface resolvedEntity = null;
+    if (!entity.hasId() && entity.hasName()) {
+      resolvedEntity = getEntityByName(entity.getName());
+      if (resolvedEntity == null) {
+        final Integer eid = retrieveValidIDByName(entity.getName());
+        entity.setId(eid);
+      }
+    }
+
+    if (entity.hasId()) {
+      // get entity from container
+      resolvedEntity = getEntityById(entity.getId());
+      if (resolvedEntity == null && entity.getId() > 0) {
+        resolvedEntity = retrieveValidEntity(entity.getId());
+      }
+    }
+
+    return resolvedEntity;
+  }
+
   /**
    * 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));
+        jobs.add(getJob(j, JobFailureSeverity.ERROR, null, transaction));
       }
     }
     return jobs;
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..732a985a78b5f67c88d1c01dff1565e887aab13c
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/JobConfig.java
@@ -0,0 +1,203 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 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.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.JobFailureSeverity;
+import org.caosdb.server.transaction.Transaction;
+import org.caosdb.server.utils.ConfigurationException;
+
+/**
+ * Configuration of the jobs which have to run during all entity transactions with "job rules".
+ *
+ * <p>A job rule contains of a quintuple (domain, entity, transaction type, job name, job failure
+ * severity).
+ *
+ * <p>The domain and entity define for which entities the job (of the given name) should be loaded.
+ * The transaction type (e.g. Insert, Update, Delete) defines the transaction where this rule
+ * applies. The job failure severity sets the related property of a job.
+ *
+ * <p>The configuration is being read by default from a config file {@link
+ * ServerProperties#KEY_JOB_RULES_CONFIG}
+ *
+ * <p>This class works as a singleton, most of the time. It is possible to instanciate it otherwise,
+ * though.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+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);
+    }
+  }
+
+  /**
+   * The keys of these rules consist of the domain, entity and transaction. The value is a pair (job
+   * name, job failure severity).
+   */
+  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);
+  }
+
+  /**
+   * Read the rules from the CSV file.
+   *
+   * @param file
+   * @return A map with all the rules.
+   * @throws FileNotFoundException
+   * @throws IOException
+   */
+  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;
+    }
+  }
+
+  /** Generate a unique key (for class-internal use only). */
+  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();
+  }
+
+  /**
+   * Read a rule from the csv-row and put it into the 'result' map
+   *
+   * @param result map with all the rules.
+   * @param row represents a single rule as a comma-separated quintuple.
+   */
+  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 five 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 JobFailureSeverity severity = JobFailureSeverity.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, severity});
+
+    } catch (final Exception e) {
+      throw new ConfigurationException("Could not parse the job rules.", e);
+    }
+  }
+
+  /**
+   * Factory method for the instanciation and configuration of jobs for a specific entity.
+   *
+   * @param domain the domain of the rule
+   * @param entity the entity of the rule (this might be a particular datatype or a role, like
+   *     "RecordType")
+   * @param target the entity for which the job will run.
+   * @param transaction the transaction for which the job will run.
+   * @return Fresh job instances, one for each matching job rule.
+   */
+  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], (JobFailureSeverity) 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/CheckDescPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java
index fc3a5114f240c796c114f92f8e6271e4fd3bd9ac..e430a0817e9083013330bc4d59d8f7377d10d62a 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java
@@ -35,12 +35,12 @@ public class CheckDescPresent extends EntityJob {
   @Override
   public final void run() {
     if (!getEntity().hasDescription()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_DESCRIPTION);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_DESCRIPTION);
         default:
           break;
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java
index f1600ce42117ab5f37fccd33b79d821efeccb774..3166057c95671273efe27357ce11b2ffe10e2c78 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java
@@ -35,12 +35,12 @@ public class CheckNamePresent extends EntityJob {
   @Override
   public final void run() {
     if (!getEntity().hasName()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_NAME);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_NAME);
           break;
         default:
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java
index b04a9151ab5c142e97a6efc9b19607896ab38daf..fb6919649242a906b36526e3725fc25e710c9d60 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java
@@ -45,7 +45,7 @@ public class CheckNoAdditionalPropertiesPresent extends EntityJob {
   }
 
   private void addMessage(EntityInterface property, Message message) {
-    if (getMode() == Mode.MUST) {
+    if (getFailureSeverity() == JobFailureSeverity.ERROR) {
       property.addError(message);
     } else {
       property.addWarning(message);
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java
index e439354e88f39675491682877dd27aa54fd7a3ef..d6cae3ee40222d3b3d6eb89e1b2d0c8bce1da0e5 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java
@@ -28,7 +28,7 @@ public class CheckNoOverridesPresent extends EntityJob {
   }
 
   private void addMessage(Property p, Message message) {
-    if (getMode() == Mode.MUST) {
+    if (getFailureSeverity() == JobFailureSeverity.ERROR) {
       p.addError(message);
     } else {
       p.addWarning(message);
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..b706afdbcd2082fa2ac3d76c0128959abfb42bd9 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,12 @@
  */
 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.Message.MessageType;
 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 +38,110 @@ import org.caosdb.server.utils.ServerMessages;
  * @author tf
  */
 public class CheckParOblPropPresent extends EntityJob {
-  @Override
-  public final void run() {
-
-    // This test needs the valid/qualified ids of all properties.
-    // Thus, run a CheckPropValid first.
-    runJobFromSchedule(getEntity(), CheckPropValid.class);
 
-    // 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);
+  public static final String OBL_IMPORTANCE_FLAG_KEY = "force-missing-obligatory";
+  public static final Message ENTITY_NOT_UNIQUE =
+      new Message(
+          MessageType.Error, 0, "Could not check importance. Parent was not uniquely resolvable.");
+  public static final Message ILLEGAL_FLAG_VALUE =
+      new Message(MessageType.Warning, "Illegal value for flag 'force-missing-obligatory'.");
 
-    if (getEntity().hasParents()) {
-      execute(new RetrieveFullEntityTransaction(getEntity().getParents()));
+  @Override
+  public final void run() {
+    if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED
+        || getEntity() instanceof Property
+        || getEntity() instanceof Parent) {
+      return;
     }
-
-    // inherit properties
-    runJobFromSchedule(getEntity(), Inheritance.class);
-
-    // loop over all parents
     if (getEntity().hasParents()) {
-      for (final EntityInterface parent : getEntity().getParents()) {
 
-        // 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;
-          }
+      handleImportanceFlags();
+      if (getFailureSeverity() == JobFailureSeverity.IGNORE) {
+        // importance is to be ignored
+        return;
+      }
 
-          // does this parent have properties?
-          if (parent.hasProperties()) {
+      // inherit properties
+      runJobFromSchedule(getEntity(), Inheritance.class);
 
-            // loop over all properties of this parent
-            outerLoop:
-            for (final EntityInterface parentProperty : parent.getProperties()) {
+      // get importance-flags
 
-              // only obligatory properties are of interest.
-              if (parentProperty.hasStatementStatus()
-                  && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) {
+      // loop over all parents
+      for (final EntityInterface parent : getEntity().getParents()) {
+        final EntityInterface resolvedParent = resolve(parent);
+        checkObligatoryPropertiesMissing(resolvedParent);
+      }
+    }
+  }
+
+  /**
+   * Check if all obligatory properties of the given parent are present.
+   *
+   * <p>Add an error or warning if not.
+   *
+   * @param parent
+   * @throws Message
+   */
+  private void checkObligatoryPropertiesMissing(final EntityInterface parent) {
+    // does this parent have properties?
+    if (parent.hasProperties()) {
 
-                // loop over all properties of the entity
-                innerLoop:
-                for (final EntityInterface entityProperty : getEntity().getProperties()) {
+      // loop over all properties of this parent
+      outerLoop:
+      for (final EntityInterface parentProperty : parent.getProperties()) {
 
-                  if (!entityProperty.hasId()) {
-                    // continue inner loop means that this
-                    // is not the entityProperty in
-                    // question, continue search for an
-                    // entityProperty.
-                    continue innerLoop;
-                  }
+        // only obligatory properties are of interest.
+        if (parentProperty.hasStatementStatus()
+            && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) {
 
-                  // 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;
-                  }
-                }
+          // loop over all properties of the entity
+          for (final EntityInterface entityProperty : getEntity().getProperties()) {
 
-                // 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;
-                }
-              }
+            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 (getFailureSeverity()) {
+            case ERROR:
+              // TODO add information WHICH property is
+              // missing.
+              getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
+              break;
+            case WARN:
+              // 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;
+          }
         }
       }
     }
   }
+
+  /** Look at the entity's importance flag first, at the container's flag second. */
+  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) {
+      try {
+        setFailureSeverity(JobFailureSeverity.valueOf(entityFlag.toUpperCase()));
+      } catch (final IllegalArgumentException e) {
+        getEntity().addWarning(ILLEGAL_FLAG_VALUE);
+      }
+    }
+  }
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java
index 8851456c236c0afd9f922f187089adc5f4e35ab1..058710c6bbbf5c2bbd3f95f7ae70a243d3bfcd20 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java
@@ -35,12 +35,12 @@ public class CheckParPresent extends EntityJob {
   @Override
   public final void run() {
     if (!getEntity().hasParents() || getEntity().getParents().isEmpty()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_PARENTS);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_PARENTS);
         default:
           break;
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java
index 31a460971bc80ccf61d775c96b4632fa38f14f66..cc23d980152c44cfdbf88500e9041044a0418034 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java
@@ -35,12 +35,12 @@ public class CheckPropPresent extends EntityJob {
   @Override
   public final void run() {
     if (getEntity().getProperties().isEmpty()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_PROPERTIES);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_PROPERTIES);
         default:
           break;
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java
index aa87ecfff69635407bc90d372c89f089e4f308dc..5e3e6120fadef8f4b519ec694ebbf7588d7c2488 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java
@@ -40,12 +40,12 @@ public class CheckUnitPresent extends EntityJob {
     runJobFromSchedule(getEntity(), Inheritance.class);
 
     if (!hasUnit(getEntity())) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_UNIT);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_UNIT);
         default:
           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/JobFailureSeverity.java
similarity index 51%
rename from src/main/java/org/caosdb/server/jobs/core/Mode.java
rename to src/main/java/org/caosdb/server/jobs/core/JobFailureSeverity.java
index 561a4b0c5fcb4e2c8c66a2b9f9a02ea91c44c119..e95bef34b7b9bea8e1b2b2126da196c1c7c53c12 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Mode.java
+++ b/src/main/java/org/caosdb/server/jobs/core/JobFailureSeverity.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2021 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 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
@@ -22,9 +24,22 @@
  */
 package org.caosdb.server.jobs.core;
 
-public enum Mode {
-  MUST,
-  SHOULD,
-  SHOULDNT,
-  MUSTNOT
+/**
+ * Describes the severity of a failure of a job (esp. consitency checks).
+ *
+ * <ul>
+ *   <li>{@link #ERROR} - a failure throws an error and the transaction will not succeed.
+ *   <li>{@link #WARN} - a failure produces a warning but the transaction may succeed anyway.
+ *   <li>{@link #IGNORE} - the job may fail silently. Jobs can also decide to not run at all if that
+ *       is not necessary in this case.
+ *       <p>Jobs where this doesn't make sense at all may also ignore the severity. It is rather a
+ *       convenient way to configure jobs where this makes sense than a definitive normative
+ *       property of a job.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public enum JobFailureSeverity {
+  ERROR,
+  WARN,
+  IGNORE,
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
index ef8df214c804fb1d55c3ed088c5f7f6e998aca07..c04fd21d588c9a9a2f6e59ce7c1fff445a225c67 100644
--- a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
+++ b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
@@ -36,7 +36,7 @@ public class LoadContainerFlagJobs extends ContainerJob {
   protected void run() {
     if (getContainer().getFlags() != null) {
       for (final Entry<String, String> flag : getContainer().getFlags().entrySet()) {
-        final Job j = Job.getJob(flag.getKey(), Mode.MUST, null, getTransaction());
+        final Job j = Job.getJob(flag.getKey(), JobFailureSeverity.ERROR, null, getTransaction());
         if (j != null) {
           if (j instanceof FlagJob) {
             ((FlagJob) j).setValue(flag.getValue());
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/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java
index 19f840e92a1a6812e2ae2610ed3ace23b7e3cee7..250df0423869ae97a766da2fc54131e04d5257ca 100644
--- a/src/main/java/org/caosdb/server/transaction/Retrieve.java
+++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java
@@ -31,7 +31,7 @@ import org.caosdb.server.entity.xml.SetFieldStrategy;
 import org.caosdb.server.entity.xml.ToElementStrategy;
 import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.jobs.ScheduledJob;
-import org.caosdb.server.jobs.core.Mode;
+import org.caosdb.server.jobs.core.JobFailureSeverity;
 import org.caosdb.server.jobs.core.RemoveDuplicates;
 import org.caosdb.server.jobs.core.ResolveNames;
 import org.caosdb.server.permissions.EntityPermission;
@@ -53,14 +53,14 @@ public class Retrieve extends Transaction<RetrieveContainer> {
     // resolve names
     {
       final ResolveNames r = new ResolveNames();
-      r.init(Mode.SHOULD, null, this);
+      r.init(JobFailureSeverity.WARN, null, this);
       ScheduledJob scheduledJob = getSchedule().add(r);
       getSchedule().runJob(scheduledJob);
     }
 
     {
       final RemoveDuplicates job = new RemoveDuplicates();
-      job.init(Mode.MUST, null, this);
+      job.init(JobFailureSeverity.ERROR, null, this);
       ScheduledJob scheduledJob = getSchedule().add(job);
       getSchedule().runJob(scheduledJob);
     }
diff --git a/src/main/java/org/caosdb/server/transaction/Transaction.java b/src/main/java/org/caosdb/server/transaction/Transaction.java
index 99c305fa17b37878cc5f3277c93623a915be4ea3..b30d13b469ebe05222d2db1ab958fe0a4d3a0f26 100644
--- a/src/main/java/org/caosdb/server/transaction/Transaction.java
+++ b/src/main/java/org/caosdb/server/transaction/Transaction.java
@@ -45,7 +45,7 @@ import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.jobs.core.AccessControl;
 import org.caosdb.server.jobs.core.CheckDatatypePresent;
 import org.caosdb.server.jobs.core.CheckEntityACLRoles;
-import org.caosdb.server.jobs.core.Mode;
+import org.caosdb.server.jobs.core.JobFailureSeverity;
 import org.caosdb.server.jobs.core.PickUp;
 import org.caosdb.server.permissions.EntityACL;
 import org.caosdb.server.utils.AbstractObservable;
@@ -96,13 +96,17 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
    */
   protected void makeSchedule() throws Exception {
     // load flag jobs
-    final Job loadContainerFlags = Job.getJob("LoadContainerFlagJobs", Mode.MUST, null, this);
+    final Job loadContainerFlags =
+        Job.getJob("LoadContainerFlagJobs", JobFailureSeverity.ERROR, null, this);
     final ScheduledJob scheduledJob = this.schedule.add(loadContainerFlags);
     this.schedule.runJob(scheduledJob);
 
     // AccessControl
-    this.schedule.add(Job.getJob(AccessControl.class.getSimpleName(), Mode.MUST, null, this));
-    this.schedule.add(Job.getJob(CheckEntityACLRoles.class.getSimpleName(), Mode.MUST, null, this));
+    this.schedule.add(
+        Job.getJob(AccessControl.class.getSimpleName(), JobFailureSeverity.ERROR, null, this));
+    this.schedule.add(
+        Job.getJob(
+            CheckEntityACLRoles.class.getSimpleName(), JobFailureSeverity.ERROR, null, this));
 
     // load permanent container jobs
     this.schedule.addAll(Job.loadPermanentContainerJobs(this));
@@ -113,12 +117,12 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
 
       // additionally load datatype job
       if (e.hasValue()) {
-        this.schedule.add(new CheckDatatypePresent().init(Mode.MUST, e, this));
+        this.schedule.add(new CheckDatatypePresent().init(JobFailureSeverity.ERROR, e, this));
       }
 
       // load pickup job if necessary
       if (e.hasFileProperties() && e.getFileProperties().isPickupable()) {
-        this.schedule.add(new PickUp().init(Mode.MUST, e, this));
+        this.schedule.add(new PickUp().init(JobFailureSeverity.ERROR, e, this));
       }
     }
   }
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..70917b53553c6b94b283ed1e39b0ee81db9d466c
--- /dev/null
+++ b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java
@@ -0,0 +1,31 @@
+package org.caosdb.server.jobs;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import org.caosdb.server.CaosDBServer;
+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.BeforeClass;
+import org.junit.Test;
+
+public class JobConfigTest {
+
+  @BeforeClass
+  public static void setup() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
+  @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"))));
+  }
+}
diff --git a/src/test/java/org/caosdb/server/jobs/ScheduleTest.java b/src/test/java/org/caosdb/server/jobs/ScheduleTest.java
index cef0ec3e076f38893ada35bbad881ccfe8a02331..9aa8e6f66788911f2c4314c44549d543277398d2 100644
--- a/src/test/java/org/caosdb/server/jobs/ScheduleTest.java
+++ b/src/test/java/org/caosdb/server/jobs/ScheduleTest.java
@@ -24,10 +24,18 @@ package org.caosdb.server.jobs;
 
 import static org.junit.Assert.fail;
 
+import java.io.IOException;
+import org.caosdb.server.CaosDBServer;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class ScheduleTest {
 
+  @BeforeClass
+  public static void setup() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
   /** The problem of ticket #297 was that a CHECK job was executed during the ROLL_BACK stage. */
   @Test
   public void testTicket297() {
diff --git a/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java b/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java
index ae5df853ba5d29e681cd0fb0e5ec8eee6ec3462c..5d81ac368f65c559eb666f003f34da522f191f21 100644
--- a/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java
+++ b/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java
@@ -4,16 +4,23 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.IOException;
+import org.caosdb.server.CaosDBServer;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class TestInsertFilesInDir {
 
+  @BeforeClass
+  public static void setup() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
   @Test
   public void testExclude() throws IOException {
-    InsertFilesInDir job = new InsertFilesInDir();
+    final InsertFilesInDir job = new InsertFilesInDir();
     job.init(null, null, null);
     job.parseValue("-e ^.*test.*$ test");
-    File testFile = new File("test.dat");
+    final File testFile = new File("test.dat");
     assertTrue(job.isExcluded(testFile));
   }
 }