diff --git a/ProcessLib/CompareJacobiansJacobianAssembler.cpp b/ProcessLib/CompareJacobiansJacobianAssembler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8c9972bd505039fdd042fb62482e3dfa6576ca3f
--- /dev/null
+++ b/ProcessLib/CompareJacobiansJacobianAssembler.cpp
@@ -0,0 +1,374 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include "CompareJacobiansJacobianAssembler.h"
+
+#include <sstream>
+
+#include "MathLib/LinAlg/Eigen/EigenMapTools.h"
+
+#include "CreateJacobianAssembler.h"
+
+namespace
+{
+//! Dumps a \c double value as a Python script snippet.
+void dump_py(std::ofstream& fh, std::string const& var, double const val)
+{
+    fh << var << " = " << val << '\n';
+}
+
+//! Dumps a \c std::size_t value as a Python script snippet.
+void dump_py(std::ofstream& fh, std::string const& var, std::size_t const val)
+{
+    fh << var << " = " << val << '\n';
+}
+
+//! Dumps an Eigen vector as a Python script snippet.
+template <typename Vec>
+void dump_py_vec(std::ofstream& fh, std::string const& var, Vec const& val)
+{
+    fh << var << " = np.array([";
+    for (decltype(val.size()) i = 0; i < val.size(); ++i)
+    {
+        if (i != 0)
+        {
+            if (i % 8 == 0)
+            {
+                // Print at most eight entries on one line,
+                // indent with four spaces.
+                fh << ",\n    ";
+            }
+            else
+            {
+                fh << ", ";
+            }
+        }
+        fh << val[i];
+    }
+    fh << "])\n";
+}
+
+//! Dumps a \c std::vector<double> as a Python script snippet.
+void dump_py(std::ofstream& fh, std::string const& var,
+             std::vector<double> const& val)
+{
+    dump_py_vec(fh, var, val);
+}
+
+template <typename Derived>
+void dump_py(std::ofstream& fh, std::string const& var,
+             Eigen::ArrayBase<Derived> const& val,
+             std::integral_constant<int, 1>)
+{
+    dump_py_vec(fh, var, val);
+}
+
+template <typename Derived, int ColsAtCompileTime>
+void dump_py(std::ofstream& fh, std::string const& var,
+             Eigen::ArrayBase<Derived> const& val,
+             std::integral_constant<int, ColsAtCompileTime>)
+{
+    fh << var << " = np.array([\n";
+    for (std::ptrdiff_t r = 0; r < val.rows(); ++r)
+    {
+        if (r != 0)
+        {
+            fh << ",\n";
+        }
+        fh << "    [";
+        for (std::ptrdiff_t c = 0; c < val.cols(); ++c)
+        {
+            if (c != 0)
+            {
+                fh << ", ";
+            }
+            fh << val(r, c);
+        }
+        fh << "]";
+    }
+    fh << "])\n";
+}
+
+//! Dumps an Eigen array as a Python script snippet.
+template <typename Derived>
+void dump_py(std::ofstream& fh, std::string const& var,
+             Eigen::ArrayBase<Derived> const& val)
+{
+    dump_py(fh, var, val,
+            std::integral_constant<int, Derived::ColsAtCompileTime>{});
+}
+
+//! Dumps an Eigen matrix as a Python script snippet.
+template <typename Derived>
+void dump_py(std::ofstream& fh, std::string const& var,
+             Eigen::MatrixBase<Derived> const& val)
+{
+    dump_py(fh, var, val.array());
+}
+
+//! Will be printed if some consistency error is detected.
+static const std::string msg_fatal =
+    "The local matrices M or K or the local vectors b assembled with the two "
+    "different Jacobian assemblers differ.";
+
+}  // anonymous namespace
+
+namespace ProcessLib
+{
+void CompareJacobiansJacobianAssembler::assembleWithJacobian(
+    LocalAssemblerInterface& local_assembler, double const t,
+    std::vector<double> const& local_x, std::vector<double> const& local_xdot,
+    const double dxdot_dx, const double dx_dx,
+    std::vector<double>& local_M_data, std::vector<double>& local_K_data,
+    std::vector<double>& local_b_data, std::vector<double>& local_Jac_data)
+{
+    ++_counter;
+
+    auto const num_dof = local_x.size();
+    auto to_mat = [num_dof](std::vector<double> const& data) {
+        if (data.empty())
+        {
+            return Eigen::Map<Eigen::Matrix<
+                double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> const>(
+                nullptr, 0, 0);
+        }
+
+        return MathLib::toMatrix(data, num_dof, num_dof);
+    };
+
+    // First assembly -- the one whose results will be added to the global
+    // equation system finally.
+    _asm1->assembleWithJacobian(local_assembler, t, local_x, local_xdot,
+                                dxdot_dx, dx_dx, local_M_data, local_K_data,
+                                local_b_data, local_Jac_data);
+
+    auto const local_M1 = to_mat(local_M_data);
+    auto const local_K1 = to_mat(local_K_data);
+    auto const local_b1 = MathLib::toVector(local_b_data);
+
+    std::vector<double> local_M_data2, local_K_data2, local_b_data2,
+        local_Jac_data2;
+
+    // Second assembly -- used for checking only.
+    _asm2->assembleWithJacobian(local_assembler, t, local_x, local_xdot,
+                                dxdot_dx, dx_dx, local_M_data2, local_K_data2,
+                                local_b_data2, local_Jac_data2);
+
+    auto const local_M2 = to_mat(local_M_data2);
+    auto const local_K2 = to_mat(local_K_data2);
+    auto const local_b2 = MathLib::toVector(local_b_data2);
+
+    auto const local_Jac1 = MathLib::toMatrix(local_Jac_data, num_dof, num_dof);
+    auto const local_Jac2 =
+        MathLib::toMatrix(local_Jac_data2, num_dof, num_dof);
+
+    auto const abs_diff = (local_Jac2 - local_Jac1).array().eval();
+    auto const rel_diff =
+        (abs_diff == 0.0)
+            .select(abs_diff,
+                    2. * abs_diff /
+                        (local_Jac1.cwiseAbs() + local_Jac2.cwiseAbs()).array())
+            .eval();
+
+    // the !(... <= ...) construct handles NaN correctly.
+    auto const abs_diff_mask = (!(abs_diff.cwiseAbs() <= _abs_tol)).eval();
+    auto const rel_diff_mask = (!(rel_diff.cwiseAbs() <= _rel_tol)).eval();
+
+    auto const abs_diff_OK = !abs_diff_mask.any();
+    auto const rel_diff_OK = !rel_diff_mask.any();
+
+    std::ostringstream msg_tolerance;
+    bool tol_exceeded = true;
+    bool fatal_error = false;
+
+    if (abs_diff_OK)
+    {
+        tol_exceeded = false;
+    }
+    else
+    {
+        msg_tolerance << "absolute tolerance of " << _abs_tol << " exceeded";
+    }
+
+    if (rel_diff_OK)
+    {
+        tol_exceeded = false;
+    }
+    else
+    {
+        if (!msg_tolerance.str().empty())
+        {
+            msg_tolerance << " and ";
+        }
+
+        msg_tolerance << "relative tolerance of " << _rel_tol << " exceeded";
+    }
+
+    // basic consistency check if something went terribly wrong
+    auto check_equality = [&fatal_error](auto mat_or_vec1, auto mat_or_vec2) {
+        if (mat_or_vec1.rows() != mat_or_vec2.rows() ||
+            mat_or_vec1.cols() != mat_or_vec2.cols())
+        {
+            fatal_error = true;
+        }
+        else if (((mat_or_vec1 - mat_or_vec2).array().cwiseAbs() >
+                  std::numeric_limits<double>::epsilon())
+                     .any())
+        {
+            fatal_error = true;
+        }
+    };
+    check_equality(local_M1, local_M2);
+    check_equality(local_K1, local_K2);
+    check_equality(local_b1, local_b2);
+
+    if (tol_exceeded)
+    {
+        WARN("Compare Jacobians: %s", msg_tolerance.str().c_str());
+    }
+
+    bool const output = tol_exceeded || fatal_error;
+
+    if (output)
+    {
+        _log_file << "\n### counter: " << std::to_string(_counter)
+                  << " (begin)\n";
+    }
+
+    if (fatal_error)
+    {
+        _log_file << '\n'
+                  << "#######################################################\n"
+                  << "# FATAL ERROR: " << msg_fatal << '\n'
+                  << "#              You cannot expect any meaningful insights "
+                     "from the Jacobian data printed below!\n"
+                  << "#              The reason for the mentioned differences "
+                     "might be\n"
+                  << "#              (a) that the assembly routine has side "
+                     "effects or\n"
+                  << "#              (b) that the assembly routines for M, K "
+                     "and b themselves differ.\n"
+                  << "#######################################################\n"
+                  << '\n';
+    }
+
+    if (tol_exceeded)
+    {
+        _log_file << "# " << msg_tolerance.str() << "\n\n";
+    }
+
+    if (output)
+    {
+        dump_py(_log_file, "counter", _counter);
+        dump_py(_log_file, "num_dof", num_dof);
+        dump_py(_log_file, "abs_tol", _abs_tol);
+        dump_py(_log_file, "rel_tol", _rel_tol);
+
+        _log_file << '\n';
+
+        dump_py(_log_file, "local_x", local_x);
+        dump_py(_log_file, "local_x_dot", local_xdot);
+        dump_py(_log_file, "dxdot_dx", dxdot_dx);
+        dump_py(_log_file, "dx_dx", dx_dx);
+
+        _log_file << '\n';
+
+        dump_py(_log_file, "Jacobian_1", local_Jac1);
+        dump_py(_log_file, "Jacobian_2", local_Jac2);
+
+        _log_file << '\n';
+
+        _log_file << "# Jacobian_2 - Jacobian_1\n";
+        dump_py(_log_file, "abs_diff", abs_diff);
+        _log_file << "# componentwise: 2 * abs_diff / (|Jacobian_1| + "
+                     "|Jacobian_2|)\n";
+        dump_py(_log_file, "rel_diff", rel_diff);
+
+        _log_file << '\n';
+
+        _log_file << "# Masks: 0 ... tolerance met, 1 ... tolerance exceeded\n";
+        dump_py(_log_file, "abs_diff_mask", abs_diff_mask);
+        dump_py(_log_file, "rel_diff_mask", rel_diff_mask);
+
+        _log_file << '\n';
+
+        dump_py(_log_file, "M_1", local_M1);
+        dump_py(_log_file, "M_2", local_M2);
+        if (fatal_error)
+        {
+            dump_py(_log_file, "delta_M", local_M2 - local_M1);
+            _log_file << '\n';
+        }
+
+        dump_py(_log_file, "K_1", local_K1);
+        dump_py(_log_file, "K_2", local_K2);
+        if (fatal_error)
+        {
+            dump_py(_log_file, "delta_K", local_K2 - local_K1);
+            _log_file << '\n';
+        }
+
+        dump_py(_log_file, "b_1", local_b_data);
+        dump_py(_log_file, "b_2", local_b_data2);
+        if (fatal_error)
+        {
+            dump_py(_log_file, "delta_b", local_b2 - local_b1);
+        }
+
+        _log_file << '\n';
+
+        _log_file << "### counter: " << std::to_string(_counter) << " (end)\n";
+    }
+
+    if (fatal_error)
+    {
+        OGS_FATAL("%s", msg_fatal.c_str());
+    }
+
+    if (tol_exceeded && _fail_on_error)
+    {
+        OGS_FATAL(
+            "OGS failed, because the two Jacobian implementations returned "
+            "different results.");
+    }
+}
+
+std::unique_ptr<CompareJacobiansJacobianAssembler>
+createCompareJacobiansJacobianAssembler(BaseLib::ConfigTree const& config)
+{
+    // TODO doc script corner case: Parameter could occur at different
+    // locations.
+    //! \ogs_file_param{prj__processes__process__jacobian_assembler__type}
+    config.checkConfigParameter("type", "CompareJacobians");
+
+    //! \ogs_file_param{prj__processes__process__jacobian_assembler__CompareJacobians__jacobian_assembler}
+    auto asm1 =
+        createJacobianAssembler(config.getConfigSubtree("jacobian_assembler"));
+
+    //! \ogs_file_param{prj__processes__process__jacobian_assembler__CompareJacobians__reference_jacobian_assembler}
+    auto asm2 = createJacobianAssembler(
+        config.getConfigSubtree("reference_jacobian_assembler"));
+
+    //! \ogs_file_param{prj__processes__process__jacobian_assembler__CompareJacobians__abs_tol}
+    auto const abs_tol = config.getConfigParameter<double>("abs_tol");
+    //! \ogs_file_param{prj__processes__process__jacobian_assembler__CompareJacobians__rel_tol}
+    auto const rel_tol = config.getConfigParameter<double>("rel_tol");
+
+    //! \ogs_file_param{prj__processes__process__jacobian_assembler__CompareJacobians__fail_on_error}
+    auto const fail_on_error = config.getConfigParameter<bool>("fail_on_error");
+
+    //! \ogs_file_param{prj__processes__process__jacobian_assembler__CompareJacobians__log_file}
+    auto const log_file = config.getConfigParameter<std::string>("log_file");
+
+    return std::make_unique<CompareJacobiansJacobianAssembler>(
+        std::move(asm1), std::move(asm2), abs_tol, rel_tol, fail_on_error,
+        log_file);
+}
+
+}  // namespace ProcessLib
diff --git a/ProcessLib/CompareJacobiansJacobianAssembler.h b/ProcessLib/CompareJacobiansJacobianAssembler.h
new file mode 100644
index 0000000000000000000000000000000000000000..71e752afa881f9a53a8593b4811c4f8c2b3084fa
--- /dev/null
+++ b/ProcessLib/CompareJacobiansJacobianAssembler.h
@@ -0,0 +1,87 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <fstream>
+#include <limits>
+#include <memory>
+#include "AbstractJacobianAssembler.h"
+
+namespace BaseLib
+{
+class ConfigTree;
+}  // namespace BaseLib
+
+namespace ProcessLib
+{
+//! Assembles the Jacobian matrix using two different Jacobian assemblers
+//! and compares the assembled local Jacobian matrices.
+//!
+//! If the provided tolerances are exceeded, debugging information is logged in
+//! the form of a Python script.
+class CompareJacobiansJacobianAssembler final : public AbstractJacobianAssembler
+{
+public:
+    CompareJacobiansJacobianAssembler(
+        std::unique_ptr<AbstractJacobianAssembler>&& asm1,
+        std::unique_ptr<AbstractJacobianAssembler>&& asm2,
+        double abs_tol,
+        double rel_tol,
+        bool fail_on_error,
+        std::string const& log_file_path)
+        : _asm1(std::move(asm1)),
+          _asm2(std::move(asm2)),
+          _abs_tol(abs_tol),
+          _rel_tol(rel_tol),
+          _fail_on_error(fail_on_error),
+          _log_file(log_file_path)
+    {
+        _log_file.precision(std::numeric_limits<double>::digits10);
+        _log_file << "#!/usr/bin/env python\n"
+                     "import numpy as np\n"
+                     "from numpy import nan\n"
+                  << std::endl;
+    }
+
+    void assembleWithJacobian(LocalAssemblerInterface& local_assembler,
+                              double const t,
+                              std::vector<double> const& local_x,
+                              std::vector<double> const& local_xdot,
+                              const double dxdot_dx, const double dx_dx,
+                              std::vector<double>& local_M_data,
+                              std::vector<double>& local_K_data,
+                              std::vector<double>& local_b_data,
+                              std::vector<double>& local_Jac_data) override;
+
+private:
+    std::unique_ptr<AbstractJacobianAssembler> _asm1;
+    std::unique_ptr<AbstractJacobianAssembler> _asm2;
+
+    // TODO change to matrix
+    double const _abs_tol;
+    double const _rel_tol;
+
+    //! Whether to abort if the tolerances are exceeded.
+    bool const _fail_on_error;
+
+    //! Path where a Python script will be placed, which contains information
+    //! about exceeded tolerances and assembled local matrices.
+    std::ofstream _log_file;
+
+    //! Counter used for identifying blocks in the \c _log_file. Is be
+    //! incremented upon each call of the assembly routine, i.e., for each
+    //! element in each iteration etc.
+    std::size_t _counter = 0;
+};
+
+std::unique_ptr<CompareJacobiansJacobianAssembler>
+createCompareJacobiansJacobianAssembler(BaseLib::ConfigTree const& config);
+
+}  // namespace ProcessLib
diff --git a/ProcessLib/CreateJacobianAssembler.cpp b/ProcessLib/CreateJacobianAssembler.cpp
index d677daed7d0f9cea3e236f08633bc3cecd910686..39f80d66313126ff8c95291034986b2f43c77e0b 100644
--- a/ProcessLib/CreateJacobianAssembler.cpp
+++ b/ProcessLib/CreateJacobianAssembler.cpp
@@ -12,6 +12,7 @@
 
 #include "AnalyticalJacobianAssembler.h"
 #include "CentralDifferencesJacobianAssembler.h"
+#include "CompareJacobiansJacobianAssembler.h"
 
 namespace ProcessLib
 {
@@ -32,6 +33,10 @@ std::unique_ptr<AbstractJacobianAssembler> createJacobianAssembler(
     {
         return createCentralDifferencesJacobianAssembler(*config);
     }
+    if (type == "CompareJacobians")
+    {
+        return createCompareJacobiansJacobianAssembler(*config);
+    }
 
     OGS_FATAL("Unknown Jacobian assembler type: `%s'.", type.c_str());
 }