diff --git a/dumux/assembly/boxlocalassembler.hh b/dumux/assembly/boxlocalassembler.hh
index add56b133acbadefd1c7584b2ebe0870d8ccc091..9487428f95ef5381fd0ae9118cd6f782b80f256e 100644
--- a/dumux/assembly/boxlocalassembler.hh
+++ b/dumux/assembly/boxlocalassembler.hh
@@ -31,177 +31,134 @@
 #include <dumux/common/properties.hh>
 #include <dumux/common/parameters.hh>
 #include <dumux/assembly/diffmethod.hh>
+#include <dumux/assembly/fvlocalassemblerbase.hh>
 
 namespace Dumux {
 
 /*!
  * \ingroup Assembly
  * \ingroup BoxDiscretization
- * \brief An assembler for Jacobian and residual contribution per element (box method)
- * \tparam TypeTag the TypeTag
- * \tparam DM the differentiation method to residual compute derivatives
- * \tparam implicit if to use an implicit or explicit time discretization
- */
-template<class TypeTag,
-         DiffMethod DM = DiffMethod::numeric,
-         bool implicit = true>
-class BoxLocalAssembler;
-
-/*!
- * \ingroup Assembly
- * \ingroup BoxDiscretization
- * \brief Box local assembler using numeric differentiation and implicit time discretization
+ * \brief A base class for all local box assemblers
+ * \tparam TypeTag The TypeTag
+ * \tparam Assembler The assembler type
+ * \tparam Implementation The actual implementation
+ * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit)
  */
-template<class TypeTag>
-class BoxLocalAssembler<TypeTag,
-                       DiffMethod::numeric,
-                       /*implicit=*/true>
+template<class TypeTag, class Assembler, class Implementation, bool implicit>
+class BoxLocalAssemblerBase : public FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit>
 {
-    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
-    using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
+    using ParentType = FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit>;
+    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
-    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
     using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
-    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
 
     enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
 
-    static constexpr int dim = GridView::dimension;
-
 public:
 
+    using ParentType::ParentType;
+
+    void bindLocalViews()
+    {
+        ParentType::bindLocalViews();
+        this->elemBcTypes().update(this->problem(), this->element(), this->fvGeometry());
+    }
+
+
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix. The element residual is written into the right hand side.
      */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
+    void assembleJacobianAndResidual(JacobianMatrix& jac, SolutionVector& res, GridVariables& gridVariables)
     {
-        assemble_(assembler, jac, res, element, curSol);
+        this->asImp_().bindLocalViews();
+
+        const auto residual = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables);
+
+        for (const auto& scv : scvs(this->fvGeometry()))
+            res[scv.dofIndex()] += residual[scv.indexInElement()];
+
+        auto applyDirichlet = [&] (const auto& scvI,
+                                   const auto& dirichletValues,
+                                   const auto eqIdx,
+                                   const auto pvIdx)
+        {
+            res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx];
+            for (const auto& scvJ : scvs(this->fvGeometry()))
+            {
+                jac[scvI.dofIndex()][scvJ.dofIndex()][eqIdx] = 0.0;
+                if (scvI.indexInElement() == scvJ.indexInElement())
+                    jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0;
+            }
+        };
 
+        this->asImp_().evalDirichletBoundaries(applyDirichlet);
     }
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix.
      */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
+    void assembleJacobian(JacobianMatrix& jac, GridVariables& gridVariables)
     {
-        auto dummyresidual = curSol;
-        assemble_(assembler, jac, dummyresidual, element, curSol);
-    }
+        this->asImp_().bindLocalViews();
+        this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation
 
-    /*!
-     * \brief Assemble the residual only
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, res, element, curSol);
-    }
+        auto applyDirichlet = [&] (const auto& scvI,
+                                   const auto& dirichletValues,
+                                   const auto eqIdx,
+                                   const auto pvIdx)
+        {
+            for (const auto& scvJ : scvs(this->fvGeometry()))
+            {
+                jac[scvI.dofIndex()][scvJ.dofIndex()][eqIdx] = 0.0;
+                if (scvI.indexInElement() == scvJ.indexInElement())
+                    jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0;
+            }
+        };
 
-    /*!
-     * \brief Computes the epsilon used for numeric differentiation
-     *        for a given value of a primary variable.
-     *
-     * \param priVar The value of the primary variable
-     */
-    static Scalar numericEpsilon(const Scalar priVar)
-    {
-        // define the base epsilon as the geometric mean of 1 and the
-        // resolution of the scalar type. E.g. for standard 64 bit
-        // floating point values, the resolution is about 10^-16 and
-        // the base epsilon is thus approximately 10^-8.
-        /*
-        static const Scalar baseEps
-            = Dumux::geometricMean<Scalar>(std::numeric_limits<Scalar>::epsilon(), 1.0);
-        */
-        static const Scalar baseEps = 1e-10;
-        assert(std::numeric_limits<Scalar>::epsilon()*1e4 < baseEps);
-        // the epsilon value used for the numeric differentiation is
-        // now scaled by the absolute value of the primary variable...
-        return baseEps*(std::abs(priVar) + 1.0);
+        this->asImp_().evalDirichletBoundaries(applyDirichlet);
     }
 
-private:
     /*!
-     * \brief Computes the residual
-     *
-     * \return The element residual at the current solution.
+     * \brief Assemble the residual only
      */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
+    void assembleResidual(SolutionVector& res)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
+        this->asImp_().bindLocalViews();
+        const auto residual = this->evalLocalResidual();
 
-        const bool isStationary = localResidual.isStationary();
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        if (!isStationary)
-            prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
+        for (const auto& scv : scvs(this->fvGeometry()))
+            res[scv.dofIndex()] += residual[scv.indexInElement()];
 
-        // the element boundary types
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // the actual element's current residual
-        ElementResidualVector residual(0.0);
-        if (isStationary)
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
-        else
+        auto applyDirichlet = [&] (const auto& scvI,
+                                   const auto& dirichletValues,
+                                   const auto eqIdx,
+                                   const auto pvIdx)
         {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
+            res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx];
+        };
 
-        for (const auto& scv : scvs(fvGeometry))
-            r[scv.dofIndex()] += residual[scv.indexInElement()];
+        this->asImp_().evalDirichletBoundaries(applyDirichlet);
+    }
 
-        // enforce Dirichlet boundaries by setting the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
+    /*!
+     * \brief Evaluates Dirichlet boundaries
+     */
+    template< typename ApplyDirichletFunctionType >
+    void evalDirichletBoundaries(ApplyDirichletFunctionType applyDirichlet)
+    {
+        // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0
+        // and set the residual to (privar - dirichletvalue)
+        if (this->elemBcTypes().hasDirichlet())
         {
-            for (const auto& scvI : scvs(fvGeometry))
+            for (const auto& scvI : scvs(this->fvGeometry()))
             {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
+                const auto bcTypes = this->elemBcTypes()[scvI.indexInElement()];
                 if (bcTypes.hasDirichlet())
                 {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
+                    const auto dirichletValues = this->problem().dirichlet(this->element(), scvI);
 
                     // set the dirichlet conditions in residual and jacobian
                     for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
@@ -210,9 +167,7 @@ private:
                         {
                             const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
                             assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
+                            applyDirichlet(scvI, dirichletValues, eqIdx, pvIdx);
                         }
                     }
                 }
@@ -220,65 +175,62 @@ private:
         }
     }
 
+};
+
+/*!
+ * \ingroup Assembly
+ * \ingroup BoxDiscretization
+ * \brief An assembler for Jacobian and residual contribution per element (box methods)
+ * \tparam TypeTag The TypeTag
+ * \tparam DM The differentiation method to residual compute derivatives
+ * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit)
+ */
+template<class TypeTag, class Assembler, DiffMethod DM = DiffMethod::numeric, bool implicit = true>
+class BoxLocalAssembler;
+
+/*!
+ * \ingroup Assembly
+ * \ingroup BoxDiscretization
+ * \brief Box local assembler using numeric differentiation and implicit time discretization
+ */
+template<class TypeTag, class Assembler>
+class BoxLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true>
+: public BoxLocalAssemblerBase<TypeTag, Assembler,
+                              BoxLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, true>, true>
+{
+    using ThisType = BoxLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, true>;
+    using ParentType = BoxLocalAssemblerBase<TypeTag, Assembler, ThisType, true>;
+    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
+    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
+    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
+    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
+
+    enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
+    enum { dim = GET_PROP_TYPE(TypeTag, GridView)::dimension };
+
+    static constexpr bool enableGridFluxVarsCache = GET_PROP_VALUE(TypeTag, EnableGridFluxVariablesCache);
+
+public:
+
+    using ParentType::ParentType;
+
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix.
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, JacobianMatrix& A, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
+    auto assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        const auto& fvGridGeometry = assembler.fvGridGeometry();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(fvGridGeometry);
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
-
-        const bool isStationary = localResidual.isStationary();
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        if (!isStationary)
-            prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
-
-        // check for boundaries on the element
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // create the element solution
-        ElementSolutionVector elemSol(element, curSol, fvGeometry);
+        // get some aliases for convenience
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        const auto& curSol = this->curSol();
+        auto&& curElemVolVars = this->curElemVolVars();
 
-        // the actual element's current residual
-        ElementResidualVector residual(0.0);
-        if (isStationary)
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
-        else
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
+        // get the vecor of the acutal element residuals
+        const auto origResiduals = this->evalLocalResidual();
 
         //////////////////////////////////////////////////////////////////////////////////////////////////
         //                                                                                              //
@@ -288,99 +240,35 @@ private:
         //                                                                                              //
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
-        static const int numericDifferenceMethod = getParamFromGroup<int>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Implicit.NumericDifferenceMethod");
+        // create the element solution
+        ElementSolutionVector elemSol(element, curSol, fvGeometry);
+
+        using ElementResidualVector = std::decay_t<decltype(origResiduals)>;
+        ElementResidualVector partialDerivs(element.subEntities(dim));
 
         // calculation of the derivatives
         for (auto&& scv : scvs(fvGeometry))
         {
             // dof index and corresponding actual pri vars
             const auto dofIdx = scv.dofIndex();
-            auto& volVars = getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
-            VolumeVariables origVolVars(volVars);
-
-            // add precalculated residual for this scv into the global container
-            r[dofIdx] += residual[scv.indexInElement()];
+            auto& curVolVars = this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
+            const VolumeVariables origVolVars(curVolVars);
 
             // calculate derivatives w.r.t to the privars at the dof at hand
             for (int pvIdx = 0; pvIdx < numEq; pvIdx++)
             {
-                ElementResidualVector partialDeriv(element.subEntities(dim));
-                Scalar eps = numericEpsilon(volVars.priVar(pvIdx));
-                Scalar delta = 0;
+                partialDerivs = 0.0;
 
-                // calculate the residual with the forward deflected primary variables
-                if (numericDifferenceMethod >= 0)
+                auto evalResiduals = [&](Scalar priVar)
                 {
-                    // we are not using backward differences, i.e. we need to
-                    // calculate f(x + \epsilon)
-
-                    // deflect primary variables
-                    elemSol[scv.indexInElement()][pvIdx] += eps;
-                    delta += eps;
+                    // update the volume variables and compute element residual
+                    elemSol[scv.indexInElement()][pvIdx] = priVar;
+                    curVolVars.update(elemSol, this->problem(), element, scv);
+                    return this->evalLocalResidual();
+                };
 
-                    // update the volume variables connected to the dof
-                    volVars.update(elemSol, problem, element, scv);
-
-                    // store the deflected residual
-                    if (isStationary)
-                    {
-                        partialDeriv = localResidual.eval(problem, element, fvGeometry,
-                                                          curElemVolVars,
-                                                          elemBcTypes, elemFluxVarsCache);
-                    }
-                    else
-                    {
-                        partialDeriv = localResidual.eval(problem, element, fvGeometry,
-                                                          prevElemVolVars, curElemVolVars,
-                                                          elemBcTypes, elemFluxVarsCache);
-                    }
-                }
-                else
-                {
-                    // we are using backward differences, i.e. we don't need
-                    // to calculate f(x + \epsilon) and we can recycle the
-                    // (already calculated) residual f(x)
-                    partialDeriv = residual;
-                }
-
-                if (numericDifferenceMethod <= 0)
-                {
-                    // we are not using forward differences, i.e. we
-                    // need to calculate f(x - \epsilon)
-
-                    // deflect the primary variables
-                    elemSol[scv.indexInElement()][pvIdx] -= delta + eps;
-                    delta += eps;
-
-                    // update the volume variables connected to the dof
-                    volVars.update(elemSol, problem, element, scv);
-
-                    // subtract the deflected residual from the derivative storage
-                    // store the deflected residual
-                    if (isStationary)
-                    {
-                        partialDeriv -= localResidual.eval(problem, element, fvGeometry,
-                                                           curElemVolVars,
-                                                           elemBcTypes, elemFluxVarsCache);
-                    }
-                    else
-                    {
-                        partialDeriv -= localResidual.eval(problem, element, fvGeometry,
-                                                           prevElemVolVars, curElemVolVars,
-                                                           elemBcTypes, elemFluxVarsCache);
-                    }
-                }
-                else
-                {
-                    // we are using forward differences, i.e. we don't need to
-                    // calculate f(x - \epsilon) and we can recycle the
-                    // (already calculated) residual f(x)
-                    partialDeriv -= residual;
-                }
-
-                // divide difference in residuals by the magnitude of the
-                // deflections between the two function evaluation
-                partialDeriv /= delta;
+                // derive the residuals numerically
+                NumericDifferentiation::partialDerivative(evalResiduals, elemSol[scv.indexInElement()][pvIdx], partialDerivs, origResiduals);
 
                 // update the global stiffness matrix with the current partial derivatives
                 for (auto&& scvJ : scvs(fvGeometry))
@@ -391,296 +279,64 @@ private:
                           // the residual of equation 'eqIdx' at dof 'i'
                           // depending on the primary variable 'pvIdx' at dof
                           // 'col'.
-                          A[scvJ.dofIndex()][dofIdx][eqIdx][pvIdx] += partialDeriv[scvJ.indexInElement()][eqIdx];
+                          A[scvJ.dofIndex()][dofIdx][eqIdx][pvIdx] += partialDerivs[scvJ.indexInElement()][eqIdx];
                       }
                 }
 
                 // restore the original state of the scv's volume variables
-                volVars = origVolVars;
+                curVolVars = origVolVars;
 
                 // restore the original element solution
                 elemSol[scv.indexInElement()][pvIdx] = curSol[scv.dofIndex()][pvIdx];
-            }
-
-            // TODO additional dof dependencies
-        }
-
-        // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0
-        // and set the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
-        {
-            for (const auto& scvI : scvs(fvGeometry))
-            {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
-                if (bcTypes.hasDirichlet())
-                {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
-
-                    // set the dirichlet conditions in residual and jacobian
-                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
-                    {
-                        if (bcTypes.isDirichlet(eqIdx))
-                        {
-                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
-                            assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
-                            for (const auto& scvJ : scvs(fvGeometry))
-                            {
-                                A[scvI.dofIndex()][scvJ.dofIndex()][eqIdx] = 0.0;
-                                if (scvI.indexInElement() == scvJ.indexInElement())
-                                    A[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0;
-                            }
-                        }
-                    }
-                }
+                // TODO additional dof dependencies
             }
         }
+        return origResiduals;
     }
-private:
-    template<class T = TypeTag>
-    static typename std::enable_if<!GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return elemVolVars[scv]; }
-
-    template<class T = TypeTag>
-    static typename std::enable_if<GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return gridVolVars.volVars(scv.elementIndex(), scv.indexInElement()); }
 
 }; // implicit BoxAssembler with numeric Jacobian
 
-
 /*!
  * \ingroup Assembly
  * \ingroup BoxDiscretization
  * \brief Box local assembler using numeric differentiation and explicit time discretization
  */
-template<class TypeTag>
-class BoxLocalAssembler<TypeTag,
-                       DiffMethod::numeric,
-                       /*implicit=*/false>
+template<class TypeTag, class Assembler>
+class BoxLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/false>
+: public BoxLocalAssemblerBase<TypeTag, Assembler,
+                              BoxLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, false>, false>
 {
+    using ThisType = BoxLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, false>;
+    using ParentType = BoxLocalAssemblerBase<TypeTag, Assembler, ThisType, false>;
     using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
-    using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
-    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
     using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
-    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
 
     enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
-
-    static constexpr int dim = GridView::dimension;
+    enum { dim = GET_PROP_TYPE(TypeTag, GridView)::dimension };
 
 public:
 
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix. The element residual is written into the right hand side.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, jac, res, element, curSol);
-
-    }
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        auto dummyresidual = curSol;
-        assemble_(assembler, jac, dummyresidual, element, curSol);
-    }
-
-    /*!
-     * \brief Assemble the residual only
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, res, element, curSol);
-    }
-
-    /*!
-     * \brief Computes the epsilon used for numeric differentiation
-     *        for a given value of a primary variable.
-     *
-     * \param priVar The value of the primary variable
-     */
-    static Scalar numericEpsilon(const Scalar priVar)
-    {
-        // define the base epsilon as the geometric mean of 1 and the
-        // resolution of the scalar type. E.g. for standard 64 bit
-        // floating point values, the resolution is about 10^-16 and
-        // the base epsilon is thus approximately 10^-8.
-        /*
-        static const Scalar baseEps
-            = Dumux::geometricMean<Scalar>(std::numeric_limits<Scalar>::epsilon(), 1.0);
-        */
-        static const Scalar baseEps = 1e-10;
-        assert(std::numeric_limits<Scalar>::epsilon()*1e4 < baseEps);
-        // the epsilon value used for the numeric differentiation is
-        // now scaled by the absolute value of the primary variable...
-        return baseEps*(std::abs(priVar) + 1.0);
-    }
-
-private:
-    /*!
-     * \brief Computes the residual
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
+    auto assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
+        // get some aliases for convenience
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        const auto& curSol = this->curSol();
+        auto&& curElemVolVars = this->curElemVolVars();
 
-        // the element boundary types
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // the actual element's current residual
-        auto residual = localResidual.eval(problem,
-                                           element,
-                                           fvGeometry,
-                                           prevElemVolVars,
-                                           elemBcTypes,
-                                           elemFluxVarsCache);
-
-        auto storageResidual = localResidual.evalStorage(problem,
-                                                         element,
-                                                         fvGeometry,
-                                                         prevElemVolVars,
-                                                         curElemVolVars,
-                                                         elemBcTypes,
-                                                         elemFluxVarsCache);
-        residual += storageResidual;
-
-        for (const auto& scv : scvs(fvGeometry))
-            r[scv.dofIndex()] += residual[scv.indexInElement()];
-
-        // enforce Dirichlet boundaries by setting the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
-        {
-            for (const auto& scvI : scvs(fvGeometry))
-            {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
-                if (bcTypes.hasDirichlet())
-                {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
-
-                    // set the dirichlet conditions in residual and jacobian
-                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
-                    {
-                        if (bcTypes.isDirichlet(eqIdx))
-                        {
-                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
-                            assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix.
-     *
-     * \return The element residual at the current solution.
-     */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, JacobianMatrix& A, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
-    {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
-
-        // the element boundary types
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // get the element solution
-        const auto numVert = element.subEntities(dim);
-        ElementSolutionVector elemSol(numVert);
-        for (const auto& scv : scvs(fvGeometry))
-            elemSol[scv.indexInElement()] = localResidual.prevSol()[scv.dofIndex()];
-
-        // the actual element's previous time step residual
-        auto residual = localResidual.eval(problem,
-                                           element,
-                                           fvGeometry,
-                                           prevElemVolVars,
-                                           elemBcTypes,
-                                           elemFluxVarsCache);
-
-        auto storageResidual = localResidual.evalStorage(problem,
-                                                         element,
-                                                         fvGeometry,
-                                                         prevElemVolVars,
-                                                         curElemVolVars,
-                                                         elemBcTypes,
-                                                         elemFluxVarsCache);
-
-        residual += storageResidual;
+        // get the vecor of the acutal element residuals
+        const auto origResiduals = this->evalLocalResidual();
 
         //////////////////////////////////////////////////////////////////////////////////////////////////
         //                                                                                              //
@@ -690,78 +346,35 @@ private:
         //                                                                                              //
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
-        static const int numericDifferenceMethod = getParamFromGroup<int>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Implicit.NumericDifferenceMethod");
+        // create the element solution
+        ElementSolutionVector elemSol(element, curSol, fvGeometry);
+
+        using ElementResidualVector = std::decay_t<decltype(origResiduals)>;
+        ElementResidualVector partialDerivs(element.subEntities(dim));
 
         // calculation of the derivatives
         for (auto&& scv : scvs(fvGeometry))
         {
             // dof index and corresponding actual pri vars
             const auto dofIdx = scv.dofIndex();
-            auto& volVars = getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
-            VolumeVariables origVolVars(volVars);
-
-            // add precalculated residual for this scv into the global container
-            r[dofIdx] += residual[scv.indexInElement()];
+            auto& curVolVars = this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
+            const VolumeVariables origVolVars(curVolVars);
 
             // calculate derivatives w.r.t to the privars at the dof at hand
             for (int pvIdx = 0; pvIdx < numEq; pvIdx++)
             {
-                ElementSolutionVector partialDeriv(element.subEntities(dim));
-                Scalar eps = numericEpsilon(volVars.priVar(pvIdx));
-                Scalar delta = 0;
-
-                // calculate the residual with the forward deflected primary variables
-                if (numericDifferenceMethod >= 0)
-                {
-                    // we are not using backward differences, i.e. we need to
-                    // calculate f(x + \epsilon)
-
-                    // deflect primary variables
-                    elemSol[scv.indexInElement()][pvIdx] += eps;
-                    delta += eps;
-
-                    // update the volume variables connected to the dof
-                    volVars.update(elemSol, problem, element, scv);
+                partialDerivs = 0.0;
 
-                    partialDeriv = localResidual.evalStorage(problem, element, fvGeometry,
-                                                             prevElemVolVars, curElemVolVars,
-                                                             elemBcTypes, elemFluxVarsCache);
-                }
-                else
-                {
-                    // we are using backward differences, i.e. we don't need
-                    // to calculate f(x + \epsilon) and we can recycle the
-                    // (already calculated) residual f(x)
-                    partialDeriv = residual;
-                }
-
-                if (numericDifferenceMethod <= 0)
+                auto evalStorage = [&](Scalar priVar)
                 {
-                    // we are not using forward differences, i.e. we
-                    // need to calculate f(x - \epsilon)
+                    // auto partialDerivsTmp = partialDerivs;
+                    elemSol[scv.indexInElement()][pvIdx] = priVar;
+                    curVolVars.update(elemSol, this->problem(), element, scv);
+                    return this->evalLocalStorageResidual();
+                };
 
-                    // deflect the primary variables
-                    elemSol[scv.indexInElement()][pvIdx] -= delta + eps;
-                    delta += eps;
-
-                    // update the volume variables connected to the dof
-                    volVars.update(elemSol, problem, element, scv);
-
-                    partialDeriv -= localResidual.evalStorage(problem, element, fvGeometry,
-                                                              prevElemVolVars, curElemVolVars,
-                                                              elemBcTypes, elemFluxVarsCache);
-                }
-                else
-                {
-                    // we are using forward differences, i.e. we don't need to
-                    // calculate f(x - \epsilon) and we can recycle the
-                    // (already calculated) residual f(x)
-                    partialDeriv -= residual;
-                }
-
-                // divide difference in residuals by the magnitude of the
-                // deflections between the two function evaluation
-                partialDeriv /= delta;
+                // derive the residuals numerically
+                NumericDifferentiation::partialDerivative(evalStorage, elemSol[scv.indexInElement()][pvIdx], partialDerivs, origResiduals);
 
                 // update the global stiffness matrix with the current partial derivatives
                 for (int eqIdx = 0; eqIdx < numEq; eqIdx++)
@@ -770,210 +383,39 @@ private:
                     // the residual of equation 'eqIdx' at dof 'i'
                     // depending on the primary variable 'pvIdx' at dof
                     // 'col'.
-                    A[dofIdx][dofIdx][eqIdx][pvIdx] += partialDeriv[scv.indexInElement()][eqIdx];
+                    A[dofIdx][dofIdx][eqIdx][pvIdx] += partialDerivs[scv.indexInElement()][eqIdx];
                 }
 
                 // restore the original state of the scv's volume variables
-                volVars = origVolVars;
+                curVolVars = origVolVars;
 
                 // restore the original element solution
                 elemSol[scv.indexInElement()][pvIdx] = curSol[scv.dofIndex()][pvIdx];
-            }
-
-            // TODO additional dof dependencies
-        }
-
-        // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0
-        // and set the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
-        {
-            for (const auto& scvI : scvs(fvGeometry))
-            {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
-                if (bcTypes.hasDirichlet())
-                {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
-
-                    // set the dirichlet conditions in residual and jacobian
-                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
-                    {
-                        if (bcTypes.isDirichlet(eqIdx))
-                        {
-                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
-                            assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
-                            A[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0;
-                        }
-                    }
-                }
+                // TODO additional dof dependencies
             }
         }
+        return origResiduals;
     }
-private:
-    template<class T = TypeTag>
-    static typename std::enable_if<!GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return elemVolVars[scv]; }
-
-    template<class T = TypeTag>
-    static typename std::enable_if<GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return gridVolVars.volVars(scv.elementIndex(), scv.indexInElement()); }
-
 }; // explicit BoxAssembler with numeric Jacobian
 
-
 /*!
  * \ingroup Assembly
  * \ingroup BoxDiscretization
- * \brief Box local assembler using analytic (hard coded) derivatives and implicit time discretization
+ * \brief Box local assembler using analytic differentiation and implicit time discretization
  */
-template<class TypeTag>
-class BoxLocalAssembler<TypeTag,
-                       DiffMethod::analytic,
-                       /*implicit=*/true>
+template<class TypeTag, class Assembler>
+class BoxLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/true>
+: public BoxLocalAssemblerBase<TypeTag, Assembler,
+                              BoxLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true>, true>
 {
-    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
-    using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
-    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
-    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
-    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    using ThisType = BoxLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true>;
+    using ParentType = BoxLocalAssemblerBase<TypeTag, Assembler, ThisType, true>;
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
 
-    enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
-
-    static constexpr int dim = GridView::dimension;
-
 public:
 
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix. The element residual is written into the right hand side.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, jac, res, element, curSol);
-
-    }
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        auto dummyresidual = curSol;
-        assemble_(assembler, jac, dummyresidual, element, curSol);
-    }
-
-    /*!
-     * \brief Assemble the residual only
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, res, element, curSol);
-    }
-
-private:
-    /*!
-     * \brief Computes the residual
-     *
-     * \return The element residual at the current solution.
-     */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
-    {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
-
-        const bool isStationary = localResidual.isStationary();
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        if (!isStationary)
-            prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
-
-        // the element boundary types
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // the actual element's current residual
-        ElementResidualVector residual(0.0);
-        if (isStationary)
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
-        else
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
-
-        for (const auto& scv : scvs(fvGeometry))
-            r[scv.dofIndex()] += residual[scv.indexInElement()];
-
-        // enforce Dirichlet boundaries by setting the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
-        {
-            for (const auto& scvI : scvs(fvGeometry))
-            {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
-                if (bcTypes.hasDirichlet())
-                {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
-
-                    // set the dirichlet conditions in residual and jacobian
-                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
-                    {
-                        if (bcTypes.isDirichlet(eqIdx))
-                        {
-                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
-                            assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
-                        }
-                    }
-                }
-            }
-        }
-    }
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
@@ -981,59 +423,17 @@ private:
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, JacobianMatrix& A, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
+    auto assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        const auto& fvGridGeometry = assembler.fvGridGeometry();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(fvGridGeometry);
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
-
-        const bool isStationary = localResidual.isStationary();
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        if (!isStationary)
-            prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
-
-        // check for boundaries on the element
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // the actual element's current residual
-        ElementResidualVector residual(0.0);
-        if (isStationary)
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
-        else
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache);
-        }
+        // get some aliases for convenience
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        const auto& problem = this->problem();
+        auto&& curElemVolVars = this->curElemVolVars();
+        auto&& elemFluxVarsCache = this->elemFluxVarsCache();
 
-        for (auto&& scv : scvs(fvGeometry))
-            r[scv.dofIndex()] += residual[scv.indexInElement()];
+        // get the vecor of the acutal element residuals
+        const auto origResiduals = this->evalLocalResidual();
 
         //////////////////////////////////////////////////////////////////////////////////////////////////
         //                                                                                              //
@@ -1052,22 +452,23 @@ private:
 
             // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping)
             // only if the problem is instationary we add derivative of storage term
-            if (!isStationary)
-                localResidual.addStorageDerivatives(A[dofIdx][dofIdx],
-                                                    problem,
-                                                    element,
-                                                    fvGeometry,
-                                                    volVars,
-                                                    scv);
+            // TODO if e.g. porosity depends on all dofs in the element, we would have off-diagonal matrix entries!?
+            if (!this->assembler().isStationaryProblem())
+                this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx],
+                                                            problem,
+                                                            element,
+                                                            fvGeometry,
+                                                            volVars,
+                                                            scv);
 
             // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping)
             // add source term derivatives
-            localResidual.addSourceDerivatives(A[dofIdx][dofIdx],
-                                               problem,
-                                               element,
-                                               fvGeometry,
-                                               volVars,
-                                               scv);
+            this->localResidual().addSourceDerivatives(A[dofIdx][dofIdx],
+                                                       problem,
+                                                       element,
+                                                       fvGeometry,
+                                                       volVars,
+                                                       scv);
         }
 
         // localJacobian[scvIdx][otherScvIdx][eqIdx][priVarIdx] of the fluxes
@@ -1076,13 +477,13 @@ private:
             if (!scvf.boundary())
             {
                 // add flux term derivatives
-                localResidual.addFluxDerivatives(A,
-                                                 problem,
-                                                 element,
-                                                 fvGeometry,
-                                                 curElemVolVars,
-                                                 elemFluxVarsCache,
-                                                 scvf);
+                this->localResidual().addFluxDerivatives(A,
+                                                         problem,
+                                                         element,
+                                                         fvGeometry,
+                                                         curElemVolVars,
+                                                         elemFluxVarsCache,
+                                                         scvf);
             }
 
             // the boundary gets special treatment to simplify
@@ -1090,261 +491,60 @@ private:
             else
             {
                 const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx());
-                if (elemBcTypes[insideScv.indexInElement()].hasNeumann())
+                if (this->elemBcTypes()[insideScv.indexInElement()].hasNeumann())
                 {
                     // add flux term derivatives
-                    localResidual.addRobinFluxDerivatives(A[insideScv.dofIndex()],
-                                                          problem,
-                                                          element,
-                                                          fvGeometry,
-                                                          curElemVolVars,
-                                                          elemFluxVarsCache,
-                                                          scvf);
+                    this->localResidual().addRobinFluxDerivatives(A[insideScv.dofIndex()],
+                                                                  problem,
+                                                                  element,
+                                                                  fvGeometry,
+                                                                  curElemVolVars,
+                                                                  elemFluxVarsCache,
+                                                                  scvf);
                 }
             }
         }
 
-        // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0
-        // and set the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
-        {
-            for (const auto& scvI : scvs(fvGeometry))
-            {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
-                if (bcTypes.hasDirichlet())
-                {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
-
-                    // set the dirichlet conditions in residual and jacobian
-                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
-                    {
-                        if (bcTypes.isDirichlet(eqIdx))
-                        {
-                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
-                            assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
-                            for (const auto& scvJ : scvs(fvGeometry))
-                            {
-                                A[scvI.dofIndex()][scvJ.dofIndex()][eqIdx] = 0.0;
-                                if (scvI.indexInElement() == scvJ.indexInElement())
-                                    A[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // TODO additional dof dependencies
+        return origResiduals;
     }
 
 }; // implicit BoxAssembler with analytic Jacobian
 
-
 /*!
  * \ingroup Assembly
  * \ingroup BoxDiscretization
- * \brief Box local assembler using analytic (hard coded) derivatives and explicit time discretization
+ * \brief Box local assembler using analytic differentiation and explicit time discretization
  */
-template<class TypeTag>
-class BoxLocalAssembler<TypeTag,
-                       DiffMethod::analytic,
-                       /*implicit=*/false>
+template<class TypeTag, class Assembler>
+class BoxLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/false>
+: public BoxLocalAssemblerBase<TypeTag, Assembler,
+                              BoxLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false>, false>
 {
-    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
-    using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
-    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
-    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
-    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    using ThisType = BoxLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false>;
+    using ParentType = BoxLocalAssemblerBase<TypeTag, Assembler, ThisType, false>;
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
 
-    enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
-
-    static constexpr int dim = GridView::dimension;
-
 public:
 
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix. The element residual is written into the right hand side.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, jac, res, element, curSol);
-
-    }
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        auto dummyresidual = curSol;
-        assemble_(assembler, jac, dummyresidual, element, curSol);
-    }
-
-    /*!
-     * \brief Assemble the residual only
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, res, element, curSol);
-    }
-
-private:
-    /*!
-     * \brief Computes the residual
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
+    auto assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
+        // get some aliases for convenience
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        const auto& problem = this->problem();
+        auto&& curElemVolVars = this->curElemVolVars();
 
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
-
-        // the element boundary types
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // the actual element's current residual
-        auto residual = localResidual.eval(problem,
-                                           element,
-                                           fvGeometry,
-                                           prevElemVolVars,
-                                           elemBcTypes,
-                                           elemFluxVarsCache);
-
-        auto storageResidual = localResidual.evalStorage(problem,
-                                                         element,
-                                                         fvGeometry,
-                                                         prevElemVolVars,
-                                                         curElemVolVars,
-                                                         elemBcTypes,
-                                                         elemFluxVarsCache);
-        residual += storageResidual;
-
-        for (const auto& scv : scvs(fvGeometry))
-            r[scv.dofIndex()] += residual[scv.indexInElement()];
-
-        // enforce Dirichlet boundaries by setting the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
-        {
-            for (const auto& scvI : scvs(fvGeometry))
-            {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
-                if (bcTypes.hasDirichlet())
-                {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
-
-                    // set the dirichlet conditions in residual and jacobian
-                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
-                    {
-                        if (bcTypes.isDirichlet(eqIdx))
-                        {
-                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
-                            assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix.
-     *
-     * \return The element residual at the current solution.
-     */
-    template<class Assembler>
-    static void assemble_(Assembler& assembler, JacobianMatrix& A, SolutionVector& r,
-                          const Element& element, const SolutionVector& curSol)
-    {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
-
-        // the element boundary types
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // the actual element's current residual
-        auto residual = localResidual.eval(problem,
-                                           element,
-                                           fvGeometry,
-                                           prevElemVolVars,
-                                           elemBcTypes,
-                                           elemFluxVarsCache);
-
-        auto storageResidual = localResidual.evalStorage(problem,
-                                                         element,
-                                                         fvGeometry,
-                                                         prevElemVolVars,
-                                                         curElemVolVars,
-                                                         elemBcTypes,
-                                                         elemFluxVarsCache);
-        residual += storageResidual;
-
-        for (const auto& scv : scvs(fvGeometry))
-            r[scv.dofIndex()] += residual[scv.indexInElement()];
+        // get the vecor of the acutal element residuals
+        const auto origResiduals = this->evalLocalResidual();
 
         //////////////////////////////////////////////////////////////////////////////////////////////////
         //                                                                                              //
@@ -1363,43 +563,15 @@ private:
 
             // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping)
             // only if the problem is instationary we add derivative of storage term
-            localResidual.addStorageDerivatives(A[dofIdx][dofIdx],
-                                                problem,
-                                                element,
-                                                fvGeometry,
-                                                volVars,
-                                                scv);
-        }
-
-        // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0
-        // and set the residual to (privar - dirichletvalue)
-        if (elemBcTypes.hasDirichlet())
-        {
-            for (const auto& scvI : scvs(fvGeometry))
-            {
-                const auto bcTypes = elemBcTypes[scvI.indexInElement()];
-                if (bcTypes.hasDirichlet())
-                {
-                    const auto dirichletValues = problem.dirichlet(element, scvI);
-
-                    // set the dirichlet conditions in residual and jacobian
-                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
-                    {
-                        if (bcTypes.isDirichlet(eqIdx))
-                        {
-                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
-                            assert(0 <= pvIdx && pvIdx < numEq);
-
-                            const auto& priVars = curElemVolVars[scvI].priVars();
-                            r[scvI.dofIndex()][eqIdx] = priVars[pvIdx] - dirichletValues[pvIdx];
-                            A[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0;
-                        }
-                    }
-                }
-            }
+            this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx],
+                                                        problem,
+                                                        element,
+                                                        fvGeometry,
+                                                        volVars,
+                                                        scv);
         }
 
-        // TODO additional dof dependencies
+        return origResiduals;
     }
 
 }; // explicit BoxAssembler with analytic Jacobian
diff --git a/dumux/assembly/boxlocalresidual.hh b/dumux/assembly/boxlocalresidual.hh
index 7beb17dc02973953b3fa9d6b6b9f907e5911d56d..2858c752202145edb07110893489d12c8647f21f 100644
--- a/dumux/assembly/boxlocalresidual.hh
+++ b/dumux/assembly/boxlocalresidual.hh
@@ -53,9 +53,9 @@ class BoxLocalResidual : public FVLocalResidual<TypeTag>
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
     using ElementFluxVariablesCache = typename GET_PROP_TYPE(TypeTag, ElementFluxVariablesCache);
     using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
 
 public:
+    using ElementResidualVector = typename ParentType::ElementResidualVector;
     using ParentType::ParentType;
 
     //! evaluate flux residuals for one sub control volume face and add to residual
diff --git a/dumux/assembly/cclocalassembler.hh b/dumux/assembly/cclocalassembler.hh
index 9d5be567a28ee279d7a5379c8ebc2116413defc3..00d9c51e2d82bf7b0d11df075241cce916f99710 100644
--- a/dumux/assembly/cclocalassembler.hh
+++ b/dumux/assembly/cclocalassembler.hh
@@ -21,180 +21,121 @@
  * \ingroup Assembly
  * \ingroup CCDiscretization
  * \brief An assembler for Jacobian and residual contribution per element (cell-centered methods)
- * \tparam TypeTag the TypeTag
- * \tparam DM the differentiation method to residual compute derivatives
- * \tparam implicit if to use an implicit or explicit time discretization
  */
 #ifndef DUMUX_CC_LOCAL_ASSEMBLER_HH
 #define DUMUX_CC_LOCAL_ASSEMBLER_HH
 
+#include <dune/common/reservedvector.hh>
 #include <dune/grid/common/gridenums.hh> // for GhostEntity
 #include <dune/istl/matrixindexset.hh>
-#include <dune/istl/bvector.hh>
 
+#include <dumux/common/reservedblockvector.hh>
 #include <dumux/common/properties.hh>
 #include <dumux/common/parameters.hh>
 #include <dumux/assembly/diffmethod.hh>
+#include <dumux/assembly/numericdifferentiation.hh>
+#include <dumux/assembly/fvlocalassemblerbase.hh>
+#include <dumux/discretization/fluxstencil.hh>
 
 namespace Dumux {
 
 /*!
  * \ingroup Assembly
  * \ingroup CCDiscretization
- * \brief An assembler for Jacobian and residual contribution per element (cell-centered methods)
- * \tparam TypeTag the TypeTag
- * \tparam DM the differentiation method to residual compute derivatives
- * \tparam implicit if to use an implicit or explicit time discretization
+ * \brief A base class for all local cell-centered assemblers
+ * \tparam TypeTag The TypeTag
+ * \tparam Assembler The assembler type
+ * \tparam Implementation The actual implementation
+ * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit)
  */
-template<class TypeTag,
-         DiffMethod DM = DiffMethod::numeric,
-         bool implicit = true>
-class CCLocalAssembler;
-
-/*!
- * \ingroup Assembly
- * \ingroup CCDiscretization
- * \brief Cell-centered scheme local assembler using numeric differentiation and implicit time discretization
- */
-template<class TypeTag>
-class CCLocalAssembler<TypeTag,
-                       DiffMethod::numeric,
-                       /*implicit=*/true>
+template<class TypeTag, class Assembler, class Implementation, bool implicit>
+class CCLocalAssemblerBase : public FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit>
 {
-    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using NumEqVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
+    using ParentType = FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit>;
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
-    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
     using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
-    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
-
-    enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
-
-    static constexpr bool enableGridFluxVarsCache = GET_PROP_VALUE(TypeTag, EnableGridFluxVariablesCache);
+    using LocalResidualValues = typename GET_PROP_TYPE(TypeTag, NumEqVector);
 
 public:
 
+    using ParentType::ParentType;
+
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix. The element residual is written into the right hand side.
      */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
+    void assembleJacobianAndResidual(JacobianMatrix& jac, SolutionVector& res, GridVariables& gridVariables)
     {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, jac, element, curSol);
+        this->asImp_().bindLocalViews();
+        const auto globalI = this->assembler().fvGridGeometry().elementMapper().index(this->element());
+        res[globalI] = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation
     }
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix.
      */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
+    void assembleJacobian(JacobianMatrix& jac, GridVariables& gridVariables)
     {
-        assemble_(assembler, jac, element, curSol);
+        this->asImp_().bindLocalViews();
+        this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation
     }
 
     /*!
      * \brief Assemble the residual only
      */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, element, curSol);
-    }
-
-    /*!
-     * \brief Computes the epsilon used for numeric differentiation
-     *        for a given value of a primary variable.
-     *
-     * \param priVar The value of the primary variable
-     */
-    static Scalar numericEpsilon(const Scalar priVar)
+    void assembleResidual(SolutionVector& res)
     {
-        // define the base epsilon as the geometric mean of 1 and the
-        // resolution of the scalar type. E.g. for standard 64 bit
-        // floating point values, the resolution is about 10^-16 and
-        // the base epsilon is thus approximately 10^-8.
-        /*
-        static const Scalar baseEps
-            = Dumux::geometricMean<Scalar>(std::numeric_limits<Scalar>::epsilon(), 1.0);
-        */
-        static const Scalar baseEps = 1e-10;
-        assert(std::numeric_limits<Scalar>::epsilon()*1e4 < baseEps);
-        // the epsilon value used for the numeric differentiation is
-        // now scaled by the absolute value of the primary variable...
-        return baseEps*(std::abs(priVar) + 1.0);
+        this->asImp_().bindLocalViews();
+        const auto globalI = this->assembler().fvGridGeometry().elementMapper().index(this->element());
+        res[globalI] = this->asImp_().evalLocalResidual()[0]; // forward to the internal implementation
     }
+};
 
-private:
-    /*!
-     * \brief Computes the residual
-     *
-     * \return The element residual at the current solution.
-     */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler,
-                                 const Element& element, const SolutionVector& curSol)
-    {
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-        if (isGhost) return NumEqVector(0.0);
-
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
+/*!
+ * \ingroup Assembly
+ * \ingroup CCDiscretization
+ * \brief An assembler for Jacobian and residual contribution per element (cell-centered methods)
+ * \tparam TypeTag The TypeTag
+ * \tparam DM The differentiation method to residual compute derivatives
+ * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit)
+ */
+template<class TypeTag, class Assembler, DiffMethod DM = DiffMethod::numeric, bool implicit = true>
+class CCLocalAssembler;
 
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
+/*!
+ * \ingroup Assembly
+ * \ingroup CCDiscretization
+ * \brief Cell-centered scheme local assembler using numeric differentiation and implicit time discretization
+ */
+template<class TypeTag, class Assembler>
+class CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true>
+: public CCLocalAssemblerBase<TypeTag, Assembler,
+                              CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, true>, true >
+{
+    using ThisType = CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, true>;
+    using ParentType = CCLocalAssemblerBase<TypeTag, Assembler, ThisType, true>;
+    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
+    using LocalResidualValues = typename GET_PROP_TYPE(TypeTag, NumEqVector);
+    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
+    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
+    using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
+    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
 
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
+    enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
+    enum { dim = GET_PROP_TYPE(TypeTag, GridView)::dimension };
 
-        // for compatibility with box models
-        ElementBoundaryTypes elemBcTypes;
+    using FluxStencil = Dumux::FluxStencil<TypeTag>;
+    static constexpr int maxNeighbors = FluxStencil::maxFluxStencilSize*FVElementGeometry::maxNumElementScvfs;
+    static constexpr bool enableGridFluxVarsCache = GET_PROP_VALUE(TypeTag, EnableGridFluxVariablesCache);
 
-        // the actual element's current residual
-        NumEqVector residual(0.0);
-        if (localResidual.isStationary())
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache)[0];
-        }
-        else
-        {
-            prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache)[0];
-        }
+public:
 
-        return residual;
-    }
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
@@ -202,117 +143,64 @@ private:
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler, JacobianMatrix& A,
-                                 const Element& element, const SolutionVector& curSol)
+    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        const auto& fvGridGeometry = assembler.fvGridGeometry();
-        const auto& connectivityMap = fvGridGeometry.connectivityMap();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-
-        // the global dof of the actual element
-        const auto globalI = fvGridGeometry.elementMapper().index(element);
-
-        // check for boundaries on the element
-        // TODO Do we need them for cell-centered models?
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-        // is the local residual stationary?
-        const bool isStationary = localResidual.isStationary();
-
-        // the actual element's current residual
-        NumEqVector residual(0.0);
-        if (!isGhost)
-        {
-            if (isStationary)
-            {
-                residual = localResidual.eval(problem,
-                                              element,
-                                              fvGeometry,
-                                              curElemVolVars,
-                                              elemBcTypes,
-                                              elemFluxVarsCache)[0];
-            }
-            else
-            {
-                prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
-                residual = localResidual.eval(problem,
-                                              element,
-                                              fvGeometry,
-                                              prevElemVolVars,
-                                              curElemVolVars,
-                                              elemBcTypes,
-                                              elemFluxVarsCache)[0];
-            }
-        }
-
-
-        // TODO Do we really need this??????????
-        // this->model_().updatePVWeights(fvGeometry);
-
         //////////////////////////////////////////////////////////////////////////////////////////////////
-        //                                                                                              //
         // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the //
         // neighboring elements we do so by computing the derivatives of the fluxes which depend on the //
         // actual element. In the actual element we evaluate the derivative of the entire residual.     //
-        //                                                                                              //
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
-        static const std::string group = GET_PROP_VALUE(TypeTag, ModelParameterGroup);
-        static const int numericDifferenceMethod = getParamFromGroup<int>(group, "Implicit.NumericDifferenceMethod");
+        // get some aliases for convenience
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        const auto& fvGridGeometry = this->assembler().fvGridGeometry();
+        auto&& curElemVolVars = this->curElemVolVars();
+        auto&& elemFluxVarsCache = this->elemFluxVarsCache();
 
         // get stencil informations
+        const auto globalI = fvGridGeometry.elementMapper().index(element);
+        const auto& connectivityMap = fvGridGeometry.connectivityMap();
         const auto numNeighbors = connectivityMap[globalI].size();
 
         // container to store the neighboring elements
-        std::vector<Element> neighborElements;
-        neighborElements.reserve(numNeighbors);
+        Dune::ReservedVector<Element, maxNeighbors+1> neighborElements;
+        neighborElements.resize(numNeighbors);
+
+        // assemble the undeflected residual
+        using Residuals = ReservedBlockVector<LocalResidualValues, maxNeighbors+1>;
+        Residuals origResiduals(numNeighbors + 1); origResiduals = 0.0;
+        origResiduals[0] = this->evalLocalResidual()[0];
+
+        // lambda for convenient evaluation of the fluxes across scvfs in the neighbors
+        auto evalNeighborFlux = [&] (const auto& neighbor, const auto& scvf)
+        {
+            return this->localResidual().evalFlux(this->problem(),
+                                                  neighbor,
+                                                  this->fvGeometry(),
+                                                  this->curElemVolVars(),
+                                                  this->elemFluxVarsCache(), scvf);
+        };
 
         // get the elements in which we need to evaluate the fluxes
         // and calculate these in the undeflected state
-        Dune::BlockVector<NumEqVector> origFlux(numNeighbors);
-        origFlux = 0.0;
-        unsigned int j = 0;
+        unsigned int j = 1;
         for (const auto& dataJ : connectivityMap[globalI])
         {
-            neighborElements.emplace_back(fvGridGeometry.element(dataJ.globalJ));
+            neighborElements[j-1] = fvGridGeometry.element(dataJ.globalJ);
             for (const auto scvfIdx : dataJ.scvfsJ)
-            {
-                origFlux[j] += localResidual.evalFlux(problem,
-                                                      neighborElements.back(),
-                                                      fvGeometry,
-                                                      curElemVolVars,
-                                                      elemFluxVarsCache,
-                                                      fvGeometry.scvf(scvfIdx));
-            }
-            // increment neighbor counter
+                origResiduals[j] += evalNeighborFlux(neighborElements[j-1], fvGeometry.scvf(scvfIdx));
+
             ++j;
         }
 
         // reference to the element's scv (needed later) and corresponding vol vars
         const auto& scv = fvGeometry.scv(globalI);
-        auto& curVolVars = getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
+        auto& curVolVars = ParentType::getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
 
         // save a copy of the original privars and vol vars in order
         // to restore the original solution after deflection
+        const auto& curSol = this->curSol();
         const auto origPriVars = curSol[globalI];
         const auto origVolVars = curVolVars;
 
@@ -320,435 +208,102 @@ private:
         ElementSolutionVector elemSol(origPriVars);
 
         // derivatives in the neighbors with repect to the current elements
-        Dune::BlockVector<NumEqVector> neighborDeriv(numNeighbors);
-        for (int pvIdx = 0; pvIdx < numEq; pvIdx++)
-        {
-            // reset derivatives of element dof with respect to itself
-            // as well as neighbor derivatives
-            NumEqVector partialDeriv(0.0);
-            neighborDeriv = 0.0;
+        // in index 0 we save the derivative of the element residual with respect to it's own dofs
+        Residuals partialDerivs(numNeighbors + 1);
 
-            if (isGhost)
-                partialDeriv[pvIdx] = 1.0;
+        for (int pvIdx = 0; pvIdx < numEq; ++pvIdx)
+        {
 
-            Scalar eps = numericEpsilon(curVolVars.priVar(pvIdx));
-            Scalar delta = 0;
+            // for ghost elements we assemble a 1.0 where the primary variable and zero everywhere else
+            // as we always solve for a delta of the solution with repect to the initial solution this
+            // results in a delta of zero for ghosts, we still need to do the neighbor derivatives though
+            // so we are not done yet here.
+            partialDerivs = 0.0;
+            if (this->elementIsGhost()) partialDerivs[0][pvIdx] = 1.0;
 
-            if (numericDifferenceMethod >= 0)
+            auto evalResiduals = [&](Scalar priVar)
             {
-                // we are not using backward differences, i.e. we need to
-                // calculate f(x + \epsilon)
-
-                // deflect primary variables
-                elemSol[0][pvIdx] += eps;
-                delta += eps;
-
+                Residuals partialDerivsTmp(numNeighbors + 1);
+                partialDerivsTmp = 0.0;
                 // update the volume variables and the flux var cache
-                curVolVars.update(elemSol, problem, element, scv);
+                elemSol[0][pvIdx] = priVar;
+                curVolVars.update(elemSol, this->problem(), element, scv);
                 if (enableGridFluxVarsCache)
                     gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars);
                 else
                     elemFluxVarsCache.update(element, fvGeometry, curElemVolVars);
 
                 // calculate the residual with the deflected primary variables
-                if (!isGhost)
-                {
-                    if (isStationary)
-                    {
-                        partialDeriv = localResidual.eval(problem,
-                                                          element,
-                                                          fvGeometry,
-                                                          curElemVolVars,
-                                                          elemBcTypes,
-                                                          elemFluxVarsCache)[0];
-                    }
-                    else
-                    {
-                        partialDeriv = localResidual.eval(problem,
-                                                          element,
-                                                          fvGeometry,
-                                                          prevElemVolVars,
-                                                          curElemVolVars,
-                                                          elemBcTypes,
-                                                          elemFluxVarsCache)[0];
-                    }
-                }
+                if (!this->elementIsGhost()) partialDerivsTmp[0] = this->evalLocalResidual()[0];
 
                 // calculate the fluxes in the neighbors with the deflected primary variables
                 for (std::size_t k = 0; k < numNeighbors; ++k)
                     for (auto scvfIdx : connectivityMap[globalI][k].scvfsJ)
-                    {
-                        neighborDeriv[k] += localResidual.evalFlux(problem,
-                                                                   neighborElements[k],
-                                                                   fvGeometry,
-                                                                   curElemVolVars,
-                                                                   elemFluxVarsCache,
-                                                                   fvGeometry.scvf(scvfIdx));
-                    }
-            }
-            else
-            {
-                // we are using backward differences, i.e. we don't need
-                // to calculate f(x + \epsilon) and we can recycle the
-                // (already calculated) residual f(x)
-                if (!isGhost)
-                    partialDeriv = residual;
-                neighborDeriv = origFlux;
-            }
-
-            if (numericDifferenceMethod <= 0)
-            {
-                // we are not using forward differences, i.e. we
-                // need to calculate f(x - \epsilon)
-
-                // deflect the primary variables
-                elemSol[0][pvIdx] -= delta + eps;
-                delta += eps;
-
-                // update the volume variables and the flux var cache
-                curVolVars.update(elemSol, problem, element, scv);
-                if (enableGridFluxVarsCache)
-                    gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars);
-                else
-                    elemFluxVarsCache.update(element, fvGeometry, curElemVolVars);
-
-                // calculate the residual with the deflected primary variables and subtract it
-                if (!isGhost)
-                {
-                    if (isStationary)
-                    {
-                        partialDeriv -= localResidual.eval(problem,
-                                                           element,
-                                                           fvGeometry,
-                                                           curElemVolVars,
-                                                           elemBcTypes,
-                                                           elemFluxVarsCache)[0];
-                    }
-                    else
-                    {
-                        partialDeriv -= localResidual.eval(problem,
-                                                           element,
-                                                           fvGeometry,
-                                                           prevElemVolVars,
-                                                           curElemVolVars,
-                                                           elemBcTypes,
-                                                           elemFluxVarsCache)[0];
-                    }
-                }
-
-                // calculate the fluxes into element with the deflected primary variables
-                for (std::size_t k = 0; k < numNeighbors; ++k)
-                    for (auto scvfIdx : connectivityMap[globalI][k].scvfsJ)
-                    {
-                        neighborDeriv[k] -= localResidual.evalFlux(problem,
-                                                                   neighborElements[k],
-                                                                   fvGeometry,
-                                                                   curElemVolVars,
-                                                                   elemFluxVarsCache,
-                                                                   fvGeometry.scvf(scvfIdx));
-                    }
-            }
-            else
-            {
-                // we are using forward differences, i.e. we don't need to
-                // calculate f(x - \epsilon) and we can recycle the
-                // (already calculated) residual f(x)
-                if (!isGhost)
-                    partialDeriv -= residual;
-                neighborDeriv -= origFlux;
-            }
+                        partialDerivsTmp[k+1] += evalNeighborFlux(neighborElements[k], fvGeometry.scvf(scvfIdx));
 
-            // divide difference in residuals by the magnitude of the
-            // deflections between the two function evaluation
-            if (!isGhost)
-                partialDeriv /= delta;
-            neighborDeriv /= delta;
+                return partialDerivsTmp;
+            };
 
-            // restore the original state of the scv's volume variables
-            curVolVars = origVolVars;
-
-            // restore the current element solution
-            elemSol[0][pvIdx] = origPriVars[pvIdx];
+            // derive the residuals numerically
+            NumericDifferentiation::partialDerivative(evalResiduals, elemSol[0][pvIdx], partialDerivs, origResiduals);
 
             // add the current partial derivatives to the global jacobian matrix
             for (int eqIdx = 0; eqIdx < numEq; eqIdx++)
             {
                 // the diagonal entries
-                A[globalI][globalI][eqIdx][pvIdx] += partialDeriv[eqIdx];
+                A[globalI][globalI][eqIdx][pvIdx] += partialDerivs[0][eqIdx];
 
                 // off-diagonal entries
-                j = 0;
+                j = 1;
                 for (const auto& dataJ : connectivityMap[globalI])
-                    A[dataJ.globalJ][globalI][eqIdx][pvIdx] += neighborDeriv[j++][eqIdx];
+                    A[dataJ.globalJ][globalI][eqIdx][pvIdx] += partialDerivs[j++][eqIdx];
             }
+
+            // restore the original state of the scv's volume variables
+            curVolVars = origVolVars;
+
+            // restore the current element solution
+            elemSol[0][pvIdx] = origPriVars[pvIdx];
         }
 
-        // Restore original state of the flux vars cache in case of global caching.
+        // restore original state of the flux vars cache in case of global caching.
         // This has to be done in order to guarantee that everything is in an undeflected
         // state before the assembly of another element is called. In the case of local caching
         // this is obsolete because the elemFluxVarsCache used here goes out of scope after this.
+        // We only have to do this for the last primary variable, for all others the flux var cache
+        // is updated with the correct element volume variables before residual evaluations
         if (enableGridFluxVarsCache)
             gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars);
 
-        //////////////////////////////////////////////////////////////////////////////////////////////
-        //                                                                                          //
-        // Calculate derivatives of the dofs in the element with respect to user-defined additional //
-        // dof dependencies. We do so by evaluating the change in the source term of the current    //
-        // element with respect to the primary variables at the given additional dofs.              //
-        //                                                                                          //
-        //////////////////////////////////////////////////////////////////////////////////////////////
-
-        // const auto& additionalDofDepedencies = problem.getAdditionalDofDependencies(globalI);
-        // if (!additionalDofDepedencies.empty() && !isGhost)
-        // {
-        //     // compute the source in the undeflected state
-        //     auto source = localResidual.computeSource(element, fvGeometry, curElemVolVars, scv);
-        //     source *= -scv.volume()*curVolVarsI.extrusionFactor();
-
-        //     // deflect solution at given dofs and recalculate the source
-        //     for (auto globalJ : additionalDofDependencies)
-        //     {
-        //         const auto& scvJ = fvGeometry.scv(globalJ);
-        //         auto& curVolVarsJ = curElemVolVars[scv];
-        //         const auto& elementJ = fvGridGeometry.element(globalJ);
-
-        //         // save a copy of the original privars and volvars
-        //         // to restore original solution after deflection
-        //         const auto origPriVars = curSol[globalJ];
-        //         const auto origVolVarsJ = curVolVarsJ;
-
-        //         // derivatives with repect to the additional DOF we depend on
-        //         for (int pvIdx = 0; pvIdx < numEq; pvIdx++)
-        //         {
-        //             // derivatives of element dof with respect to itself
-        //             NumEqVector partialDeriv(0.0);
-        //             const auto eps = numericEpsilon(curVolVarsJ.priVar(pvIdx));
-        //             Scalar delta = 0;
-
-        //             if (numericDifferenceMethod >= 0)
-        //             {
-        //                 // we are not using backward differences, i.e. we need to
-        //                 // calculate f(x + \epsilon)
-
-        //                 // deflect primary variables
-        //                 curSol[globalJ][pvIdx] += eps;
-        //                 delta += eps;
-
-        //                 // update the volume variables and the flux var cache
-        //                 curVolVarsJ.update(gridVariables.elementSolution(elementJ, curSol), problem, elementJ, scvJ);
-
-        //                 // calculate the source with the deflected primary variables
-        //                 auto deflSource = localResidual.computeSource(element, fvGeometry, curElemVolVars, scv);
-        //                 deflSource *= -scv.volume()*curVolVarsI.extrusionFactor();
-        //                 partialDeriv = std::move(deflSource);
-        //             }
-        //             else
-        //             {
-        //                 // we are using backward differences, i.e. we don't need
-        //                 // to calculate f(x + \epsilon) and we can recycle the
-        //                 // (already calculated) source f(x)
-        //                 partialDeriv = source;
-        //             }
-
-        //             if (numericDifferenceMethod <= 0)
-        //             {
-        //                 // we are not using forward differences, i.e. we
-        //                 // need to calculate f(x - \epsilon)
-
-        //                 // deflect the primary variables
-        //                 curSol[globalJ][pvIdx] -= delta + eps;
-        //                 delta += eps;
-
-        //                 // update the volume variables and the flux var cache
-        //                 curVolVarsJ.update(gridVariables.elementSolution(elementJ, curSol), problem, elementJ, scvJ);
-
-        //                 // calculate the source with the deflected primary variables and subtract
-        //                 auto deflSource = localResidual.computeSource(element, fvGeometry, curElemVolVars, scv);
-        //                 deflSource *= -scv.volume()*curVolVarsI.extrusionFactor();
-        //                 partialDeriv -= std::move(deflSource);
-        //             }
-        //             else
-        //             {
-        //                 // we are using forward differences, i.e. we don't need to
-        //                 // calculate f(x - \epsilon) and we can recycle the
-        //                 // (already calculated) source f(x)
-        //                 partialDeriv -= source;
-        //             }
-
-        //             // divide difference in residuals by the magnitude of the
-        //             // deflections between the two function evaluation
-        //             partialDeriv /= delta;
-
-        //             // restore the original state of the dofs privars and the volume variables
-        //             curSol[globalJ] = origPriVars;
-        //             curVolVarsJ = origVolVarsJ;
-
-        //             // add the current partial derivatives to the global jacobian matrix
-        //             for (int eqIdx = 0; eqIdx < numEq; eqIdx++)
-        //                 A[globalI][globalJ][eqIdx][pvIdx] += partialDeriv[eqIdx];
-        //         }
-        //     }
-        // }
-
         // return the original residual
-        return residual;
+        return origResiduals[0];
     }
-private:
-    template<class T = TypeTag>
-    static typename std::enable_if<!GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return elemVolVars[scv]; }
-
-    template<class T = TypeTag>
-    static typename std::enable_if<GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return gridVolVars.volVars(scv); }
 };
 
 
 /*!
  * \ingroup Assembly
  * \ingroup CCDiscretization
- * \brief Cell-centered scheme local assembler using numeric differentiation and explicits time discretization
+ * \brief Cell-centered scheme local assembler using numeric differentiation and explicit time discretization
  */
-template<class TypeTag>
-class CCLocalAssembler<TypeTag,
-                       DiffMethod::numeric,
-                       /*implicit=*/false>
+template<class TypeTag, class Assembler>
+class CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/false>
+: public CCLocalAssemblerBase<TypeTag, Assembler,
+            CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, false>, false>
 {
+    using ThisType = CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, false>;
+    using ParentType = CCLocalAssemblerBase<TypeTag, Assembler, ThisType, false>;
     using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using NumEqVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
+    using LocalResidualValues = typename GET_PROP_TYPE(TypeTag, NumEqVector);
     using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
-    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
     using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
-    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
 
     enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
 
 public:
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix. The element residual is written into the right hand side.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, jac, element, curSol);
-    }
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, jac, element, curSol);
-    }
-
-    /*!
-     * \brief Assemble the residual only
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, element, curSol);
-    }
-
-    /*!
-     * \brief Computes the epsilon used for numeric differentiation
-     *        for a given value of a primary variable.
-     *
-     * \param priVar The value of the primary variable
-     */
-    static Scalar numericEpsilon(const Scalar priVar)
-    {
-        // define the base epsilon as the geometric mean of 1 and the
-        // resolution of the scalar type. E.g. for standard 64 bit
-        // floating point values, the resolution is about 10^-16 and
-        // the base epsilon is thus approximately 10^-8.
-        /*
-        static const Scalar baseEps
-            = Dumux::geometricMean<Scalar>(std::numeric_limits<Scalar>::epsilon(), 1.0);
-        */
-        static const Scalar baseEps = 1e-10;
-        assert(std::numeric_limits<Scalar>::epsilon()*1e4 < baseEps);
-        // the epsilon value used for the numeric differentiation is
-        // now scaled by the absolute value of the primary variable...
-        return baseEps*(std::abs(priVar) + 1.0);
-    }
-
-private:
-
-    /*!
-     * \brief Computes the residual
-     *
-     * \return The element residual at the current solution.
-     */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler,
-                                 const Element& element, const SolutionVector& curSol)
-    {
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-        if (isGhost) return NumEqVector(0.0);
-
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
-
-        // compatibility with box method
-        ElementBoundaryTypes elemBcTypes;
-
-        // the actual element's previous time step residual
-        auto residual = localResidual.eval(problem,
-                                           element,
-                                           fvGeometry,
-                                           prevElemVolVars,
-                                           elemBcTypes,
-                                           elemFluxVarsCache)[0];
-
-        auto storageResidual = localResidual.evalStorage(problem,
-                                                         element,
-                                                         fvGeometry,
-                                                         prevElemVolVars,
-                                                         curElemVolVars,
-                                                         elemBcTypes,
-                                                         elemFluxVarsCache)[0];
-
-        residual += storageResidual;
-
-        return residual;
-    }
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
@@ -756,65 +311,13 @@ private:
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler, JacobianMatrix& A,
-                                 const Element& element, const SolutionVector& curSol)
+    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        const auto& fvGridGeometry = assembler.fvGridGeometry();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
+        if (this->assembler().isStationaryProblem())
             DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
 
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
-
-        // the global dof of the actual element
-        const auto globalI = fvGridGeometry.elementMapper().index(element);
-
-        // check for boundaries on the element
-        // TODO Do we need them for cell-centered models?
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-
-        // the actual element's previous time step residual
-        NumEqVector residual(0.0), storageResidual(0.0);
-        if (!isGhost)
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache)[0];
-
-            storageResidual = localResidual.evalStorage(problem,
-                                                        element,
-                                                        fvGeometry,
-                                                        prevElemVolVars,
-                                                        curElemVolVars,
-                                                        elemBcTypes,
-                                                        elemFluxVarsCache)[0];
-
-            residual += storageResidual;
-        }
+        // assemble the undeflected residual
+        const auto residual = this->evalLocalResidual()[0];
 
         //////////////////////////////////////////////////////////////////////////////////////////////////
         // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the //
@@ -822,132 +325,66 @@ private:
         // derivatives are non-zero.                                                                    //
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
-        static const std::string group = GET_PROP_VALUE(TypeTag, ModelParameterGroup);
-        static const int numericDifferenceMethod = getParamFromGroup<int>(group, "Implicit.NumericDifferenceMethod");
+        // get some aliases for convenience
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        const auto& fvGridGeometry = this->assembler().fvGridGeometry();
+        auto&& curElemVolVars = this->curElemVolVars();
 
         // reference to the element's scv (needed later) and corresponding vol vars
+        const auto globalI = fvGridGeometry.elementMapper().index(element);
         const auto& scv = fvGeometry.scv(globalI);
-        auto& curVolVars = getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
+        auto& curVolVars = ParentType::getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
 
         // save a copy of the original privars and vol vars in order
         // to restore the original solution after deflection
+        const auto& curSol = this->curSol();
         const auto origPriVars = curSol[globalI];
         const auto origVolVars = curVolVars;
 
         // element solution container to be deflected
-        ElementSolutionVector elemSol({origPriVars});
+        ElementSolutionVector elemSol(origPriVars);
+        LocalResidualValues partialDeriv;
 
         // derivatives in the neighbors with repect to the current elements
-        for (int pvIdx = 0; pvIdx < numEq; pvIdx++)
+        for (int pvIdx = 0; pvIdx < numEq; ++pvIdx)
         {
             // reset derivatives of element dof with respect to itself
-            // as well as neighbor derivatives
-            NumEqVector partialDeriv(0.0);
-
-            if (isGhost)
-                partialDeriv[pvIdx] = 1.0;
-
-            Scalar eps = numericEpsilon(curVolVars.priVar(pvIdx));
-            Scalar delta = 0;
-
-            if (numericDifferenceMethod >= 0)
-            {
-                // we are not using backward differences, i.e. we need to
-                // calculate f(x + \epsilon)
-
-                // deflect primary variables
-                elemSol[0][pvIdx] += eps;
-                delta += eps;
-
-                // update the volume variables and the flux var cache
-                curVolVars.update(elemSol, problem, element, scv);
-
-                // calculate the residual with the deflected primary variables
-                if (!isGhost)
-                {
-                    partialDeriv = localResidual.evalStorage(problem,
-                                                             element,
-                                                             fvGeometry,
-                                                             prevElemVolVars,
-                                                             curElemVolVars,
-                                                             elemBcTypes,
-                                                             elemFluxVarsCache)[0];
-                }
-            }
-            else
-            {
-                // we are using backward differences, i.e. we don't need
-                // to calculate f(x + \epsilon) and we can recycle the
-                // (already calculated) residual f(x)
-                if (!isGhost)
-                    partialDeriv = storageResidual;
-            }
+            partialDeriv = 0.0;
 
-            if (numericDifferenceMethod <= 0)
+            auto evalStorage = [&](Scalar priVar)
             {
-                // we are not using forward differences, i.e. we
-                // need to calculate f(x - \epsilon)
+                // update the volume variables and calculate
+                // the residual with the deflected primary variables
+                elemSol[0][pvIdx] = priVar;
+                curVolVars.update(elemSol, this->problem(), element, scv);
+                return this->evalLocalStorageResidual()[0];
+            };
+
+            // for non-ghosts compute the derivative numerically
+            if (!this->elementIsGhost())
+                NumericDifferentiation::partialDerivative(evalStorage, elemSol[0][pvIdx], partialDeriv, residual);
+
+            // for ghost elements we assemble a 1.0 where the primary variable and zero everywhere else
+            // as we always solve for a delta of the solution with repect to the initial solution this
+            // results in a delta of zero for ghosts
+            else partialDeriv[pvIdx] = 1.0;
 
-                // deflect the primary variables
-                elemSol[0][pvIdx] -= delta + eps;
-                delta += eps;
-
-                // update the volume variables and the flux var cache
-                curVolVars.update(elemSol, problem, element, scv);
-
-                // calculate the residual with the deflected primary variables and subtract it
-                if (!isGhost)
-                {
-                   partialDeriv -= localResidual.evalStorage(problem,
-                                                             element,
-                                                             fvGeometry,
-                                                             prevElemVolVars,
-                                                             curElemVolVars,
-                                                             elemBcTypes,
-                                                             elemFluxVarsCache)[0];
-                }
-            }
-            else
-            {
-                // we are using forward differences, i.e. we don't need to
-                // calculate f(x - \epsilon) and we can recycle the
-                // (already calculated) residual f(x)
-                if (!isGhost)
-                    partialDeriv -= storageResidual;
-            }
-
-            // divide difference in residuals by the magnitude of the
-            // deflections between the two function evaluation
-            if (!isGhost)
-                partialDeriv /= delta;
+            // add the current partial derivatives to the global jacobian matrix
+            // only diagonal entries for explicit jacobians
+            for (int eqIdx = 0; eqIdx < numEq; eqIdx++)
+                A[globalI][globalI][eqIdx][pvIdx] += partialDeriv[eqIdx];
 
             // restore the original state of the scv's volume variables
             curVolVars = origVolVars;
 
             // restore the current element solution
             elemSol[0][pvIdx] = origPriVars[pvIdx];
-
-            // add the current partial derivatives to the global jacobian matrix
-            for (int eqIdx = 0; eqIdx < numEq; eqIdx++)
-            {
-                // the diagonal entries
-                A[globalI][globalI][eqIdx][pvIdx] += partialDeriv[eqIdx];
-            }
         }
 
         // return the original residual
         return residual;
     }
-private:
-    template<class T = TypeTag>
-    static typename std::enable_if<!GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return elemVolVars[scv]; }
-
-    template<class T = TypeTag>
-    static typename std::enable_if<GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), VolumeVariables&>::type
-    getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
-    { return gridVolVars.volVars(scv); }
 };
 
 /*!
@@ -955,123 +392,19 @@ private:
  * \ingroup CCDiscretization
  * \brief Cell-centered scheme local assembler using analytic (hand-coded) differentiation and implicit time discretization
  */
-template<class TypeTag>
-class CCLocalAssembler<TypeTag,
-                       DiffMethod::analytic,
-                       /*implicit=*/true>
+template<class TypeTag, class Assembler>
+class CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/true>
+: public CCLocalAssemblerBase<TypeTag, Assembler,
+            CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true>, true>
 {
-    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using NumEqVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
-    using IndexType = typename GET_PROP_TYPE(TypeTag, GridView)::IndexSet::IndexType;
-    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
+    using ThisType = CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true>;
+    using ParentType = CCLocalAssemblerBase<TypeTag, Assembler, ThisType, true>;
+    using LocalResidualValues = typename GET_PROP_TYPE(TypeTag, NumEqVector);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
-    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
-    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
-
-    enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
 
 public:
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix. The element residual is written into the right hand side.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, jac, element, curSol);
-    }
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, jac, element, curSol);
-    }
-
-    /*!
-     * \brief Assemble the residual only
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, element, curSol);
-    }
-
-private:
-
-    /*!
-     * \brief Computes the residual
-     *
-     * \return The element residual at the current solution.
-     */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler,
-                                 const Element& element, const SolutionVector& curSol)
-    {
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-        if (isGhost) return NumEqVector(0.0);
-
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-
-        // check for boundaries on the element
-        // TODO Do we need them for cell-centered models?
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        NumEqVector residual(0.0);
-        if (localResidual.isStationary())
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache)[0];
-        }
-        else
-        {
-            prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          curElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache)[0];
-        }
-
-        return residual;
-    }
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
@@ -1079,137 +412,55 @@ private:
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler, JacobianMatrix& A,
-                                 const Element& element, const SolutionVector& curSol)
+    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, const GridVariables& gridVariables)
     {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        const auto& fvGridGeometry = assembler.fvGridGeometry();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bind(element, fvGeometry, curSol);
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-
-        // the global dof of the actual element
-        const auto globalI = fvGridGeometry.elementMapper().index(element);
-
-        // check for boundaries on the element
-        // TODO Do we need them for cell-centered models?
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-        // is this a stationary simulation?
-        const bool isStationary = localResidual.isStationary();
+        // assemble the undeflected residual
+        const auto residual = this->evalLocalResidual()[0];
 
-        // the actual element's current residual (will be returned by this function)
-        NumEqVector residual(0.0);
-        if (!isGhost)
-        {
-            if (isStationary)
-            {
-                residual = localResidual.eval(problem,
-                                              element,
-                                              fvGeometry,
-                                              curElemVolVars,
-                                              elemBcTypes,
-                                              elemFluxVarsCache)[0];
-            }
-            else
-            {
-                prevElemVolVars.bindElement(element, fvGeometry, localResidual.prevSol());
-                residual = localResidual.eval(problem,
-                                              element,
-                                              fvGeometry,
-                                              prevElemVolVars,
-                                              curElemVolVars,
-                                              elemBcTypes,
-                                              elemFluxVarsCache)[0];
-            }
-        }
+        // get some aliases for convenience
+        const auto& problem = this->problem();
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        auto&& curElemVolVars = this->curElemVolVars();
+        auto&& elemFluxVarsCache = this->elemFluxVarsCache();
 
         // get reference to the element's current vol vars
+        const auto globalI = this->assembler().fvGridGeometry().elementMapper().index(element);
         const auto& scv = fvGeometry.scv(globalI);
         const auto& volVars = curElemVolVars[scv];
 
         // if the problem is instationary, add derivative of storage term
-        if (!isStationary)
-            localResidual.addStorageDerivatives(A[globalI][globalI],
-                                                problem,
-                                                element,
-                                                fvGeometry,
-                                                volVars,
-                                                scv);
+        if (!this->assembler().isStationaryProblem())
+            this->localResidual().addStorageDerivatives(A[globalI][globalI], problem, element, fvGeometry, volVars, scv);
 
         // add source term derivatives
-        localResidual.addSourceDerivatives(A[globalI][globalI],
-                                           problem,
-                                           element,
-                                           fvGeometry,
-                                           volVars,
-                                           scv);
+        this->localResidual().addSourceDerivatives(A[globalI][globalI], problem, element, fvGeometry, volVars, scv);
 
         // add flux derivatives for each scvf
         for (const auto& scvf : scvfs(fvGeometry))
         {
+            // inner faces
             if (!scvf.boundary())
-            {
-                localResidual.addFluxDerivatives(A[globalI],
-                                                 problem,
-                                                 element,
-                                                 fvGeometry,
-                                                 curElemVolVars,
-                                                 elemFluxVarsCache,
-                                                 scvf);
-            }
+                this->localResidual().addFluxDerivatives(A[globalI], problem, element, fvGeometry, curElemVolVars, elemFluxVarsCache, scvf);
+
+            // boundary faces
             else
             {
                 const auto& bcTypes = problem.boundaryTypes(element, scvf);
 
                 // add Dirichlet boundary flux derivatives
                 if (bcTypes.hasDirichlet() && !bcTypes.hasNeumann())
-                {
-                    localResidual.addCCDirichletFluxDerivatives(A[globalI],
-                                                                problem,
-                                                                element,
-                                                                fvGeometry,
-                                                                curElemVolVars,
-                                                                elemFluxVarsCache,
-                                                                scvf);
-                }
+                    this->localResidual().addCCDirichletFluxDerivatives(A[globalI], problem, element, fvGeometry, curElemVolVars, elemFluxVarsCache, scvf);
+
                 // add Robin ("solution dependent Neumann") boundary flux derivatives
                 else if (bcTypes.hasNeumann() && !bcTypes.hasDirichlet())
-                {
-                    localResidual.addRobinFluxDerivatives(A[globalI],
-                                                          problem,
-                                                          element,
-                                                          fvGeometry,
-                                                          curElemVolVars,
-                                                          elemFluxVarsCache,
-                                                          scvf);
-                }
+                    this->localResidual().addRobinFluxDerivatives(A[globalI], problem, element, fvGeometry, curElemVolVars, elemFluxVarsCache, scvf);
+
                 else
                     DUNE_THROW(Dune::NotImplemented, "Mixed boundary conditions. Use pure boundary conditions by converting Dirichlet BCs to Robin BCs");
             }
         }
 
-        // TODO Do we really need this??????????
-        // this->model_().updatePVWeights(fvGeometry);
-
-        // TODO: Additional dof dependencies???
-
         // return element residual
         return residual;
     }
@@ -1220,202 +471,38 @@ private:
  * \ingroup CCDiscretization
  * \brief Cell-centered scheme local assembler using analytic (hand-coded) differentiation and explicit time discretization
  */
-template<class TypeTag>
-class CCLocalAssembler<TypeTag,
-                       DiffMethod::analytic,
-                       /*implicit=*/false>
+template<class TypeTag, class Assembler>
+class CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/false>
+: public CCLocalAssemblerBase<TypeTag, Assembler,
+            CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false>, false>
 {
-    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using NumEqVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
-    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
-    using IndexType = typename GET_PROP_TYPE(TypeTag, GridView)::IndexSet::IndexType;
-    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
+    using ThisType = CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false>;
+    using ParentType = CCLocalAssemblerBase<TypeTag, Assembler, ThisType, false>;
+    using LocalResidualValues = typename GET_PROP_TYPE(TypeTag, NumEqVector);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
-    using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector);
-    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
-    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
-
-    enum { numEq = GET_PROP_VALUE(TypeTag, NumEq) };
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
 
 public:
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix. The element residual is written into the right hand side.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, jac, element, curSol);
-    }
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
      *        to the global matrix.
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, JacobianMatrix& jac,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        assemble_(assembler, jac, element, curSol);
-    }
-
-    /*!
-     * \brief Assemble the residual only
-     */
-    template<class Assembler>
-    static void assemble(Assembler& assembler, SolutionVector& res,
-                         const Element& element, const SolutionVector& curSol)
-    {
-        const auto globalI = assembler.fvGridGeometry().elementMapper().index(element);
-        res[globalI] = assemble_(assembler, element, curSol);
-    }
-
-private:
-
-    /*!
-     * \brief Computes the residual
      *
      * \return The element residual at the current solution.
      */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler,
-                                 const Element& element, const SolutionVector& curSol)
+    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, const GridVariables& gridVariables)
     {
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-        if (isGhost) return NumEqVector(0.0);
-
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
-
-        // check for boundaries on the element
-        // TODO Do we need them for cell-centered models?
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // the actual element's previous time step residual
-        auto residual = localResidual.eval(problem,
-                                           element,
-                                           fvGeometry,
-                                           prevElemVolVars,
-                                           elemBcTypes,
-                                           elemFluxVarsCache)[0];
-
-        auto storageResidual = localResidual.evalStorage(problem,
-                                                         element,
-                                                         fvGeometry,
-                                                         prevElemVolVars,
-                                                         curElemVolVars,
-                                                         elemBcTypes,
-                                                         elemFluxVarsCache)[0];
-
-        residual += storageResidual;
-
-        return residual;
-    }
-
-    /*!
-     * \brief Computes the derivatives with respect to the given element and adds them
-     *        to the global matrix.
-     *
-     * \return The element residual at the current solution.
-     */
-    template<class Assembler>
-    static NumEqVector assemble_(Assembler& assembler, JacobianMatrix& A,
-                                 const Element& element, const SolutionVector& curSol)
-    {
-        // get some references for convenience
-        const auto& problem = assembler.problem();
-        const auto& fvGridGeometry = assembler.fvGridGeometry();
-        auto& localResidual = assembler.localResidual();
-        auto& gridVariables = assembler.gridVariables();
-
-        // using an explicit assembler doesn't make sense for stationary problems
-        if (localResidual.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // prepare the local views
-        auto fvGeometry = localView(assembler.fvGridGeometry());
-        fvGeometry.bind(element);
-
-        auto curElemVolVars = localView(gridVariables.curGridVolVars());
-        curElemVolVars.bindElement(element, fvGeometry, curSol);
-
-        auto prevElemVolVars = localView(gridVariables.prevGridVolVars());
-        prevElemVolVars.bind(element, fvGeometry, localResidual.prevSol());
-
-        auto elemFluxVarsCache = localView(gridVariables.gridFluxVarsCache());
-        elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
-
-        // the global dof of the actual element
-        const auto globalI = fvGridGeometry.elementMapper().index(element);
-
-        // check for boundaries on the element
-        // TODO Do we need them for cell-centered models?
-        ElementBoundaryTypes elemBcTypes;
-        elemBcTypes.update(problem, element, fvGeometry);
-
-        // is the actual element a ghost element?
-        const bool isGhost = (element.partitionType() == Dune::GhostEntity);
-
-        // the actual element's previous time step residual
-        NumEqVector residual(0.0), storageResidual(0.0);
-        if (!isGhost)
-        {
-            residual = localResidual.eval(problem,
-                                          element,
-                                          fvGeometry,
-                                          prevElemVolVars,
-                                          elemBcTypes,
-                                          elemFluxVarsCache)[0];
-
-            storageResidual = localResidual.evalStorage(problem,
-                                                        element,
-                                                        fvGeometry,
-                                                        prevElemVolVars,
-                                                        curElemVolVars,
-                                                        elemBcTypes,
-                                                        elemFluxVarsCache)[0];
-
-            residual += storageResidual;
-        }
+        // assemble the undeflected residual
+        const auto residual = this->evalLocalResidual()[0];
 
         // get reference to the element's current vol vars
-        const auto& scv = fvGeometry.scv(globalI);
-        const auto& volVars = curElemVolVars[scv];
+        const auto globalI = this->assembler().fvGridGeometry().elementMapper().index(this->element());
+        const auto& scv = this->fvGeometry().scv(globalI);
+        const auto& volVars = this->curElemVolVars()[scv];
 
-        // add derivative of storage term
-        localResidual.addStorageDerivatives(A[globalI][globalI],
-                                            problem,
-                                            element,
-                                            fvGeometry,
-                                            volVars,
-                                            scv);
+        // add hand-code derivative of storage term
+        this->localResidual().addStorageDerivatives(A[globalI][globalI], this->problem(), this->element(), this->fvGeometry(), volVars, scv);
 
         // return the original residual
         return residual;
diff --git a/dumux/assembly/cclocalresidual.hh b/dumux/assembly/cclocalresidual.hh
index bc13dd2a8961fe643a804fab43c520c7b0f22aa7..c90d2d47765ea392936d19076fab772adf8488ad 100644
--- a/dumux/assembly/cclocalresidual.hh
+++ b/dumux/assembly/cclocalresidual.hh
@@ -25,8 +25,7 @@
 #ifndef DUMUX_CC_LOCAL_RESIDUAL_HH
 #define DUMUX_CC_LOCAL_RESIDUAL_HH
 
-#include <dune/istl/matrix.hh>
-
+#include <dumux/common/reservedblockvector.hh>
 #include <dumux/common/properties.hh>
 #include <dumux/assembly/fvlocalresidual.hh>
 
@@ -43,7 +42,6 @@ class CCLocalResidual : public FVLocalResidual<TypeTag>
     using ParentType = FVLocalResidual<TypeTag>;
     using Problem = typename GET_PROP_TYPE(TypeTag, Problem);
     using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
     using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
     using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
     using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
@@ -52,6 +50,7 @@ class CCLocalResidual : public FVLocalResidual<TypeTag>
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
 
 public:
+    using ElementResidualVector = typename ParentType::ElementResidualVector;
     using ParentType::ParentType;
 
     //! evaluate the flux residual for a sub control volume face and add to residual
diff --git a/dumux/assembly/fvassembler.hh b/dumux/assembly/fvassembler.hh
index d8142264a3c4bbc22864c6633c7064cf14340a34..4310071b2a7d97005dc21e045ac3cd38a330c8a6 100644
--- a/dumux/assembly/fvassembler.hh
+++ b/dumux/assembly/fvassembler.hh
@@ -41,10 +41,10 @@ namespace Dumux {
 
 /*!
  * \ingroup Assembly
- * \brief A linear system assembler (residual and Jacobian) for finite volume schemes
- * \tparam TypeTag the TypeTag
- * \tparam diffMethod the differentiation method to residual compute derivatives
- * \tparam isImplicit if to use an implicit or explicit time discretization
+ * \brief A linear system assembler (residual and Jacobian) for finite volume schemes (box, tpfa, mpfa, ...)
+ * \tparam TypeTag The TypeTag
+ * \tparam diffMethod The differentiation method to residual compute derivatives
+ * \tparam isImplicit Specifies whether the time discretization is implicit or not not (i.e. explicit)
  */
 template<class TypeTag, DiffMethod diffMethod, bool isImplicit = true>
 class FVAssembler
@@ -52,44 +52,55 @@ class FVAssembler
     using Problem = typename GET_PROP_TYPE(TypeTag, Problem);
     using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
     using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
-    using LocalResidual = typename GET_PROP_TYPE(TypeTag, LocalResidual);
     using VertexMapper = typename GET_PROP_TYPE(TypeTag, VertexMapper);
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
     using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
     using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
+    using LocalResidual = typename GET_PROP_TYPE(TypeTag, LocalResidual);
+    using Element = typename GridView::template Codim<0>::Entity;
     using TimeLoop = TimeLoopBase<Scalar>;
 
-    static constexpr int dim = GridView::dimension;
     static constexpr bool isBox = GET_PROP_VALUE(TypeTag, DiscretizationMethod) == DiscretizationMethods::Box;
-    using LocalAssembler = std::conditional_t<isBox, BoxLocalAssembler<TypeTag, diffMethod, isImplicit>,
-                                                     CCLocalAssembler<TypeTag, diffMethod, isImplicit>>;
+
+    using ThisType = FVAssembler<TypeTag, diffMethod, isImplicit>;
+    using LocalAssembler = std::conditional_t<isBox, BoxLocalAssembler<TypeTag, ThisType, diffMethod, isImplicit>,
+                                                     CCLocalAssembler<TypeTag, ThisType, diffMethod, isImplicit>>;
 
 public:
     using ResidualType = SolutionVector;
 
-    //! The constructor for stationary problems
+    /*!
+     * \brief The constructor for stationary problems
+     * \note the grid variables might be temporarily changed during assembly (if caching is enabled)
+     *       it is however guaranteed that the state after assembly will be the same as before
+     */
     FVAssembler(std::shared_ptr<const Problem> problem,
                 std::shared_ptr<const FVGridGeometry> fvGridGeometry,
                 std::shared_ptr<GridVariables> gridVariables)
     : problem_(problem)
     , fvGridGeometry_(fvGridGeometry)
     , gridVariables_(gridVariables)
-    , stationary_(true)
+    , timeLoop_()
+    , isStationaryProblem_(true)
     {
         static_assert(isImplicit, "Explicit assembler for stationary problem doesn't make sense!");
     }
 
-    //! The constructor for instationary problems
+    /*!
+     * \brief The constructor for instationary problems
+     * \note the grid variables might be temporarily changed during assembly (if caching is enabled)
+     *       it is however guaranteed that the state after assembly will be the same as before
+     */
     FVAssembler(std::shared_ptr<const Problem> problem,
                 std::shared_ptr<const FVGridGeometry> fvGridGeometry,
                 std::shared_ptr<GridVariables> gridVariables,
-                std::shared_ptr<TimeLoop> timeLoop)
+                std::shared_ptr<const TimeLoop> timeLoop)
     : problem_(problem)
     , fvGridGeometry_(fvGridGeometry)
     , gridVariables_(gridVariables)
-    , localResidual_(timeLoop)
-    , stationary_(false)
+    , timeLoop_(timeLoop)
+    , isStationaryProblem_(false)
     {}
 
     /*!
@@ -98,50 +109,15 @@ public:
      */
     void assembleJacobianAndResidual(const SolutionVector& curSol)
     {
-        if (!stationary_ && localResidual_.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Assembling instationary problem but previous solution was not set!");
-
-        if(!jacobian_)
-        {
-            jacobian_ = std::make_shared<JacobianMatrix>();
-            jacobian_->setBuildMode(JacobianMatrix::random);
-            setJacobianPattern();
-        }
-
-        if(!residual_)
-        {
-            residual_ = std::make_shared<SolutionVector>();
-            setResidualSize();
-        }
-
+        checkAssemblerState_();
         resetJacobian_();
         resetResidual_();
 
-        bool succeeded;
-        // try assembling the global linear system
-        try
+        assemble_([&](const Element& element)
         {
-            // let the local assembler add the element contributions
-            for (const auto element : elements(gridView()))
-                LocalAssembler::assemble(*this, *jacobian_, *residual_, element, curSol);
-
-            // if we get here, everything worked well
-            succeeded = true;
-            if (gridView().comm().size() > 1)
-                succeeded = gridView().comm().min(succeeded);
-        }
-        // throw exception if a problem ocurred
-        catch (NumericalProblem &e)
-        {
-            std::cout << "rank " << gridView().comm().rank()
-                      << " caught an exception while assembling:" << e.what()
-                      << "\n";
-            succeeded = false;
-            if (gridView().comm().size() > 1)
-                succeeded = gridView().comm().min(succeeded);
-        }
-        if (!succeeded)
-            DUNE_THROW(NumericalProblem, "A process did not succeed in linearizing the system");
+            LocalAssembler localAssembler(*this, element, curSol);
+            localAssembler.assembleJacobianAndResidual(*jacobian_, *residual_, *gridVariables_);
+        });
     }
 
     /*!
@@ -149,72 +125,39 @@ public:
      */
     void assembleJacobian(const SolutionVector& curSol)
     {
-        if (!stationary_ && localResidual_.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Assembling instationary problem but previous solution was not set!");
-
-        if(!jacobian_)
-        {
-            jacobian_ = std::make_shared<JacobianMatrix>();
-            jacobian_->setBuildMode(JacobianMatrix::random);
-            setJacobianPattern();
-        }
-
+        checkAssemblerState_();
         resetJacobian_();
 
-        bool succeeded;
-        // try assembling the global linear system
-        try
-        {
-            // let the local assembler add the element contributions
-            for (const auto element : elements(gridView()))
-                LocalAssembler::assemble(*this, *jacobian_, element, curSol);
-
-            // if we get here, everything worked well
-            succeeded = true;
-            if (gridView().comm().size() > 1)
-                succeeded = gridView().comm().min(succeeded);
-        }
-        // throw exception if a problem ocurred
-        catch (NumericalProblem &e)
+        assemble_([&](const Element& element)
         {
-            std::cout << "rank " << gridView().comm().rank()
-                      << " caught an exception while assembling:" << e.what()
-                      << "\n";
-            succeeded = false;
-            if (gridView().comm().size() > 1)
-                succeeded = gridView().comm().min(succeeded);
-        }
-        if (!succeeded)
-            DUNE_THROW(NumericalProblem, "A process did not succeed in linearizing the system");
+            LocalAssembler localAssembler(*this, element, curSol);
+            localAssembler.assembleJacobianAndResidual(*jacobian_, *gridVariables_);
+        });
     }
 
-    //! compute the residuals
+    //! compute the residuals using the internal residual
     void assembleResidual(const SolutionVector& curSol)
     {
-        if(!residual_)
-        {
-            residual_ = std::make_shared<SolutionVector>();
-            setResidualSize();
-        }
-
+        resetResidual_();
         assembleResidual(*residual_, curSol);
     }
 
-    //! compute the residuals
+    //! assemble a residual r
     void assembleResidual(ResidualType& r, const SolutionVector& curSol) const
     {
-        if (!stationary_ && localResidual_.isStationary())
-            DUNE_THROW(Dune::InvalidStateException, "Assembling instationary problem but previous solution was not set!");
+        checkAssemblerState_();
 
         // update the grid variables for the case of active caching
         gridVariables_->update(curSol);
 
-        // let the local assembler add the element contributions
-        for (const auto element : elements(gridView()))
-            LocalAssembler::assemble(*this, r, element, curSol);
+        assemble_([&](const Element& element)
+        {
+            LocalAssembler localAssembler(*this, element, curSol);
+            localAssembler.assembleResidual(r);
+        });
     }
 
-    //! computes the residual norm
+    //! compute the residual and return it's vector norm
     Scalar residualNorm(const SolutionVector& curSol) const
     {
         ResidualType residual(numDofs());
@@ -262,170 +205,256 @@ public:
 
     /*!
      * \brief The version without arguments uses the default constructor to create
-     *        the jacobian and residual objects in this assembler.
+     *        the jacobian and residual objects in this assembler if you don't need them outside this class
      */
     void setLinearSystem()
     {
         jacobian_ = std::make_shared<JacobianMatrix>();
         jacobian_->setBuildMode(JacobianMatrix::random);
-
         residual_ = std::make_shared<SolutionVector>();
 
         setJacobianPattern();
         setResidualSize();
     }
 
-    /*!
-     * \brief Sets the solution from which to start the time integration. Has to be
-     *        called prior to assembly for time-dependent problems.
-     */
-    void setPreviousSolution(const SolutionVector& u)
-    { localResidual_.setPreviousSolution(u); }
-
-    /*!
-     * \brief Return the solution that has been set as the previous one.
-     */
-    const SolutionVector& prevSol() const
-    { return localResidual_.prevSol(); }
-
     /*!
      * \brief Resizes the jacobian and sets the jacobian' sparsity pattern.
      */
-
     void setJacobianPattern()
     {
         // resize the jacobian and the residual
         const auto numDofs = this->numDofs();
         jacobian_->setSize(numDofs, numDofs);
 
-        // get occupation pattern of the jacobian
+        // create occupation pattern of the jacobian
         Dune::MatrixIndexSet occupationPattern;
         occupationPattern.resize(numDofs, numDofs);
 
-        // matrix pattern for implicit jacobians
-        if (isImplicit)
-            setImplicitJacobianPattern_(occupationPattern, numDofs);
-
-        // matrix pattern for explicit jacobians -> diagonal matrix
-        else
-            for (unsigned int globalI = 0; globalI < numDofs; ++globalI)
-                occupationPattern.add(globalI, globalI);
+        // set the jacobian pattern depending on space and time discretization
+        setJacobianPattern_(occupationPattern, numDofs);
 
         // export pattern to jacobian
         occupationPattern.exportIdx(*jacobian_);
     }
 
-    /*!
-     * \brief Resizes the residual
-     */
+    //! Resizes the residual
     void setResidualSize()
     { residual_->resize(numDofs()); }
 
-    //! cell-centered schemes have one dof per cell
+    //! Returns the number of degrees of freedom
     std::size_t numDofs() const
     { return fvGridGeometry_->numDofs(); }
 
+    //! The problem
     const Problem& problem() const
     { return *problem_; }
 
+    //! The global finite volume geometry
     const FVGridGeometry& fvGridGeometry() const
     { return *fvGridGeometry_; }
 
+    //! The gridview
     const GridView& gridView() const
     { return fvGridGeometry().gridView(); }
 
+    //! The global grid variables
     GridVariables& gridVariables()
     { return *gridVariables_; }
 
+    //! The global grid variables
     const GridVariables& gridVariables() const
     { return *gridVariables_; }
 
+    //! The jacobian matrix
     JacobianMatrix& jacobian()
-    {
-       if (!residual_)
-            DUNE_THROW(Dune::InvalidStateException, "No jacobian was set.");
-        return *jacobian_;
-    }
+    { return *jacobian_; }
 
+    //! The residual vector (rhs)
     SolutionVector& residual()
-    {
-        if (!residual_)
-            DUNE_THROW(Dune::InvalidStateException, "No residual was set.");
-        return *residual_;
-    }
+    { return *residual_; }
+
+    //! The solution of the previous time step
+    const SolutionVector& prevSol() const
+    { return *prevSol_; }
+
+    /*!
+     * \brief Set time loop for instationary problems
+     * \note calling this turns this into a stationary assembler
+     */
+    void setTimeManager(std::shared_ptr<const TimeLoop> timeLoop)
+    { timeLoop_ = timeLoop_; isStationaryProblem_ = true; }
 
-    const LocalResidual& localResidual() const
-    { return localResidual_; }
+    /*!
+     * \brief Sets the solution from which to start the time integration. Has to be
+     *        called prior to assembly for time-dependent problems.
+     */
+    void setPreviousSolution(const SolutionVector& u)
+    { prevSol_ = &u;  }
+
+    /*!
+     * \brief Whether we are assembling a stationary or instationary problem
+     */
+    bool isStationaryProblem() const
+    { return isStationaryProblem_; }
+
+    /*!
+     * \brief Create a local residual object (used by the local assembler)
+     */
+    LocalResidual localResidual() const
+    { return LocalResidual(problem_.get(), timeLoop_.get()); }
 
 private:
-    // reset the residual to 0.0
+    // reset the residual vector to 0.0
     void resetResidual_()
     {
+        if(!residual_)
+        {
+            residual_ = std::make_shared<SolutionVector>();
+            setResidualSize();
+        }
+
         (*residual_) = 0.0;
     }
 
-    // reset the jacobian to 0.0
+    // reset the jacobian vector to 0.0
     void resetJacobian_()
     {
+        if(!jacobian_)
+        {
+            jacobian_ = std::make_shared<JacobianMatrix>();
+            jacobian_->setBuildMode(JacobianMatrix::random);
+            setJacobianPattern();
+        }
+
        (*jacobian_)  = 0.0;
     }
 
+    // check if the assembler is in a correct state for assembly
+    void checkAssemblerState_() const
+    {
+        if (!isStationaryProblem_ && !prevSol_)
+            DUNE_THROW(Dune::InvalidStateException, "Assembling instationary problem but previous solution was not set!");
+    }
+
     //! Implicit jacobian pattern for cell-centered fv schemes
-    template<typename T = TypeTag>
-    std::enable_if_t<GET_PROP_VALUE(T, DiscretizationMethod) != DiscretizationMethods::Box, void>
-    setImplicitJacobianPattern_(Dune::MatrixIndexSet& pattern, std::size_t numDofs)
+    template<class T = TypeTag, typename std::enable_if_t<GET_PROP_VALUE(T, DiscretizationMethod) != DiscretizationMethods::Box, int> = 0>
+    void setJacobianPattern_(Dune::MatrixIndexSet& pattern, std::size_t numDofs)
     {
-        for (unsigned int globalI = 0; globalI < numDofs; ++globalI)
+        // matrix pattern for implicit Jacobians
+        if (isImplicit)
         {
-            pattern.add(globalI, globalI);
-            for (const auto& dataJ : fvGridGeometry().connectivityMap()[globalI])
-                pattern.add(dataJ.globalJ, globalI);
-
-            // reserve index for additional user defined DOF dependencies
-            // const auto& additionalDofDependencies = problem().getAdditionalDofDependencies(globalI);
-            // for (auto globalJ : additionalDofDependencies)
-            //     pattern.add(globalI, globalJ);
+            for (unsigned int globalI = 0; globalI < numDofs; ++globalI)
+            {
+                pattern.add(globalI, globalI);
+                for (const auto& dataJ : fvGridGeometry().connectivityMap()[globalI])
+                    pattern.add(dataJ.globalJ, globalI);
+
+                // reserve index for additional user defined DOF dependencies
+                // const auto& additionalDofDependencies = problem().getAdditionalDofDependencies(globalI);
+                // for (auto globalJ : additionalDofDependencies)
+                //     pattern.add(globalI, globalJ);
+            }
+        }
+
+        // matrix pattern for explicit Jacobians -> diagonal matrix
+        else
+        {
+            for (unsigned int globalI = 0; globalI < numDofs; ++globalI)
+                pattern.add(globalI, globalI);
         }
     }
 
     //! Implicit jacobian pattern for vertex-centered fv schemes
-    template<typename T = TypeTag>
-    std::enable_if_t<GET_PROP_VALUE(T, DiscretizationMethod) == DiscretizationMethods::Box, void>
-    setImplicitJacobianPattern_(Dune::MatrixIndexSet& pattern, std::size_t numDofs)
+    template<class T = TypeTag, typename std::enable_if_t<GET_PROP_VALUE(T, DiscretizationMethod) == DiscretizationMethods::Box, int> = 0>
+    void setJacobianPattern_(Dune::MatrixIndexSet& pattern, std::size_t numDofs)
     {
-        for (const auto& element : elements(fvGridGeometry().gridView()))
+        // matrix pattern for implicit Jacobians
+        if (isImplicit)
         {
-            for (unsigned int vIdx = 0; vIdx < element.subEntities(dim); ++vIdx)
+            static constexpr int dim = GridView::dimension;
+            for (const auto& element : elements(fvGridGeometry().gridView()))
             {
-                const auto globalI = fvGridGeometry().vertexMapper().subIndex(element, vIdx, dim);
-                for (unsigned int vIdx2 = vIdx; vIdx2 < element.subEntities(dim); ++vIdx2)
+                for (unsigned int vIdx = 0; vIdx < element.subEntities(dim); ++vIdx)
                 {
-                    const auto globalJ = fvGridGeometry().vertexMapper().subIndex(element, vIdx2, dim);
-                    pattern.add(globalI, globalJ);
-                    pattern.add(globalJ, globalI);
+                    const auto globalI = fvGridGeometry().vertexMapper().subIndex(element, vIdx, dim);
+                    for (unsigned int vIdx2 = vIdx; vIdx2 < element.subEntities(dim); ++vIdx2)
+                    {
+                        const auto globalJ = fvGridGeometry().vertexMapper().subIndex(element, vIdx2, dim);
+                        pattern.add(globalI, globalJ);
+                        pattern.add(globalJ, globalI);
+                    }
                 }
             }
         }
+
+        // matrix pattern for explicit Jacobians -> diagonal matrix
+        else
+        {
+            for (unsigned int globalI = 0; globalI < numDofs; ++globalI)
+                pattern.add(globalI, globalI);
+        }
+    }
+
+    /*!
+     * \brief A method assembling something per element
+     * \note Handles exceptions for parallel runs
+     * \throws NumericalProblem on all processes if something throwed during assembly
+     */
+    template<typename AssembleElementFunc>
+    void assemble_(AssembleElementFunc&& assembleElement) const
+    {
+        // a state that will be checked on all processes
+        bool succeeded = false;
+
+        // try assembling using the local assembly function
+        try
+        {
+            // let the local assembler add the element contributions
+            for (const auto& element : elements(gridView()))
+                assembleElement(element);
+
+            // if we get here, everything worked well on this process
+            succeeded = true;
+        }
+        // throw exception if a problem ocurred
+        catch (NumericalProblem &e)
+        {
+            std::cout << "rank " << gridView().comm().rank()
+                      << " caught an exception while assembling:" << e.what()
+                      << "\n";
+            succeeded = false;
+        }
+
+        // make sure everything worked well on all processes
+        if (gridView().comm().size() > 1)
+            succeeded = gridView().comm().min(succeeded);
+
+        // if not succeeded rethrow the error on all processes
+        if (!succeeded)
+            DUNE_THROW(NumericalProblem, "A process did not succeed in linearizing the system");
     }
 
-    // pointer to the problem to be solved
+    //! pointer to the problem to be solved
     std::shared_ptr<const Problem> problem_;
 
-    // the finite volume geometry of the grid
+    //! the finite volume geometry of the grid
     std::shared_ptr<const FVGridGeometry> fvGridGeometry_;
 
-    // the variables container for the grid
+    //! the variables container for the grid
     std::shared_ptr<GridVariables> gridVariables_;
 
-    // shared pointers to the jacobian matrix and residual
-    std::shared_ptr<JacobianMatrix> jacobian_;
-    std::shared_ptr<SolutionVector> residual_;
+    //! the time loop for instationary problem assembly
+    std::shared_ptr<const TimeLoop> timeLoop_;
+
+    //! an observing pointer to the previous solution for instationary problems
+    const SolutionVector* prevSol_ = nullptr;
 
-    // class computing the residual of an element
-    LocalResidual localResidual_;
+    //! if this assembler is assembling an instationary problem
+    bool isStationaryProblem_;
 
-    // if this assembler is assembling a time dependent problem
-    bool stationary_;
+    //! shared pointers to the jacobian matrix and residual
+    std::shared_ptr<JacobianMatrix> jacobian_;
+    std::shared_ptr<SolutionVector> residual_;
 };
 
 } // namespace Dumux
diff --git a/dumux/assembly/fvlocalassemblerbase.hh b/dumux/assembly/fvlocalassemblerbase.hh
new file mode 100644
index 0000000000000000000000000000000000000000..dbae670f0b5ca590c0bb6867d820e56afccde073
--- /dev/null
+++ b/dumux/assembly/fvlocalassemblerbase.hh
@@ -0,0 +1,290 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/*****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program is free software: you can redistribute it and/or modify    *
+ *   it under the terms of the GNU General Public License as published by    *
+ *   the Free Software Foundation, either version 2 of the License, or       *
+ *   (at your option) any later version.                                     *
+ *                                                                           *
+ *   This program is distributed in the hope that it will be useful,         *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
+ *   GNU General Public License for more details.                            *
+ *                                                                           *
+ *   You should have received a copy of the GNU General Public License       *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \ingroup Assembly
+ * \copydoc Dumux::FVLocalAssemblerBase
+ */
+#ifndef DUMUX_FV_LOCAL_ASSEMBLER_BASE_HH
+#define DUMUX_FV_LOCAL_ASSEMBLER_BASE_HH
+
+#include <dune/common/reservedvector.hh>
+#include <dune/grid/common/gridenums.hh> // for GhostEntity
+#include <dune/istl/matrixindexset.hh>
+
+#include <dumux/common/reservedblockvector.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/common/parameters.hh>
+#include <dumux/assembly/diffmethod.hh>
+#include <dumux/assembly/numericdifferentiation.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup Assembly
+ * \brief A base class for all local assemblers
+ * \tparam TypeTag The TypeTag
+ * \tparam Assembler The assembler type
+ * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit)
+ */
+template<class TypeTag, class Assembler, class Implementation, bool implicit>
+class FVLocalAssemblerBase
+{
+    using Problem = typename GET_PROP_TYPE(TypeTag, Problem);
+    using LocalResidual = typename GET_PROP_TYPE(TypeTag, LocalResidual);
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
+    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
+    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
+    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
+    using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
+    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
+    using GridVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables);
+    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
+    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
+    using ElementFluxVariablesCache = typename GET_PROP_TYPE(TypeTag, ElementFluxVariablesCache);
+    using Element = typename GridView::template Codim<0>::Entity;
+
+public:
+
+    /*!
+     * \brief The constructor
+     */
+    explicit FVLocalAssemblerBase(const Assembler& assembler,
+                                  const Element& element,
+                                  const SolutionVector& curSol)
+    : assembler_(assembler)
+    , element_(element)
+    , curSol_(curSol)
+    , fvGeometry_(localView(assembler.fvGridGeometry()))
+    , curElemVolVars_(localView(assembler.gridVariables().curGridVolVars()))
+    , prevElemVolVars_(localView(assembler.gridVariables().prevGridVolVars()))
+    , elemFluxVarsCache_(localView(assembler.gridVariables().gridFluxVarsCache()))
+    , localResidual_(assembler.localResidual())
+    , elementIsGhost_((element.partitionType() == Dune::GhostEntity))
+    {}
+
+    /*!
+     * \brief Convenience function to evaluate the complete local residual for the current element. Automatically chooses the the appropriate
+     *        element volume variables.
+     */
+    auto evalLocalResidual() const
+    {
+        if(this->assembler().isStationaryProblem() && !isImplicit())
+            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
+
+        if(elementIsGhost())
+        {
+            using ResdiualType = decltype(evalLocalResidual(std::declval<ElementVolumeVariables>()));
+            return ResdiualType(0.0);
+        }
+
+        return isImplicit() ? evalLocalResidual(curElemVolVars())
+                            : evalLocalResidual(prevElemVolVars());
+    }
+
+    /*!
+     * \brief Evaluates the complete local residual for the current element.
+     * \param elemVolVars The element volume variables
+     */
+    auto evalLocalResidual(const ElementVolumeVariables& elemVolVars) const
+    {
+        if (!assembler().isStationaryProblem())
+        {
+            auto residual = evalLocalFluxAndSourceResidual(elemVolVars);
+            residual += evalLocalStorageResidual();
+            return residual;
+        }
+        else
+            return evalLocalFluxAndSourceResidual(elemVolVars);
+    }
+
+    /*!
+     * \brief Convenience function to evaluate the flux and source terms (i.e, the terms without a time derivative)
+     *        of the local residual for the current element. Automatically chooses the the appropriate
+     *        element volume variables.
+     */
+    auto evalLocalFluxAndSourceResidual() const
+    {
+        return isImplicit() ? evalLocalFluxAndSourceResidual(curElemVolVars())
+                            : evalLocalFluxAndSourceResidual(prevElemVolVars());
+     }
+
+    /*!
+     * \brief Evaluates the flux and source terms (i.e, the terms without a time derivative)
+     *        of the local residual for the current element.
+     *
+     * \param elemVolVars The element volume variables
+     */
+    auto evalLocalFluxAndSourceResidual(const ElementVolumeVariables& elemVolVars) const
+    {
+        return localResidual_.evalFluxSource(element_, fvGeometry_, elemVolVars, elemFluxVarsCache_, elemBcTypes_);
+    }
+
+    /*!
+     * \brief Convenience function to evaluate storage term (i.e, the term with a time derivative)
+     *        of the local residual for the current element. Automatically chooses the the appropriate
+     *        element volume variables.
+     */
+    auto evalLocalStorageResidual() const
+    {
+        return localResidual_.evalStorage(element_, fvGeometry_, prevElemVolVars_, curElemVolVars_);
+    }
+
+    /*!
+     * \brief Convenience function bind and prepare all relevant variables required for the
+     *        evaluation of the local residual.
+     */
+    void bindLocalViews()
+    {
+        // get some references for convenience
+        const auto& element = this->element();
+        const auto& curSol = this->curSol();
+        const auto& prevSol = this->assembler().prevSol();
+        auto&& fvGeometry = this->fvGeometry();
+        auto&& curElemVolVars = this->curElemVolVars();
+        auto&& prevElemVolVars = this->prevElemVolVars();
+        auto&& elemFluxVarsCache = this->elemFluxVarsCache();
+
+        // bind the caches
+        fvGeometry.bind(element);
+
+        if(isImplicit())
+        {
+            curElemVolVars.bind(element, fvGeometry, curSol);
+            elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
+            if (!this->assembler().isStationaryProblem())
+                prevElemVolVars.bindElement(element, fvGeometry, this->assembler().prevSol());
+        }
+        else
+        {
+            curElemVolVars.bindElement(element, fvGeometry, curSol);
+            prevElemVolVars.bind(element, fvGeometry, prevSol);
+            elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
+        }
+    }
+
+    //! The problem
+    const Problem& problem() const
+    { return assembler_.problem(); }
+
+    //! The assembler
+    const Assembler& assembler() const
+    { return assembler_; }
+
+    //! The current element
+    const Element& element() const
+    { return element_; }
+
+    //! Returns if element is a ghost entity
+    bool elementIsGhost() const
+    { return elementIsGhost_; }
+
+    //! The current solution
+    const SolutionVector& curSol() const
+    { return curSol_; }
+
+    //! The global finite volume geometry
+    FVElementGeometry& fvGeometry()
+    { return fvGeometry_; }
+
+    //! The current element volume variables
+    ElementVolumeVariables& curElemVolVars()
+    { return curElemVolVars_; }
+
+    //! The element volume variables of the provious time step
+    ElementVolumeVariables& prevElemVolVars()
+    { return prevElemVolVars_; }
+
+    //! The element flux variables cache
+    ElementFluxVariablesCache& elemFluxVarsCache()
+    { return elemFluxVarsCache_; }
+
+    //! The local residual for the current element
+    LocalResidual& localResidual()
+    { return localResidual_; }
+
+    //! The element's boundary types
+    ElementBoundaryTypes& elemBcTypes()
+    { return elemBcTypes_; }
+
+    //! The finite volume geometry
+    const FVElementGeometry& fvGeometry() const
+    { return fvGeometry_; }
+
+    //! The current element volume variables
+    const ElementVolumeVariables& curElemVolVars() const
+    { return curElemVolVars_; }
+
+    //! The element volume variables of the provious time step
+    const ElementVolumeVariables& prevElemVolVars() const
+    { return prevElemVolVars_; }
+
+    //! The element flux variables cache
+    const ElementFluxVariablesCache& elemFluxVarsCache() const
+    { return elemFluxVarsCache_; }
+
+    //! The element's boundary types
+    const ElementBoundaryTypes& elemBcTypes() const
+    { return elemBcTypes_; }
+
+    //! The local residual for the current element
+    const LocalResidual& localResidual() const
+    { return localResidual_; }
+
+    constexpr bool isImplicit() const
+    { return implicit; }
+
+protected:
+    Implementation &asImp_()
+    { return *static_cast<Implementation*>(this); }
+
+    const Implementation &asImp_() const
+    { return *static_cast<const Implementation*>(this); }
+
+    template<class T = TypeTag, typename std::enable_if_t<!GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), int> = 0>
+    VolumeVariables& getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
+    { return elemVolVars[scv]; }
+
+    template<class T = TypeTag, typename std::enable_if_t<GET_PROP_VALUE(T, EnableGridVolumeVariablesCache), int> = 0>
+    VolumeVariables& getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv)
+    { return gridVolVars.volVars(scv); }
+
+private:
+
+    const Assembler& assembler_; //!< access pointer to assembler instance
+    const Element& element_; //!< the element whose residual is assembled
+    const SolutionVector& curSol_; //!< the current solution
+
+    FVElementGeometry fvGeometry_;
+    ElementVolumeVariables curElemVolVars_;
+    ElementVolumeVariables prevElemVolVars_;
+    ElementFluxVariablesCache elemFluxVarsCache_;
+    ElementBoundaryTypes elemBcTypes_;
+
+    LocalResidual localResidual_; //!< the local residual evaluating the equations per element
+    bool elementIsGhost_; //!< whether the element's partitionType is ghost
+};
+
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/assembly/fvlocalresidual.hh b/dumux/assembly/fvlocalresidual.hh
index 67b4f2b5350d6eb7d4975d63bc733bd17e9b7421..3e06c94d388f9e506e94027724775678c5d5f0d1 100644
--- a/dumux/assembly/fvlocalresidual.hh
+++ b/dumux/assembly/fvlocalresidual.hh
@@ -24,11 +24,13 @@
 #ifndef DUMUX_FV_LOCAL_RESIDUAL_HH
 #define DUMUX_FV_LOCAL_RESIDUAL_HH
 
+#include <dune/common/deprecated.hh>
 #include <dune/common/exceptions.hh>
 #include <dune/istl/bvector.hh>
 
 #include <dumux/common/properties.hh>
 #include <dumux/common/timeloop.hh>
+#include <dumux/common/reservedblockvector.hh>
 #include <dumux/discretization/methods.hh>
 
 namespace Dumux {
@@ -45,13 +47,13 @@ class FVLocalResidual
     using Implementation = typename GET_PROP_TYPE(TypeTag, LocalResidual);
     using Problem = typename GET_PROP_TYPE(TypeTag, Problem);
     using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
-    using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    using Element = typename GridView::template Codim<0>::Entity;
     using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
     using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
     using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
     using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
     using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
     using ElementFluxVariablesCache = typename GET_PROP_TYPE(TypeTag, ElementFluxVariablesCache);
@@ -61,13 +63,14 @@ class FVLocalResidual
     using TimeLoop = TimeLoopBase<Scalar>;
 
 public:
-    //! the constructor for stationary problems
-    FVLocalResidual() : prevSol_(nullptr) {}
-
-    //! the constructor for instationary problems
-    FVLocalResidual(std::shared_ptr<TimeLoop> timeLoop)
-    : timeLoop_(timeLoop)
-    , prevSol_(nullptr)
+    //! the container storing all element residuals
+    using ElementResidualVector = ReservedBlockVector<ResidualVector, FVElementGeometry::maxNumElementScvs>;
+
+    //! the constructor
+    FVLocalResidual(const Problem* problem,
+                    const TimeLoop* timeLoop = nullptr)
+    : problem_(problem)
+    , timeLoop_(timeLoop)
     {}
 
     /*!
@@ -141,6 +144,7 @@ public:
      * \param bcTypes The types of the boundary conditions for all boundary entities of an element
      * \param elemFluxVarsCache The flux variable caches for the element stencil
      */
+    DUNE_DEPRECATED_MSG("eval is deprecated because it doesn't allow to specify on which time level to evaluate. Use evalFluxSource, and evalStorage instead!")
     ElementResidualVector eval(const Problem& problem,
                                const Element& element,
                                const FVElementGeometry& fvGeometry,
@@ -150,7 +154,6 @@ public:
                                const ElementFluxVariablesCache& elemFluxVarsCache) const
     {
         assert(timeLoop_ && "no time loop set for storage term evaluation");
-        assert(prevSol_ && "no solution set for storage term evaluation");
 
         // initialize the residual vector for all scvs in this element
         ElementResidualVector residual(fvGeometry.numScv());
@@ -177,7 +180,6 @@ public:
      * \brief Compute the storage local residual, i.e. the deviation of the
      *        storage term from zero for instationary problems.
      *
-     * \param problem The problem to solve
      * \param element The DUNE Codim<0> entity for which the residual
      *                ought to be calculated
      * \param fvGeometry The finite-volume geometry of the element
@@ -185,30 +187,21 @@ public:
      *                        sub-control volumes of the element at the previous time level
      * \param curElemVolVars The volume averaged variables for all
      *                       sub-control volumes of the element at the current  time level
-     * \param bcTypes The types of the boundary conditions for all boundary entities of an element
-     * \param elemFluxVarsCache The flux variable caches for the element stencil
      */
-    ElementResidualVector evalStorage(const Problem& problem,
-                                      const Element& element,
+    ElementResidualVector evalStorage(const Element& element,
                                       const FVElementGeometry& fvGeometry,
                                       const ElementVolumeVariables& prevElemVolVars,
-                                      const ElementVolumeVariables& curElemVolVars,
-                                      const ElementBoundaryTypes &bcTypes,
-                                      const ElementFluxVariablesCache& elemFluxVarsCache) const
+                                      const ElementVolumeVariables& curElemVolVars) const
     {
         assert(timeLoop_ && "no time loop set for storage term evaluation");
-        assert(prevSol_ && "no solution set for storage term evaluation");
 
         // initialize the residual vector for all scvs in this element
         ElementResidualVector residual(fvGeometry.numScv());
-        residual = 0.0;
 
         // evaluate the volume terms (storage + source terms)
+        // forward to the local residual specialized for the discretization methods
         for (auto&& scv : scvs(fvGeometry))
-        {
-            //! foward to the local residual specialized for the discretization methods
-            asImp().evalStorage(residual, problem, element, fvGeometry, prevElemVolVars, curElemVolVars, scv);
-        }
+            asImp().evalStorage(residual, this->problem(), element, fvGeometry, prevElemVolVars, curElemVolVars, scv);
 
         return residual;
     }
@@ -226,29 +219,34 @@ public:
      * \param bcTypes The types of the boundary conditions for all boundary entities of an element
      * \param elemFluxVarsCache The flux variable caches for the element stencil
      */
+    DUNE_DEPRECATED_MSG("Use evalFluxSource instead!")
     ElementResidualVector eval(const Problem& problem,
                                const Element& element,
                                const FVElementGeometry& fvGeometry,
                                const ElementVolumeVariables& curElemVolVars,
                                const ElementBoundaryTypes &bcTypes,
                                const ElementFluxVariablesCache& elemFluxVarsCache) const
+    {
+        return evalFluxSource(element, fvGeometry, curElemVolVars, elemFluxVarsCache, bcTypes);
+    }
+
+    ElementResidualVector evalFluxSource(const Element& element,
+                                         const FVElementGeometry& fvGeometry,
+                                         const ElementVolumeVariables& elemVolVars,
+                                         const ElementFluxVariablesCache& elemFluxVarsCache,
+                                         const ElementBoundaryTypes &bcTypes) const
     {
         // initialize the residual vector for all scvs in this element
         ElementResidualVector residual(fvGeometry.numScv());
-        residual = 0.0;
 
         // evaluate the volume terms (storage + source terms)
+        // forward to the local residual specialized for the discretization methods
         for (auto&& scv : scvs(fvGeometry))
-        {
-            //! foward to the local residual specialized for the discretization methods
-            asImp().evalSource(residual, problem, element, fvGeometry, curElemVolVars, scv);
-        }
+            asImp().evalSource(residual, this->problem(), element, fvGeometry, elemVolVars, scv);
 
+        // forward to the local residual specialized for the discretization methods
         for (auto&& scvf : scvfs(fvGeometry))
-        {
-            //! foward to the local residual specialized for the discretization methods
-            asImp().evalFlux(residual, problem, element, fvGeometry, curElemVolVars, bcTypes, elemFluxVarsCache, scvf);
-        }
+            asImp().evalFlux(residual, this->problem(), element, fvGeometry, elemVolVars, bcTypes, elemFluxVarsCache, scvf);
 
         return residual;
     }
@@ -552,40 +550,22 @@ public:
     //\}
 
     /*!
-     * \brief Sets the solution from which to start the time integration.
-     * \note Has to be called prior to assembly for time-dependent problems.
-     */
-    void setPreviousSolution(const SolutionVector& u)
-    { prevSol_ = &u; }
-
-    /*!
-     * \brief Return the solution of the previous time level that has been set
-     */
-    const SolutionVector& prevSol() const
-    {
-        assert(prevSol_ && "no solution set for storage term evaluation");
-        return *prevSol_;
-    }
-
-    /*!
-     * \brief if the residual is stationary (storage residual is strictly zero)
-     * \note If no solution has been set, we treat the problem as stationary.
+     * \name Interfaces accessed by local residual implementations
      */
-    bool isStationary() const
-    { return !prevSol_; }
+    // \{
 
-    // \}
-protected:
-    //! the timeloop for instationary problems
-    //! calling this for stationary leads to undefined behaviour
-    TimeLoop& timeLoop()
-    { return *timeLoop_; }
+    //! the problem
+    const Problem& problem() const
+    { return *problem_; }
 
     //! the timeloop for instationary problems
     //! calling this for stationary leads to undefined behaviour
     const TimeLoop& timeLoop() const
     { return *timeLoop_; }
 
+    // \}
+protected:
+
     Implementation &asImp()
     { return *static_cast<Implementation*>(this); }
 
@@ -593,8 +573,8 @@ protected:
     { return *static_cast<const Implementation*>(this); }
 
 private:
-    std::shared_ptr<TimeLoop> timeLoop_; //!< the timeloop for instationary problems
-    const SolutionVector* prevSol_; //!< the solution of the previous time level for instationary problems
+    const Problem* problem_; //!< the problem we are assembling this residual for
+    const TimeLoop* timeLoop_; //!< the timeloop for instationary problems
 };
 
 } // end namespace Dumux
diff --git a/dumux/assembly/numericdifferentiation.hh b/dumux/assembly/numericdifferentiation.hh
new file mode 100644
index 0000000000000000000000000000000000000000..cdf0ee3f1bb810e5a3d1d4f0196e2baad6c87332
--- /dev/null
+++ b/dumux/assembly/numericdifferentiation.hh
@@ -0,0 +1,126 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program is free software: you can redistribute it and/or modify    *
+ *   it under the terms of the GNU General Public License as published by    *
+ *   the Free Software Foundation, either version 2 of the License, or       *
+ *   (at your option) any later version.                                     *
+ *                                                                           *
+ *   This program is distributed in the hope that it will be useful,         *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
+ *   GNU General Public License for more details.                            *
+ *                                                                           *
+ *   You should have received a copy of the GNU General Public License       *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \ingroup Assembly
+ * \brief A class for numeric differentiation
+ *
+ */
+#ifndef DUMUX_NUMERIC_DIFFERENTIATION_HH
+#define DUMUX_NUMERIC_DIFFERENTIATION_HH
+
+#include <cmath>
+#include <limits>
+#include <dumux/common/parameters.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup Assembly
+ * \brief A class for numeric differentiation with respect to a scalar parameter
+ */
+class NumericDifferentiation
+{
+public:
+
+    /*!
+     * \brief Computes the epsilon used for numeric differentiation
+     * \param value The value of the variable with respect to which we are differentiating
+     */
+    template<class Scalar>
+    static Scalar epsilon(const Scalar value)
+    {
+        static constexpr Scalar baseEps = 1e-10;
+        assert(std::numeric_limits<Scalar>::epsilon()*1e4 < baseEps);
+        // the epsilon value used for the numeric differentiation is
+        // now scaled by the absolute value of the primary variable...
+        using std::abs;
+        return baseEps*(abs(value) + 1.0);
+    }
+
+    /*!
+     * \brief Computes the derivative of a function with repect to a function parameter
+     * \note Overload using default epsilon computation
+     */
+    template<class Function, class Scalar, class FunctionEvalType>
+    static void partialDerivative(const Function& function, Scalar x0,
+                                  FunctionEvalType& derivative,
+                                  const FunctionEvalType& fx0)
+    { partialDerivative(function, x0, derivative, fx0, epsilon(x0)); }
+
+    /*!
+     * \brief Computes the derivative of a function with repect to a function parameter
+     * \param function The function to derive
+     * \param x0 The parameter at which the derivative is ought to be evaluated
+     * \param derivative The partial derivative (output)
+     * \param fx0 The result of the function evaluated at x0
+     * \param eps The numeric epsilon used in the differentiation
+     */
+    template<class Function, class Scalar, class FunctionEvalType>
+    static void partialDerivative(const Function& function, Scalar x0,
+                                  FunctionEvalType& derivative,
+                                  const FunctionEvalType& fx0,
+                                  const Scalar eps)
+    {
+        static const int numericDifferenceMethod = numDiffMethod();
+
+        Scalar delta = 0.0;
+        // we are using forward or central differences, i.e. we need to calculate f(x + \epsilon)
+        if (numericDifferenceMethod >= 0)
+        {
+            delta += eps;
+            // calculate the function evaluated with the deflected variable
+            derivative = function(x0 + eps);
+        }
+
+        // we are using backward differences,
+        // i.e. we don't need to calculate f(x + \epsilon)
+        // we can recycle the (possibly cached) f(x)
+        else derivative = fx0;
+
+        // we are using backward or central differences,
+        // i.e. we need to calculate f(x - \epsilon)
+        if (numericDifferenceMethod <= 0)
+        {
+            delta += eps;
+            // subtract the function evaluated with the deflected variable
+            derivative -= function(x0 - eps);
+        }
+
+        // we are using forward differences,
+        // i.e. we don't need to calculate f(x - \epsilon)
+        // we can recycle the (possibly cached) f(x)
+        else derivative -= fx0;
+
+        // divide difference in residuals by the magnitude of the
+        // deflections between the two function evaluation
+        derivative /= delta;
+    }
+
+    static int numDiffMethod()
+    {
+        static const int numericDifferenceMethod
+            = getParam<int>("Assembly.NumericDifferenceMethod", 1);
+        return numericDifferenceMethod;
+    }
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/assembly/staggeredfvassembler.hh b/dumux/assembly/staggeredfvassembler.hh
index 7042b115c2a7e310ff76590de67a02116d3cc2b3..4b144c880197922f9fdd7d743eae951f8da9f7a7 100644
--- a/dumux/assembly/staggeredfvassembler.hh
+++ b/dumux/assembly/staggeredfvassembler.hh
@@ -128,7 +128,7 @@ public:
         try
         {
             // let the local assembler add the element contributions
-            for (const auto element : elements(gridView()))
+            for (const auto& element : elements(gridView()))
                 LocalAssembler::assembleJacobianAndResidual(*this, *jacobian_, *residual_, element, curSol);
 
             // if we get here, everything worked well
@@ -170,7 +170,7 @@ public:
             DUNE_THROW(Dune::InvalidStateException, "Assembling instationary problem but previous solution was not set!");
 
         // let the local assembler add the element contributions
-        for (const auto element : elements(gridView()))
+        for (const auto& element : elements(gridView()))
             LocalAssembler::assembleResidual(*this, r, element, curSol);
     }
 
@@ -362,6 +362,9 @@ public:
     const LocalResidual& localResidual() const
     { return localResidual_; }
 
+    bool isStationaryProblem() const
+    { return stationary_; }
+
 private:
     //! reset the residual to 0.0
     void resetResidual_()
diff --git a/dumux/common/properties.hh b/dumux/common/properties.hh
index 2cbc1b8ed7f371161403edffa0f0962cea284e1b..ca9b5c443d63cca5184e0e87b6fb27ce10ef43f3 100644
--- a/dumux/common/properties.hh
+++ b/dumux/common/properties.hh
@@ -98,6 +98,7 @@ NEW_PROP_TAG(ElementFluxVariablesCache);           //!< A local vector of flux v
 NEW_PROP_TAG(GridFluxVariablesCache);              //!< The global vector of flux variable containers
 NEW_PROP_TAG(EnableGridFluxVariablesCache);        //!< specifies if data on flux vars should be saved (faster, but more memory consuming)
 NEW_PROP_TAG(GridVariables);                       //!< The grid variables object managing variable data on the grid (volvars/fluxvars cache)
+NEW_PROP_TAG(MaxNumNeighborsPerScvf);              //!< The maximum number of neighboring elements allowed per scvf (for static memory allocation)
 
 /////////////////////////////////////////////////////////////////
 // Additional properties used by the cell-centered mpfa schemes:
@@ -106,7 +107,7 @@ NEW_PROP_TAG(MpfaMethod);                          //!< Specifies the mpfa metho
 NEW_PROP_TAG(MpfaHelper);                          //!< A Helper class depending on the mpfa method and grid dimension
 NEW_PROP_TAG(PrimaryInteractionVolume);            //!< The primary interaction volume type
 NEW_PROP_TAG(SecondaryInteractionVolume);          //!< The secondary interaction volume type used e.g. on the boundaries
-
+NEW_PROP_TAG(DualGridNodalIndexSet);               //!< The type used for the nodal index sets of the dual grid
 
 /////////////////////////////////////////////////////////////
 // Properties used by models involving flow in porous media:
diff --git a/dumux/common/reservedblockvector.hh b/dumux/common/reservedblockvector.hh
new file mode 100644
index 0000000000000000000000000000000000000000..277cb10994b0a8972dfffde73fdbc3f58a5a071a
--- /dev/null
+++ b/dumux/common/reservedblockvector.hh
@@ -0,0 +1,96 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/*****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program is free software: you can redistribute it and/or modify    *
+ *   it under the terms of the GNU General Public License as published by    *
+ *   the Free Software Foundation, either version 2 of the License, or       *
+ *   (at your option) any later version.                                     *
+ *                                                                           *
+ *   This program is distributed in the hope that it will be useful,         *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
+ *   GNU General Public License for more details.                            *
+ *                                                                           *
+ *   You should have received a copy of the GNU General Public License       *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \ingroup Common
+ * \brief A arithmetic block vector type based on DUNE's reserved vector
+ */
+#ifndef DUMUX_RESERVED_BLOCK_VECTOR_HH
+#define DUMUX_RESERVED_BLOCK_VECTOR_HH
+
+#include <algorithm>
+#include <dune/common/reservedvector.hh>
+
+namespace Dumux {
+
+template<class BlockType, int capacity>
+class ReservedBlockVector : public Dune::ReservedVector<BlockType, capacity>
+{
+    using Base = Dune::ReservedVector<BlockType, capacity>;
+public:
+
+    using size_type = typename Base::size_type;
+    using value_type = BlockType;
+
+    using Base::Base;
+
+    explicit ReservedBlockVector() : Base() {}
+    explicit ReservedBlockVector(size_type size) : Base() { this->resize(size); }
+
+    ReservedBlockVector(const ReservedBlockVector&) = default;
+    ReservedBlockVector(ReservedBlockVector&&) = default;
+
+    ReservedBlockVector& operator= (const ReservedBlockVector&) = default;
+    ReservedBlockVector& operator= (ReservedBlockVector&&) = default;
+
+    ~ReservedBlockVector() = default;
+
+    //! assigment from scalar
+    ReservedBlockVector& operator= (const typename BlockType::field_type& v)
+    {
+       std::fill(this->begin(), this->end(), v);
+       return *this;
+    }
+
+    //! vector space addition
+    ReservedBlockVector& operator+= (const ReservedBlockVector& other)
+    {
+      for (size_type i = 0; i < this->size(); ++i)
+          (*this)[i] += other[i];
+      return *this;
+    }
+
+    //! vector space subtraction
+    ReservedBlockVector& operator-= (const ReservedBlockVector& other)
+    {
+      for (size_type i = 0; i < this->size(); ++i)
+          (*this)[i] -= other[i];
+      return *this;
+    }
+
+    //! division by scalar
+    ReservedBlockVector& operator/= (const typename BlockType::field_type& v)
+    {
+      for (size_type i = 0; i < this->size(); ++i)
+          (*this)[i] /= v;
+      return *this;
+    }
+
+    //! multiplication by scalar
+    ReservedBlockVector& operator*= (const typename BlockType::field_type& v)
+    {
+      for (size_type i = 0; i < this->size(); ++i)
+          (*this)[i] *= v;
+      return *this;
+    }
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/discretization/box/fvelementgeometry.hh b/dumux/discretization/box/fvelementgeometry.hh
index e0639746b5c43a45212ddb8ef6d6d29a2da5c1b2..393b8cd8b94d53f454a6289a4b182cef70993c72 100644
--- a/dumux/discretization/box/fvelementgeometry.hh
+++ b/dumux/discretization/box/fvelementgeometry.hh
@@ -54,28 +54,33 @@ class BoxFVElementGeometry
 template<class TypeTag>
 class BoxFVElementGeometry<TypeTag, true>
 {
-    using ThisType = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
     using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
-    using IndexType = typename GridView::IndexSet::IndexType;
+    static constexpr int dim = GridView::dimension;
+    static constexpr int dimWorld = GridView::dimensionworld;
+public:
+    //! export type of subcontrol volume
     using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    //! export type of subcontrol volume face
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
-    using Element = typename GridView::template Codim<0>::Entity;
+    //! export type of finite volume grid geometry
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    //! the maximum number of scvs per element (2^dim for cubes)
+    static constexpr std::size_t maxNumElementScvs = (1<<dim);
+
+private:
+    using ThisType = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
+    using IndexType = typename GridView::IndexSet::IndexType;
+    using Element = typename GridView::template Codim<0>::Entity;
 
     using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
     using CoordScalar = typename GridView::ctype;
 
-    static const int dim = GridView::dimension;
-    static const int dimWorld = GridView::dimensionworld;
-
     using FeCache = Dune::PQkLocalFiniteElementCache<CoordScalar, Scalar, dim, 1>;
     using FeLocalBasis = typename FeCache::FiniteElementType::Traits::LocalBasisType;
     using ReferenceElements = typename Dune::ReferenceElements<CoordScalar, dim>;
 
-    using ScvIterator = Dumux::ScvIterator<SubControlVolume, std::vector<IndexType>, ThisType>;
-    using ScvfIterator = Dumux::ScvfIterator<SubControlVolumeFace, std::vector<IndexType>, ThisType>;
-
 public:
+
     //! Constructor
     BoxFVElementGeometry(const FVGridGeometry& fvGridGeometry)
     : fvGridGeometryPtr_(&fvGridGeometry) {}
@@ -169,16 +174,22 @@ template<class TypeTag>
 class BoxFVElementGeometry<TypeTag, false>
 {
     using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
-    using IndexType = typename GridView::IndexSet::IndexType;
+    static constexpr int dim = GridView::dimension;
+    static constexpr int dimWorld = GridView::dimensionworld;
+public:
+    //! export type of subcontrol volume
     using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    //! export type of subcontrol volume face
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
-    using LocalIndexType = typename SubControlVolumeFace::Traits::LocalIndexType;
-    using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
+    //! export type of finite volume grid geometry
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    //! the maximum number of scvs per element (2^dim for cubes)
+    static constexpr std::size_t maxNumElementScvs = (1<<dim);
 
-    static const int dim = GridView::dimension;
-    static const int dimWorld = GridView::dimensionworld;
-
+private:
+    using IndexType = typename GridView::IndexSet::IndexType;
+    using LocalIndexType = typename SubControlVolumeFace::Traits::LocalIndexType;
+    using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
     using Element = typename GridView::template Codim<0>::Entity;
 
     using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
diff --git a/dumux/discretization/box/gridvolumevariables.hh b/dumux/discretization/box/gridvolumevariables.hh
index 7875b88304c981892fe6a4fa40bbd779882c2770..b4aa07fd2645d209dfd3bbf60e01528926387d96 100644
--- a/dumux/discretization/box/gridvolumevariables.hh
+++ b/dumux/discretization/box/gridvolumevariables.hh
@@ -86,6 +86,12 @@ public:
     friend inline ElementVolumeVariables localView(const BoxGridVolumeVariables& global)
     { return ElementVolumeVariables(global); }
 
+    const VolumeVariables& volVars(const SubControlVolume& scv) const
+    { return volumeVariables_[scv.elementIndex()][scv.indexInElement()]; }
+
+    VolumeVariables& volVars(const SubControlVolume& scv)
+    { return volumeVariables_[scv.elementIndex()][scv.indexInElement()]; }
+
     const VolumeVariables& volVars(const IndexType eIdx, const IndexType scvIdx) const
     { return volumeVariables_[eIdx][scvIdx]; }
 
diff --git a/dumux/discretization/cellcentered/connectivitymap.hh b/dumux/discretization/cellcentered/connectivitymap.hh
index f675817c404424acc13afee6e9a6b060bfd20e1c..0f935b288ec93fa1040d5f4121963ba02a01cec2 100644
--- a/dumux/discretization/cellcentered/connectivitymap.hh
+++ b/dumux/discretization/cellcentered/connectivitymap.hh
@@ -30,6 +30,7 @@
 #include <utility>
 #include <algorithm>
 
+#include <dune/common/reservedvector.hh>
 #include <dumux/common/properties.hh>
 #include <dumux/discretization/fluxstencil.hh>
 
@@ -49,6 +50,7 @@ template<class TypeTag>
 class CCSimpleConnectivityMap
 {
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
     using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
     using IndexType = typename GridView::IndexSet::IndexType;
     using FluxStencil = Dumux::FluxStencil<TypeTag>;
@@ -56,10 +58,10 @@ class CCSimpleConnectivityMap
     struct DataJ
     {
         IndexType globalJ;
-        std::vector<IndexType> scvfsJ;
+        Dune::ReservedVector<IndexType, FluxStencil::maxNumScvfJForI> scvfsJ;
         // A list of additional scvfs is needed for compatibility
         // reasons with more complex connectivity maps (see mpfa)
-        std::vector<IndexType> additionalScvfs;
+        Dune::ReservedVector<IndexType, FluxStencil::maxNumScvfJForI> additionalScvfs;
     };
 
     using Map = std::vector<std::vector<DataJ>>;
@@ -75,16 +77,21 @@ public:
     {
         map_.clear();
         map_.resize(fvGridGeometry.gridView().size(0));
+
+        // container to store for each element J the elements I that appear in J's flux stencils
+        static constexpr int maxNumJ = FluxStencil::maxFluxStencilSize*FVElementGeometry::maxNumElementScvfs;
+        Dune::ReservedVector<std::pair<IndexType, DataJ>, maxNumJ> dataJForI;
+
         for (const auto& element : elements(fvGridGeometry.gridView()))
         {
             // We are looking for the elements I, for which this element J is in the flux stencil
-            auto globalJ = fvGridGeometry.elementMapper().index(element);
+            const auto globalJ = fvGridGeometry.elementMapper().index(element);
 
             auto fvGeometry = localView(fvGridGeometry);
             fvGeometry.bindElement(element);
 
             // obtain the data of J in elements I
-            std::vector<std::pair<IndexType, DataJ>> dataJForI;
+            dataJForI.clear();
 
             // loop over sub control faces
             for (auto&& scvf : scvfs(fvGeometry))
@@ -97,16 +104,13 @@ public:
                     if (globalI == globalJ)
                         continue;
 
-                    auto it = std::find_if(dataJForI.begin(),
-                                           dataJForI.end(),
+                    auto it = std::find_if(dataJForI.begin(), dataJForI.end(),
                                            [globalI](const auto& pair) { return pair.first == globalI; });
 
                     if (it != dataJForI.end())
                         it->second.scvfsJ.push_back(scvf.index());
                     else
-                        dataJForI.emplace_back(std::make_pair(globalI, DataJ({globalJ,
-                                                                              std::vector<IndexType>({scvf.index()}),
-                                                                              std::vector<IndexType>()})));
+                        dataJForI.push_back(std::make_pair(globalI, DataJ({globalJ, {scvf.index()}, {}})));
                 }
             }
 
diff --git a/dumux/discretization/cellcentered/mpfa/darcyslaw.hh b/dumux/discretization/cellcentered/mpfa/darcyslaw.hh
index 0d7f2a7be9c275cea7faf65ef00b5d8562e03ea2..9ff81414195549d3849e38f683cee513a8b94a82 100644
--- a/dumux/discretization/cellcentered/mpfa/darcyslaw.hh
+++ b/dumux/discretization/cellcentered/mpfa/darcyslaw.hh
@@ -94,6 +94,9 @@ class DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         static constexpr int dimWorld = GridView::dimensionworld;
         static constexpr int numPhases = GET_PROP_VALUE(TypeTag, NumPhases);
 
+        using DualGridNodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
+        using Stencil = typename DualGridNodalIndexSet::GridStencilType;
+
         using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
         static constexpr bool considerSecondaryIVs = MpfaHelper::considerSecondaryIVs();
 
@@ -109,7 +112,6 @@ class DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         using PrimaryIvVector = typename PrimaryInteractionVolume::Traits::Vector;
         using PrimaryIvMatrix = typename PrimaryInteractionVolume::Traits::Matrix;
         using PrimaryIvTij = typename PrimaryIvMatrix::row_type;
-        using PrimaryIvStencil = typename PrimaryInteractionVolume::Traits::Stencil;
 
         using SecondaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, SecondaryInteractionVolume);
         using SecondaryIvLocalFaceData = typename SecondaryInteractionVolume::Traits::LocalFaceData;
@@ -117,7 +119,6 @@ class DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         using SecondaryIvVector = typename SecondaryInteractionVolume::Traits::Vector;
         using SecondaryIvMatrix = typename SecondaryInteractionVolume::Traits::Matrix;
         using SecondaryIvTij = typename SecondaryIvMatrix::row_type;
-        using SecondaryIvStencil = typename SecondaryInteractionVolume::Traits::Stencil;
 
     public:
         // export the filler type
@@ -138,7 +139,7 @@ class DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
                              const SubControlVolumeFace &scvf)
         {
             switchFluxSign_ = localFaceData.isOutside();
-            primaryStencil_ = &iv.stencil();
+            stencil_ = &iv.stencil();
 
             for (unsigned int pIdx = 0; pIdx < numPhases; ++pIdx)
                 primaryPj_[pIdx] = &dataHandle.pressures(pIdx);
@@ -191,7 +192,7 @@ class DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
                              const SubControlVolumeFace &scvf)
         {
             switchFluxSign_ = localFaceData.isOutside();
-            secondaryStencil_ = &iv.stencil();
+            stencil_ = &iv.stencil();
 
             for (unsigned int pIdx = 0; pIdx < numPhases; ++pIdx)
                 secondaryPj_[pIdx] = &dataHandle.pressures(pIdx);
@@ -229,10 +230,7 @@ class DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         }
 
         //! The stencil corresponding to the transmissibilities (primary type)
-        const PrimaryIvStencil& advectionStencilPrimaryIv() const { return *primaryStencil_; }
-
-        //! The stencil corresponding to the transmissibilities (secondary type)
-        const SecondaryIvStencil& advectionStencilSecondaryIv() const { return *secondaryStencil_; }
+        const Stencil& advectionStencil() const { return *stencil_; }
 
         //! Coefficients for the cell (& Dirichlet) unknowns in flux expressions (primary type)
         const PrimaryIvTij& advectionTijPrimaryIv() const { return *primaryTij_; }
@@ -259,8 +257,7 @@ class DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         bool switchFluxSign_;
 
         //! The stencil, i.e. the grid indices j
-        const PrimaryIvStencil* primaryStencil_;
-        const SecondaryIvStencil* secondaryStencil_;
+        const Stencil* stencil_;
 
         //! The transmissibilities such that f = Tij*pj
         const PrimaryIvTij* primaryTij_;
diff --git a/dumux/discretization/cellcentered/mpfa/dualgridindexset.hh b/dumux/discretization/cellcentered/mpfa/dualgridindexset.hh
index 52817810a3787d72f3259f47afd5935c80cae7d2..a93c7ce63970a589ca92cdf883dd9547556e1ee2 100644
--- a/dumux/discretization/cellcentered/mpfa/dualgridindexset.hh
+++ b/dumux/discretization/cellcentered/mpfa/dualgridindexset.hh
@@ -28,6 +28,8 @@
 #include <vector>
 #include <algorithm>
 
+#include <dune/common/reservedvector.hh>
+
 namespace Dumux
 {
 
@@ -39,20 +41,40 @@ namespace Dumux
  * \tparam GI The type used for indices on the grid
  * \tparam LI The type used for indexing in interaction volumes
  * \tparam dim The dimension of the grid
+ * \tparam maxE The maximum admissible number of elements around vertices.
+ * \tparam maxB The maximum admissible number of branches on intersections.
+ *              This is only to be specified for network grids and defaults to 1
+ *              for normal grids.
  */
-template< class GI, class LI, int dim >
-class DualGridNodalIndexSet
+template< class GI, class LI, int dim, int maxE, int maxB = 2 >
+class CCMpfaDualGridNodalIndexSet
 {
+    using DimIndexVector = Dune::ReservedVector<GI, dim>;
+
 public:
+    //! Export the used index types
     using GridIndexType = GI;
     using LocalIndexType = LI;
 
-    // we use dynamic containers to store indices here
-    using GridIndexContainer = std::vector<GridIndexType>;
-    using LocalIndexContainer = std::vector<LocalIndexType>;
+    //! Export the specified maximum admissible sizes
+    static constexpr int dimension = dim;
+    static constexpr int maxBranches = maxB;
+    static constexpr int maxNumElementsAtNode = maxE*(maxBranches-1);
+    static constexpr int maxNumScvfsAtNode = maxNumElementsAtNode*dim;
+
+    //! Export the stencil types used
+    using GridStencilType = Dune::ReservedVector<GridIndexType, maxNumElementsAtNode>;
+    using LocalStencilType = Dune::ReservedVector<LocalIndexType, maxNumElementsAtNode>;
+
+    //! Export the type used for storing the global scvf indices at this node
+    using GridScvfStencilType = Dune::ReservedVector<GridIndexType, maxNumScvfsAtNode>;
+
+    //! Data structure to store the neighboring scv indices of an scvf (grid/local indices)
+    using ScvfNeighborIndexSet = Dune::ReservedVector<GridIndexType, maxBranches>;
+    using ScvfNeighborLocalIndexSet = Dune::ReservedVector<LocalIndexType, maxBranches>;
 
     //! Constructor
-    DualGridNodalIndexSet() : numBoundaryScvfs_(0) {}
+    CCMpfaDualGridNodalIndexSet() : numBoundaryScvfs_(0) {}
 
     //! Inserts data for a given scvf
     template<typename SubControlVolumeFace>
@@ -78,10 +100,10 @@ public:
         const LocalIndexType curScvfLocalIdx = scvfIndices_.size();
 
         // add grid scvf data
-        GridIndexContainer scvIndices;
-        scvIndices.reserve(outsideScvIndices.size()+1);
+        ScvfNeighborIndexSet scvIndices;
         scvIndices.push_back(insideScvIdx);
-        scvIndices.insert(scvIndices.end(), outsideScvIndices.begin(), outsideScvIndices.end());
+        for (const auto& outsideIdx : outsideScvIndices)
+                scvIndices.push_back(outsideIdx);
 
         // if scvf is on boundary, increase counter
         if (boundary)
@@ -90,7 +112,7 @@ public:
         // insert data on the new scv
         scvfIndices_.push_back(scvfIdx);
         scvfIsOnBoundary_.push_back(boundary);
-        scvfNeighborScvIndices_.emplace_back( std::move(scvIndices) );
+        scvfNeighborScvIndices_.push_back(scvIndices);
 
         // if entry for the inside scv exists, append scvf local index, create otherwise
         auto it = std::find( scvIndices_.begin(), scvIndices_.end(), insideScvIdx );
@@ -98,10 +120,8 @@ public:
             localScvfIndicesInScv_[ std::distance(scvIndices_.begin(), it) ].push_back(curScvfLocalIdx);
         else
         {
-            LocalIndexContainer localScvfIndices;
-            localScvfIndices.reserve(dim);
-            localScvfIndices.push_back(curScvfLocalIdx);
-            localScvfIndicesInScv_.emplace_back( std::move(localScvfIndices) );
+            localScvfIndicesInScv_.push_back({});
+            localScvfIndicesInScv_.back().push_back(curScvfLocalIdx);
             scvIndices_.push_back(insideScvIdx);
         }
     }
@@ -116,10 +136,10 @@ public:
     std::size_t numBoundaryScvfs() const { return numBoundaryScvfs_; }
 
     //! returns the global scv indices connected to this dual grid node
-    const GridIndexContainer& globalScvIndices() const { return scvIndices_; }
+    const GridStencilType& globalScvIndices() const { return scvIndices_; }
 
     //! returns the global scvf indices connected to this dual grid node
-    const GridIndexContainer& globalScvfIndices() const { return scvfIndices_; }
+    const GridScvfStencilType& globalScvfIndices() const { return scvfIndices_; }
 
     //! returns whether or not the i-th scvf is on a domain boundary
     bool scvfIsOnBoundary(unsigned int i) const { return scvfIsOnBoundary_[i]; }
@@ -145,39 +165,40 @@ public:
     }
 
     //! returns the indices of the neighboring scvs of the i-th scvf
-    const GridIndexContainer& neighboringScvIndices(unsigned int i) const
+    const ScvfNeighborIndexSet& neighboringScvIndices(unsigned int i) const
     { return scvfNeighborScvIndices_[i]; }
 
 private:
-    GridIndexContainer scvIndices_;                          //!< The indices of the scvs around a dual grid node
-    std::vector<LocalIndexContainer> localScvfIndicesInScv_; //!< Maps to each scv a list of scvf indices embedded in it
+    GridStencilType scvIndices_;                                                       //!< The indices of the scvs around a dual grid node
+    Dune::ReservedVector<DimIndexVector, maxNumElementsAtNode> localScvfIndicesInScv_; //!< Maps to each scv a list of scvf indices embedded in it
+
+    GridScvfStencilType scvfIndices_;                             //!< the indices of the scvfs around a dual grid node
+    std::size_t numBoundaryScvfs_;                                //!< stores how many boundary scvfs are embedded in this dual grid node
+    Dune::ReservedVector<bool, maxNumScvfsAtNode> scvfIsOnBoundary_; //!< Maps to each scvf a boolean to indicate if it is on the boundary
 
-    GridIndexContainer scvfIndices_;                         //!< the indices of the scvfs around a dual grid node
-    std::vector<bool> scvfIsOnBoundary_;                     //!< Maps to each scvf a boolean to indicate if it is on the boundary
-    std::vector<GridIndexContainer> scvfNeighborScvIndices_; //!< The neighboring scvs for the scvfs (order: 0 - inside, 1..n - outside)
-    std::size_t numBoundaryScvfs_;                           //!< stores how many boundary scvfs are embedded in this dual grid node
+    //! The neighboring scvs for the scvfs (order: 0 - inside, 1..n - outside)
+    Dune::ReservedVector<ScvfNeighborIndexSet, maxNumScvfsAtNode> scvfNeighborScvIndices_;
 };
 
 /*!
  * \ingroup CCMpfaDiscretization
  * \brief Class for the index sets of the dual grid in mpfa schemes.
  *
- * \tparam GridIndexType The type used for indices on the grid
- * \tparam LocalIndexType The type used for indexing in interaction volumes
- * \tparam dim The dimension of the grid
+ * \tparam NI The type used for the nodal index sets.
  */
-template< class GridIndexType, class LocalIndexType, int dim >
+template< class NI >
 class CCMpfaDualGridIndexSet
 {
 public:
-    using NodalIndexSet = DualGridNodalIndexSet< GridIndexType, LocalIndexType, dim >;
+    using NodalIndexSet = NI;
+    using GridIndexType = typename NodalIndexSet::GridIndexType;
 
     //! Default constructor should not be used
     CCMpfaDualGridIndexSet() = delete;
 
     //! Constructor taking a grid view
     template< class GridView >
-    CCMpfaDualGridIndexSet(const GridView& gridView) : nodalIndexSets_(gridView.size(dim)) {}
+    CCMpfaDualGridIndexSet(const GridView& gridView) : nodalIndexSets_(gridView.size(NodalIndexSet::dimension)) {}
 
     //! Access with an scvf
     template< class SubControlVolumeFace >
diff --git a/dumux/discretization/cellcentered/mpfa/fickslaw.hh b/dumux/discretization/cellcentered/mpfa/fickslaw.hh
index be10d54f81548e4e79bf883b6cffde8bf49ad9c2..1f3ea99206a341aff67ccd83bdc78c8ca4ba6d34 100644
--- a/dumux/discretization/cellcentered/mpfa/fickslaw.hh
+++ b/dumux/discretization/cellcentered/mpfa/fickslaw.hh
@@ -90,6 +90,9 @@ class FicksLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
     //! The cache used in conjunction with the mpfa Fick's Law
     class MpfaFicksLawCache
     {
+        using DualGridNodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
+        using Stencil = typename DualGridNodalIndexSet::GridStencilType;
+
         using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
         static constexpr bool considerSecondaryIVs = MpfaHelper::considerSecondaryIVs();
 
@@ -105,7 +108,6 @@ class FicksLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         using PrimaryIvVector = typename PrimaryInteractionVolume::Traits::Vector;
         using PrimaryIvMatrix = typename PrimaryInteractionVolume::Traits::Matrix;
         using PrimaryIvTij = typename PrimaryIvMatrix::row_type;
-        using PrimaryIvStencil = typename PrimaryInteractionVolume::Traits::Stencil;
 
         using SecondaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, SecondaryInteractionVolume);
         using SecondaryIvLocalFaceData = typename SecondaryInteractionVolume::Traits::LocalFaceData;
@@ -113,7 +115,6 @@ class FicksLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         using SecondaryIvVector = typename SecondaryInteractionVolume::Traits::Vector;
         using SecondaryIvMatrix = typename SecondaryInteractionVolume::Traits::Matrix;
         using SecondaryIvTij = typename SecondaryIvMatrix::row_type;
-        using SecondaryIvStencil = typename SecondaryInteractionVolume::Traits::Stencil;
 
         static constexpr int dim = GridView::dimension;
         static constexpr int dimWorld = GridView::dimensionworld;
@@ -138,7 +139,7 @@ class FicksLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
                              const SubControlVolumeFace &scvf,
                              unsigned int phaseIdx, unsigned int compIdx)
         {
-            primaryStencil_[phaseIdx][compIdx] = &iv.stencil();
+            stencil_[phaseIdx][compIdx] = &iv.stencil();
             switchFluxSign_[phaseIdx][compIdx] = localFaceData.isOutside();
 
             // store pointer to the mole fraction vector of this iv
@@ -168,7 +169,7 @@ class FicksLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
                              const SubControlVolumeFace &scvf,
                              unsigned int phaseIdx, unsigned int compIdx)
         {
-            secondaryStencil_[phaseIdx][compIdx] = &iv.stencil();
+            stencil_[phaseIdx][compIdx] = &iv.stencil();
             switchFluxSign_[phaseIdx][compIdx] = localFaceData.isOutside();
 
             // store pointer to the mole fraction vector of this iv
@@ -205,20 +206,15 @@ class FicksLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         const SecondaryIvVector& moleFractionsSecondaryIv(unsigned int phaseIdx, unsigned int compIdx) const
         { return *secondaryXj_[phaseIdx][compIdx]; }
 
-        //! The stencils corresponding to the transmissibilities (primary type)
-        const PrimaryIvStencil& diffusionStencilPrimaryIv(unsigned int phaseIdx, unsigned int compIdx) const
-        { return *primaryStencil_[phaseIdx][compIdx]; }
-
-        //! The stencils corresponding to the transmissibilities (secondary type)
-        const SecondaryIvStencil& diffusionStencilSecondaryIv(unsigned int phaseIdx, unsigned int compIdx) const
-        { return *secondaryStencil_[phaseIdx][compIdx]; }
+        //! The stencils corresponding to the transmissibilities
+        const Stencil& diffusionStencil(unsigned int phaseIdx, unsigned int compIdx) const
+        { return *stencil_[phaseIdx][compIdx]; }
 
     private:
         std::array< std::array<bool, numComponents>, numPhases > switchFluxSign_;
 
         //! The stencils, i.e. the grid indices j
-        std::array< std::array<const PrimaryIvStencil*, numComponents>, numPhases > primaryStencil_;
-        std::array< std::array<const SecondaryIvStencil*, numComponents>, numPhases > secondaryStencil_;
+        std::array< std::array<const Stencil*, numComponents>, numPhases > stencil_;
 
         //! The transmissibilities such that f = Tij*xj
         std::array< std::array<const PrimaryIvVector*, numComponents>, numPhases > primaryTij_;
diff --git a/dumux/discretization/cellcentered/mpfa/fourierslaw.hh b/dumux/discretization/cellcentered/mpfa/fourierslaw.hh
index 11e2a4accd03b1fa82106cc6efae2618ee8650fa..ebdb9d76aa4e3e286acd5d59142653bf0158e81f 100644
--- a/dumux/discretization/cellcentered/mpfa/fourierslaw.hh
+++ b/dumux/discretization/cellcentered/mpfa/fourierslaw.hh
@@ -88,6 +88,9 @@ class FouriersLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
     //! The cache used in conjunction with the mpfa Fourier's Law
     class MpfaFouriersLawCache
     {
+        using DualGridNodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
+        using Stencil = typename DualGridNodalIndexSet::GridStencilType;
+
         using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
         static constexpr bool considerSecondaryIVs = MpfaHelper::considerSecondaryIVs();
 
@@ -103,7 +106,6 @@ class FouriersLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         using PrimaryIvVector = typename PrimaryInteractionVolume::Traits::Vector;
         using PrimaryIvMatrix = typename PrimaryInteractionVolume::Traits::Matrix;
         using PrimaryIvTij = typename PrimaryIvMatrix::row_type;
-        using PrimaryIvStencil = typename PrimaryInteractionVolume::Traits::Stencil;
 
         using SecondaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, SecondaryInteractionVolume);
         using SecondaryIvLocalFaceData = typename SecondaryInteractionVolume::Traits::LocalFaceData;
@@ -111,7 +113,6 @@ class FouriersLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         using SecondaryIvVector = typename SecondaryInteractionVolume::Traits::Vector;
         using SecondaryIvMatrix = typename SecondaryInteractionVolume::Traits::Matrix;
         using SecondaryIvTij = typename SecondaryIvMatrix::row_type;
-        using SecondaryIvStencil = typename SecondaryInteractionVolume::Traits::Stencil;
 
         static constexpr int dim = GridView::dimension;
         static constexpr int dimWorld = GridView::dimensionworld;
@@ -134,7 +135,7 @@ class FouriersLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
                                   const PrimaryIvDataHandle& dataHandle,
                                   const SubControlVolumeFace &scvf)
         {
-            primaryStencil_ = &iv.stencil();
+            stencil_ = &iv.stencil();
             switchFluxSign_ = localFaceData.isOutside();
 
             // store pointer to the temperature vector of this iv
@@ -163,7 +164,7 @@ class FouriersLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
                                   const SecondaryIvDataHandle& dataHandle,
                                   const SubControlVolumeFace &scvf)
         {
-            secondaryStencil_ = &iv.stencil();
+            stencil_ = &iv.stencil();
             switchFluxSign_ = localFaceData.isOutside();
 
             // store pointer to the temperature vector of this iv
@@ -184,10 +185,7 @@ class FouriersLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         const SecondaryIvTij& heatConductionTijSecondaryIv() const { return *secondaryTij_; }
 
         //! The stencil corresponding to the transmissibilities (primary type)
-        const PrimaryIvStencil& heatConductionStencilPrimaryIv() const { return *primaryStencil_; }
-
-        //! The stencil corresponding to the transmissibilities (secondary type)
-        const SecondaryIvStencil& heatConductionStencilSecondaryIv() const { return *secondaryStencil_; }
+        const Stencil& heatConductionStencil() const { return *stencil_; }
 
         //! The cell (& Dirichlet) temperatures within this interaction volume (primary type)
         const PrimaryIvVector& temperaturesPrimaryIv() const { return *primaryTj_; }
@@ -205,8 +203,7 @@ class FouriersLawImplementation<TypeTag, DiscretizationMethods::CCMpfa>
         bool switchFluxSign_;
 
         //! The stencil, i.e. the grid indices j
-        const PrimaryIvStencil* primaryStencil_;
-        const SecondaryIvStencil* secondaryStencil_;
+        const Stencil* stencil_;
 
         //! The transmissibilities such that f = Tij*Tj
         const PrimaryIvTij* primaryTij_;
diff --git a/dumux/discretization/cellcentered/mpfa/fvelementgeometry.hh b/dumux/discretization/cellcentered/mpfa/fvelementgeometry.hh
index f3fbca56476ed92c90b1194dbb6e5c5f1e1e73ff..44b8f899fe6fdb14221087ea24a8bd92bf36fb6b 100644
--- a/dumux/discretization/cellcentered/mpfa/fvelementgeometry.hh
+++ b/dumux/discretization/cellcentered/mpfa/fvelementgeometry.hh
@@ -59,16 +59,22 @@ class CCMpfaFVElementGeometry<TypeTag, true>
     using ThisType = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
     using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
     using Element = typename GridView::template Codim<0>::Entity;
-
     using GridIndexType = typename GridView::IndexSet::IndexType;
+
+    static constexpr int dim = GridView::dimension;
+
+public:
+    //! export type of subcontrol volume
     using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    //! export type of subcontrol volume face
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
+    //! export type of finite volume grid geometry
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    //! the maximum number of scvs per element
+    static constexpr std::size_t maxNumElementScvs = 1;
+    //! the maximum number of scvfs per element (use cubes for maximum)
+    static constexpr std::size_t maxNumElementScvfs = dim == 3 ? 24 : 8;
 
-    using ScvIterator = Dumux::ScvIterator<SubControlVolume, std::vector<GridIndexType>, ThisType>;
-    using ScvfIterator = Dumux::ScvfIterator<SubControlVolumeFace, std::vector<GridIndexType>, ThisType>;
-
-public:
     //! Constructor
     CCMpfaFVElementGeometry(const FVGridGeometry& fvGridGeometry)
     : fvGridGeometryPtr_(&fvGridGeometry) {}
@@ -97,9 +103,10 @@ public:
     //! This is a free function found by means of ADL
     //! To iterate over all sub control volumes of this FVElementGeometry use
     //! for (auto&& scv : scvs(fvGeometry))
-    friend inline Dune::IteratorRange<ScvIterator>
+    friend inline Dune::IteratorRange< ScvIterator<SubControlVolume, std::array<GridIndexType, 1>, ThisType> >
     scvs(const CCMpfaFVElementGeometry& fvGeometry)
     {
+        using ScvIterator = Dumux::ScvIterator<SubControlVolume, std::array<GridIndexType, 1>, ThisType>;
         return Dune::IteratorRange<ScvIterator>(ScvIterator(fvGeometry.scvIndices_.begin(), fvGeometry),
                                                 ScvIterator(fvGeometry.scvIndices_.end(), fvGeometry));
     }
@@ -109,11 +116,12 @@ public:
     //! This is a free function found by means of ADL
     //! To iterate over all sub control volume faces of this FVElementGeometry use
     //! for (auto&& scvf : scvfs(fvGeometry))
-    friend inline Dune::IteratorRange<ScvfIterator>
+    friend inline Dune::IteratorRange< ScvfIterator<SubControlVolumeFace, std::vector<GridIndexType>, ThisType> >
     scvfs(const CCMpfaFVElementGeometry& fvGeometry)
     {
         const auto& g = fvGeometry.fvGridGeometry();
         const auto scvIdx = fvGeometry.scvIndices_[0];
+        using ScvfIterator = Dumux::ScvfIterator<SubControlVolumeFace, std::vector<GridIndexType>, ThisType>;
         return Dune::IteratorRange<ScvfIterator>(ScvfIterator(g.scvfIndicesOfScv(scvIdx).begin(), fvGeometry),
                                                  ScvfIterator(g.scvfIndicesOfScv(scvIdx).end(), fvGeometry));
     }
@@ -139,7 +147,7 @@ public:
     //! Bind only element-local
     void bindElement(const Element& element)
     {
-        scvIndices_ = std::vector<GridIndexType>({fvGridGeometry().elementMapper().index(element)});
+        scvIndices_[0] = fvGridGeometry().elementMapper().index(element);
     }
 
     //! The global finite volume geometry we are a restriction of
@@ -148,7 +156,7 @@ public:
 
 private:
 
-    std::vector<GridIndexType> scvIndices_;
+    std::array<GridIndexType, 1> scvIndices_;
     const FVGridGeometry* fvGridGeometryPtr_;
 };
 
@@ -168,12 +176,6 @@ class CCMpfaFVElementGeometry<TypeTag, false>
     using GridIndexType = typename GridView::IndexSet::IndexType;
 
     using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
-    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
-    using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
-
-    using ScvIterator = Dumux::ScvIterator<SubControlVolume, std::vector<GridIndexType>, ThisType>;
-    using ScvfIterator = Dumux::ScvfIterator<SubControlVolumeFace, std::vector<GridIndexType>, ThisType>;
 
     static const int dim = GridView::dimension;
     static const int dimWorld = GridView::dimensionworld;
@@ -182,6 +184,17 @@ class CCMpfaFVElementGeometry<TypeTag, false>
     using GlobalPosition = Dune::FieldVector<Scalar, dimWorld>;
 
 public:
+    //! export type of subcontrol volume
+    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    //! export type of subcontrol volume face
+    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
+    //! export type of finite volume grid geometry
+    using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    //! the maximum number of scvs per element
+    static constexpr std::size_t maxNumElementScvs = 1;
+    //! the maximum number of scvfs per element (use cubes for maximum)
+    static constexpr std::size_t maxNumElementScvfs = dim == 3 ? 24 : 8;
+
     //! Constructor
     CCMpfaFVElementGeometry(const FVGridGeometry& fvGridGeometry)
     : fvGridGeometryPtr_(&fvGridGeometry) {}
@@ -234,11 +247,11 @@ public:
     //! This is a free function found by means of ADL
     //! To iterate over all sub control volumes of this FVElementGeometry use
     //! for (auto&& scv : scvs(fvGeometry))
-    friend inline Dune::IteratorRange<typename std::vector<SubControlVolume>::const_iterator>
+    friend inline Dune::IteratorRange<typename std::array<SubControlVolume, 1>::const_iterator>
     scvs(const ThisType& g)
     {
-        using Iter = typename std::vector<SubControlVolume>::const_iterator;
-        return Dune::IteratorRange<Iter>(g.scvs_.begin(), g.scvs_.end());
+        using IteratorType = typename std::array<SubControlVolume, 1>::const_iterator;
+        return Dune::IteratorRange<IteratorType>(g.scvs_.begin(), g.scvs_.end());
     }
 
     //! iterator range for sub control volumes faces. Iterates over
@@ -249,8 +262,8 @@ public:
     friend inline Dune::IteratorRange<typename std::vector<SubControlVolumeFace>::const_iterator>
     scvfs(const ThisType& g)
     {
-        using Iter = typename std::vector<SubControlVolumeFace>::const_iterator;
-        return Dune::IteratorRange<Iter>(g.scvfs_.begin(), g.scvfs_.end());
+        using IteratorType = typename std::vector<SubControlVolumeFace>::const_iterator;
+        return Dune::IteratorRange<IteratorType>(g.scvfs_.begin(), g.scvfs_.end());
     }
 
     //! number of sub control volumes in this fv element geometry
@@ -336,8 +349,8 @@ private:
     {
         // make the scv
         const auto eIdx = fvGridGeometry().elementMapper().index(element);
-        scvs_.emplace_back(element.geometry(), eIdx);
-        scvIndices_.emplace_back(eIdx);
+        scvs_[0] = SubControlVolume(element.geometry(), eIdx);
+        scvIndices_[0] = eIdx;
 
         // get data on the scv faces
         const auto& scvFaceIndices = fvGridGeometry().scvfIndicesOfScv(eIdx);
@@ -617,9 +630,7 @@ private:
     //! clear all containers
     void clear()
     {
-        scvIndices_.clear();
         scvfIndices_.clear();
-        scvs_.clear();
         scvfs_.clear();
 
         neighborScvIndices_.clear();
@@ -634,9 +645,9 @@ private:
     const FVGridGeometry* fvGridGeometryPtr_;
 
     // local storage after binding an element
-    std::vector<GridIndexType> scvIndices_;
+    std::array<GridIndexType, 1> scvIndices_;
     std::vector<GridIndexType> scvfIndices_;
-    std::vector<SubControlVolume> scvs_;
+    std::array<SubControlVolume, 1> scvs_;
     std::vector<SubControlVolumeFace> scvfs_;
 
     std::vector<GridIndexType> neighborScvIndices_;
diff --git a/dumux/discretization/cellcentered/mpfa/fvgridgeometry.hh b/dumux/discretization/cellcentered/mpfa/fvgridgeometry.hh
index 0b1e09f5d9722e5a3babc41ab78ad4a2b3adb46d..dfba3592e209700e45d4642e397f517b5433f180 100644
--- a/dumux/discretization/cellcentered/mpfa/fvgridgeometry.hh
+++ b/dumux/discretization/cellcentered/mpfa/fvgridgeometry.hh
@@ -64,6 +64,7 @@ class CCMpfaFVGridGeometry<TypeTag, true> : public BaseFVGridGeometry<TypeTag>
 
     using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
     using PrimaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, PrimaryInteractionVolume);
+    using DualGridNodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
 
     using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
@@ -164,7 +165,7 @@ public:
         const auto isGhostVertex = MpfaHelper::findGhostVertices(this->gridView(), this->vertexMapper());
 
         // instantiate the dual grid index set (to be used for construction of interaction volumes)
-        CCMpfaDualGridIndexSet<GridIndexType, LocalIndexType, dim> dualIdSet(this->gridView());
+        CCMpfaDualGridIndexSet< DualGridNodalIndexSet > dualIdSet(this->gridView());
 
         // Build the SCVs and SCV faces
         GridIndexType scvfIdx = 0;
@@ -405,6 +406,7 @@ class CCMpfaFVGridGeometry<TypeTag, false> : public BaseFVGridGeometry<TypeTag>
 
     using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
     using PrimaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, PrimaryInteractionVolume);
+    using DualGridNodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
 
     using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
@@ -505,7 +507,7 @@ public:
         isGhostVertex_ = MpfaHelper::findGhostVertices(this->gridView(), this->vertexMapper());
 
         // instantiate the dual grid index set (to be used for construction of interaction volumes)
-        CCMpfaDualGridIndexSet<GridIndexType, LocalIndexType, dim> dualIdSet(this->gridView());
+        CCMpfaDualGridIndexSet< DualGridNodalIndexSet > dualIdSet(this->gridView());
 
         // Build the SCVs and SCV faces
         numScvf_ = 0;
diff --git a/dumux/discretization/cellcentered/mpfa/gridinteractionvolumeindexsets.hh b/dumux/discretization/cellcentered/mpfa/gridinteractionvolumeindexsets.hh
index 5a7a7882f0ddfcfe07e9b4ed0005244d9e0ef336..7ace724febac7f73d204653a76a66ea72fc4ac84 100644
--- a/dumux/discretization/cellcentered/mpfa/gridinteractionvolumeindexsets.hh
+++ b/dumux/discretization/cellcentered/mpfa/gridinteractionvolumeindexsets.hh
@@ -43,6 +43,7 @@ class CCMpfaGridInteractionVolumeIndexSets
     using GridIndexType = typename GridView::IndexSet::IndexType;
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
+    using DualGridNodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
 
     using PrimaryIV = typename GET_PROP_TYPE(TypeTag, PrimaryInteractionVolume);
     using PrimaryIVIndexSet = typename PrimaryIV::Traits::IndexSet;
@@ -51,7 +52,7 @@ class CCMpfaGridInteractionVolumeIndexSets
 
     static constexpr int dim = GridView::dimension;
     using LocalIndexType = typename PrimaryIV::Traits::LocalIndexType;
-    using DualGridIndexSet = CCMpfaDualGridIndexSet< GridIndexType, LocalIndexType, dim>;
+    using DualGridIndexSet = CCMpfaDualGridIndexSet< DualGridNodalIndexSet >;
 
 public:
     /*!
diff --git a/dumux/discretization/cellcentered/mpfa/helper.hh b/dumux/discretization/cellcentered/mpfa/helper.hh
index f129138540b5be817f3789ac4880a4f75802f17c..5a32421a9ed42b371e90f08cf5fd5fae8155fb0c 100644
--- a/dumux/discretization/cellcentered/mpfa/helper.hh
+++ b/dumux/discretization/cellcentered/mpfa/helper.hh
@@ -654,8 +654,8 @@ public:
     { return !std::is_same<PrimaryInteractionVolume, SecondaryInteractionVolume>::value; }
 
     //! returns whether or not a value exists in a vector
-    template<typename V1, typename V2>
-    static bool vectorContainsValue(const std::vector<V1>& vector, const V2 value)
+    template<typename V, typename T>
+    static bool vectorContainsValue(const V& vector, const T value)
     { return std::find(vector.begin(), vector.end(), value) != vector.end(); }
 };
 
diff --git a/dumux/discretization/cellcentered/mpfa/interactionvolumebase.hh b/dumux/discretization/cellcentered/mpfa/interactionvolumebase.hh
index 51ac0be10eebe29b0c12ce5c8df5480725f1c059..d5e46c9b0ff4a71744be6ffe45a8dd158bcac447 100644
--- a/dumux/discretization/cellcentered/mpfa/interactionvolumebase.hh
+++ b/dumux/discretization/cellcentered/mpfa/interactionvolumebase.hh
@@ -65,8 +65,6 @@ namespace Dumux
  * using Matrix = ...;
  * //! export the type used for iv-local vectors
  * using Vector = ...;
- * //! export the type used for the iv-stencils
- * using Stencil = ...;
  * //! export the data handle type for this iv
  * using DataHandle = ...;
  * \endcode
@@ -123,7 +121,7 @@ public:
     { DUNE_THROW(Dune::NotImplemented, "Interaction volume implementation does not provide a localFaceData() funtion"); }
 
     //! returns the cell-stencil of this interaction volume
-    const typename Traits::Stencil& stencil() const { return asImp().stencil(); }
+    const typename Traits::IndexSet::GridStencilType& stencil() const { return asImp().stencil(); }
 
     //! returns the local scvf entity corresponding to a given iv-local scvf idx
     const typename Traits::LocalScvfType& localScvf(typename Traits::LocalIndexType ivLocalScvfIdx) const
diff --git a/dumux/discretization/cellcentered/mpfa/omethod/interactionvolume.hh b/dumux/discretization/cellcentered/mpfa/omethod/interactionvolume.hh
index 298ec506bd6461955c8b68fe6ee4c25d3417d8ab..06fefbd08a6d351b67beb5e264c78e17b3a76743 100644
--- a/dumux/discretization/cellcentered/mpfa/omethod/interactionvolume.hh
+++ b/dumux/discretization/cellcentered/mpfa/omethod/interactionvolume.hh
@@ -51,7 +51,8 @@ template< class TypeTag >
 struct CCMpfaODefaultInteractionVolumeTraits
 {
 private:
-    using GridIndexType = typename GET_PROP_TYPE(TypeTag, GridView)::IndexSet::IndexType;
+    using NodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
+    using GridIndexType = typename NodalIndexSet::GridIndexType;
     static constexpr int dim = GET_PROP_TYPE(TypeTag, GridView)::dimension;
     static constexpr int dimWorld = GET_PROP_TYPE(TypeTag, GridView)::dimensionworld;
 
@@ -69,9 +70,9 @@ public:
     //! export the type of the mpfa helper class
     using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
     //! export the type used for local indices
-    using LocalIndexType = std::uint8_t;
+    using LocalIndexType = typename NodalIndexSet::LocalIndexType;
     //! export the type for the interaction volume index set
-    using IndexSet = CCMpfaOInteractionVolumeIndexSet< DualGridNodalIndexSet<GridIndexType, LocalIndexType, dim> >;
+    using IndexSet = CCMpfaOInteractionVolumeIndexSet< NodalIndexSet >;
     //! export the type of interaction-volume local scvs
     using LocalScvType = CCMpfaOInteractionVolumeLocalScv< IndexSet, ScalarType, dim, dimWorld >;
     //! export the type of interaction-volume local scvfs
@@ -82,8 +83,6 @@ public:
     using Matrix = Dune::DynamicMatrix< ScalarType >;
     //! export the type used for iv-local vectors
     using Vector = Dune::DynamicVector< ScalarType >;
-    //! export the type used for the iv-stencils
-    using Stencil = std::vector< GridIndexType >;
     //! export the data handle type for this iv
     using DataHandle = InteractionVolumeDataHandle< TypeTag, Matrix, Vector, LocalIndexType >;
 };
@@ -119,15 +118,7 @@ class CCMpfaOInteractionVolume : public CCMpfaInteractionVolumeBase< CCMpfaOInte
     using IndexSet = typename Traits::IndexSet;
     using GridIndexType = typename GridView::IndexSet::IndexType;
     using LocalIndexType = typename Traits::LocalIndexType;
-    using Stencil = typename Traits::Stencil;
-
-    //! For the o method, the interaction volume stencil can be taken directly
-    //! from the nodal index set, which always uses dynamic types to be compatible
-    //! on the boundaries and unstructured grids. Thus, we have to make sure that
-    //! the type set for the stencils in the traits is castable.
-    static_assert( std::is_convertible<Stencil*, typename IndexSet::GridIndexContainer*>::value,
-                   "The o-method uses the (dynamic) nodal index set's stencil as the interaction volume stencil. "
-                   "Using a different type is not permissive here." );
+    using Stencil = typename IndexSet::GridStencilType;
 
     //! Data attached to scvf touching Dirichlet boundaries.
     //! For the default o-scheme, we only store the corresponding vol vars index.
diff --git a/dumux/discretization/cellcentered/mpfa/omethod/interactionvolumeindexset.hh b/dumux/discretization/cellcentered/mpfa/omethod/interactionvolumeindexset.hh
index 84a26f7a4e4a253f905b1ca6405f6b1e2a2bdd65..1d63d804f259675a3619111b2056aa24dcd9430f 100644
--- a/dumux/discretization/cellcentered/mpfa/omethod/interactionvolumeindexset.hh
+++ b/dumux/discretization/cellcentered/mpfa/omethod/interactionvolumeindexset.hh
@@ -24,6 +24,8 @@
 #ifndef DUMUX_DISCRETIZATION_MPFA_O_INTERACTIONVOLUME_INDEXSET_HH
 #define DUMUX_DISCRETIZATION_MPFA_O_INTERACTIONVOLUME_INDEXSET_HH
 
+#include <dune/common/reservedvector.hh>
+
 #include <dumux/discretization/cellcentered/mpfa/dualgridindexset.hh>
 
 namespace Dumux
@@ -38,46 +40,27 @@ template< class DualGridNodalIndexSet >
 class CCMpfaOInteractionVolumeIndexSet
 {
 public:
+    //! Export the type used for the nodal grid index sets
+    using NodalIndexSet = DualGridNodalIndexSet;
+
+    //! Export the types used for local/grid indices
     using LocalIndexType = typename DualGridNodalIndexSet::LocalIndexType;
     using GridIndexType = typename DualGridNodalIndexSet::GridIndexType;
 
-    using LocalIndexContainer = typename DualGridNodalIndexSet::LocalIndexContainer;
-    using GridIndexContainer = typename DualGridNodalIndexSet::GridIndexContainer;
-
-    /*!
-     * \brief The constructor
-     * \note The actual type used for the nodal index sets might be different, as maybe
-     *       a different type for the local indexes is used. We therefore template this
-     *       constructor. However, a static assertion enforces you to use the same LocalIndexType
-     *       in the traits for both the secondary and the primary interaction volume traits.
-     *
-     * \tparam NodalIndexSet Possibly differing type for the DualGridNodalIndexSet
-     */
-    template< class NodalIndexSet >
-    CCMpfaOInteractionVolumeIndexSet(const NodalIndexSet& nodalIndexSet)
-    : nodalIndexSet_( static_cast<const DualGridNodalIndexSet&>(nodalIndexSet) )
-    {
-        // make sure the index types used are the same in order to avoid any losses due to type conversion
-        static_assert(std::is_same<GridIndexType, typename NodalIndexSet::GridIndexType>::value,
-                      "Provided nodal index set does not use the same type for grid indices as the given template argument");
-        static_assert(std::is_same<LocalIndexType, typename NodalIndexSet::LocalIndexType>::value,
-                      "Provided nodal index set does not use the same type for local indices as the given template argument");
+    // Export the types used for local/grid stencils
+    using LocalStencilType = typename DualGridNodalIndexSet::LocalStencilType;
+    using GridStencilType = typename DualGridNodalIndexSet::GridStencilType;
+    using GridScvfStencilType = typename DualGridNodalIndexSet::GridScvfStencilType;
 
+    //! Export the type used for the neighbor scv index sets of the scvfs
+    using ScvfNeighborLocalIndexSet = typename DualGridNodalIndexSet::ScvfNeighborLocalIndexSet;
+
+    //! The constructor
+    CCMpfaOInteractionVolumeIndexSet(const NodalIndexSet& nodalIndexSet) : nodalIndexSet_(nodalIndexSet)
+    {
         // determine the number of iv-local faces for memory reservation
         // note that this might be a vast overestimation on surface grids!
         const auto numNodalScvfs = nodalIndexSet.numScvfs();
-        const auto numBoundaryScvfs = nodalIndexSet.numBoundaryScvfs();
-        const std::size_t numFaceEstimate = numBoundaryScvfs + (numNodalScvfs-numBoundaryScvfs)/2;
-
-        // make sure we found a reasonable number of faces
-        assert((numNodalScvfs-numBoundaryScvfs)%2 == 0);
-
-        // index transformation from interaction-volume-local to node-local
-        ivToNodeScvf_.reserve(numFaceEstimate);
-        nodeToIvScvf_.resize(numNodalScvfs);
-
-        // the local neighboring scv indices of the faces
-        scvfNeighborScvLocalIndices_.reserve(numFaceEstimate);
 
         // keeps track of which nodal scvfs have been handled already
         std::vector<bool> isHandled(numNodalScvfs, false);
@@ -172,7 +155,7 @@ public:
     { return nodeToIvScvf_[ nodalIndexSet_.scvfIdxLocal(scvIdxLocal, i) ]; }
 
     //! returns the local indices of the neighboring scvs of an scvf
-    const LocalIndexContainer& neighboringLocalScvIndices(LocalIndexType ivLocalScvfIdx) const
+    const ScvfNeighborLocalIndexSet& neighboringLocalScvIndices(LocalIndexType ivLocalScvfIdx) const
     { return scvfNeighborScvLocalIndices_[ivLocalScvfIdx]; }
 
     //! returns the number of faces in the interaction volume
@@ -182,10 +165,10 @@ public:
     std::size_t numScvs() const { return nodalIndexSet_.numScvs(); }
 
     //! returns the global scv indices connected to this dual grid node
-    const GridIndexContainer& globalScvIndices() const { return nodalIndexSet_.globalScvIndices(); }
+    const GridStencilType& globalScvIndices() const { return nodalIndexSet_.globalScvIndices(); }
 
     //! returns the global scvf indices connected to this dual grid node
-    const GridIndexContainer& globalScvfIndices() const { return nodalIndexSet_.globalScvfIndices(); }
+    const GridScvfStencilType& globalScvfIndices() const { return nodalIndexSet_.globalScvfIndices(); }
 
 private:
     //! returns the local scv index to a given global scv index
@@ -199,11 +182,12 @@ private:
     const DualGridNodalIndexSet& nodalIndexSet_;
 
     std::size_t numFaces_;
-    std::vector<LocalIndexType> ivToNodeScvf_;
-    std::vector<LocalIndexType> nodeToIvScvf_;
+    Dune::ReservedVector< LocalIndexType, NodalIndexSet::maxNumScvfsAtNode > ivToNodeScvf_;
+    Dune::ReservedVector< LocalIndexType, NodalIndexSet::maxNumScvfsAtNode > nodeToIvScvf_;
+
     // maps to each scvf a list of neighbouring scv indices
     // ordering: 0 - inside scv idx; 1..n - outside scv indices
-    std::vector< LocalIndexContainer > scvfNeighborScvLocalIndices_;
+    Dune::ReservedVector< ScvfNeighborLocalIndexSet, NodalIndexSet::maxNumScvfsAtNode > scvfNeighborScvLocalIndices_;
 };
 
 } // end namespace Dumux
diff --git a/dumux/discretization/cellcentered/mpfa/omethod/localassembler.hh b/dumux/discretization/cellcentered/mpfa/omethod/localassembler.hh
index 4b5f11aa27cf41a64c3a2cb8b0124f6db21bfb4d..fd6700c7273d300f4433326e262320f653e77614 100644
--- a/dumux/discretization/cellcentered/mpfa/omethod/localassembler.hh
+++ b/dumux/discretization/cellcentered/mpfa/omethod/localassembler.hh
@@ -627,7 +627,7 @@ private:
      * \param getTensor Lambda to evaluate the scv-wise tensors
      */
     template< class IV, class GetTensor >
-    void assembleLocalMatrices_(Matrix& A, Matrix& B,  Matrix& C, Matrix& D,
+    void assembleLocalMatrices_(Matrix& A, Matrix& B, Matrix& C, Matrix& D,
                                 IV& iv, const GetTensor& getTensor)
     {
         // Matrix D is assumed to have the right size already
@@ -672,7 +672,7 @@ private:
             // we require the matrices A,B,C to have the correct size already
             assert(A.rows() == iv.numUnknowns() && A.cols() == iv.numUnknowns() && "Matrix A does not have the correct size");
             assert(B.rows() == iv.numUnknowns() && B.cols() == iv.numKnowns() && "Matrix B does not have the correct size");
-            assert(C.rows() == iv.numFaces() && C.cols() == iv.numKnowns() && "Matrix C does not have the correct size");
+            assert(C.rows() == iv.numFaces() && C.cols() == iv.numUnknowns() && "Matrix C does not have the correct size");
 
             // reset matrices
             A = 0.0;
diff --git a/dumux/discretization/cellcentered/mpfa/omethod/localsubcontrolentities.hh b/dumux/discretization/cellcentered/mpfa/omethod/localsubcontrolentities.hh
index 7ddded18c3abba12cc578707e0358606da02597d..510ace43c52a0393a4acfa1003eb7d3482409b6a 100644
--- a/dumux/discretization/cellcentered/mpfa/omethod/localsubcontrolentities.hh
+++ b/dumux/discretization/cellcentered/mpfa/omethod/localsubcontrolentities.hh
@@ -134,7 +134,7 @@ private:
 template< class IvIndexSet >
 struct CCMpfaOInteractionVolumeLocalScvf
 {
-  using LocalIndexContainer = typename IvIndexSet::LocalIndexContainer;
+  using ScvfNeighborLocalIndexSet = typename IvIndexSet::ScvfNeighborLocalIndexSet;
 
 public:
     // export index types
@@ -154,7 +154,7 @@ public:
      */
     template< class SubControlVolumeFace >
     CCMpfaOInteractionVolumeLocalScvf(const SubControlVolumeFace& scvf,
-                                      const LocalIndexContainer& localScvIndices,
+                                      const ScvfNeighborLocalIndexSet& localScvIndices,
                                       const LocalIndexType localDofIdx,
                                       const bool isDirichlet)
     : isDirichlet_(isDirichlet)
@@ -171,7 +171,7 @@ public:
     GridIndexType globalScvfIndex() const { return scvfIdxGlobal_; }
 
     //! Returns the local indices of the scvs neighboring this scvf
-    const LocalIndexContainer& neighboringLocalScvIndices() const { return *neighborScvIndicesLocal_; }
+    const ScvfNeighborLocalIndexSet& neighboringLocalScvIndices() const { return *neighborScvIndicesLocal_; }
 
     //! states if this is scvf is on a Dirichlet boundary
     bool isDirichlet() const { return isDirichlet_; }
@@ -180,7 +180,7 @@ private:
     bool isDirichlet_;
     GridIndexType scvfIdxGlobal_;
     LocalIndexType localDofIndex_;
-    const LocalIndexContainer* neighborScvIndicesLocal_;
+    const ScvfNeighborLocalIndexSet* neighborScvIndicesLocal_;
 };
 
 } // end namespace Dumux
diff --git a/dumux/discretization/cellcentered/mpfa/omethod/staticinteractionvolume.hh b/dumux/discretization/cellcentered/mpfa/omethod/staticinteractionvolume.hh
index d4401e178678120e2d3c807aa683281c052f6a35..a800e18829a237495894504e040109f07ea70073 100644
--- a/dumux/discretization/cellcentered/mpfa/omethod/staticinteractionvolume.hh
+++ b/dumux/discretization/cellcentered/mpfa/omethod/staticinteractionvolume.hh
@@ -52,7 +52,8 @@ template< class TypeTag, int localSize >
 struct CCMpfaODefaultStaticInteractionVolumeTraits
 {
 private:
-    using GridIndexType = typename GET_PROP_TYPE(TypeTag, GridView)::IndexSet::IndexType;
+    using NodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
+    using GridIndexType = typename NodalIndexSet::GridIndexType;
     static constexpr int dim = GET_PROP_TYPE(TypeTag, GridView)::dimension;
     static constexpr int dimWorld = GET_PROP_TYPE(TypeTag, GridView)::dimensionworld;
 
@@ -70,9 +71,9 @@ public:
     //! export the type of the mpfa helper class
     using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper);
     //! export the type used for local indices
-    using LocalIndexType = std::uint8_t;
+    using LocalIndexType = typename NodalIndexSet::LocalIndexType;
     //! export the type for the interaction volume index set
-    using IndexSet = CCMpfaOInteractionVolumeIndexSet< DualGridNodalIndexSet<GridIndexType, LocalIndexType, dim> >;
+    using IndexSet = CCMpfaOInteractionVolumeIndexSet< NodalIndexSet >;
     //! export the type of interaction-volume local scvs
     using LocalScvType = CCMpfaOInteractionVolumeLocalScv< IndexSet, ScalarType, dim, dimWorld >;
     //! export the type of interaction-volume local scvfs
@@ -83,8 +84,6 @@ public:
     using Matrix = Dune::FieldMatrix< ScalarType, localSize, localSize >;
     //! export the type used for iv-local vectors
     using Vector = Dune::FieldVector< ScalarType, localSize >;
-    //! export the type used for the iv-stencils
-    using Stencil = std::vector< GridIndexType >;
     //! export the data handle type for this iv
     using DataHandle = InteractionVolumeDataHandle< TypeTag, Matrix, Vector, LocalIndexType >;
 };
@@ -126,15 +125,7 @@ class CCMpfaOStaticInteractionVolume : public CCMpfaInteractionVolumeBase< CCMpf
     using IndexSet = typename Traits::IndexSet;
     using GridIndexType = typename GridView::IndexSet::IndexType;
     using LocalIndexType = typename Traits::LocalIndexType;
-    using Stencil = typename Traits::Stencil;
-
-    //! For the o method, the interaction volume stencil can be taken directly
-    //! from the nodal index set, which always uses dynamic types to be compatible
-    //! on the boundaries and unstructured grids. Thus, we have to make sure that
-    //! the type set for the stencils in the traits is castable.
-    static_assert( std::is_convertible<Stencil*, typename IndexSet::GridIndexContainer*>::value,
-                   "The o-method uses the (dynamic) nodal index set's stencil as the interaction volume stencil. "
-                   "Using a different type is not permissive here." );
+    using Stencil = typename IndexSet::GridStencilType;
 
     //! Data attached to scvf touching Dirichlet boundaries.
     //! For the default o-scheme, we only store the corresponding vol vars index.
diff --git a/dumux/discretization/cellcentered/mpfa/properties.hh b/dumux/discretization/cellcentered/mpfa/properties.hh
index 1a5e96d98a389f00c0976233008b749cc5f4e1d0..3a20ad88449e6cfb1d6809240a930c123f73a2a1 100644
--- a/dumux/discretization/cellcentered/mpfa/properties.hh
+++ b/dumux/discretization/cellcentered/mpfa/properties.hh
@@ -47,10 +47,10 @@
 #include <dumux/discretization/cellcentered/mpfa/elementvolumevariables.hh>
 #include <dumux/discretization/cellcentered/mpfa/elementfluxvariablescache.hh>
 #include <dumux/discretization/cellcentered/mpfa/subcontrolvolumeface.hh>
+#include <dumux/discretization/cellcentered/mpfa/dualgridindexset.hh>
 #include <dumux/discretization/cellcentered/mpfa/helper.hh>
 
 #include <dumux/discretization/cellcentered/mpfa/omethod/interactionvolume.hh>
-#include <dumux/discretization/cellcentered/mpfa/omethod/staticinteractionvolume.hh>
 
 namespace Dumux
 {
@@ -71,6 +71,44 @@ SET_PROP(CCMpfaModel, MpfaMethod)
     static const MpfaMethods value = GET_PROP_TYPE(TypeTag, PrimaryInteractionVolume)::MpfaMethod;
 };
 
+//! Set the maximum admissible number of branches per scvf
+SET_PROP(CCMpfaModel, MaxNumNeighborsPerScvf)
+{
+private:
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    static constexpr int dim = GridView::dimension;
+    static constexpr int dimWorld = GridView::dimensionworld;
+
+public:
+    // Per default, we allow for 8 neighbors on network/surface grids
+    static const std::size_t value = dim < dimWorld ? 9 : 2;
+};
+
+//! Set the index set type used on the dual grid nodes
+SET_PROP(CCMpfaModel, DualGridNodalIndexSet)
+{
+private:
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+
+    // Use the grid view's index type for grid indices
+    using GI = typename GridView::IndexSet::IndexType;
+
+    // per default, use uint8_t as iv-local index type
+    using LI = std::uint8_t;
+
+    // the specified maximum admissible number of branches per scvf
+    static constexpr int maxB = GET_PROP_VALUE(TypeTag, MaxNumNeighborsPerScvf);
+
+    // maximum admissible number of elements around a node
+    // if for a given grid this number is still not high enough,
+    // overwrite this property in your problem with a higher number
+    static constexpr int dim = GridView::dimension;
+    static constexpr int maxE = dim == 3 ? 45 : 15;
+
+public:
+    using type = CCMpfaDualGridNodalIndexSet<GI, LI, dim, maxE, maxB>;
+};
+
 //! The mpfa helper class
 SET_TYPE_PROP(CCMpfaModel, MpfaHelper, CCMpfaHelper<TypeTag>);
 
diff --git a/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh b/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh
index 12837c09b265a23fdba0819608df3a9daac31aeb..014c54320a1529052b78b67b2c8ef9654dd42195 100644
--- a/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh
+++ b/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh
@@ -61,15 +61,20 @@ class CCTpfaFVElementGeometry<TypeTag, true>
     using ThisType = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
     using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
     using IndexType = typename GridView::IndexSet::IndexType;
+    using Element = typename GridView::template Codim<0>::Entity;
+
+public:
+    //! export type of subcontrol volume
     using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    //! export type of subcontrol volume face
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
-    using Element = typename GridView::template Codim<0>::Entity;
+    //! export type of finite volume grid geometry
     using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    //! the maximum number of scvs per element
+    static constexpr std::size_t maxNumElementScvs = 1;
+    //! the maximum number of scvfs per element (use cubes for maximum)
+    static constexpr std::size_t maxNumElementScvfs = 2*GridView::dimension;
 
-    using ScvIterator = Dumux::ScvIterator<SubControlVolume, std::vector<IndexType>, ThisType>;
-    using ScvfIterator = Dumux::ScvfIterator<SubControlVolumeFace, std::vector<IndexType>, ThisType>;
-
-public:
     //! Constructor
     CCTpfaFVElementGeometry(const FVGridGeometry& fvGridGeometry)
     : fvGridGeometryPtr_(&fvGridGeometry) {}
@@ -100,9 +105,10 @@ public:
     //! This is a free function found by means of ADL
     //! To iterate over all sub control volumes of this FVElementGeometry use
     //! for (auto&& scv : scvs(fvGeometry))
-    friend inline Dune::IteratorRange<ScvIterator>
+    friend inline Dune::IteratorRange< ScvIterator<SubControlVolume, std::array<IndexType, 1>, ThisType> >
     scvs(const CCTpfaFVElementGeometry& fvGeometry)
     {
+        using ScvIterator = Dumux::ScvIterator<SubControlVolume, std::array<IndexType, 1>, ThisType>;
         return Dune::IteratorRange<ScvIterator>(ScvIterator(fvGeometry.scvIndices_.begin(), fvGeometry),
                                                 ScvIterator(fvGeometry.scvIndices_.end(), fvGeometry));
     }
@@ -112,11 +118,12 @@ public:
     //! This is a free function found by means of ADL
     //! To iterate over all sub control volume faces of this FVElementGeometry use
     //! for (auto&& scvf : scvfs(fvGeometry))
-    friend inline Dune::IteratorRange<ScvfIterator>
+    friend inline Dune::IteratorRange< ScvfIterator<SubControlVolumeFace, std::vector<IndexType>, ThisType> >
     scvfs(const CCTpfaFVElementGeometry& fvGeometry)
     {
         const auto& g = fvGeometry.fvGridGeometry();
         const auto scvIdx = fvGeometry.scvIndices_[0];
+        using ScvfIterator = Dumux::ScvfIterator<SubControlVolumeFace, std::vector<IndexType>, ThisType>;
         return Dune::IteratorRange<ScvfIterator>(ScvfIterator(g.scvfIndicesOfScv(scvIdx).begin(), fvGeometry),
                                                  ScvfIterator(g.scvfIndicesOfScv(scvIdx).end(), fvGeometry));
     }
@@ -143,7 +150,7 @@ public:
     void bindElement(const Element& element)
     {
         elementPtr_ = &element;
-        scvIndices_ = std::vector<IndexType>({fvGridGeometry().elementMapper().index(*elementPtr_)});
+        scvIndices_[0] = fvGridGeometry().elementMapper().index(*elementPtr_);
     }
 
     //! The global finite volume geometry we are a restriction of
@@ -153,7 +160,7 @@ public:
 private:
 
     const Element* elementPtr_;
-    std::vector<IndexType> scvIndices_;
+    std::array<IndexType, 1> scvIndices_;
     const FVGridGeometry* fvGridGeometryPtr_;
 };
 
@@ -168,18 +175,23 @@ class CCTpfaFVElementGeometry<TypeTag, false>
     using ThisType = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
     using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
     using IndexType = typename GridView::IndexSet::IndexType;
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
-    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
     using Element = typename GridView::template Codim<0>::Entity;
-    using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
 
     static const int dim = GridView::dimension;
     static const int dimWorld = GridView::dimensionworld;
 
-    using ScvIterator = Dumux::ScvIterator<SubControlVolume, std::vector<IndexType>, ThisType>;
-    using ScvfIterator = Dumux::ScvfIterator<SubControlVolumeFace, std::vector<IndexType>, ThisType>;
-
 public:
+    //! export type of subcontrol volume
+    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
+    //! export type of subcontrol volume face
+    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
+    //! export type of finite volume grid geometry
+    using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    //! the maximum number of scvs per element
+    static constexpr std::size_t maxNumElementScvs = 1;
+    //! the maximum number of scvfs per element (use cubes for maximum)
+    static constexpr std::size_t maxNumElementScvfs = 2*dim;
+
     //! Constructor
     CCTpfaFVElementGeometry(const FVGridGeometry& fvGridGeometry)
     : fvGridGeometryPtr_(&fvGridGeometry) {}
@@ -231,11 +243,11 @@ public:
     //! This is a free function found by means of ADL
     //! To iterate over all sub control volumes of this FVElementGeometry use
     //! for (auto&& scv : scvs(fvGeometry))
-    friend inline Dune::IteratorRange<typename std::vector<SubControlVolume>::const_iterator>
+    friend inline Dune::IteratorRange<typename std::array<SubControlVolume, 1>::const_iterator>
     scvs(const ThisType& g)
     {
-        using Iter = typename std::vector<SubControlVolume>::const_iterator;
-        return Dune::IteratorRange<Iter>(g.scvs_.begin(), g.scvs_.end());
+        using IteratorType = typename std::array<SubControlVolume, 1>::const_iterator;
+        return Dune::IteratorRange<IteratorType>(g.scvs_.begin(), g.scvs_.end());
     }
 
     //! iterator range for sub control volumes faces. Iterates over
@@ -246,8 +258,8 @@ public:
     friend inline Dune::IteratorRange<typename std::vector<SubControlVolumeFace>::const_iterator>
     scvfs(const ThisType& g)
     {
-        using Iter = typename std::vector<SubControlVolumeFace>::const_iterator;
-        return Dune::IteratorRange<Iter>(g.scvfs_.begin(), g.scvfs_.end());
+        using IteratorType = typename std::vector<SubControlVolumeFace>::const_iterator;
+        return Dune::IteratorRange<IteratorType>(g.scvfs_.begin(), g.scvfs_.end());
     }
 
     //! number of sub control volumes in this fv element geometry
@@ -384,8 +396,8 @@ private:
     void makeElementGeometries(const Element& element)
     {
         const auto eIdx = fvGridGeometry().elementMapper().index(element);
-        scvs_.emplace_back(element.geometry(), eIdx);
-        scvIndices_.emplace_back(eIdx);
+        scvs_[0] = SubControlVolume(element.geometry(), eIdx);
+        scvIndices_[0] = eIdx;
 
         const auto& scvFaceIndices = fvGridGeometry().scvfIndicesOfScv(eIdx);
         const auto& neighborVolVarIndices = fvGridGeometry().neighborVolVarIndices(eIdx);
@@ -523,9 +535,7 @@ private:
     //! Clear all local data
     void clear()
     {
-        scvIndices_.clear();
         scvfIndices_.clear();
-        scvs_.clear();
         scvfs_.clear();
         flippedScvfIndices_.clear();
 
@@ -536,21 +546,21 @@ private:
         flippedNeighborScvfIndices_.clear();
     }
 
-    //! the bound element
-    const Element* elementPtr_;
-
-    const FVGridGeometry* fvGridGeometryPtr_;
+    const Element* elementPtr_; //!< the element to which this fvgeometry is bound
+    const FVGridGeometry* fvGridGeometryPtr_;  //!< the grid fvgeometry
 
     // local storage after binding an element
-    std::vector<IndexType> scvIndices_;
+    std::array<IndexType, 1> scvIndices_;
+    std::array<SubControlVolume, 1> scvs_;
+
     std::vector<IndexType> scvfIndices_;
-    std::vector<SubControlVolume> scvs_;
     std::vector<SubControlVolumeFace> scvfs_;
     std::vector<std::vector<IndexType>> flippedScvfIndices_;
 
     std::vector<IndexType> neighborScvIndices_;
-    std::vector<IndexType> neighborScvfIndices_;
     std::vector<SubControlVolume> neighborScvs_;
+
+    std::vector<IndexType> neighborScvfIndices_;
     std::vector<SubControlVolumeFace> neighborScvfs_;
     std::vector<std::vector<IndexType>> flippedNeighborScvfIndices_;
 };
diff --git a/dumux/discretization/cellcentered/tpfa/properties.hh b/dumux/discretization/cellcentered/tpfa/properties.hh
index 8e2bca2a4857885649ade46a74e54528762004b1..3710cf0fa7f339df01da81fc79ae8b570dac3f81 100644
--- a/dumux/discretization/cellcentered/tpfa/properties.hh
+++ b/dumux/discretization/cellcentered/tpfa/properties.hh
@@ -82,6 +82,19 @@ SET_TYPE_PROP(CCTpfaModel, ElementFluxVariablesCache, CCTpfaElementFluxVariables
 //! The global current volume variables vector class
 SET_TYPE_PROP(CCTpfaModel, GridVolumeVariables, CCGridVolumeVariables<TypeTag, GET_PROP_VALUE(TypeTag, EnableGridVolumeVariablesCache)>);
 
+//! Set the maximum admissible number of branches per scvf
+SET_PROP(CCTpfaModel, MaxNumNeighborsPerScvf)
+{
+private:
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    static constexpr int dim = GridView::dimension;
+    static constexpr int dimWorld = GridView::dimensionworld;
+
+public:
+    // Per default, we allow for 8 neighbors on network/surface grids
+    static const std::size_t value = dim < dimWorld ? 9 : 2;
+};
+
 //! The sub control volume
 SET_PROP(CCTpfaModel, SubControlVolume)
 {
diff --git a/dumux/discretization/fluxstencil.hh b/dumux/discretization/fluxstencil.hh
index 0f24ee01654c42b9d7f7e074f9c0915a65b96ab1..a138efb0a86c1d47f8812807a75e088fcde77d3e 100644
--- a/dumux/discretization/fluxstencil.hh
+++ b/dumux/discretization/fluxstencil.hh
@@ -24,6 +24,7 @@
 #ifndef DUMUX_DISCRETIZATION_FLUXSTENCIL_HH
 #define DUMUX_DISCRETIZATION_FLUXSTENCIL_HH
 
+#include <dune/common/reservedvector.hh>
 #include <dumux/common/properties.hh>
 #include <dumux/discretization/methods.hh>
 
@@ -58,9 +59,17 @@ class FluxStencilImplementation<TypeTag, DiscretizationMethods::CCTpfa>
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
     using Element = typename GridView::template Codim<0>::Entity;
     using IndexType = typename GridView::IndexSet::IndexType;
-    using Stencil = std::vector<IndexType>;
 
 public:
+    //! The maximum number of elements in a flux stencil
+    static constexpr int maxFluxStencilSize = GET_PROP_VALUE(TypeTag, MaxNumNeighborsPerScvf);
+
+    //! States how many scvfs of an element J might have an element I in the flux stencil
+    static constexpr int maxNumScvfJForI = 1;
+
+    //! The flux stencil type
+    using Stencil = Dune::ReservedVector<IndexType, maxFluxStencilSize>;
+
     static Stencil stencil(const Element& element,
                            const FVElementGeometry& fvGeometry,
                            const SubControlVolumeFace& scvf)
@@ -91,12 +100,36 @@ class FluxStencilImplementation<TypeTag, DiscretizationMethods::CCMpfa>
     using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
     using Element = typename GridView::template Codim<0>::Entity;
     using IndexType = typename GridView::IndexSet::IndexType;
-    using Stencil = std::vector<IndexType>;
+    static constexpr int dim = GridView::dimension;
+
+    // Use the stencil type of the primary interaction volume
+    using NodalIndexSet = typename GET_PROP_TYPE(TypeTag, DualGridNodalIndexSet);
 
 public:
-    static Stencil stencil(const Element& element,
-                           const FVElementGeometry& fvGeometry,
-                           const SubControlVolumeFace& scvf)
+    //! The maximum number of elements in a flux stencil (equal to number of elements at node)
+    static constexpr int maxFluxStencilSize = NodalIndexSet::maxNumElementsAtNode;
+
+    //! States how many scvfs of an element J might have an element I in the flux stencil
+    //! We use cubes here for determining the maximum. Only basic geometries are not supported.
+    static constexpr int maxNumScvfJForI = dim == 3 ? 12 : 4;
+
+    //! The flux stencil type
+    using Stencil = typename NodalIndexSet::GridStencilType;
+
+    /*
+     * \brief Returns a set of grid element indices that participate in the
+     *        flux calculations on a given scvf.
+     *
+     * \note The interaction volume index sets must use the same type for the
+     *       stencils as the nodal index set. If not, the compiler will complain here.
+     *
+     * \param element The grid element
+     * \param fvGeometry The finite volume geometry of this element
+     * \param scvf The sub-control volume face embedded in this element
+     */
+    static const Stencil& stencil(const Element& element,
+                                  const FVElementGeometry& fvGeometry,
+                                  const SubControlVolumeFace& scvf)
     {
         const auto& fvGridGeometry = fvGeometry.fvGridGeometry();
 
diff --git a/dumux/discretization/upwindscheme.hh b/dumux/discretization/upwindscheme.hh
index 38eb68b2299b36883176f3e61143c08bb56ab213..e2056f47faf943f2889144b35f73fe47c2c2066a 100644
--- a/dumux/discretization/upwindscheme.hh
+++ b/dumux/discretization/upwindscheme.hh
@@ -56,14 +56,15 @@ public:
                         const UpwindTermFunction& upwindTerm,
                         Scalar flux, int phaseIdx)
     {
-        // retrieve the upwind weight for the mass conservation equations. Use the value
-        // specified via the property system as default, and overwrite
-        // it by the run-time parameter from the Dune::ParameterTree
-        static const Scalar upwindWeight = getParamFromGroup<Scalar>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Implicit.UpwindWeight");
-
-        const auto& insideVolVars = fluxVars.elemVolVars()[fluxVars.scvFace().insideScvIdx()];
-        const auto& outsideVolVars = fluxVars.elemVolVars()[fluxVars.scvFace().outsideScvIdx()];
-        if (std::signbit(flux))
+        static const Scalar upwindWeight = getParamFromGroup<Scalar>
+            (GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Implicit.UpwindWeight");
+
+        const auto& scvf = fluxVars.scvFace();
+        const auto& elemVolVars = fluxVars.elemVolVars();
+        const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
+        const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()];
+
+        if (std::signbit(flux)) // if sign of flux is negative
             return flux*(upwindWeight*upwindTerm(outsideVolVars)
                          + (1.0 - upwindWeight)*upwindTerm(insideVolVars));
         else
@@ -91,17 +92,16 @@ public:
           const UpwindTermFunction& upwindTerm,
           Scalar flux, int phaseIdx)
     {
-        // retrieve the upwind weight for the mass conservation equations. Use the value
-        // specified via the property system as default, and overwrite
-        // it by the run-time parameter from the Dune::ParameterTree
-        static const std::string modelParamGroup = GET_PROP_VALUE(TypeTag, ModelParameterGroup);
-        static const Scalar upwindWeight = getParamFromGroup<Scalar>(modelParamGroup, "Implicit.UpwindWeight");
+        static const Scalar upwindWeight = getParamFromGroup<Scalar>
+            (GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Implicit.UpwindWeight");
 
         // the volume variables of the inside sub-control volume
-        const auto& insideVolVars = fluxVars.elemVolVars()[fluxVars.scvFace().insideScvIdx()];
+        const auto& scvf = fluxVars.scvFace();
+        const auto& elemVolVars = fluxVars.elemVolVars();
+        const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
 
         // check if this is a branching point
-        if (fluxVars.scvFace().numOutsideScvs() > 1)
+        if (scvf.numOutsideScvs() > 1)
         {
             // more complicated upwind scheme
             // we compute a flux-weighted average of all inflowing branches
@@ -114,23 +114,24 @@ public:
             else
                 sumUpwindFluxes += flux;
 
-            for (unsigned int i = 0; i < fluxVars.scvFace().numOutsideScvs(); ++i)
+            for (unsigned int i = 0; i < scvf.numOutsideScvs(); ++i)
             {
-                 // compute the outside flux
-                const auto outsideScvIdx = fluxVars.scvFace().outsideScvIdx(i);
-                const auto outsideElement = fluxVars.fvGeometry().fvGridGeometry().element(outsideScvIdx);
-                const auto& flippedScvf = fluxVars.fvGeometry().flipScvf(fluxVars.scvFace().index(), i);
+                // compute the outside flux
+                const auto& fvGeometry = fluxVars.fvGeometry();
+                const auto outsideScvIdx = scvf.outsideScvIdx(i);
+                const auto outsideElement = fvGeometry.fvGridGeometry().element(outsideScvIdx);
+                const auto& flippedScvf = fvGeometry.flipScvf(scvf.index(), i);
 
                 const auto outsideFlux = AdvectionType::flux(fluxVars.problem(),
                                                              outsideElement,
-                                                             fluxVars.fvGeometry(),
-                                                             fluxVars.elemVolVars(),
+                                                             fvGeometry,
+                                                             elemVolVars,
                                                              flippedScvf,
                                                              phaseIdx,
                                                              fluxVars.elemFluxVarsCache());
 
                 if (!std::signbit(outsideFlux))
-                    branchingPointUpwindTerm += upwindTerm(fluxVars.elemVolVars()[outsideScvIdx])*outsideFlux;
+                    branchingPointUpwindTerm += upwindTerm(elemVolVars[outsideScvIdx])*outsideFlux;
                 else
                     sumUpwindFluxes += outsideFlux;
             }
@@ -153,7 +154,7 @@ public:
         else
         {
             // upwind scheme
-            const auto& outsideVolVars = fluxVars.elemVolVars()[fluxVars.scvFace().outsideScvIdx()];
+            const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()];
             if (std::signbit(flux))
                 return flux*(upwindWeight*upwindTerm(outsideVolVars)
                              + (1.0 - upwindWeight)*upwindTerm(insideVolVars));
@@ -170,14 +171,15 @@ public:
           const UpwindTermFunction& upwindTerm,
           Scalar flux, int phaseIdx)
     {
-        // retrieve the upwind weight for the mass conservation equations. Use the value
-        // specified via the property system as default, and overwrite
-        // it by the run-time parameter from the Dune::ParameterTree
-        static const Scalar upwindWeight = getParamFromGroup<Scalar>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Implicit.UpwindWeight");
-
-        const auto& insideVolVars = fluxVars.elemVolVars()[fluxVars.scvFace().insideScvIdx()];
-        const auto& outsideVolVars = fluxVars.elemVolVars()[fluxVars.scvFace().outsideScvIdx()];
-        if (std::signbit(flux))
+        static const Scalar upwindWeight = getParamFromGroup<Scalar>
+            (GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Implicit.UpwindWeight");
+
+        const auto& scvf = fluxVars.scvFace();
+        const auto& elemVolVars = fluxVars.elemVolVars();
+        const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
+        const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()];
+
+        if (std::signbit(flux)) // if sign of flux is negative
             return flux*(upwindWeight*upwindTerm(outsideVolVars)
                          + (1.0 - upwindWeight)*upwindTerm(insideVolVars));
         else
diff --git a/dumux/nonlinear/newtoncontroller.hh b/dumux/nonlinear/newtoncontroller.hh
index 39ff8a2ebb8a0a4349f02fad3ee217da4b192cd7..4a5513fb17152b067decd4ca522b97943fbe59d8 100644
--- a/dumux/nonlinear/newtoncontroller.hh
+++ b/dumux/nonlinear/newtoncontroller.hh
@@ -456,7 +456,7 @@ public:
     template<class Assembler, class SolutionVector>
     void newtonFail(Assembler& assembler, SolutionVector& u)
     {
-        if (!assembler.localResidual().isStationary())
+        if (!assembler.isStationaryProblem())
         {
             // set solution to previous solution
             u = assembler.prevSol();
diff --git a/dumux/porousmediumflow/1p/incompressiblelocalresidual.hh b/dumux/porousmediumflow/1p/incompressiblelocalresidual.hh
index 9e0a78bb7a16aa579d48d212d11011b094491a2f..dfdc173f082d9f9aced2fd6b416775cca63f27c4 100644
--- a/dumux/porousmediumflow/1p/incompressiblelocalresidual.hh
+++ b/dumux/porousmediumflow/1p/incompressiblelocalresidual.hh
@@ -124,9 +124,9 @@ public:
                                  / curElemVolVars[scvf.insideScvIdx()].viscosity();
 
         const auto& fluxVarsCache = elemFluxVarsCache[scvf];
+        const auto& stencil = fluxVarsCache.advectionStencil();
         if (fluxVarsCache.usesSecondaryIv())
         {
-            const auto& stencil = fluxVarsCache.advectionStencilSecondaryIv();
             const auto& tij = fluxVarsCache.advectionTijSecondaryIv();
             assert(stencil.size() == tij.size());
 
@@ -141,7 +141,6 @@ public:
         }
         else
         {
-            const auto& stencil = fluxVarsCache.advectionStencilPrimaryIv();
             const auto& tij = fluxVarsCache.advectionTijPrimaryIv();
             assert(stencil.size() == tij.size());
 
diff --git a/dumux/porousmediumflow/mpnc/localresidual.hh b/dumux/porousmediumflow/mpnc/localresidual.hh
index 5bf07dc6ea32bc0eac0a81c242a6525ce41e5219..d982de82f85d55f6c8ae25fa820ff196f0967036 100644
--- a/dumux/porousmediumflow/mpnc/localresidual.hh
+++ b/dumux/porousmediumflow/mpnc/localresidual.hh
@@ -45,16 +45,10 @@ template<class TypeTag>
 class MPNCLocalResidual : public CompositionalLocalResidual<TypeTag>
 {
     using ParentType = CompositionalLocalResidual<TypeTag>;
-    using Problem = typename GET_PROP_TYPE(TypeTag, Problem);
-    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
     using Element = typename GET_PROP_TYPE(TypeTag, GridView)::template Codim<0>::Entity;
     using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry);
-    using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume);
-    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
-    using ElementResidualVector = Dune::BlockVector<typename GET_PROP_TYPE(TypeTag, NumEqVector)>;
     using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
     using ElementFluxVariablesCache = typename GET_PROP_TYPE(TypeTag, ElementFluxVariablesCache);
-    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
     using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables);
     using Indices = typename GET_PROP_TYPE(TypeTag, Indices);
 
@@ -64,102 +58,22 @@ class MPNCLocalResidual : public CompositionalLocalResidual<TypeTag>
 public:
     using ParentType::ParentType;
 
+    using typename ParentType::ElementResidualVector;
 
-    /*!
-     * \brief Compute the local residual, i.e. the deviation of the
-     *        equations from zero for instationary problems.
-     * \param problem The problem to solve
-
-     * \param element The DUNE Codim<0> entity for which the residual
-     *                ought to be calculated
-     * \param fvGeometry The finite-volume geometry of the element
-     * \param prevVolVars The volume averaged variables for all
-     *                    sub-control volumes of the element at the previous
-     *                    time level
-     * \param curVolVars The volume averaged variables for all
-     *                   sub-control volumes of the element at the current
-     *                   time level
-     * \param bcTypes The types of the boundary conditions for all
-     *                vertices of the element
-     */
-    ElementResidualVector eval(const Problem& problem,
-                               const Element& element,
-                               const FVElementGeometry& fvGeometry,
-                               const ElementVolumeVariables& prevElemVolVars,
-                               const ElementVolumeVariables& curElemVolVars,
-                               const ElementBoundaryTypes &bcTypes,
-                               const ElementFluxVariablesCache& elemFluxVarsCache) const
-    {
-        // initialize the residual vector for all scvs in this element
-        ElementResidualVector residual(fvGeometry.numScv());
-        residual = 0.0;
-
-        // evaluate the volume terms (storage + source terms)
-        for (auto&& scv : scvs(fvGeometry))
-        {
-            //! foward to the local residual specialized for the discretization methods
-            this->asImp().evalStorage(residual, problem, element, fvGeometry, prevElemVolVars, curElemVolVars, scv);
-            this->asImp().evalSource(residual, problem, element, fvGeometry, curElemVolVars, scv);
-             //here we need to set the constraints of the mpnc model into the residual
-                const auto localScvIdx = scv.indexInElement();
-                for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
-                {
-                    residual[localScvIdx][phase0NcpIdx + phaseIdx] = curElemVolVars[scv].phaseNcp(phaseIdx);
-                }
-        }
-
-        for (auto&& scvf : scvfs(fvGeometry))
-        {
-            //! foward to the local residual specialized for the discretization methods
-            this->asImp().evalFlux(residual, problem, element, fvGeometry, curElemVolVars, bcTypes, elemFluxVarsCache, scvf);
-        }
-
-        return residual;
-    }
-
-    /*!
-     * \brief Compute the local residual, i.e. the deviation of the
-     *        equations from zero for stationary problem.
-     * \param problem The problem to solve
-
-     * \param element The DUNE Codim<0> entity for which the residual
-     *                ought to be calculated
-     * \param fvGeometry The finite-volume geometry of the element
-     * \param curVolVars The volume averaged variables for all
-     *                   sub-control volumes of the element at the current
-     *                   time level
-     * \param bcTypes The types of the boundary conditions for all
-     *                vertices of the element
-     */
-    ElementResidualVector eval(const Problem& problem,
-                               const Element& element,
-                               const FVElementGeometry& fvGeometry,
-                               const ElementVolumeVariables& curElemVolVars,
-                               const ElementBoundaryTypes &bcTypes,
-                               const ElementFluxVariablesCache& elemFluxVarsCache) const
+    ElementResidualVector evalFluxSource(const Element& element,
+                                         const FVElementGeometry& fvGeometry,
+                                         const ElementVolumeVariables& elemVolVars,
+                                         const ElementFluxVariablesCache& elemFluxVarsCache,
+                                         const ElementBoundaryTypes &bcTypes) const
     {
-        // initialize the residual vector for all scvs in this element
-        ElementResidualVector residual(fvGeometry.numScv());
-        residual = 0.0;
+        ElementResidualVector residual = ParentType::evalFluxSource(element, fvGeometry, elemVolVars, elemFluxVarsCache, bcTypes);
 
-        // evaluate the volume terms (storage + source terms)
         for (auto&& scv : scvs(fvGeometry))
         {
-            //! foward to the local residual specialized for the discretization methods
-            this->asImp().evalSource(residual, problem, element, fvGeometry, curElemVolVars, scv);
-
-               //here we need to set the constraints of the mpnc model into the residual
-                const auto localScvIdx = scv.indexInElement();
-                for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
-                {
-                    residual[localScvIdx][phase0NcpIdx + phaseIdx] = curElemVolVars[scv].phaseNcp(phaseIdx);
-                }
-        }
-
-        for (auto&& scvf : scvfs(fvGeometry))
-        {
-            //! foward to the local residual specialized for the discretization methods
-            this->asImp().evalFlux(residual, problem, element, fvGeometry, curElemVolVars, bcTypes, elemFluxVarsCache, scvf);
+            //here we need to set the constraints of the mpnc model into the residual
+            const auto localScvIdx = scv.indexInElement();
+            for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
+                residual[localScvIdx][phase0NcpIdx + phaseIdx] = elemVolVars[scv].phaseNcp(phaseIdx);
         }
 
         return residual;