diff --git a/Applications/ApplicationsLib/ProjectData.cpp b/Applications/ApplicationsLib/ProjectData.cpp
index 6c4950e92020aed7acce5db681f66b2aac712317..1f9f86beafbc864ef460bafbd686894a6f685453 100644
--- a/Applications/ApplicationsLib/ProjectData.cpp
+++ b/Applications/ApplicationsLib/ProjectData.cpp
@@ -53,6 +53,9 @@
 #ifdef OGS_BUILD_PROCESS_HEATCONDUCTION
 #include "ProcessLib/HeatConduction/CreateHeatConductionProcess.h"
 #endif
+#ifdef OGS_BUILD_PROCESS_HEATTRANSPORTBHE
+#include "ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.h"
+#endif
 #ifdef OGS_BUILD_PROCESS_HYDROMECHANICS
 #include "ProcessLib/HydroMechanics/CreateHydroMechanicsProcess.h"
 #endif
@@ -371,6 +374,17 @@ void ProjectData::parseProcesses(BaseLib::ConfigTree const& processes_config,
         }
         else
 #endif
+#ifdef OGS_BUILD_PROCESS_HEATTRANSPORTBHE
+            if (type == "HEAT_TRANSPORT_BHE")
+        {
+            process =
+                ProcessLib::HeatTransportBHE::createHeatTransportBHEProcess(
+                    *_mesh_vec[0], std::move(jacobian_assembler),
+                    _process_variables, _parameters, integration_order,
+                    process_config, _curves);
+        }
+        else
+#endif
 #ifdef OGS_BUILD_PROCESS_HYDROMECHANICS
             if (type == "HYDRO_MECHANICS")
         {
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b7287fa978964c837fbdae946c768143d58c3da5..456c89de82e6053f7d3b94cba1b9d8cda8faa644 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -90,6 +90,7 @@ set(ProcessesList
     GroundwaterFlow
     HT
     HeatConduction
+    HeatTransportBHE
     HydroMechanics
     LiquidFlow
     LIE
diff --git a/ProcessLib/BoundaryCondition/BHEBottomDirichletBoundaryCondition.cpp b/ProcessLib/BoundaryCondition/BHEBottomDirichletBoundaryCondition.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b87ec86a71c6c38e298bf4beb3ba1b2af4d726fe
--- /dev/null
+++ b/ProcessLib/BoundaryCondition/BHEBottomDirichletBoundaryCondition.cpp
@@ -0,0 +1,113 @@
+/**
+ * \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 "BHEBottomDirichletBoundaryCondition.h"
+
+#include <algorithm>
+#include <logog/include/logog.hpp>
+#include <vector>
+#include "ProcessLib/Utils/ProcessUtils.h"
+
+namespace ProcessLib
+{
+BHEBottomDirichletBoundaryCondition::BHEBottomDirichletBoundaryCondition(
+    GlobalIndexType const global_idx_T_in_bottom,
+    GlobalIndexType const global_idx_T_out_bottom,
+    MeshLib::Mesh const& bulk_mesh,
+    std::vector<MeshLib::Node*> const& vec_outflow_bc_nodes,
+    int const variable_id,
+    int const component_id)
+    : _bulk_mesh(bulk_mesh)
+{
+    DBUG(
+        "Found %d nodes for BHE bottom Dirichlet BCs for the variable %d "
+        "and "
+        "component %d",
+        vec_outflow_bc_nodes.size(), variable_id, component_id);
+
+    MeshLib::MeshSubset bc_mesh_subset{_bulk_mesh, vec_outflow_bc_nodes};
+
+    // create memory to store Tout values
+    _T_in_values.ids.clear();
+    _T_in_values.values.clear();
+
+    _bc_values.ids.clear();
+    _bc_values.values.clear();
+
+    // convert mesh node ids to global index for the given component
+    _bc_values.ids.reserve(bc_mesh_subset.getNumberOfNodes());
+    _bc_values.values.reserve(bc_mesh_subset.getNumberOfNodes());
+
+    const auto g_T_out_idx = global_idx_T_out_bottom;
+    if (g_T_out_idx >= 0)
+    {
+        _bc_values.ids.emplace_back(g_T_out_idx);
+        _bc_values.values.emplace_back(298.15);
+    }
+
+    const auto g_T_in_idx = global_idx_T_in_bottom;
+
+    if (g_T_in_idx >= 0)
+    {
+        _T_in_values.ids.emplace_back(g_T_in_idx);
+        _T_in_values.values.emplace_back(298.15);
+    }
+}
+
+void BHEBottomDirichletBoundaryCondition::getEssentialBCValues(
+    const double /*t*/, GlobalVector const& x,
+    NumLib::IndexValueVector<GlobalIndexType>& bc_values) const
+{
+    bc_values.ids.clear();
+    bc_values.values.clear();
+
+    bc_values.ids.resize(_bc_values.ids.size());
+    bc_values.values.resize(_bc_values.values.size());
+
+    const std::size_t n_nodes = _T_in_values.ids.size();
+    for (std::size_t i = 0; i < n_nodes; i++)
+    {
+        bc_values.ids[i] = _bc_values.ids[i];
+        // here, the outflow temperature is always
+        // the same as the inflow temperature
+        // get the inflow temperature from here.
+        bc_values.values[i] = x[_T_in_values.ids[i]];
+    }
+}
+
+// update new values and corresponding indices.
+void BHEBottomDirichletBoundaryCondition::preTimestep(const double /*t*/,
+                                                      const GlobalVector& x)
+{
+    // At the bottom of each BHE, the outflow temperature
+    // is the same as the inflow temperature.
+    // Here the task is to get the inflow temperature and
+    // save it locally
+    auto const n_nodes = _bc_values.ids.size();
+    for (std::size_t i = 0; i < n_nodes; i++)
+    {
+        // read the T_out
+        _T_in_values.values[i] = x[_T_in_values.ids[i]];
+    }
+}
+
+std::unique_ptr<BHEBottomDirichletBoundaryCondition>
+createBHEBottomDirichletBoundaryCondition(
+    GlobalIndexType global_idx_T_in_bottom,
+    GlobalIndexType global_idx_T_out_bottom, MeshLib::Mesh const& bulk_mesh,
+    std::vector<MeshLib::Node*> const& vec_outflow_bc_nodes,
+    int const variable_id, int const component_id)
+{
+    DBUG("Constructing BHEBottomDirichletBoundaryCondition from config.");
+
+    return std::make_unique<BHEBottomDirichletBoundaryCondition>(
+        global_idx_T_in_bottom, global_idx_T_out_bottom, bulk_mesh,
+        vec_outflow_bc_nodes, variable_id, component_id);
+}
+}  // namespace ProcessLib
diff --git a/ProcessLib/BoundaryCondition/BHEBottomDirichletBoundaryCondition.h b/ProcessLib/BoundaryCondition/BHEBottomDirichletBoundaryCondition.h
new file mode 100644
index 0000000000000000000000000000000000000000..063eac02a09b4d864baea5aea30f7e9796e82529
--- /dev/null
+++ b/ProcessLib/BoundaryCondition/BHEBottomDirichletBoundaryCondition.h
@@ -0,0 +1,51 @@
+/**
+ * \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 "BoundaryCondition.h"
+#include "NumLib/DOF/LocalToGlobalIndexMap.h"
+#include "NumLib/IndexValueVector.h"
+#include "ProcessLib/HeatTransportBHE/BHE/BHEAbstract.h"
+#include "ProcessLib/Parameter/Parameter.h"
+
+namespace ProcessLib
+{
+class BHEBottomDirichletBoundaryCondition final : public BoundaryCondition
+{
+public:
+    BHEBottomDirichletBoundaryCondition(
+        GlobalIndexType global_idx_T_in_bottom,
+        GlobalIndexType global_idx_T_out_bottom,
+        MeshLib::Mesh const& bulk_mesh,
+        std::vector<MeshLib::Node*> const& vec_outflow_bc_nodes,
+        int const variable_id,
+        int const component_id);
+
+    void getEssentialBCValues(
+        const double t, GlobalVector const& x,
+        NumLib::IndexValueVector<GlobalIndexType>& bc_values) const override;
+
+    void preTimestep(const double t, const GlobalVector& x) override;
+
+private:
+    MeshLib::Mesh const& _bulk_mesh;
+
+    NumLib::IndexValueVector<GlobalIndexType> _bc_values;
+
+    NumLib::IndexValueVector<GlobalIndexType> _T_in_values;
+};
+
+std::unique_ptr<BHEBottomDirichletBoundaryCondition>
+createBHEBottomDirichletBoundaryCondition(
+    GlobalIndexType global_idx_T_in_bottom,
+    GlobalIndexType global_idx_T_out_bottom, MeshLib::Mesh const& bulk_mesh,
+    std::vector<MeshLib::Node*> const& vec_outflow_bc_nodes,
+    int const variable_id, int const component_id);
+}  // namespace ProcessLib
diff --git a/ProcessLib/BoundaryCondition/BHEInflowDirichletBoundaryCondition.cpp b/ProcessLib/BoundaryCondition/BHEInflowDirichletBoundaryCondition.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b152909b53e648ed4ba08001d252996e21153086
--- /dev/null
+++ b/ProcessLib/BoundaryCondition/BHEInflowDirichletBoundaryCondition.cpp
@@ -0,0 +1,117 @@
+/**
+ * \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 "BHEInflowDirichletBoundaryCondition.h"
+
+#include <algorithm>
+#include <logog/include/logog.hpp>
+#include <vector>
+#include "ProcessLib/Utils/ProcessUtils.h"
+
+namespace ProcessLib
+{
+BHEInflowDirichletBoundaryCondition::BHEInflowDirichletBoundaryCondition(
+    GlobalIndexType global_idx_T_in_top,
+    GlobalIndexType global_idx_T_out_top,
+    MeshLib::Mesh const& bc_mesh,
+    std::vector<MeshLib::Node*> const& vec_inflow_bc_nodes,
+    int const variable_id,
+    int const component_id,
+    std::unique_ptr<ProcessLib::HeatTransportBHE::BHE::BHEAbstract> const&
+        pt_bhe)
+    : _bc_mesh(bc_mesh), _pt_bhe(pt_bhe)
+{
+    DBUG(
+        "Found %d nodes for BHE Inflow Dirichlet BCs for the variable %d "
+        "and "
+        "component %d",
+        vec_inflow_bc_nodes.size(), variable_id, component_id);
+
+    MeshLib::MeshSubset bc_mesh_subset{_bc_mesh, vec_inflow_bc_nodes};
+
+    // create memory to store Tout values
+    _T_out_values.clear();
+    _T_out_indices.clear();
+
+    _bc_values.ids.clear();
+    _bc_values.values.clear();
+
+    // convert mesh node ids to global index for the given component
+    assert(bc_mesh_subset.getNumberOfNodes() == 1);
+    _bc_values.ids.reserve(bc_mesh_subset.getNumberOfNodes());
+    _bc_values.values.reserve(bc_mesh_subset.getNumberOfNodes());
+
+    // that might be slow, but only done once
+    const auto g_idx_T_in = global_idx_T_in_top;
+    const auto g_idx_T_out = global_idx_T_out_top;
+
+    if (g_idx_T_in >= 0 && g_idx_T_out >= 0)
+    {
+        _T_out_indices.emplace_back(g_idx_T_out);
+        _T_out_values.emplace_back(320.0 /*using initial value*/);
+        _bc_values.ids.emplace_back(g_idx_T_in);
+        _bc_values.values.emplace_back(320.0 /*using initial value*/);
+    }
+}
+
+void BHEInflowDirichletBoundaryCondition::getEssentialBCValues(
+    const double t, GlobalVector const& x,
+    NumLib::IndexValueVector<GlobalIndexType>& bc_values) const
+{
+    bc_values.ids.clear();
+    bc_values.values.clear();
+
+    bc_values.ids.resize(_bc_values.ids.size());
+    bc_values.values.resize(_bc_values.values.size());
+
+    const std::size_t n_nodes = _T_out_values.size();
+
+    for (std::size_t i = 0; i < n_nodes; i++)
+    {
+        bc_values.ids[i] = _bc_values.ids[i];
+        // here call the corresponding BHE functions
+        auto const tmp_T_out = x[_T_out_indices[i]];
+        bc_values.values[i] = _pt_bhe->getTinByTout(tmp_T_out, t);
+    }
+}
+
+// update new values and corresponding indices.
+void BHEInflowDirichletBoundaryCondition::preTimestep(const double /*t*/,
+                                                      const GlobalVector& x)
+{
+    // for each BHE, the inflow temperature is dependent on
+    // the ouflow temperature of the BHE.
+    // Here the task is to get the outflow temperature and
+    // save it locally
+    auto const n_nodes = _bc_values.ids.size();
+    for (std::size_t i = 0; i < n_nodes; i++)
+    {
+        // read the T_out
+        _T_out_values[i] = x[_T_out_indices[i]];
+    }
+}
+
+std::unique_ptr<BHEInflowDirichletBoundaryCondition>
+createBHEInflowDirichletBoundaryCondition(
+    GlobalIndexType global_idx_T_in_top, GlobalIndexType global_idx_T_out_top,
+    MeshLib::Mesh const& bc_mesh,
+    std::vector<MeshLib::Node*> const& vec_inflow_bc_nodes,
+    int const variable_id, int const component_id,
+    std::unique_ptr<ProcessLib::HeatTransportBHE::BHE::BHEAbstract> const&
+        pt_bhe)
+{
+    DBUG("Constructing BHEInflowDirichletBoundaryCondition.");
+
+    //! \ogs_file_param{prj__process_variables__process_variable__boundary_conditions__boundary_condition__Dirichlet__parameter}
+
+    return std::make_unique<BHEInflowDirichletBoundaryCondition>(
+        global_idx_T_in_top, global_idx_T_out_top, bc_mesh, vec_inflow_bc_nodes,
+        variable_id, component_id, pt_bhe);
+}
+}  // namespace ProcessLib
diff --git a/ProcessLib/BoundaryCondition/BHEInflowDirichletBoundaryCondition.h b/ProcessLib/BoundaryCondition/BHEInflowDirichletBoundaryCondition.h
new file mode 100644
index 0000000000000000000000000000000000000000..53c2afc0f4f784cfcee5c674e98f5128bf83ac29
--- /dev/null
+++ b/ProcessLib/BoundaryCondition/BHEInflowDirichletBoundaryCondition.h
@@ -0,0 +1,60 @@
+/**
+ * \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 "BoundaryCondition.h"
+#include "NumLib/DOF/LocalToGlobalIndexMap.h"
+#include "NumLib/IndexValueVector.h"
+#include "ProcessLib/HeatTransportBHE/BHE/BHEAbstract.h"
+#include "ProcessLib/Parameter/Parameter.h"
+
+namespace ProcessLib
+{
+class BHEInflowDirichletBoundaryCondition final : public BoundaryCondition
+{
+public:
+    BHEInflowDirichletBoundaryCondition(
+        GlobalIndexType global_idx_T_in_top,
+        GlobalIndexType global_idx_T_out_top,
+        MeshLib::Mesh const& bc_mesh,
+        std::vector<MeshLib::Node*> const& vec_inflow_bc_nodes,
+        int const variable_id,
+        int const component_id,
+        std::unique_ptr<ProcessLib::HeatTransportBHE::BHE::BHEAbstract> const&
+            pt_bhe);
+
+    void getEssentialBCValues(
+        const double t, GlobalVector const& x,
+        NumLib::IndexValueVector<GlobalIndexType>& bc_values) const override;
+
+    void preTimestep(const double t, const GlobalVector& x) override;
+
+private:
+    MeshLib::Mesh const& _bc_mesh;
+
+    /// Stores the results of the outflow temperatures per boundary node.
+    std::vector<double> _T_out_values;
+    std::vector<GlobalIndexType> _T_out_indices;
+
+    NumLib::IndexValueVector<GlobalIndexType> _bc_values;
+
+    std::unique_ptr<ProcessLib::HeatTransportBHE::BHE::BHEAbstract> const&
+        _pt_bhe;
+};
+
+std::unique_ptr<BHEInflowDirichletBoundaryCondition>
+createBHEInflowDirichletBoundaryCondition(
+    GlobalIndexType global_idx_T_in_top, GlobalIndexType global_idx_T_out_top,
+    MeshLib::Mesh const& bc_mesh,
+    std::vector<MeshLib::Node*> const& vec_outflow_bc_nodes,
+    int const variable_id, int const component_id,
+    std::unique_ptr<ProcessLib::HeatTransportBHE::BHE::BHEAbstract> const&
+        pt_bhe);
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/BHE/MeshUtils.cpp b/ProcessLib/HeatTransportBHE/BHE/MeshUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1a55cc0c94d3f7f0588cff3c1c901db71b8e3b93
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/BHE/MeshUtils.cpp
@@ -0,0 +1,101 @@
+/**
+ * \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 "MeshUtils.h"
+
+#include "BaseLib/Algorithm.h"
+#include "MeshLib/Elements/Element.h"
+#include "MeshLib/Mesh.h"
+#include "MeshLib/MeshSearch/NodeSearch.h"
+#include "MeshLib/Node.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+void getBHEDataInMesh(
+    MeshLib::Mesh const& mesh,
+    std::vector<MeshLib::Element*>& vec_soil_elements,
+    std::vector<int>& vec_BHE_mat_IDs,
+    std::vector<std::vector<MeshLib::Element*>>& vec_BHE_elements,
+    std::vector<MeshLib::Node*>& vec_soil_nodes,
+    std::vector<std::vector<MeshLib::Node*>>& vec_BHE_nodes)
+{
+    // get vectors of matrix elements and BHE elements
+    vec_soil_elements.reserve(mesh.getNumberOfElements());
+    std::vector<MeshLib::Element*> all_BHE_elements;
+    for (MeshLib::Element* e : mesh.getElements())
+    {
+        // As for the first step, all elements are counted as a soil element
+        // first. Those elements connected with a BHE will picked up and
+        // reorganized into a seperate vector at the end of the function.
+        if (e->getDimension() == mesh.getDimension())
+            vec_soil_elements.push_back(e);
+        else if (e->getDimension() == (unsigned int)1)
+            all_BHE_elements.push_back(e);
+    }
+
+    // get BHE material IDs
+    auto opt_material_ids(
+        mesh.getProperties().getPropertyVector<int>("MaterialIDs"));
+    for (MeshLib::Element* e : all_BHE_elements)
+        vec_BHE_mat_IDs.push_back((*opt_material_ids)[e->getID()]);
+    BaseLib::makeVectorUnique(vec_BHE_mat_IDs);
+    DBUG("-> found %d BHE material groups", vec_BHE_mat_IDs.size());
+
+    // create a vector of BHE elements for each group
+    vec_BHE_elements.resize(vec_BHE_mat_IDs.size());
+    for (unsigned bhe_id = 0; bhe_id < vec_BHE_mat_IDs.size(); bhe_id++)
+    {
+        const auto bhe_mat_id = vec_BHE_mat_IDs[bhe_id];
+        std::vector<MeshLib::Element*>& vec_elements = vec_BHE_elements[bhe_id];
+        std::copy_if(all_BHE_elements.begin(), all_BHE_elements.end(),
+                     std::back_inserter(vec_elements),
+                     [&](MeshLib::Element* e) {
+                         return (*opt_material_ids)[e->getID()] == bhe_mat_id;
+                     });
+        DBUG("-> found %d elements on the BHE_%d", vec_elements.size(), bhe_id);
+    }
+
+    // get a vector of BHE nodes
+    vec_BHE_nodes.resize(vec_BHE_mat_IDs.size());
+    for (unsigned bhe_id = 0; bhe_id < vec_BHE_mat_IDs.size(); bhe_id++)
+    {
+        std::vector<MeshLib::Node*>& vec_nodes = vec_BHE_nodes[bhe_id];
+        for (MeshLib::Element* e : vec_BHE_elements[bhe_id])
+        {
+            for (unsigned i = 0; i < e->getNumberOfNodes(); i++)
+            {
+                vec_nodes.push_back(const_cast<MeshLib::Node*>(e->getNode(i)));
+            }
+        }
+        BaseLib::makeVectorUnique(
+            vec_nodes, [](MeshLib::Node* node1, MeshLib::Node* node2) {
+                return node1->getID() < node2->getID();
+            });
+        DBUG("-> found %d nodes on the BHE_%d", vec_nodes.size(), bhe_id);
+    }
+
+    // get all the nodes into the pure soil nodes vector
+    vec_soil_nodes.reserve(mesh.getNumberOfNodes());
+    for (MeshLib::Node* n : mesh.getNodes())
+    {
+        // All elements are counted as a soil element
+        vec_soil_nodes.push_back(n);
+    }
+
+    // final count of 3 types of elements
+    // They are
+    // (i)  soil,
+    // (ii) BHE
+    DBUG("-> found total %d soil elements and %d BHE elements",
+         vec_soil_elements.size(),
+         all_BHE_elements.size());
+}
+}  // end of namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/BHE/MeshUtils.h b/ProcessLib/HeatTransportBHE/BHE/MeshUtils.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac75431d2e0057279b536fc2a13ba6b2f4d32154
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/BHE/MeshUtils.h
@@ -0,0 +1,42 @@
+/**
+ * \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 <vector>
+
+namespace MeshLib
+{
+class Element;
+class Mesh;
+class Node;
+}  // namespace MeshLib
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+/**
+ * get data about fracture and matrix elements/nodes from a mesh
+ *
+ * @param mesh  A mesh which includes BHE elements, i.e. 1-dimensional
+ * elements. It is assumed that elements forming a BHE have a distinct
+ * material ID.
+ * @param vec_soil_elements a vector of soil elements
+ * @param vec_BHE_elements  a vector of BHE elements (grouped by BHE IDs)
+ * @param vec_BHE_nodes   a vector of BHE nodes (grouped by BHE IDs)
+ */
+void getBHEDataInMesh(
+    MeshLib::Mesh const& mesh,
+    std::vector<MeshLib::Element*>& vec_soil_elements,
+    std::vector<int>& vec_BHE_mat_IDs,
+    std::vector<std::vector<MeshLib::Element*>>& vec_BHE_elements,
+    std::vector<MeshLib::Node*>& vec_pure_soil_nodes,
+    std::vector<std::vector<MeshLib::Node*>>& vec_BHE_nodes);
+}  // end of namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/CMakeLists.txt b/ProcessLib/HeatTransportBHE/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2d9def64402e62d9154925303a0ade8532756dab
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/CMakeLists.txt
@@ -0,0 +1,8 @@
+APPEND_SOURCE_FILES(SOURCES)
+APPEND_SOURCE_FILES(SOURCES BHE)
+APPEND_SOURCE_FILES(SOURCES LocalAssemblers)
+
+add_library(HeatTransportBHE ${SOURCES})
+target_link_libraries(HeatTransportBHE PUBLIC ProcessLib)
+
+# include(Tests.cmake)
diff --git a/ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.cpp b/ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ece955939dc2f849f42eeb98efa69634d8d2c30d
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.cpp
@@ -0,0 +1,306 @@
+/**
+ * \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 "CreateHeatTransportBHEProcess.h"
+
+#include "BHE/BHEAbstract.h"
+#include "BHE/BHE_1U.h"
+#include "BHE/BHE_2U.h"
+#include "BHE/BHE_CXA.h"
+#include "BHE/BHE_CXC.h"
+#include "BHE/CreateBHE1U.h"
+#include "BHE/CreateBHE2U.h"
+#include "BHE/CreateBHECXA.h"
+#include "BHE/CreateBHECXC.h"
+#include "BaseLib/Algorithm.h"
+#include "HeatTransportBHEProcess.h"
+#include "HeatTransportBHEProcessData.h"
+#include "MaterialLib/Fluid/Density/CreateFluidDensityModel.h"
+#include "MaterialLib/Fluid/FluidProperty.h"
+#include "MaterialLib/Fluid/SpecificHeatCapacity/CreateSpecificFluidHeatCapacityModel.h"
+#include "MaterialLib/Fluid/ThermalConductivity/CreateFluidThermalConductivityModel.h"
+#include "MaterialLib/Fluid/Viscosity/CreateViscosityModel.h"
+#include "ProcessLib/Output/CreateSecondaryVariables.h"
+#include "ProcessLib/Utils/ProcessUtils.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+std::unique_ptr<Process> createHeatTransportBHEProcess(
+    MeshLib::Mesh& mesh,
+    std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&& jacobian_assembler,
+    std::vector<ProcessVariable> const& variables,
+    std::vector<std::unique_ptr<ParameterBase>> const& parameters,
+    unsigned const integration_order,
+    BaseLib::ConfigTree const& config,
+    std::map<std::string,
+             std::unique_ptr<MathLib::PiecewiseLinearInterpolation>> const&
+        curves)
+{
+    //! \ogs_file_param{prj__processes__process__type}
+    config.checkConfigParameter("type", "HEAT_TRANSPORT_BHE");
+
+    DBUG("Create HeatTransportBHE Process.");
+
+    // Process variable.
+
+    //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__process_variables}
+    auto const pv_config = config.getConfigSubtree("process_variables");
+    std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>
+        process_variables;
+
+    // reading primary variables for each
+    // BHE----------------------------------------------------------
+    auto range =
+        //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__process_variables__process_variable}
+        pv_config.getConfigParameterList<std::string>("process_variable");
+    std::vector<std::reference_wrapper<ProcessVariable>> per_process_variables;
+
+    for (std::string const& pv_name : range)
+    {
+        if (pv_name != "temperature_soil" &&
+            pv_name.find("temperature_BHE") != 0)
+        {
+            OGS_FATAL(
+                "Found a process variable name '%s'. It should be "
+                "'temperature_soil' or 'temperature_BHE_X'");
+        }
+        auto variable = std::find_if(variables.cbegin(), variables.cend(),
+                                     [&pv_name](ProcessVariable const& v) {
+                                         return v.getName() == pv_name;
+                                     });
+
+        if (variable == variables.end())
+        {
+            OGS_FATAL(
+                "Could not find process variable '%s' in the provided "
+                "variables "
+                "list for config tag <%s>.",
+                pv_name.c_str(), "process_variable");
+        }
+        DBUG("Found process variable \'%s\' for config tag <%s>.",
+             variable->getName().c_str(), "process_variable");
+
+        per_process_variables.emplace_back(
+            const_cast<ProcessVariable&>(*variable));
+    }
+    process_variables.push_back(std::move(per_process_variables));
+    // end of reading primary variables for each
+    // BHE----------------------------------------------------------
+
+    // solid phase thermal conductivity parameter.
+    auto& thermal_conductivity_solid = findParameter<double>(
+        config,
+        //! \ogs_file_param_special
+        //! prj__processes__process__HEAT_TRANSPORT_BHE__thermal_conductivity_solid}
+        "thermal_conductivity_solid", parameters, 1);
+
+    DBUG("Use \'%s\' as solid phase thermal conductivity parameter.",
+         thermal_conductivity_solid.name.c_str());
+
+    // solid phase thermal conductivity parameter.
+    auto& thermal_conductivity_fluid = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__thermal_conductivity_fluid}
+        "thermal_conductivity_fluid", parameters, 1);
+
+    DBUG("Use \'%s\' as fluid phase thermal conductivity parameter.",
+         thermal_conductivity_fluid.name.c_str());
+
+    // gas phase thermal conductivity parameter.
+    auto& thermal_conductivity_gas = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__thermal_conductivity_gas}
+        "thermal_conductivity_gas", parameters, 1);
+
+    DBUG("Use \'%s\' as gas phase thermal conductivity parameter.",
+         thermal_conductivity_gas.name.c_str());
+
+    // solid phase heat capacity parameter.
+    auto& heat_capacity_solid = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__heat_capacity_solid}
+        "heat_capacity_solid", parameters, 1);
+
+    DBUG("Use \'%s\' as solid phase heat capacity parameter.",
+         heat_capacity_solid.name.c_str());
+
+    // fluid phase heat capacity parameter.
+    auto& heat_capacity_fluid = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__heat_capacity_fluid}
+        "heat_capacity_fluid", parameters, 1);
+
+    DBUG("Use \'%s\' as fluid phase heat capacity parameter.",
+         heat_capacity_fluid.name.c_str());
+
+    // gas phase heat capacity parameter.
+    auto& heat_capacity_gas = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__heat_capacity_gas}
+        "heat_capacity_gas", parameters, 1);
+
+    DBUG("Use \'%s\' as gas phase heat capacity parameter.",
+         heat_capacity_gas.name.c_str());
+
+    // solid phase density parameter.
+    auto& density_solid = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__density_solid}
+        "density_solid", parameters, 1);
+
+    DBUG("Use \'%s\' as solid phase density parameter.",
+         density_solid.name.c_str());
+
+    // fluid phase density parameter.
+    auto& density_fluid = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__density_fluid}
+        "density_fluid", parameters, 1);
+
+    DBUG("Use \'%s\' as fluid phase density parameter.",
+         density_fluid.name.c_str());
+
+    // gas phase density parameter.
+    auto& density_gas = findParameter<double>(
+        config,
+        //! \ogs_file_param_special{prj__processes__process__HEAT_TRANSPORT_BHE__density_gas}
+        "density_gas", parameters, 1);
+
+    DBUG("Use \'%s\' as gas phase density parameter.",
+         density_gas.name.c_str());
+
+    // get the refrigerant properties from fluid property class
+    //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__material_property__fluid}
+    auto const& fluid_config = config.getConfigSubtree("fluid");
+    //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__material_property__refrigerant_density}
+    auto const& rho_conf = fluid_config.getConfigSubtree("refrigerant_density");
+    auto bhe_refrigerant_density =
+        MaterialLib::Fluid::createFluidDensityModel(rho_conf);
+    //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__material_property__refrigerant_viscosity}
+    auto const& mu_conf =
+        fluid_config.getConfigSubtree("refrigerant_viscosity");
+    auto bhe_refrigerant_viscosity =
+        MaterialLib::Fluid::createViscosityModel(mu_conf);
+    //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__material_property__refrigerant_specific_heat_capacity}
+    auto const& cp_conf =
+        fluid_config.getConfigSubtree("refrigerant_specific_heat_capacity");
+    auto bhe_refrigerant_heat_capacity =
+        MaterialLib::Fluid::createSpecificFluidHeatCapacityModel(cp_conf);
+    //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__material_property__refrigerant_thermal_conductivity}
+    auto const& lambda_conf =
+        fluid_config.getConfigSubtree("refrigerant_thermal_conductivity");
+    auto bhe_regrigerant_heat_conductivity =
+        MaterialLib::Fluid::createFluidThermalConductivityModel(lambda_conf);
+
+    // reading BHE
+    // parameters--------------------------------------------------------------
+    std::vector<std::unique_ptr<BHE::BHEAbstract>> vec_BHEs;
+    // BHE::BHE_Net BHE_network;
+
+    // now read the BHE configurations
+    auto const& bhe_configs =
+        //! \ogs_file_param{prj__processes__process__HEAT_TRANSPORT_BHE__borehole_heat_exchangers}
+        config.getConfigSubtree("borehole_heat_exchangers");
+
+    for (
+        auto const& bhe_conf :
+        //! \ogs_file_param
+        //! prj__processes__process___HEAT_TRANSPORT_BHE__borehole_heat_exchangers__borehole_heat_exchanger}
+        bhe_configs.getConfigSubtreeList("borehole_heat_exchanger"))
+    {
+        // read in the parameters
+        using namespace BHE;
+        const std::string bhe_type_str =
+            bhe_conf.getConfigParameter<std::string>("bhe_type");
+
+        // convert BHE type
+        if (bhe_type_str == "BHE_TYPE_1U")
+        {
+            // initialize the 1U type BHE
+            BHE::BHE_1U* m_bhe_1u =
+                BHE::CreateBHE1U(bhe_conf,
+                                 curves,
+                                 bhe_refrigerant_density,
+                                 bhe_refrigerant_viscosity,
+                                 bhe_refrigerant_heat_capacity,
+                                 bhe_regrigerant_heat_conductivity);
+
+            vec_BHEs.emplace_back(std::make_unique<BHE_1U>(*m_bhe_1u));
+        }
+        else if (bhe_type_str == "BHE_TYPE_2U")
+        {
+            // initialize the 2U type BHE
+            BHE::BHE_2U* m_bhe_2u =
+                BHE::CreateBHE2U(bhe_conf,
+                                 curves,
+                                 bhe_refrigerant_density,
+                                 bhe_refrigerant_viscosity,
+                                 bhe_refrigerant_heat_capacity,
+                                 bhe_regrigerant_heat_conductivity);
+
+            vec_BHEs.emplace_back(std::make_unique<BHE_2U>(*m_bhe_2u));
+        }
+        else if (bhe_type_str == "BHE_TYPE_CXC")
+        {
+            // initialize the CXC type BHE
+            BHE::BHE_CXC* m_bhe_CXC =
+                BHE::CreateBHECXC(bhe_conf,
+                                  curves,
+                                  bhe_refrigerant_density,
+                                  bhe_refrigerant_viscosity,
+                                  bhe_refrigerant_heat_capacity,
+                                  bhe_regrigerant_heat_conductivity);
+
+            vec_BHEs.emplace_back(std::make_unique<BHE_CXC>(*m_bhe_CXC));
+        }
+        else if (bhe_type_str == "BHE_TYPE_CXA")
+        {
+            // initialize the CXA type BHE
+            BHE::BHE_CXA* m_bhe_CXA =
+                BHE::CreateBHECXA(bhe_conf,
+                                  curves,
+                                  bhe_refrigerant_density,
+                                  bhe_refrigerant_viscosity,
+                                  bhe_refrigerant_heat_capacity,
+                                  bhe_regrigerant_heat_conductivity);
+
+            vec_BHEs.emplace_back(std::make_unique<BHE_CXA>(*m_bhe_CXA));
+        }
+    }
+    // end of reading BHE
+    // parameters-------------------------------------------------------
+
+    HeatTransportBHEProcessData process_data{thermal_conductivity_solid,
+                                             thermal_conductivity_fluid,
+                                             thermal_conductivity_gas,
+                                             heat_capacity_solid,
+                                             heat_capacity_fluid,
+                                             heat_capacity_gas,
+                                             density_solid,
+                                             density_fluid,
+                                             density_gas,
+                                             std::move(vec_BHEs)};
+
+    SecondaryVariableCollection secondary_variables;
+
+    NumLib::NamedFunctionCaller named_function_caller(
+        {"HeatTransportBHE_Temperature"});
+
+    ProcessLib::createSecondaryVariables(config, secondary_variables,
+                                         named_function_caller);
+
+    return std::make_unique<HeatTransportBHEProcess>(
+        mesh, std::move(jacobian_assembler), parameters, integration_order,
+        std::move(process_variables), std::move(process_data),
+        std::move(secondary_variables), std::move(named_function_caller));
+}
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.h b/ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.h
new file mode 100644
index 0000000000000000000000000000000000000000..e6ee4f3d9db41ec449cc3313f2cd238f4b65d1cd
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.h
@@ -0,0 +1,30 @@
+/**
+ * \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 <memory>
+#include "ProcessLib/Process.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+std::unique_ptr<Process> createHeatTransportBHEProcess(
+    MeshLib::Mesh& mesh,
+    std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&& jacobian_assembler,
+    std::vector<ProcessVariable> const& variables,
+    std::vector<std::unique_ptr<ParameterBase>> const& parameters,
+    unsigned const integration_order,
+    BaseLib::ConfigTree const& config,
+    std::map<std::string,
+             std::unique_ptr<MathLib::PiecewiseLinearInterpolation>> const&
+        curves);
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/HeatTransportBHEProcess.cpp b/ProcessLib/HeatTransportBHE/HeatTransportBHEProcess.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a8f1a0c8a3477dad8ee81aa169949e6534a57b08
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/HeatTransportBHEProcess.cpp
@@ -0,0 +1,419 @@
+/**
+ * \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 "HeatTransportBHEProcess.h"
+
+#include <cassert>
+#include <iostream>
+#include "ProcessLib/HeatTransportBHE/BHE/MeshUtils.h"
+#include "ProcessLib/HeatTransportBHE/LocalAssemblers/CreateLocalAssemblers.h"
+
+#include "ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerBHE.h"
+#include "ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerSoil.h"
+
+#include "ProcessLib/BoundaryCondition/BHEBottomDirichletBoundaryCondition.h"
+#include "ProcessLib/BoundaryCondition/BHEInflowDirichletBoundaryCondition.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+HeatTransportBHEProcess::HeatTransportBHEProcess(
+    MeshLib::Mesh& mesh,
+    std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&& jacobian_assembler,
+    std::vector<std::unique_ptr<ParameterBase>> const& parameters,
+    unsigned const integration_order,
+    std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>&&
+        process_variables,
+    HeatTransportBHEProcessData&& process_data,
+    SecondaryVariableCollection&& secondary_variables,
+    NumLib::NamedFunctionCaller&& named_function_caller)
+    : Process(mesh, std::move(jacobian_assembler), parameters,
+              integration_order, std::move(process_variables),
+              std::move(secondary_variables), std::move(named_function_caller)),
+      _process_data(std::move(process_data))
+{
+    getBHEDataInMesh(mesh,
+                     _vec_pure_soil_elements,
+                     _vec_BHE_mat_IDs,
+                     _vec_BHE_elements,
+                     _vec_pure_soil_nodes,
+                     _vec_BHE_nodes);
+
+    if (_vec_BHE_mat_IDs.size() != _process_data._vec_BHE_property.size())
+    {
+        OGS_FATAL(
+            "The number of the given BHE properties (%d) are not "
+            "consistent"
+            " with the number of BHE groups in a mesh (%d).",
+            _process_data._vec_BHE_property.size(),
+            _vec_BHE_mat_IDs.size());
+    }
+
+    // create a map from a material ID to a BHE ID
+    auto max_BHE_mat_id =
+        std::max_element(_vec_BHE_mat_IDs.begin(), _vec_BHE_mat_IDs.end());
+    const std::size_t nBHEmatIDs = *max_BHE_mat_id + 1;
+    _process_data._map_materialID_to_BHE_ID.resize(nBHEmatIDs);
+    for (std::size_t i = 0; i < _vec_BHE_mat_IDs.size(); i++)
+    {
+        // by default, it is assumed that the soil compartment takes material ID
+        // 0 and the BHE take the successive material group.
+        _process_data._map_materialID_to_BHE_ID[_vec_BHE_mat_IDs[i]] = i;
+    }
+
+    MeshLib::PropertyVector<int> const* material_ids(
+        mesh.getProperties().getPropertyVector<int>("MaterialIDs"));
+    _process_data._mesh_prop_materialIDs = material_ids;
+}
+
+void HeatTransportBHEProcess::constructDofTable()
+{
+    // Create single component dof in every of the mesh's nodes.
+    _mesh_subset_all_nodes =
+        std::make_unique<MeshLib::MeshSubset>(_mesh, _mesh.getNodes());
+
+    //------------------------------------------------------------
+    // prepare mesh subsets to define DoFs
+    //------------------------------------------------------------
+    // first all the soil nodes
+    _mesh_subset_pure_soil_nodes =
+        std::make_unique<MeshLib::MeshSubset>(_mesh, _vec_pure_soil_nodes);
+
+    std::vector<int> vec_n_BHE_unknowns;
+    vec_n_BHE_unknowns.reserve(_vec_BHE_nodes.size());
+    // the BHE nodes need to be cherry-picked from the vector
+    for (unsigned i = 0; i < _vec_BHE_nodes.size(); i++)
+    {
+        _mesh_subset_BHE_nodes.push_back(
+            std::make_unique<MeshLib::MeshSubset const>(_mesh,
+                                                        _vec_BHE_nodes[i]));
+        int n_BHE_unknowns =
+            _process_data._vec_BHE_property[i]->getNumUnknowns();
+        vec_n_BHE_unknowns.emplace_back(n_BHE_unknowns);
+    }
+
+    // Collect the mesh subsets in a vector.
+    // All the soil nodes has 1 temperature variable
+    std::vector<MeshLib::MeshSubset> all_mesh_subsets{
+        *_mesh_subset_pure_soil_nodes};
+
+    // All the BHE nodes have additinal variables
+    std::size_t count = 0;
+    for (auto& ms : _mesh_subset_BHE_nodes)
+    {
+        std::generate_n(std::back_inserter(all_mesh_subsets),
+                        // Here the number of components equals to
+                        // the number of unknowns on the BHE
+                        vec_n_BHE_unknowns[count],
+                        [&]() { return *ms; });
+        count++;
+    }
+
+    std::vector<int> vec_n_components;
+    // This is the soil temperature for first mesh subset.
+    // 1, because for the soil part there is just one variable which is the soil
+    // temperature.
+    vec_n_components.push_back(1);
+    // now the BHE subsets
+    for (std::size_t i = 0; i < _vec_BHE_mat_IDs.size(); i++)
+    {
+        // Here the number of components equals to
+        // the number of unknowns on the BHE
+        vec_n_components.push_back(vec_n_BHE_unknowns[i]);
+    }
+
+    std::vector<std::vector<MeshLib::Element*> const*> vec_var_elements;
+    // vec_var_elements.push_back(&_vec_pure_soil_elements);
+    vec_var_elements.push_back(&(_mesh.getElements()));
+    for (std::size_t i = 0; i < _vec_BHE_elements.size(); i++)
+    {
+        vec_var_elements.push_back(&_vec_BHE_elements[i]);
+    }
+
+    _local_to_global_index_map =
+        std::make_unique<NumLib::LocalToGlobalIndexMap>(
+            std::move(all_mesh_subsets),
+            vec_n_components,
+            vec_var_elements,
+            NumLib::ComponentOrder::BY_COMPONENT);
+
+    // in case of debugging the dof table, activate the following line
+    // std::cout << *_local_to_global_index_map << "\n";
+}
+
+void HeatTransportBHEProcess::initializeConcreteProcess(
+    NumLib::LocalToGlobalIndexMap const& dof_table,
+    MeshLib::Mesh const& mesh,
+    unsigned const integration_order)
+{
+    const int process_id = 0;
+
+    // this process can only run with 3-dimensional mesh
+    ProcessLib::HeatTransportBHE::createLocalAssemblers<
+        3, /*mesh.getDimension(),*/
+        HeatTransportBHELocalAssemblerSoil, HeatTransportBHELocalAssemblerBHE>(
+        mesh.getElements(), dof_table, _local_assemblers,
+        _process_data._vec_ele_connected_BHE_IDs,
+        _process_data._vec_BHE_property, mesh.isAxiallySymmetric(),
+        integration_order, _process_data);
+
+    // create BHE boundary conditions
+    // for each BHE, one BC on the top
+    // and one BC at the bottom.
+    std::vector<std::unique_ptr<BoundaryCondition>> bc_collections =
+        createBHEBoundaryConditionTopBottom();
+    auto& current_process_BCs = _boundary_conditions[process_id];
+    auto const bc_collections_size = bc_collections.size();
+    for (unsigned i = 0; i < bc_collections_size; i++)
+        current_process_BCs.addCreatedBC(std::move(bc_collections[i]));
+}
+
+void HeatTransportBHEProcess::assembleConcreteProcess(const double t,
+                                                      GlobalVector const& x,
+                                                      GlobalMatrix& M,
+                                                      GlobalMatrix& K,
+                                                      GlobalVector& b)
+{
+    DBUG("Assemble HeatTransportBHE process.");
+
+    std::vector<std::reference_wrapper<NumLib::LocalToGlobalIndexMap>>
+        dof_table = {std::ref(*_local_to_global_index_map)};
+    // Call global assembler for each local assembly item.
+    GlobalExecutor::executeMemberDereferenced(
+        _global_assembler, &VectorMatrixAssembler::assemble, _local_assemblers,
+        dof_table, t, x, M, K, b, _coupled_solutions);
+}
+
+void HeatTransportBHEProcess::assembleWithJacobianConcreteProcess(
+    const double t, GlobalVector const& x, GlobalVector const& xdot,
+    const double dxdot_dx, const double dx_dx, GlobalMatrix& M, GlobalMatrix& K,
+    GlobalVector& b, GlobalMatrix& Jac)
+{
+    DBUG("AssembleWithJacobian HeatTransportBHE process.");
+
+    std::vector<std::reference_wrapper<NumLib::LocalToGlobalIndexMap>>
+        dof_table = {std::ref(*_local_to_global_index_map)};
+    // Call global assembler for each local assembly item.
+    GlobalExecutor::executeMemberDereferenced(
+        _global_assembler, &VectorMatrixAssembler::assembleWithJacobian,
+        _local_assemblers, dof_table, t, x, xdot, dxdot_dx, dx_dx, M, K, b, Jac,
+        _coupled_solutions);
+}
+
+void HeatTransportBHEProcess::computeSecondaryVariableConcrete(
+    const double t, GlobalVector const& x)
+{
+    DBUG("Compute heat flux for HeatTransportBHE process.");
+    GlobalExecutor::executeMemberOnDereferenced(
+        &HeatTransportBHELocalAssemblerInterface::computeSecondaryVariable,
+        _local_assemblers, *_local_to_global_index_map, t, x,
+        _coupled_solutions);
+}
+
+std::vector<std::unique_ptr<BoundaryCondition>>
+HeatTransportBHEProcess::createBHEBoundaryConditionTopBottom()
+{
+    /**
+     * boundary conditions
+     */
+    std::vector<std::unique_ptr<BoundaryCondition>> bcs;
+
+    int const n_BHEs = static_cast<int>(_process_data._vec_BHE_property.size());
+
+    // bulk dof table
+    NumLib::LocalToGlobalIndexMap dof_table_bulk = *_local_to_global_index_map;
+
+    std::vector<MeshLib::Node*> bc_top_nodes;
+    std::vector<MeshLib::Node*> bc_bottom_nodes;
+
+    // for each BHE
+    for (int bhe_i = 0; bhe_i < n_BHEs; bhe_i++)
+    {
+        // get the BHE type
+        auto const& bhe_typ = _process_data._vec_BHE_property[bhe_i]->bhe_type;
+        // find the variable ID
+        // the soil temperature is 0-th variable
+        // the BHE temperature is therefore bhe_i + 1
+        const int variable_id = bhe_i + 1;
+        // find the node in mesh that are at the top
+        auto const n_bhe_nodes = _vec_BHE_nodes[bhe_i].size();
+        unsigned int idx_top = 0;
+        unsigned int idx_bottom = _vec_BHE_nodes[bhe_i].size() - 1;
+        double top_z_coord = _vec_BHE_nodes[bhe_i][idx_top]->getCoords()[2];
+        double bottom_z_coord =
+            _vec_BHE_nodes[bhe_i][idx_bottom]->getCoords()[2];
+        for (unsigned bhe_node_i = 0; bhe_node_i < n_bhe_nodes; bhe_node_i++)
+        {
+            if (_vec_BHE_nodes[bhe_i][bhe_node_i]->getCoords()[2] >=
+                top_z_coord)
+                idx_top = bhe_node_i;
+
+            if (_vec_BHE_nodes[bhe_i][bhe_node_i]->getCoords()[2] <=
+                bottom_z_coord)
+                idx_bottom = bhe_node_i;
+        }
+        bc_top_nodes.clear();
+        bc_top_nodes.emplace_back(_vec_BHE_nodes[bhe_i][idx_top]);
+        bc_bottom_nodes.clear();
+        bc_bottom_nodes.emplace_back(_vec_BHE_nodes[bhe_i][idx_bottom]);
+
+        MeshLib::MeshSubset bc_mesh_subset_top{_mesh, bc_top_nodes};
+        MeshLib::Mesh const& bc_mesh_top = bc_mesh_subset_top.getMesh();
+
+        MeshLib::MeshSubset bc_mesh_subset_bottom{_mesh, bc_bottom_nodes};
+        MeshLib::Mesh const& bc_mesh_bottom = bc_mesh_subset_bottom.getMesh();
+
+        auto get_global_bhe_bc_index_top = [&](std::size_t const mesh_id,
+                                               int const component_id) {
+            return dof_table_bulk.getGlobalIndex(
+                {mesh_id, MeshLib::MeshItemType::Node,
+                 bc_top_nodes[0]->getID()},
+                variable_id, component_id);
+        };
+        auto get_global_bhe_bc_index_bottom = [&](std::size_t const mesh_id,
+                                                  int const component_id) {
+            return dof_table_bulk.getGlobalIndex(
+                {mesh_id, MeshLib::MeshItemType::Node,
+                 bc_bottom_nodes[0]->getID()},
+                variable_id, component_id);
+        };
+
+        // the create_BC function will be repeatedly used
+        auto create_BC_top_inflow = [&](std::size_t const mesh_id,
+                                        int const component_id_in,
+                                        int const component_id_out) {
+            auto const global_index_in =
+                get_global_bhe_bc_index_top(mesh_id, component_id_in);
+            auto const global_index_out =
+                get_global_bhe_bc_index_top(mesh_id, component_id_out);
+            return ProcessLib::createBHEInflowDirichletBoundaryCondition(
+                global_index_in, global_index_out, _mesh, bc_top_nodes,
+                variable_id, component_id_in,
+                _process_data._vec_BHE_property[bhe_i]);
+        };
+        auto create_BC_bottom_outflow = [&](std::size_t const mesh_id,
+                                            int const component_id_in,
+                                            int const component_id_out) {
+            auto const global_index_in =
+                get_global_bhe_bc_index_bottom(mesh_id, component_id_in);
+            auto const global_index_out =
+                get_global_bhe_bc_index_bottom(mesh_id, component_id_out);
+            return ProcessLib::createBHEBottomDirichletBoundaryCondition(
+                global_index_in, global_index_out, _mesh, bc_top_nodes,
+                variable_id, component_id_in);
+        };
+
+        // depending on the BHE type
+        switch (bhe_typ)
+        {
+            case ProcessLib::HeatTransportBHE::BHE::BHE_TYPE::TYPE_2U:
+            {
+                unsigned const component_id_T_in_1 = 0;
+                unsigned const component_id_T_out_1 = 2;
+
+                unsigned const component_id_T_out_2 = 3;
+                unsigned const component_id_T_in_2 = 1;
+
+                // there are 2 BCs on the top node
+                std::unique_ptr<BHEInflowDirichletBoundaryCondition> bc_top_1 =
+                    create_BC_top_inflow(bc_mesh_top.getID(),
+                                         component_id_T_in_1,
+                                         component_id_T_out_1);
+                std::unique_ptr<BHEInflowDirichletBoundaryCondition> bc_top_2 =
+                    create_BC_top_inflow(bc_mesh_top.getID(),
+                                         component_id_T_in_2,
+                                         component_id_T_out_2);
+
+                // there are also 2 BCs on the bottom node
+                std::unique_ptr<BHEBottomDirichletBoundaryCondition>
+                    bc_bottom_1 = create_BC_bottom_outflow(
+                        bc_mesh_bottom.getID(), component_id_T_in_1,
+                        component_id_T_out_1);
+                std::unique_ptr<BHEBottomDirichletBoundaryCondition>
+                    bc_bottom_2 = create_BC_bottom_outflow(
+                        bc_mesh_bottom.getID(), component_id_T_in_2,
+                        component_id_T_out_2);
+
+                // add bc_top and bc_bottom to the vector
+                bcs.push_back(std::move(bc_top_1));
+                bcs.push_back(std::move(bc_top_2));
+                bcs.push_back(std::move(bc_bottom_1));
+                bcs.push_back(std::move(bc_bottom_2));
+            }
+            break;
+            case ProcessLib::HeatTransportBHE::BHE::BHE_TYPE::TYPE_1U:
+            {
+                unsigned const component_id_T_in = 0;
+                unsigned const component_id_T_out = 1;
+                // there is one BC on the top node
+                std::unique_ptr<BHEInflowDirichletBoundaryCondition> bc_top =
+                    create_BC_top_inflow(bc_mesh_top.getID(), component_id_T_in,
+                                         component_id_T_out);
+
+                // there is also 1 BC on the bottom node
+                std::unique_ptr<BHEBottomDirichletBoundaryCondition> bc_bottom =
+                    create_BC_bottom_outflow(bc_mesh_bottom.getID(),
+                                             component_id_T_in,
+                                             component_id_T_out);
+
+                // add bc_top and bc_bottom to the vector
+                bcs.push_back(std::move(bc_top));
+                bcs.push_back(std::move(bc_bottom));
+            }
+            break;
+            case ProcessLib::HeatTransportBHE::BHE::BHE_TYPE::TYPE_CXC:
+            {
+                unsigned const component_id_T_in = 1;
+                unsigned const component_id_T_out = 0;
+                // there is one BC on the top node
+                std::unique_ptr<BHEInflowDirichletBoundaryCondition> bc_top =
+                    create_BC_top_inflow(bc_mesh_top.getID(), component_id_T_in,
+                                         component_id_T_out);
+
+                // there is also 1 BC on the bottom node
+                std::unique_ptr<BHEBottomDirichletBoundaryCondition> bc_bottom =
+                    create_BC_bottom_outflow(bc_mesh_bottom.getID(),
+                                             component_id_T_in,
+                                             component_id_T_out);
+
+                // add bc_top and bc_bottom to the vector
+                bcs.push_back(std::move(bc_top));
+                bcs.push_back(std::move(bc_bottom));
+            }
+            break;
+            case ProcessLib::HeatTransportBHE::BHE::BHE_TYPE::TYPE_CXA:
+            {
+                unsigned const component_id_T_in = 0;
+                unsigned const component_id_T_out = 1;
+                // there is one BC on the top node
+                std::unique_ptr<BHEInflowDirichletBoundaryCondition> bc_top =
+                    create_BC_top_inflow(bc_mesh_top.getID(), component_id_T_in,
+                                         component_id_T_out);
+
+                // there is also 1 BC on the bottom node
+                std::unique_ptr<BHEBottomDirichletBoundaryCondition> bc_bottom =
+                    create_BC_bottom_outflow(bc_mesh_bottom.getID(),
+                                             component_id_T_in,
+                                             component_id_T_out);
+
+                // add bc_top and bc_bottom to the vector
+                bcs.push_back(std::move(bc_top));
+                bcs.push_back(std::move(bc_bottom));
+            }
+            break;
+            default:
+                OGS_FATAL("WRONG BHE TYPE");
+        }
+    }
+
+    return bcs;
+}
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/HeatTransportBHEProcess.h b/ProcessLib/HeatTransportBHE/HeatTransportBHEProcess.h
new file mode 100644
index 0000000000000000000000000000000000000000..217e4da211538bd1e1b2a59f75c329ed2a4d215e
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/HeatTransportBHEProcess.h
@@ -0,0 +1,98 @@
+/**
+ * \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 "HeatTransportBHEProcessData.h"
+#include "ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHEProcessAssemblerInterface.h"
+#include "ProcessLib/Process.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+class HeatTransportBHEProcess final : public Process
+{
+public:
+    HeatTransportBHEProcess(
+        MeshLib::Mesh& mesh,
+        std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&&
+            jacobian_assembler,
+        std::vector<std::unique_ptr<ParameterBase>> const& parameters,
+        unsigned const integration_order,
+        std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>&&
+            process_variables,
+        HeatTransportBHEProcessData&& process_data,
+        SecondaryVariableCollection&& secondary_variables,
+        NumLib::NamedFunctionCaller&& named_function_caller);
+
+    //! \name ODESystem interface
+    //! @{
+    bool isLinear() const override { return false; }
+
+    void computeSecondaryVariableConcrete(double const t,
+                                          GlobalVector const& x) override;
+
+private:
+    void constructDofTable() override;
+
+    void initializeConcreteProcess(
+        NumLib::LocalToGlobalIndexMap const& dof_table,
+        MeshLib::Mesh const& mesh,
+        unsigned const integration_order) override;
+
+    void assembleConcreteProcess(const double t, GlobalVector const& x,
+                                 GlobalMatrix& M, GlobalMatrix& K,
+                                 GlobalVector& b) override;
+
+    void assembleWithJacobianConcreteProcess(
+        const double t, GlobalVector const& x, GlobalVector const& xdot,
+        const double dxdot_dx, const double dx_dx, GlobalMatrix& M,
+        GlobalMatrix& K, GlobalVector& b, GlobalMatrix& Jac) override;
+
+    std::vector<std::unique_ptr<BoundaryCondition>>
+    createBHEBoundaryConditionTopBottom();
+
+    HeatTransportBHEProcessData _process_data;
+
+    std::vector<std::unique_ptr<HeatTransportBHELocalAssemblerInterface>>
+        _local_assemblers;
+
+    /**
+     * These are the elements that are representing BHEs
+     */
+    std::vector<std::vector<MeshLib::Element*>> _vec_BHE_elements;
+
+    /**
+     * These are the elements that are not connected with any BHE
+     */
+    std::vector<MeshLib::Element*> _vec_pure_soil_elements;
+
+    /**
+     * These are the soil nodes that are not connected with a BHE
+     */
+    std::vector<MeshLib::Node*> _vec_pure_soil_nodes;
+
+    /**
+     * Mesh nodes that are located on any BHE
+     * ordered according to each BHE
+     */
+    std::vector<std::vector<MeshLib::Node*>> _vec_BHE_nodes;
+
+    std::vector<std::unique_ptr<MeshLib::MeshSubset const>>
+        _mesh_subset_BHE_nodes;
+    std::vector<std::unique_ptr<MeshLib::MeshSubset const>>
+        _mesh_subset_BHE_soil_nodes;
+    std::unique_ptr<MeshLib::MeshSubset const> _mesh_subset_pure_soil_nodes;
+    std::unique_ptr<MeshLib::MeshSubset const>
+        _mesh_subset_soil_nodes_connected_with_BHE;
+    std::vector<int> _vec_BHE_mat_IDs;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/HeatTransportBHEProcessData.h b/ProcessLib/HeatTransportBHE/HeatTransportBHEProcessData.h
new file mode 100644
index 0000000000000000000000000000000000000000..28224822f0ecdfa0bc48b733deaa84ad6822011a
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/HeatTransportBHEProcessData.h
@@ -0,0 +1,90 @@
+/**
+ * \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 "MeshLib/PropertyVector.h"
+#include "ProcessLib/HeatTransportBHE/BHE/BHEAbstract.h"
+
+namespace MeshLib
+{
+class Element;
+}
+
+namespace ProcessLib
+{
+template <typename T>
+struct Parameter;
+
+namespace HeatTransportBHE
+{
+using namespace BHE;
+
+struct HeatTransportBHEProcessData
+{
+    HeatTransportBHEProcessData(
+        Parameter<double> const& thermal_conductivity_solid_,
+        Parameter<double> const& thermal_conductivity_fluid_,
+        Parameter<double> const& thermal_conductivity_gas_,
+        Parameter<double> const& heat_capacity_solid_,
+        Parameter<double> const& heat_capacity_fluid_,
+        Parameter<double> const& heat_capacity_gas_,
+        Parameter<double> const& density_solid_,
+        Parameter<double> const& density_fluid_,
+        Parameter<double> const& density_gas_,
+        std::vector<std::unique_ptr<BHE::BHEAbstract>>&& vec_BHEs_)
+        : thermal_conductivity_solid(thermal_conductivity_solid_),
+          thermal_conductivity_fluid(thermal_conductivity_fluid_),
+          thermal_conductivity_gas(thermal_conductivity_gas_),
+          heat_capacity_solid(heat_capacity_solid_),
+          heat_capacity_fluid(heat_capacity_fluid_),
+          heat_capacity_gas(heat_capacity_gas_),
+          density_solid(density_solid_),
+          density_fluid(density_fluid_),
+          density_gas(density_gas_),
+          _vec_BHE_property(std::move(vec_BHEs_))
+    {
+    }
+
+    HeatTransportBHEProcessData(HeatTransportBHEProcessData&& other) = default;
+
+    //! Copies are forbidden.
+    HeatTransportBHEProcessData(HeatTransportBHEProcessData const&) = delete;
+
+    //! Assignments are not needed.
+    void operator=(HeatTransportBHEProcessData const&) = delete;
+
+    //! Assignments are not needed.
+    void operator=(HeatTransportBHEProcessData&&) = delete;
+
+    // ! thermal conductivity values for the three phases
+    Parameter<double> const& thermal_conductivity_solid;
+    Parameter<double> const& thermal_conductivity_fluid;
+    Parameter<double> const& thermal_conductivity_gas;
+
+    // ! heat capacity values for the three phases
+    Parameter<double> const& heat_capacity_solid;
+    Parameter<double> const& heat_capacity_fluid;
+    Parameter<double> const& heat_capacity_gas;
+
+    // ! density values for the three phases
+    Parameter<double> const& density_solid;
+    Parameter<double> const& density_fluid;
+    Parameter<double> const& density_gas;
+
+    MeshLib::PropertyVector<int> const* _mesh_prop_materialIDs = nullptr;
+    std::vector<std::size_t> _map_materialID_to_BHE_ID;
+
+    std::vector<std::unique_ptr<BHE::BHEAbstract>> _vec_BHE_property;
+
+    // a table of connected BHE IDs for each element
+    std::vector<std::vector<std::size_t>> _vec_ele_connected_BHE_IDs;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/CreateLocalAssemblers.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/CreateLocalAssemblers.h
new file mode 100644
index 0000000000000000000000000000000000000000..03f6444c70de73ac825591fc99951107afb98d8c
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/CreateLocalAssemblers.h
@@ -0,0 +1,86 @@
+/**
+ * \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 <vector>
+
+#include <logog/include/logog.hpp>
+
+#include "LocalDataInitializer.h"
+#include "NumLib/DOF/LocalToGlobalIndexMap.h"
+#include "ProcessLib/HeatTransportBHE/BHE/BHEAbstract.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+namespace detail
+{
+template <
+    int GlobalDim,
+    template <typename, typename, int> class LocalAssemblerSoilImplementation,
+    template <typename, typename, int> class LocalAssemblerBHEImplementation,
+    typename LocalAssemblerInterface, typename... ExtraCtorArgs>
+void createLocalAssemblers(
+    NumLib::LocalToGlobalIndexMap const& dof_table,
+    std::vector<MeshLib::Element*> const& mesh_elements,
+    std::vector<std::unique_ptr<LocalAssemblerInterface>>& local_assemblers,
+    const std::vector<std::vector<std::size_t>>& vec_ele_connected_BHE_IDs,
+    const std::vector<std::unique_ptr<BHE::BHEAbstract>>& vec_BHE_property,
+    ExtraCtorArgs&&... extra_ctor_args)
+{
+    // Shape matrices initializer
+    using LocalDataInitializer = LocalDataInitializer<
+        LocalAssemblerInterface, LocalAssemblerSoilImplementation,
+        LocalAssemblerBHEImplementation, 3, ExtraCtorArgs...>;
+
+    DBUG("Create local assemblers for the HeatTransportBHE process.");
+    // Populate the vector of local assemblers.
+    local_assemblers.resize(mesh_elements.size());
+
+    LocalDataInitializer initializer(dof_table);
+
+    DBUG("Calling local assembler builder for all mesh elements.");
+    GlobalExecutor::transformDereferenced(
+        initializer, mesh_elements, local_assemblers, vec_ele_connected_BHE_IDs,
+        vec_BHE_property, std::forward<ExtraCtorArgs>(extra_ctor_args)...);
+}
+}  // namespace detail
+
+/*! Creates local assemblers for each element of the given \c mesh.
+ *
+ * \tparam LocalAssemblerImplementation the individual local assembler type
+ * \tparam LocalAssemblerInterface the general local assembler interface
+ * \tparam ExtraCtorArgs types of additional constructor arguments.
+ *         Those arguments will be passed to the constructor of
+ *         \c LocalAssemblerImplementation.
+ *
+ * The first two template parameters cannot be deduced from the arguments.
+ * Therefore they always have to be provided manually.
+ */
+template <
+    int GlobalDim,
+    template <typename, typename, int> class LocalAssemblerSoilImplementation,
+    template <typename, typename, int> class LocalAssemblerBHEImplementation,
+    typename LocalAssemblerInterface, typename... ExtraCtorArgs>
+void createLocalAssemblers(
+    std::vector<MeshLib::Element*> const& mesh_elements,
+    NumLib::LocalToGlobalIndexMap const& dof_table,
+    std::vector<std::unique_ptr<LocalAssemblerInterface>>& local_assemblers,
+    ExtraCtorArgs&&... extra_ctor_args)
+{
+    DBUG("Create local assemblers for the HeatTransportBHE process.");
+
+    detail::createLocalAssemblers<GlobalDim, LocalAssemblerSoilImplementation,
+                                  LocalAssemblerBHEImplementation>(
+        dof_table, mesh_elements, local_assemblers,
+        std::forward<ExtraCtorArgs>(extra_ctor_args)...);
+}
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerBHE.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerBHE.h
new file mode 100644
index 0000000000000000000000000000000000000000..f7ea07d91062fd54fbce295eec09fb9b6a299b76
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerBHE.h
@@ -0,0 +1,106 @@
+/**
+ * \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 <vector>
+
+#include "NumLib/Fem/ShapeMatrixPolicy.h"
+
+#include "ProcessLib/HeatTransportBHE/HeatTransportBHEProcessData.h"
+
+#include "HeatTransportBHEProcessAssemblerInterface.h"
+#include "IntegrationPointDataBHE.h"
+#include "SecondaryData.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+template <typename ShapeFunction, typename IntegrationMethod, int BHE_Dim>
+class HeatTransportBHELocalAssemblerBHE
+    : public HeatTransportBHELocalAssemblerInterface
+{
+public:
+    using ShapeMatricesType = ShapeMatrixPolicyType<ShapeFunction, BHE_Dim>;
+    using ShapeMatrices = typename ShapeMatricesType::ShapeMatrices;
+    // Using dynamic size, because the number of unknowns in the BHE is runtime
+    // value.
+    using BheLocalMatrixType =
+        typename ShapeMatricesType::template MatrixType<Eigen::Dynamic,
+                                                        Eigen::Dynamic>;
+    HeatTransportBHELocalAssemblerBHE(
+        HeatTransportBHELocalAssemblerBHE const&) = delete;
+    HeatTransportBHELocalAssemblerBHE(HeatTransportBHELocalAssemblerBHE&&) =
+        delete;
+
+    HeatTransportBHELocalAssemblerBHE(
+        MeshLib::Element const& e,
+        std::size_t const local_matrix_size,
+        std::vector<unsigned> const& dofIndex_to_localIndex,
+        bool const is_axially_symmetric,
+        unsigned const integration_order,
+        HeatTransportBHEProcessData& process_data);
+
+    void assemble(double const /*t*/, std::vector<double> const& /*local_x*/,
+                  std::vector<double>& /*local_M_data*/,
+                  std::vector<double>& /*local_K_data*/,
+                  std::vector<double>& /*local_b_data*/) override;
+
+    void assembleWithJacobian(double const /*t*/,
+                              Eigen::VectorXd const& /*local_u*/,
+                              Eigen::VectorXd& /*local_b*/,
+                              Eigen::MatrixXd& /*local_J*/) override
+    {
+        OGS_FATAL(
+            "HeatTransportBHELocalAssembler: assembly with jacobian is not "
+            "implemented.");
+    }
+
+    void preTimestepConcrete(std::vector<double> const& /*local_x*/,
+                             double const /*t*/,
+                             double const /*delta_t*/) override
+    {
+    }
+
+    void postTimestepConcrete(std::vector<double> const& /*local_x*/) override;
+
+    Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix(
+        const unsigned integration_point) const override
+    {
+        auto const& N = _secondary_data.N[integration_point];
+
+        // assumes N is stored contiguously in memory
+        return Eigen::Map<const Eigen::RowVectorXd>(N.data(), N.size());
+    }
+
+private:
+    HeatTransportBHEProcessData& _process_data;
+
+    std::vector<
+        IntegrationPointDataBHE<ShapeMatricesType>,
+        Eigen::aligned_allocator<IntegrationPointDataBHE<ShapeMatricesType>>>
+        _ip_data;
+
+    IntegrationMethod _integration_method;
+
+    std::size_t const element_id;
+
+    SecondaryData<typename ShapeMatrices::ShapeType> _secondary_data;
+
+    BheLocalMatrixType _R_matrix;
+
+    BheLocalMatrixType _R_s_matrix;
+
+    BheLocalMatrixType _R_pi_s_matrix;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
+
+#include "HeatTransportBHELocalAssemblerBHE_impl.h"
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerBHE_impl.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerBHE_impl.h
new file mode 100644
index 0000000000000000000000000000000000000000..5285b4b2785a62419eda825da18d9d556ea7fd19
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerBHE_impl.h
@@ -0,0 +1,226 @@
+/**
+ * \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 <Eigen/Eigen>
+#include "HeatTransportBHELocalAssemblerBHE.h"
+#include "MathLib/LinAlg/Eigen/EigenMapTools.h"
+#include "ProcessLib/HeatTransportBHE/LocalAssemblers/IntegrationPointDataBHE.h"
+#include "ProcessLib/Utils/InitShapeMatrices.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+using namespace BHE;
+
+template <typename ShapeFunction, typename IntegrationMethod, int BHE_Dim>
+HeatTransportBHELocalAssemblerBHE<ShapeFunction, IntegrationMethod, BHE_Dim>::
+    HeatTransportBHELocalAssemblerBHE(
+        MeshLib::Element const& e,
+        std::size_t const /*local_matrix_size*/,
+        std::vector<unsigned> const& dofIndex_to_localIndex,
+        bool const is_axially_symmetric,
+        unsigned const integration_order,
+        HeatTransportBHEProcessData& process_data)
+    : HeatTransportBHELocalAssemblerInterface(
+          ShapeFunction::NPOINTS * BHE_Dim,  // no intersection
+          dofIndex_to_localIndex),
+      _process_data(process_data),
+      _integration_method(integration_order),
+      element_id(e.getID())
+{
+    // need to make sure that the BHE elements are one-dimensional
+    assert(e.getDimension() == 1);
+
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+
+    _ip_data.reserve(n_integration_points);
+    _secondary_data.N.resize(n_integration_points);
+
+    auto const shape_matrices =
+        initShapeMatrices<ShapeFunction, ShapeMatricesType, IntegrationMethod,
+                          BHE_Dim>(e, is_axially_symmetric,
+                                   _integration_method);
+
+    auto mat_id = (*_process_data._mesh_prop_materialIDs)[e.getID()];
+    auto BHE_id = _process_data._map_materialID_to_BHE_ID[mat_id];
+
+    SpatialPosition x_position;
+    x_position.setElementID(element_id);
+
+    // ip data initialization
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        x_position.setIntegrationPoint(ip);
+
+        IntegrationPointDataBHE<ShapeMatricesType> int_Point_Data_BHE(
+            *(_process_data._vec_BHE_property[BHE_id]));
+        _ip_data.emplace_back(int_Point_Data_BHE);
+        auto const& sm = shape_matrices[ip];
+        auto& ip_data = _ip_data[ip];
+        ip_data.integration_weight =
+            _integration_method.getWeightedPoint(ip).getWeight() *
+            sm.integralMeasure * sm.detJ;
+        ip_data.N = sm.N;
+        ip_data.dNdx = sm.dNdx;
+
+        _secondary_data.N[ip] = sm.N;
+    }
+
+    const int BHE_n_unknowns = _ip_data[0]._bhe_instance.getNumUnknowns();
+    const int NumRMatrixRows = ShapeFunction::NPOINTS * BHE_n_unknowns;
+    _R_matrix.setZero(NumRMatrixRows, NumRMatrixRows);
+    _R_pi_s_matrix.setZero(NumRMatrixRows, ShapeFunction::NPOINTS);
+    _R_s_matrix.setZero(ShapeFunction::NPOINTS, ShapeFunction::NPOINTS);
+    // formulate the local BHE R matrix
+    Eigen::MatrixXd matBHE_loc_R =
+        Eigen::MatrixXd::Zero(ShapeFunction::NPOINTS, ShapeFunction::NPOINTS);
+    for (int idx_bhe_unknowns = 0; idx_bhe_unknowns < BHE_n_unknowns;
+         idx_bhe_unknowns++)
+    {
+        matBHE_loc_R.setZero();
+        // Loop over Gauss points
+        for (unsigned ip = 0; ip < n_integration_points; ip++)
+        {
+            x_position.setIntegrationPoint(ip);
+
+            auto const& N = _ip_data[ip].N;
+            auto const& w = _ip_data[ip].integration_weight;
+
+            // get coefficient of R matrix for corresponding BHE.
+            auto R_coeff = _process_data._vec_BHE_property[BHE_id]
+                               ->getBoundaryHeatExchangeCoeff(idx_bhe_unknowns);
+
+            // calculate mass matrix for current unknown
+            matBHE_loc_R += N.transpose() * R_coeff * N * w;
+        }  // end of loop over integration point
+
+        // The following assembly action is according to Diersch (2013) FEFLOW
+        // book please refer to M.127 and M.128 on page 955 and 956
+        _process_data._vec_BHE_property[BHE_id]->setRMatrices(
+            idx_bhe_unknowns, ShapeFunction::NPOINTS, matBHE_loc_R, _R_matrix,
+            _R_pi_s_matrix, _R_s_matrix);
+
+    }  // end of loop over BHE unknowns
+
+    // debugging
+    // std::string sep = "\n----------------------------------------\n";
+    // Eigen::IOFormat CleanFmt(4, 0, ", ", "\n", "[", "]");
+    // std::cout << "_R_matrix: \n" << sep;
+    // std::cout << _R_matrix.format(CleanFmt) << sep;
+    // std::cout << "_R_s_matrix: \n" << sep;
+    // std::cout << _R_s_matrix.format(CleanFmt) << sep;
+    // std::cout << "_R_pi_s_matrix: \n" << sep;
+    // std::cout << _R_pi_s_matrix.format(CleanFmt) << sep;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod, int BHE_Dim>
+void HeatTransportBHELocalAssemblerBHE<ShapeFunction, IntegrationMethod,
+                                       BHE_Dim>::
+    assemble(
+        double const /*t*/, std::vector<double> const& local_x,
+        std::vector<double>& local_M_data, std::vector<double>& local_K_data,
+        std::vector<double>& /*local_b_data*/)  // local b vector is not touched
+{
+    auto const local_matrix_size = local_x.size();
+    const int BHE_n_unknowns = _ip_data[0]._bhe_instance.getNumUnknowns();
+    // plus one because the soil temperature is included in local_x
+    assert(local_matrix_size == ShapeFunction::NPOINTS * (BHE_n_unknowns + 1));
+
+    auto local_M = MathLib::createZeroedMatrix<BheLocalMatrixType>(
+        local_M_data, local_matrix_size, local_matrix_size);
+    auto local_K = MathLib::createZeroedMatrix<BheLocalMatrixType>(
+        local_K_data, local_matrix_size, local_matrix_size);
+
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+
+    SpatialPosition x_position;
+    x_position.setElementID(element_id);
+
+    int shift = 0;
+    static const int local_BHE_matrix_size =
+        ShapeFunction::NPOINTS * BHE_n_unknowns;
+    static const int shift_start = ShapeFunction::NPOINTS;
+
+    // the mass and conductance matrix terms
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        x_position.setIntegrationPoint(ip);
+        auto& ip_data = _ip_data[ip];
+
+        auto const& w = ip_data.integration_weight;
+        auto const& N = ip_data.N;
+        auto const& dNdx = ip_data.dNdx;
+
+        // looping over all unknowns.
+        for (int idx_bhe_unknowns = 0; idx_bhe_unknowns < BHE_n_unknowns;
+             idx_bhe_unknowns++)
+        {
+            // get coefficient of mass from corresponding BHE.
+            auto& mass_coeff = ip_data._vec_mass_coefficients[idx_bhe_unknowns];
+            auto& laplace_mat = ip_data._vec_mat_Laplace[idx_bhe_unknowns];
+            auto& advection_vec =
+                ip_data._vec_Advection_vectors[idx_bhe_unknowns];
+
+            // calculate shift.
+            shift = shift_start + ShapeFunction::NPOINTS * idx_bhe_unknowns;
+            // local M
+            local_M
+                .template block<ShapeFunction::NPOINTS, ShapeFunction::NPOINTS>(
+                    shift, shift)
+                .noalias() += N.transpose() * mass_coeff * N * w;
+
+            // local K
+            // laplace part
+            local_K
+                .block(shift, shift, ShapeFunction::NPOINTS,
+                       ShapeFunction::NPOINTS)
+                .noalias() += dNdx.transpose() * laplace_mat * dNdx * w;
+            // advection part
+            local_K
+                .block(shift, shift, ShapeFunction::NPOINTS,
+                       ShapeFunction::NPOINTS)
+                .noalias() +=
+                N.transpose() * advection_vec.transpose() * dNdx * w;
+        }
+    }
+
+    // add the R matrix to local_K
+    local_K.block(shift_start, shift_start, local_BHE_matrix_size,
+                  local_BHE_matrix_size) += _R_matrix;
+
+    // add the R_pi_s matrix to local_K
+    local_K.block(shift_start, 0, local_BHE_matrix_size, shift_start) +=
+        _R_pi_s_matrix;
+    local_K.block(0, shift_start, shift_start, local_BHE_matrix_size) +=
+        _R_pi_s_matrix.transpose();
+
+    // add the R_s matrix to local_K
+    local_K.block(0, 0, shift_start, shift_start) += 2.0 * _R_s_matrix;
+
+    // debugging
+    // std::string sep =
+    //     "\n----------------------------------------\n";
+    // Eigen::IOFormat CleanFmt(4, 0, ", ", "\n", "[", "]");
+    // std::cout << local_K.format(CleanFmt) << sep;
+    // std::cout << local_M.format(CleanFmt) << sep;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod, int BHE_Dim>
+void HeatTransportBHELocalAssemblerBHE<
+    ShapeFunction, IntegrationMethod,
+    BHE_Dim>::postTimestepConcrete(std::vector<double> const& /*local_x*/)
+{
+}
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerSoil.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerSoil.h
new file mode 100644
index 0000000000000000000000000000000000000000..3ee83392e2c0674c856465e1fdbf1e399875bd22
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerSoil.h
@@ -0,0 +1,113 @@
+/**
+ * \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 <vector>
+
+#include "NumLib/Fem/ShapeMatrixPolicy.h"
+#include "ProcessLib/Deformation/BMatrixPolicy.h"
+#include "ProcessLib/Deformation/LinearBMatrix.h"
+#include "ProcessLib/Utils/InitShapeMatrices.h"
+
+#include "ProcessLib/HeatTransportBHE/HeatTransportBHEProcessData.h"
+
+#include "HeatTransportBHEProcessAssemblerInterface.h"
+#include "IntegrationPointDataSoil.h"
+#include "SecondaryData.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+const unsigned NUM_NODAL_DOF_SOIL = 1;
+
+template <typename ShapeFunction, typename IntegrationMethod, int GlobalDim>
+class HeatTransportBHELocalAssemblerSoil
+    : public HeatTransportBHELocalAssemblerInterface
+{
+public:
+    using ShapeMatricesType = ShapeMatrixPolicyType<ShapeFunction, GlobalDim>;
+    using NodalMatrixType = typename ShapeMatricesType::NodalMatrixType;
+    using NodalVectorType = typename ShapeMatricesType::NodalVectorType;
+    using ShapeMatrices = typename ShapeMatricesType::ShapeMatrices;
+
+    HeatTransportBHELocalAssemblerSoil(
+        HeatTransportBHELocalAssemblerSoil const&) = delete;
+    HeatTransportBHELocalAssemblerSoil(HeatTransportBHELocalAssemblerSoil&&) =
+        delete;
+
+    HeatTransportBHELocalAssemblerSoil(
+        MeshLib::Element const& e,
+        std::size_t const local_matrix_size,
+        std::vector<unsigned> const& dofIndex_to_localIndex,
+        bool is_axially_symmetric,
+        unsigned const integration_order,
+        HeatTransportBHEProcessData& process_data);
+
+    void assemble(double const /*t*/, std::vector<double> const& /*local_x*/,
+                  std::vector<double>& /*local_M_data*/,
+                  std::vector<double>& /*local_K_data*/,
+                  std::vector<double>& /*local_b_data*/) override;
+
+    void assembleWithJacobian(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
+    {
+        OGS_FATAL(
+            "HeatTransportBHELocalAssemblerMatrix: assembly with jacobian is "
+            "not "
+            "implemented.");
+    }
+
+    void preTimestepConcrete(std::vector<double> const& /*local_x*/,
+                             double const /*t*/,
+                             double const /*delta_t*/) override
+    {
+    }
+
+    void postTimestepConcrete(std::vector<double> const& /*local_x*/) override;
+
+    Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix(
+        const unsigned integration_point) const override
+    {
+        auto const& N = _secondary_data.N[integration_point];
+
+        // assumes N is stored contiguously in memory
+        return Eigen::Map<const Eigen::RowVectorXd>(N.data(), N.size());
+    }
+
+private:
+    HeatTransportBHEProcessData& _process_data;
+
+    std::vector<
+        IntegrationPointDataSoil<ShapeMatricesType>,
+        Eigen::aligned_allocator<IntegrationPointDataSoil<ShapeMatricesType>>>
+        _ip_data;
+
+    IntegrationMethod const _integration_method;
+
+    std::vector<ShapeMatrices, Eigen::aligned_allocator<ShapeMatrices>>
+        _shape_matrices;
+
+    std::size_t const element_id;
+
+    bool const _is_axially_symmetric;
+
+    SecondaryData<typename ShapeMatrices::ShapeType> _secondary_data;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
+
+#include "HeatTransportBHELocalAssemblerSoil_impl.h"
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerSoil_impl.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerSoil_impl.h
new file mode 100644
index 0000000000000000000000000000000000000000..3cf4209eefe010d583da12f566ae84e8f0c5f8a5
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHELocalAssemblerSoil_impl.h
@@ -0,0 +1,137 @@
+/**
+ * \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 "HeatTransportBHELocalAssemblerSoil.h"
+
+#include <valarray>
+#include <vector>
+
+#include <Eigen/Eigen>
+
+#include "MathLib/LinAlg/Eigen/EigenMapTools.h"
+
+#include "NumLib/Fem/ShapeMatrixPolicy.h"
+#include "ProcessLib/Utils/InitShapeMatrices.h"
+
+#include "ProcessLib/HeatTransportBHE/HeatTransportBHEProcessData.h"
+
+// #include "IntegrationPointDataMatrix.h"
+#include "HeatTransportBHEProcessAssemblerInterface.h"
+#include "SecondaryData.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+template <typename ShapeFunction, typename IntegrationMethod, int GlobalDim>
+HeatTransportBHELocalAssemblerSoil<ShapeFunction,
+                                   IntegrationMethod,
+                                   GlobalDim>::
+    HeatTransportBHELocalAssemblerSoil(
+        MeshLib::Element const& e,
+        std::size_t const /*local_matrix_size*/,
+        std::vector<unsigned> const& dofIndex_to_localIndex,
+        bool const is_axially_symmetric,
+        unsigned const integration_order,
+        HeatTransportBHEProcessData& process_data)
+    : HeatTransportBHELocalAssemblerInterface(
+          ShapeFunction::NPOINTS * GlobalDim, dofIndex_to_localIndex),
+      _process_data(process_data),
+      _integration_method(integration_order),
+      element_id(e.getID()),
+      _is_axially_symmetric(is_axially_symmetric)
+{
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+
+    _ip_data.reserve(n_integration_points);
+    _secondary_data.N.resize(n_integration_points);
+
+    _shape_matrices = initShapeMatrices<ShapeFunction,
+                                        ShapeMatricesType,
+                                        IntegrationMethod,
+                                        GlobalDim>(
+        e, is_axially_symmetric, _integration_method);
+}
+
+template <typename ShapeFunction, typename IntegrationMethod, int GlobalDim>
+void HeatTransportBHELocalAssemblerSoil<
+    ShapeFunction,
+    IntegrationMethod,
+    GlobalDim>::assemble(double const t,
+                         std::vector<double> const& local_x,
+                         std::vector<double>& local_M_data,
+                         std::vector<double>& local_K_data,
+                         std::vector<double>& /*local_b_data*/)
+{
+    auto const local_matrix_size = local_x.size();
+
+    assert(local_matrix_size == ShapeFunction::NPOINTS * NUM_NODAL_DOF_SOIL);
+
+    auto local_M = MathLib::createZeroedMatrix<NodalMatrixType>(
+        local_M_data, local_matrix_size, local_matrix_size);
+    auto local_K = MathLib::createZeroedMatrix<NodalMatrixType>(
+        local_K_data, local_matrix_size, local_matrix_size);
+
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+
+    SpatialPosition pos;
+    pos.setElementID(element_id);
+
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        pos.setIntegrationPoint(ip);
+        auto const& sm = _shape_matrices[ip];
+        auto const& wp = _integration_method.getWeightedPoint(ip);
+
+        // auto const k_f = _process_data.thermal_conductivity_fluid(t, pos)[0];
+        // auto const k_g = _process_data.thermal_conductivity_gas(t, pos)[0];
+        auto const k_s = _process_data.thermal_conductivity_solid(t, pos)[0];
+
+        // auto const heat_capacity_f = _process_data.heat_capacity_fluid(t,
+        // pos)[0]; auto const heat_capacity_g =
+        // _process_data.heat_capacity_gas(t, pos)[0];
+        auto const heat_capacity_s =
+            _process_data.heat_capacity_solid(t, pos)[0];
+
+        // auto const density_f = _process_data.density_fluid(t, pos)[0];
+        // auto const density_g = _process_data.density_gas(t, pos)[0];
+        auto const density_s = _process_data.density_solid(t, pos)[0];
+
+        // for now only using the solid phase parameters
+
+        // assemble Conductance matrix
+        local_K.noalias() += sm.dNdx.transpose() * k_s * sm.dNdx * sm.detJ *
+                             wp.getWeight() * sm.integralMeasure;
+
+        // assemble Mass matrix
+        local_M.noalias() += sm.N.transpose() * density_s * heat_capacity_s *
+                             sm.N * sm.detJ * wp.getWeight() *
+                             sm.integralMeasure;
+    }
+
+    // debugging
+    // std::string sep = "\n----------------------------------------\n";
+    // Eigen::IOFormat CleanFmt(4, 0, ", ", "\n", "[", "]");
+    // std::cout << local_K.format(CleanFmt) << sep;
+    // std::cout << local_M.format(CleanFmt) << sep;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod, int GlobalDim>
+void HeatTransportBHELocalAssemblerSoil<
+    ShapeFunction,
+    IntegrationMethod,
+    GlobalDim>::postTimestepConcrete(std::vector<double> const& /*local_x*/)
+{
+}
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHEProcessAssemblerInterface.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHEProcessAssemblerInterface.h
new file mode 100644
index 0000000000000000000000000000000000000000..2af5e87a70b990a0cb760e2a32c51aa3055a188b
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/HeatTransportBHEProcessAssemblerInterface.h
@@ -0,0 +1,64 @@
+/**
+ * \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 <utility>
+#include <vector>
+
+#include "BaseLib/Error.h"
+
+#include "NumLib/Extrapolation/ExtrapolatableElement.h"
+#include "ProcessLib/LocalAssemblerInterface.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+class HeatTransportBHELocalAssemblerInterface
+    : public ProcessLib::LocalAssemblerInterface,
+      public NumLib::ExtrapolatableElement
+{
+public:
+    HeatTransportBHELocalAssemblerInterface() : _dofIndex_to_localIndex{} {}
+    HeatTransportBHELocalAssemblerInterface(
+        std::size_t n_local_size, std::vector<unsigned> dofIndex_to_localIndex)
+        : _dofIndex_to_localIndex(std::move(dofIndex_to_localIndex))
+    {
+    }
+
+    void assembleWithJacobian(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
+    {
+        OGS_FATAL(
+            "HeatTransportBHELocalAssemblerInterface::assembleWithJacobian() "
+            "is not implemented");
+    }
+
+    virtual void assembleWithJacobian(double const /*t*/,
+                                      Eigen::VectorXd const& /*local_T*/,
+                                      Eigen::VectorXd& /*local_b*/,
+                                      Eigen::MatrixXd& /*local_A*/)
+    {
+        OGS_FATAL(
+            "HeatTransportBHELocalAssemblerInterface::assembleWithJacobian() "
+            "is not implemented");
+    }
+
+private:
+    std::vector<unsigned> const _dofIndex_to_localIndex;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/IntegrationPointDataBHE.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/IntegrationPointDataBHE.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f09811c48612ad01e62dd6ce2095f579f723931
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/IntegrationPointDataBHE.h
@@ -0,0 +1,74 @@
+/**
+ * \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 <Eigen/Eigen>
+
+#include "ProcessLib/HeatTransportBHE/BHE/BHEAbstract.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+using namespace BHE;
+
+template <typename ShapeMatrixType>
+struct IntegrationPointDataBHE final
+{
+    explicit IntegrationPointDataBHE(BHEAbstract& bhe_instance)
+        : _bhe_instance(bhe_instance)
+    {
+        // depending on the type of BHE
+        const int unknown_size = _bhe_instance.getNumUnknowns();
+        // initialization
+        _vec_mass_coefficients.resize(unknown_size);
+        for (int i = 0; i < unknown_size; i++)
+        {
+            Eigen::MatrixXd mat_laplace(3, 3);
+            mat_laplace.setZero();
+            _vec_mat_Laplace.push_back(mat_laplace);
+            Eigen::VectorXd vec_advection(3);
+            vec_advection.setZero();
+            _vec_Advection_vectors.push_back(vec_advection);
+        }
+
+        // set parameter values
+        for (int j = 0; j < unknown_size; j++)
+        {
+            // mass matrix coefficients
+            _vec_mass_coefficients[j] = _bhe_instance.getMassCoeff(j);
+            // laplace matrix
+            _bhe_instance.getLaplaceMatrix(j, _vec_mat_Laplace[j]);
+            // advection vector
+            _bhe_instance.getAdvectionVector(j, _vec_Advection_vectors[j]);
+        }
+    }
+
+    BHEAbstract& _bhe_instance;
+
+    typename ShapeMatrixType::NodalRowVectorType N;
+    typename ShapeMatrixType::GlobalDimNodalMatrixType dNdx;
+    double integration_weight;
+
+    // mass coefficients, length depending on the type of BHE
+    std::vector<double> _vec_mass_coefficients;
+
+    // Laplace matrices
+    std::vector<Eigen::MatrixXd> _vec_mat_Laplace;
+
+    // Advection vectors
+    std::vector<Eigen::VectorXd> _vec_Advection_vectors;
+
+    void pushBackState() {}
+
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/IntegrationPointDataSoil.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/IntegrationPointDataSoil.h
new file mode 100644
index 0000000000000000000000000000000000000000..97abc53d0dc8d7a9091c545c44147f8aaa9db837
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/IntegrationPointDataSoil.h
@@ -0,0 +1,35 @@
+/**
+ * \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 <memory>
+#include <vector>
+
+#include "MaterialLib/SolidModels/MechanicsBase.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+template <typename ShapeMatrixType>
+struct IntegrationPointDataSoil final
+{
+    explicit IntegrationPointDataSoil() {}
+
+    typename ShapeMatrixType::NodalRowVectorType N;
+    typename ShapeMatrixType::GlobalDimNodalMatrixType dNdx;
+    double integration_weight;
+
+    void pushBackState() {}
+
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/LocalDataInitializer.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/LocalDataInitializer.h
new file mode 100644
index 0000000000000000000000000000000000000000..f395d3551890211b73d693412b5a2fb014f50709
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/LocalDataInitializer.h
@@ -0,0 +1,378 @@
+/**
+ * \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 <functional>
+#include <memory>
+#include <type_traits>
+#include <typeindex>
+#include <typeinfo>
+#include <unordered_map>
+
+#include "MeshLib/Elements/Elements.h"
+#include "NumLib/DOF/LocalToGlobalIndexMap.h"
+#include "NumLib/Fem/Integration/GaussLegendreIntegrationPolicy.h"
+#include "ProcessLib/HeatTransportBHE/BHE/BHEAbstract.h"
+
+#ifndef OGS_MAX_ELEMENT_DIM
+static_assert(false, "The macro OGS_MAX_ELEMENT_DIM is undefined.");
+#endif
+
+#ifndef OGS_MAX_ELEMENT_ORDER
+static_assert(false, "The macro OGS_MAX_ELEMENT_ORDER is undefined.");
+#endif
+
+// The following macros decide which element types will be compiled, i.e.
+// which element types will be available for use in simulations.
+
+#ifdef OGS_ENABLE_ELEMENT_SIMPLEX
+#define ENABLED_ELEMENT_TYPE_SIMPLEX 1u
+#else
+#define ENABLED_ELEMENT_TYPE_SIMPLEX 0u
+#endif
+
+#ifdef OGS_ENABLE_ELEMENT_CUBOID
+#define ENABLED_ELEMENT_TYPE_CUBOID 1u << 1
+#else
+#define ENABLED_ELEMENT_TYPE_CUBOID 0u
+#endif
+
+#ifdef OGS_ENABLE_ELEMENT_PRISM
+#define ENABLED_ELEMENT_TYPE_PRISM 1u << 2
+#else
+#define ENABLED_ELEMENT_TYPE_PRISM 0u
+#endif
+
+#ifdef OGS_ENABLE_ELEMENT_PYRAMID
+#define ENABLED_ELEMENT_TYPE_PYRAMID 1u << 3
+#else
+#define ENABLED_ELEMENT_TYPE_PYRAMID 0u
+#endif
+
+// Dependent element types.
+// Faces of tets, pyramids and prisms are triangles
+#define ENABLED_ELEMENT_TYPE_TRI                                       \
+    ((ENABLED_ELEMENT_TYPE_SIMPLEX) | (ENABLED_ELEMENT_TYPE_PYRAMID) | \
+     (ENABLED_ELEMENT_TYPE_PRISM))
+// Faces of hexes, pyramids and prisms are quads
+#define ENABLED_ELEMENT_TYPE_QUAD                                     \
+    ((ENABLED_ELEMENT_TYPE_CUBOID) | (ENABLED_ELEMENT_TYPE_PYRAMID) | \
+     (ENABLED_ELEMENT_TYPE_PRISM))
+
+// All enabled element types
+#define OGS_ENABLED_ELEMENTS                                          \
+    ((ENABLED_ELEMENT_TYPE_SIMPLEX) | (ENABLED_ELEMENT_TYPE_CUBOID) | \
+     (ENABLED_ELEMENT_TYPE_PYRAMID) | (ENABLED_ELEMENT_TYPE_PRISM))
+
+// Include only what is needed (Well, the conditions are not sharp).
+#if OGS_ENABLED_ELEMENTS != 0
+#include "NumLib/Fem/ShapeFunction/ShapeLine2.h"
+#include "NumLib/Fem/ShapeFunction/ShapeLine3.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_SIMPLEX) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeTet10.h"
+#include "NumLib/Fem/ShapeFunction/ShapeTet4.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_TRI) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeTri3.h"
+#include "NumLib/Fem/ShapeFunction/ShapeTri6.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_CUBOID) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeHex20.h"
+#include "NumLib/Fem/ShapeFunction/ShapeHex8.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_QUAD) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeQuad4.h"
+#include "NumLib/Fem/ShapeFunction/ShapeQuad8.h"
+#include "NumLib/Fem/ShapeFunction/ShapeQuad9.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PRISM) != 0
+#include "NumLib/Fem/ShapeFunction/ShapePrism15.h"
+#include "NumLib/Fem/ShapeFunction/ShapePrism6.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PYRAMID) != 0
+#include "NumLib/Fem/ShapeFunction/ShapePyra13.h"
+#include "NumLib/Fem/ShapeFunction/ShapePyra5.h"
+#endif
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+/// The LocalDataInitializer is a functor creating a local assembler data with
+/// corresponding to the mesh element type shape functions and calling
+/// initialization of the new local assembler data.
+/// For example for MeshLib::Quad a local assembler data with template argument
+/// NumLib::ShapeQuad4 is created.
+template <typename LocalAssemblerInterface,
+          template <typename, typename, int> class LocalAssemblerDataSoil,
+          template <typename, typename, int> class LocalAssemblerDataBHE,
+          int GlobalDim, typename... ConstructorArgs>
+class LocalDataInitializer final
+{
+public:
+    using LADataIntfPtr = std::unique_ptr<LocalAssemblerInterface>;
+
+    explicit LocalDataInitializer(
+        NumLib::LocalToGlobalIndexMap const& dof_table)
+        : _dof_table(dof_table)
+    {
+        // REMARKS: At the moment, only a 3D mesh (soil) with 1D elements (BHE)
+        // are supported.
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_QUAD) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 1
+        _builder[std::type_index(typeid(MeshLib::Quad))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeQuad4>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_CUBOID) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
+        _builder[std::type_index(typeid(MeshLib::Hex))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeHex8>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_QUAD) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 2
+        _builder[std::type_index(typeid(MeshLib::Quad8))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeQuad8>();
+        _builder[std::type_index(typeid(MeshLib::Quad9))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeQuad9>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_CUBOID) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+        _builder[std::type_index(typeid(MeshLib::Hex20))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeHex20>();
+#endif
+
+        // /// Simplices ////////////////////////////////////////////////
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_TRI) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 1
+        _builder[std::type_index(typeid(MeshLib::Tri))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeTri3>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_SIMPLEX) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
+        _builder[std::type_index(typeid(MeshLib::Tet))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeTet4>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_TRI) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 2
+        _builder[std::type_index(typeid(MeshLib::Tri6))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeTri6>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_SIMPLEX) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+        _builder[std::type_index(typeid(MeshLib::Tet10))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeTet10>();
+#endif
+
+        // /// Prisms ////////////////////////////////////////////////////
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PRISM) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
+        _builder[std::type_index(typeid(MeshLib::Prism))] =
+            makeLocalAssemblerBuilder<NumLib::ShapePrism6>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PRISM) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+        _builder[std::type_index(typeid(MeshLib::Prism15))] =
+            makeLocalAssemblerBuilder<NumLib::ShapePrism15>();
+#endif
+
+        // /// Pyramids //////////////////////////////////////////////////
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PYRAMID) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
+        _builder[std::type_index(typeid(MeshLib::Pyramid))] =
+            makeLocalAssemblerBuilder<NumLib::ShapePyra5>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PYRAMID) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+        _builder[std::type_index(typeid(MeshLib::Pyramid13))] =
+            makeLocalAssemblerBuilder<NumLib::ShapePyra13>();
+#endif
+        // /// Lines ///////////////////////////////////
+
+#if OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 1
+        _builder[std::type_index(typeid(MeshLib::Line))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeLine2>();
+#endif
+
+#if OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+        _builder[std::type_index(typeid(MeshLib::Line3))] =
+            makeLocalAssemblerBuilder<NumLib::ShapeLine3>();
+#endif
+    }
+
+    /// Sets the provided \c data_ptr to the newly created local assembler data.
+    ///
+    /// \attention
+    /// The index \c id is not necessarily the mesh item's id. Especially when
+    /// having multiple meshes it will differ from the latter.
+    void operator()(
+        std::size_t const id,
+        MeshLib::Element const& mesh_item,
+        LADataIntfPtr& data_ptr,
+        const std::vector<
+            std::vector<std::size_t>>& /*vec_ele_connected_BHE_IDs*/,
+        const std::vector<
+            std::unique_ptr<BHE::BHEAbstract>>& /*vec_BHE_property*/,
+        ConstructorArgs&&... args) const
+    {
+        auto const type_idx = std::type_index(typeid(mesh_item));
+        auto const it = _builder.find(type_idx);
+
+        if (it == _builder.end())
+        {
+            OGS_FATAL(
+                "You are trying to build a local assembler for an unknown mesh "
+                "element type (%s)."
+                " Maybe you have disabled this mesh element type in your build "
+                "configuration or this process requires higher order elements.",
+                type_idx.name());
+        }
+
+        auto varIDs = _dof_table.getElementVariableIDs(id);
+        auto n_local_dof = _dof_table.getNumberOfElementDOF(id);
+        std::vector<unsigned> dofIndex_to_localIndex;
+
+        if (mesh_item.getDimension() < GlobalDim)
+        {
+            // this is a BHE element
+            dofIndex_to_localIndex.resize(n_local_dof);
+            unsigned dof_id = 0;
+            unsigned local_id = 0;
+            for (auto i : varIDs)
+            {
+                auto const n_global_components =
+                    _dof_table.getNumberOfElementComponents(i);
+                for (unsigned j = 0; j < n_global_components; j++)
+                {
+                    auto const& ms = _dof_table.getMeshSubset(i, j);
+                    auto const mesh_id = ms.getMeshID();
+                    for (unsigned k = 0; k < mesh_item.getNumberOfNodes(); k++)
+                    {
+                        MeshLib::Location l(mesh_id,
+                                            MeshLib::MeshItemType::Node,
+                                            mesh_item.getNodeIndex(k));
+                        auto global_index = _dof_table.getGlobalIndex(l, i, j);
+                        if (global_index != NumLib::MeshComponentMap::nop)
+                        {
+                            dofIndex_to_localIndex[dof_id++] = local_id;
+                        }
+                        local_id++;
+                    }
+                }
+            }
+        }
+
+        data_ptr = it->second(mesh_item, varIDs.size(), n_local_dof,
+                              dofIndex_to_localIndex,
+                              std::forward<ConstructorArgs>(args)...);
+    }
+
+private:
+    using LADataBuilder = std::function<LADataIntfPtr(
+        MeshLib::Element const& e,
+        std::size_t const n_variables,
+        std::size_t const local_matrix_size,
+        std::vector<unsigned> const& dofIndex_to_localIndex,
+        ConstructorArgs&&...)>;
+
+    template <typename ShapeFunction>
+    using IntegrationMethod = typename NumLib::GaussLegendreIntegrationPolicy<
+        typename ShapeFunction::MeshElement>::IntegrationMethod;
+
+    // local assembler builder implementations.
+    template <typename ShapeFunction>
+    using LADataSoil =
+        LocalAssemblerDataSoil<ShapeFunction, IntegrationMethod<ShapeFunction>,
+                               GlobalDim>;
+
+    template <typename ShapeFunction>
+    using LADataBHE =
+        LocalAssemblerDataBHE<ShapeFunction, IntegrationMethod<ShapeFunction>,
+                              GlobalDim>;
+    /// A helper forwarding to the correct version of makeLocalAssemblerBuilder
+    /// depending whether the global dimension is less than the shape function's
+    /// dimension or not.
+    template <typename ShapeFunction>
+    static LADataBuilder makeLocalAssemblerBuilder()
+    {
+        return makeLocalAssemblerBuilder<ShapeFunction>(
+            static_cast<std::integral_constant<
+                bool, (GlobalDim >= ShapeFunction::DIM)>*>(nullptr));
+    }
+
+    /// Mapping of element types to local assembler constructors.
+    std::unordered_map<std::type_index, LADataBuilder> _builder;
+
+    NumLib::LocalToGlobalIndexMap const& _dof_table;
+
+private:
+    /// Generates a function that creates a new LocalAssembler of type
+    /// LAData<ShapeFunction>. Only functions with shape function's dimension
+    /// less or equal to the global dimension are instantiated, e.g.  following
+    /// combinations of shape functions and global dimensions: (Line2, 1),
+    /// (Line2, 2), (Line2, 3), (Hex20, 3) but not (Hex20, 2) or (Hex20, 1).
+    template <typename ShapeFunction>
+    static LADataBuilder makeLocalAssemblerBuilder(std::true_type*)
+    {
+        return [](MeshLib::Element const& e,
+                  std::size_t const /*n_variables*/,
+                  std::size_t const local_matrix_size,
+                  std::vector<unsigned> const& dofIndex_to_localIndex,
+                  ConstructorArgs&&... args) {
+            if (e.getDimension() == GlobalDim)  // soil elements
+            {
+                return LADataIntfPtr{new LADataSoil<ShapeFunction>{
+                    e, local_matrix_size, dofIndex_to_localIndex,
+                    std::forward<ConstructorArgs>(args)...}};
+            }
+
+            return LADataIntfPtr{new LADataBHE<ShapeFunction>{
+                // BHE elements
+                e, local_matrix_size, dofIndex_to_localIndex,
+                std::forward<ConstructorArgs>(args)...}};
+        };
+    }
+
+    /// Returns nullptr for shape functions whose dimensions are less than the
+    /// global dimension.
+    template <typename ShapeFunction>
+    static LADataBuilder makeLocalAssemblerBuilder(std::false_type*)
+    {
+        return nullptr;
+    }
+};  // namespace HeatTransportBHE
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib
+
+#undef ENABLED_ELEMENT_TYPE_SIMPLEX
+#undef ENABLED_ELEMENT_TYPE_CUBOID
+#undef ENABLED_ELEMENT_TYPE_PYRAMID
+#undef ENABLED_ELEMENT_TYPE_PRISM
+#undef ENABLED_ELEMENT_TYPE_TRI
+#undef ENABLED_ELEMENT_TYPE_QUAD
+#undef OGS_ENABLED_ELEMENTS
diff --git a/ProcessLib/HeatTransportBHE/LocalAssemblers/SecondaryData.h b/ProcessLib/HeatTransportBHE/LocalAssemblers/SecondaryData.h
new file mode 100644
index 0000000000000000000000000000000000000000..4a7484928e3e4ead1059862392fe1be081d3d89f
--- /dev/null
+++ b/ProcessLib/HeatTransportBHE/LocalAssemblers/SecondaryData.h
@@ -0,0 +1,30 @@
+/**
+ * \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 <vector>
+
+#include <Eigen/Eigen>
+
+#include "NumLib/Fem/CoordinatesMapping/ShapeMatrices.h"
+
+namespace ProcessLib
+{
+namespace HeatTransportBHE
+{
+/// Used by for extrapolation of the integration point values. It is ordered
+/// (and stored) by integration points.
+template <typename ShapeMatrixType>
+struct SecondaryData
+{
+    std::vector<ShapeMatrixType, Eigen::aligned_allocator<ShapeMatrixType>> N;
+};
+}  // namespace HeatTransportBHE
+}  // namespace ProcessLib