diff --git a/dumux/assembly/cclocalassembler.hh b/dumux/assembly/cclocalassembler.hh
index 8c17c62c4d7f0f3d483a04ffa4ab8397883a9272..1d5248ba5fd0ff2826055ae03cc37da9e8eae662 100644
--- a/dumux/assembly/cclocalassembler.hh
+++ b/dumux/assembly/cclocalassembler.hh
@@ -28,13 +28,15 @@
 #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>
 
 namespace Dumux {
 
@@ -79,6 +81,7 @@ public:
     , prevElemVolVars_(localView(assembler.gridVariables().prevGridVolVars()))
     , elemFluxVarsCache_(localView(assembler.gridVariables().gridFluxVarsCache()))
     , localResidual_(assembler.localResidual())
+    , elementIsGhost_((element.partitionType() == Dune::GhostEntity))
     {}
 
     /*!
@@ -112,39 +115,6 @@ public:
         res[globalI] = asImp_().assembleResidualImpl(); // forward to the internal implementation
     }
 
-    /*!
-     * \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);
-    }
-
-    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); }
-
     LocalResidualValues evalLocalResidual(const ElementVolumeVariables& elemVolVars) const
     {
         if (!assembler().isStationaryProblem())
@@ -183,7 +153,7 @@ public:
     { return element_; }
 
     bool elementIsGhost() const
-    { return (element_.partitionType() == Dune::GhostEntity); }
+    { return elementIsGhost_; }
 
     const SolutionVector& curSol() const
     { return curSol_; }
@@ -200,6 +170,9 @@ public:
     ElementFluxVariablesCache& elemFluxVarsCache()
     { return elemFluxVarsCache_; }
 
+    LocalResidual& localResidual()
+    { return localResidual_; }
+
     ElementBoundaryTypes& elemBcTypes()
     { return elemBcTypes_; }
 
@@ -218,6 +191,17 @@ public:
     const ElementBoundaryTypes& elemBcTypes() const
     { return elemBcTypes_; }
 
+    const LocalResidual& localResidual() const
+    { return localResidual_; }
+
+    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:
     Implementation &asImp_()
     { return *static_cast<Implementation*>(this); }
@@ -236,6 +220,7 @@ private:
     ElementBoundaryTypes elemBcTypes_;
 
     LocalResidual localResidual_; //!< the local residual evaluating the equations per element
+    bool elementIsGhost_; //!< whether the element's partitionType is ghost
 };
 
 /*!
@@ -375,8 +360,10 @@ class CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/tru
     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);
+    static constexpr int maxNeighbors = 4*(2*dim);
 
 public:
     using ParentType::ParentType;
@@ -389,21 +376,13 @@ public:
      */
     LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
     {
-        // assemble the undeflected residual
-        const auto residual = this->assembleResidualImpl();
-
         //////////////////////////////////////////////////////////////////////////////////////////////////
         // 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.     //
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
-        // get the numeric differentiation type (-1 : backward differences, 0: central differences, 1: forward differences)
-        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& problem = this->problem();
         const auto& element = this->element();
         const auto& fvGeometry = this->fvGeometry();
         const auto& fvGridGeometry = this->assembler().fvGridGeometry();
@@ -416,20 +395,24 @@ public:
         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);
 
-        // all undeflected fluxes influences by this dof's primary variables
-        Dune::BlockVector<LocalResidualValues> origFlux(numNeighbors); origFlux = 0.0;
+        // assemble the undeflected residual
+        using Residuals = ReservedBlockVector<LocalResidualValues, maxNeighbors+1>;
+        Residuals origResiduals(numNeighbors + 1); origResiduals = 0.0;
+        origResiduals[0] = this->assembleResidualImpl();
 
         // get the elements in which we need to evaluate the fluxes
         // and calculate these in the undeflected state
-        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++] += this->evalFluxResidual(neighborElements.back(), fvGeometry.scvf(scvfIdx));
+                origResiduals[j] += this->evalFluxResidual(neighborElements[j-1], fvGeometry.scvf(scvfIdx));
+
+            ++j;
         }
 
         // reference to the element's scv (needed later) and corresponding vol vars
@@ -446,99 +429,55 @@ public:
         ElementSolutionVector elemSol(origPriVars);
 
         // derivatives in the neighbors with repect to the current elements
-        Dune::BlockVector<LocalResidualValues> neighborDeriv(numNeighbors);
+        // in index 0 we save the derivative of the element residual with respect to it's own dofs
+        Residuals partialDerivs(numNeighbors + 1);
+
         for (int pvIdx = 0; pvIdx < numEq; ++pvIdx)
         {
-            // reset derivatives of element dof with respect to itself
-            // as well as neighbor derivatives
-            LocalResidualValues partialDeriv(0.0);
-            neighborDeriv = 0.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
-            if (this->elementIsGhost()) partialDeriv[pvIdx] = 1.0;
-
-            Scalar eps = ParentType::numericEpsilon(curVolVars.priVar(pvIdx));
-            Scalar delta = 0;
+            // 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;
 
-            // we are using forward or central differences, i.e. we need to calculate f(x + \epsilon)
-            if (numericDifferenceMethod >= 0)
+            auto evalResiduals = [&](Scalar priVar)
             {
-                // 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 (!this->elementIsGhost()) partialDeriv = this->evalLocalResidual();
+                if (!this->elementIsGhost()) partialDerivsTmp[0] = this->evalLocalResidual();
 
                 // 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] += this->evalFluxResidual(neighborElements[k], fvGeometry.scvf(scvfIdx));
-            }
+                        partialDerivsTmp[k+1] += this->evalFluxResidual(neighborElements[k], fvGeometry.scvf(scvfIdx));
 
-            // we are using backward differences, i.e. we don't need to calculate f(x + \epsilon)
-            // we can recycle the (already calculated) residual f(x) for non-ghosts
-            else
-            {
-                if (!this->elementIsGhost()) partialDeriv = residual;
-                neighborDeriv = origFlux;
-            }
+                return partialDerivsTmp;
+            };
 
-            // we are using backward or central differences, i.e. we need to calculate f(x - \epsilon)
-            if (numericDifferenceMethod <= 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);
-                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 (!this->elementIsGhost()) partialDeriv -= this->evalLocalResidual();
-
-                // 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] -= this->evalFluxResidual(neighborElements[k], fvGeometry.scvf(scvfIdx));
-            }
-
-            // we are using forward differences, i.e. we don't need to calculate f(x - \epsilon)
-            // we can recycle the (already calculated) residual f(x) for non-ghosts
-            else
-            {
-                if (!this->elementIsGhost()) partialDeriv -= residual;
-                neighborDeriv -= origFlux;
-            }
-
-            // divide difference in residuals by the magnitude of the
-            // deflections between the two function evaluation
-            if (!this->elementIsGhost()) partialDeriv /= delta;
-            neighborDeriv /= delta;
+            // 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
@@ -558,7 +497,7 @@ public:
             gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars);
 
         // return the original residual
-        return residual;
+        return origResiduals[0];
     }
 };
 
@@ -566,7 +505,7 @@ public:
 /*!
  * \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 Assembler>
 class CCLocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/false>
@@ -613,17 +552,11 @@ public:
         // derivatives are non-zero.                                                                    //
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
-        // get the numeric differentiation type (-1 : backward differences, 0: central differences, 1: forward differences)
-        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& problem = this->problem();
         const auto& element = this->element();
         const auto& fvGeometry = this->fvGeometry();
         const auto& fvGridGeometry = this->assembler().fvGridGeometry();
         auto&& curElemVolVars = this->curElemVolVars();
-        auto&& elemFluxVarsCache = this->elemFluxVarsCache();
 
         // reference to the element's scv (needed later) and corresponding vol vars
         const auto globalI = fvGridGeometry.elementMapper().index(element);
@@ -638,63 +571,31 @@ public:
 
         // element solution container to be deflected
         ElementSolutionVector elemSol(origPriVars);
+        LocalResidualValues partialDeriv;
 
         // derivatives in the neighbors with repect to the current elements
         for (int pvIdx = 0; pvIdx < numEq; ++pvIdx)
         {
             // reset derivatives of element dof with respect to itself
-            // as well as neighbor derivatives
-            LocalResidualValues partialDeriv(0.0);
-            if (this->elementIsGhost()) partialDeriv[pvIdx] = 1.0;
-
-            Scalar eps = ParentType::numericEpsilon(curVolVars.priVar(pvIdx));
-            Scalar delta = 0;
+            partialDeriv = 0.0;
 
-            // we are using forward or central differences, i.e. we need to calculate f(x + \epsilon)
-            if (numericDifferenceMethod >= 0)
+            auto evalStorage = [&](Scalar priVar)
             {
-                // 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 (!this->elementIsGhost()) partialDeriv = this->evalLocalStorageResidual();
-            }
-
-            // we are using backward differences, i.e. we don't need to calculate f(x + \epsilon)
-            // we can recycle the (already calculated) residual f(x) for non-ghosts
-            else
-            {
-                if (!this->elementIsGhost()) partialDeriv = storageResidual;
-            }
-
-            // we are using backward or central differences, i.e. we need to calculate f(x - \epsilon)
-            if (numericDifferenceMethod <= 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 (!this->elementIsGhost()) partialDeriv -= this->evalLocalStorageResidual();
-            }
+                // 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();
+            };
 
-            // we are using forward differences, i.e. we don't need to calculate f(x - \epsilon)
-            // we can recycle the (already calculated) residual f(x) for non-ghosts
-            else
-            {
-                if (!this->elementIsGhost()) partialDeriv -= storageResidual;
-            }
+            // for non-ghosts compute the derivative numerically
+            if (!this->elementIsGhost())
+                NumericDifferentiation::partialDerivative(evalStorage, elemSol[0][pvIdx], partialDeriv, residual);
 
-            // divide difference in residuals by the magnitude of the
-            // deflections between the two function evaluation
-            if (!this->elementIsGhost()) partialDeriv /= delta;
+            // 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;
 
             // add the current partial derivatives to the global jacobian matrix
             // only diagonal entries for explicit jacobians
@@ -720,7 +621,7 @@ public:
  */
 template<class TypeTag, class Assembler>
 class CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/true>
-: public CCLocalAssemblerExplicitBase<TypeTag, Assembler,
+: public CCLocalAssemblerImplicitBase<TypeTag, Assembler,
             CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true> >
 {
     using ThisType = CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true>;
@@ -730,6 +631,7 @@ class CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/tr
     using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
 
 public:
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
@@ -737,7 +639,7 @@ public:
      *
      * \return The element residual at the current solution.
      */
-    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
+    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, const GridVariables& gridVariables)
     {
         // assemble the undeflected residual
         const auto residual = this->assembleResidualImpl();
@@ -802,12 +704,13 @@ class CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/fa
             CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false> >
 {
     using ThisType = CCLocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false>;
-    using ParentType = CCLocalAssemblerImplicitBase<TypeTag, Assembler, ThisType>;
+    using ParentType = CCLocalAssemblerExplicitBase<TypeTag, Assembler, ThisType>;
     using LocalResidualValues = typename GET_PROP_TYPE(TypeTag, NumEqVector);
     using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
     using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
 
 public:
+    using ParentType::ParentType;
 
     /*!
      * \brief Computes the derivatives with respect to the given element and adds them
@@ -815,24 +718,18 @@ public:
      *
      * \return The element residual at the current solution.
      */
-    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables)
+    LocalResidualValues assembleJacobianAndResidualImpl(JacobianMatrix& A, const GridVariables& gridVariables)
     {
         // assemble the undeflected residual
         const auto residual = this->assembleResidualImpl();
 
-        // get some aliases for convenience
-        const auto& problem = this->problem();
-        const auto& element = this->element();
-        const auto& fvGeometry = this->fvGeometry();
-        auto&& curElemVolVars = this->curElemVolVars();
-
         // 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];
+        const auto globalI = this->assembler().fvGridGeometry().elementMapper().index(this->element());
+        const auto& scv = this->fvGeometry().scv(globalI);
+        const auto& volVars = this->curElemVolVars()[scv];
 
         // add hand-code derivative of storage term
-        this->localResidual().addStorageDerivatives(A[globalI][globalI], problem, element, fvGeometry, volVars, scv);
+        this->localResidual().addStorageDerivatives(A[globalI][globalI], this->problem(), this->element(), this->fvGeometry(), volVars, scv);
 
         // return the original residual
         return residual;