diff --git a/dumux/nonlinear/newtonsolver.hh b/dumux/nonlinear/newtonsolver.hh
index c0def89e056b20c4e1134c81d0b82f64042df6ab..261d2434d61efef7792b0ed674ac1783dfd49e52 100644
--- a/dumux/nonlinear/newtonsolver.hh
+++ b/dumux/nonlinear/newtonsolver.hh
@@ -28,12 +28,17 @@
 #include <memory>
 #include <iostream>
 #include <type_traits>
+#include <algorithm>
+#include <numeric>
 
 #include <dune/common/timer.hh>
 #include <dune/common/exceptions.hh>
 #include <dune/common/parallel/mpicommunication.hh>
 #include <dune/common/parallel/mpihelper.hh>
 #include <dune/common/std/type_traits.hh>
+#include <dune/common/indices.hh>
+#include <dune/common/hybridutilities.hh>
+
 #include <dune/istl/bvector.hh>
 #include <dune/istl/multitypeblockvector.hh>
 
@@ -69,6 +74,81 @@ using DetectPVSwitch = typename Assembler::GridVariables::VolumeVariables::Prima
 template<class Assembler>
 using GetPVSwitch = Dune::Std::detected_or<int, DetectPVSwitch, Assembler>;
 
+// helpers to implement max relative shift
+template<class C> using dynamicIndexAccess = decltype(std::declval<C>()[0]);
+template<class C> using staticIndexAccess = decltype(std::declval<C>()[Dune::Indices::_0]);
+template<class C> static constexpr auto hasDynamicIndexAccess = Dune::Std::is_detected<dynamicIndexAccess, C>{};
+template<class C> static constexpr auto hasStaticIndexAccess = Dune::Std::is_detected<staticIndexAccess, C>{};
+
+template<class V, class Scalar, class Reduce, class Transform>
+auto hybridInnerProduct(const V& v1, const V& v2, Scalar init, Reduce&& r, Transform&& t)
+-> std::enable_if_t<hasDynamicIndexAccess<V>(), Scalar>
+{
+    return std::inner_product(v1.begin(), v1.end(), v2.begin(), init, std::forward<Reduce>(r), std::forward<Transform>(t));
+}
+
+template<class V, class Scalar, class Reduce, class Transform>
+auto hybridInnerProduct(const V& v1, const V& v2, Scalar init, Reduce&& r, Transform&& t)
+-> std::enable_if_t<hasStaticIndexAccess<V>() && !hasDynamicIndexAccess<V>(), Scalar>
+{
+    using namespace Dune::Hybrid;
+    forEach(std::make_index_sequence<V::N()>{}, [&](auto i){
+        init = r(init, hybridInnerProduct(v1[Dune::index_constant<i>{}], v2[Dune::index_constant<i>{}], init, std::forward<Reduce>(r), std::forward<Transform>(t)));
+    });
+    return init;
+}
+
+// Maximum relative shift at a degree of freedom.
+// For (primary variables) values below 1.0 we use
+// an absolute shift.
+template<class Scalar, class V>
+auto maxRelativeShift(const V& v1, const V& v2)
+-> std::enable_if_t<Dune::IsNumber<V>::value, Scalar>
+{
+    using std::abs; using std::max;
+    return abs(v1 - v2)/max<Scalar>(1.0, abs(v1 + v2)*0.5);
+}
+
+// Maximum relative shift for generic vector types.
+// Recursively calls maxRelativeShift until Dune::IsNumber is true.
+template<class Scalar, class V>
+auto maxRelativeShift(const V& v1, const V& v2)
+-> std::enable_if_t<!Dune::IsNumber<V>::value, Scalar>
+{
+    return hybridInnerProduct(v1, v2, Scalar(0.0),
+        [](const auto& a, const auto& b){ using std::max; return max(a, b); },
+        [](const auto& a, const auto& b){ return maxRelativeShift<Scalar>(a, b); }
+    );
+}
+
+template<class To, class From>
+void assign(To& to, const From& from)
+{
+    if constexpr (std::is_assignable<To&, From>::value)
+        to = from;
+
+    else if constexpr (hasStaticIndexAccess<To>() && hasStaticIndexAccess<To>() && !hasDynamicIndexAccess<From>() && !hasDynamicIndexAccess<From>())
+    {
+        using namespace Dune::Hybrid;
+        forEach(std::make_index_sequence<To::N()>{}, [&](auto i){
+            assign(to[Dune::index_constant<i>{}], from[Dune::index_constant<i>{}]);
+        });
+    }
+
+    else if constexpr (hasDynamicIndexAccess<From>() && hasDynamicIndexAccess<From>())
+        for (decltype(to.size()) i = 0; i < to.size(); ++i)
+            assign(to[i], from[i]);
+
+    else
+        DUNE_THROW(Dune::Exception, "Values are not assignable to each other!");
+}
+
+template<class T, std::enable_if_t<Dune::IsNumber<std::decay_t<T>>::value, int> = 0>
+constexpr std::size_t blockSize() { return 1; }
+
+template<class T, std::enable_if_t<!Dune::IsNumber<std::decay_t<T>>::value, int> = 0>
+constexpr std::size_t blockSize() { return std::decay_t<T>::size(); }
+
 } // end namespace Detail
 
 /*!
@@ -949,44 +1029,14 @@ private:
     virtual void newtonUpdateShift_(const SolutionVector &uLastIter,
                                     const SolutionVector &deltaU)
     {
-        shift_ = 0;
-        newtonUpdateShiftImpl_(uLastIter, deltaU);
+        auto uNew = uLastIter;
+        uNew -= deltaU;
+        shift_ = Detail::maxRelativeShift<Scalar>(uLastIter, uNew);
 
         if (comm_.size() > 1)
             shift_ = comm_.max(shift_);
     }
 
-    template<class SolVec>
-    void newtonUpdateShiftImpl_(const SolVec &uLastIter,
-                                const SolVec &deltaU)
-    {
-        for (int i = 0; i < int(uLastIter.size()); ++i)
-        {
-            auto uNewI = uLastIter[i];
-            uNewI -= deltaU[i];
-
-            Scalar shiftAtDof = relativeShiftAtDof_(uLastIter[i], uNewI);
-            using std::max;
-            shift_ = max(shift_, shiftAtDof);
-        }
-    }
-
-    template<class ...Args>
-    void newtonUpdateShiftImpl_(const Dune::MultiTypeBlockVector<Args...> &uLastIter,
-                                const Dune::MultiTypeBlockVector<Args...> &deltaU)
-    {
-        // There seems to be a bug in g++5 which which prevents compilation when
-        // passing the call to the implementation directly to Dune::Hybrid::forEach.
-        // We therefore store this call in a lambda and pass it to the for loop afterwards.
-        auto doUpdate = [&](const auto subVectorIdx)
-        {
-            this->newtonUpdateShiftImpl_(uLastIter[subVectorIdx], deltaU[subVectorIdx]);
-        };
-
-        using namespace Dune::Hybrid;
-        forEach(integralRange(Dune::Hybrid::size(uLastIter)), doUpdate);
-    }
-
     virtual void lineSearchUpdate_(SolutionVector &uCurrentIter,
                                    const SolutionVector &uLastIter,
                                    const SolutionVector &deltaU)
@@ -1050,19 +1100,14 @@ private:
         //! to this field vector type in Dune ISTL
         //! Could be avoided for vectors that already have the right type using SFINAE
         //! but it shouldn't impact performance too much
-        constexpr auto blockSize = std::decay_t<decltype(b[0])>::dimension;
+        constexpr auto blockSize = Detail::blockSize<decltype(b[0])>();
         using BlockType = Dune::FieldVector<Scalar, blockSize>;
         Dune::BlockVector<BlockType> xTmp; xTmp.resize(b.size());
         Dune::BlockVector<BlockType> bTmp(xTmp);
-        for (unsigned int i = 0; i < b.size(); ++i)
-            for (unsigned int j = 0; j < blockSize; ++j)
-                bTmp[i][j] = b[i][j];
 
+        Detail::assign(bTmp, b);
         const int converged = ls.solve(A, xTmp, bTmp);
-
-        for (unsigned int i = 0; i < x.size(); ++i)
-            for (unsigned int j = 0; j < blockSize; ++j)
-                x[i][j] = xTmp[i][j];
+        Detail::assign(x, xTmp);
 
         return converged;
     }
@@ -1188,7 +1233,7 @@ private:
             nextPriVars -= uDelta[i];
 
             // add the current relative shift for this degree of freedom
-            auto shift = relativeShiftAtDof_(currentPriVars, nextPriVars);
+            auto shift = Detail::maxRelativeShift<Scalar>(currentPriVars, nextPriVars);
             distanceFromLastLinearization_[i] += shift;
         }
     }
@@ -1213,30 +1258,6 @@ private:
         DUNE_THROW(Dune::NotImplemented, "Reassembly for MultiTypeBlockVector");
     }
 
-    /*!
-     * \brief Returns the maximum relative shift between two vectors of
-     *        primary variables.
-     *
-     * \param priVars1 The first vector of primary variables
-     * \param priVars2 The second vector of primary variables
-     */
-    template<class PrimaryVariables>
-    Scalar relativeShiftAtDof_(const PrimaryVariables &priVars1,
-                               const PrimaryVariables &priVars2) const
-    {
-        Scalar result = 0.0;
-        using std::abs;
-        using std::max;
-        // iterate over all primary variables
-        for (int j = 0; j < PrimaryVariables::dimension; ++j) {
-            Scalar eqErr = abs(priVars1[j] - priVars2[j]);
-            eqErr /= max<Scalar>(1.0,abs(priVars1[j] + priVars2[j])/2);
-
-            result = max(result, eqErr);
-        }
-        return result;
-    }
-
     //! The communication object
     Communication comm_;
 
diff --git a/test/nonlinear/newton/test_newton.cc b/test/nonlinear/newton/test_newton.cc
index 5f37e2c3656bbdaa8f0574888023e0823139cd6b..7394386149898ec3212761a79b835f00c57b6195 100644
--- a/test/nonlinear/newton/test_newton.cc
+++ b/test/nonlinear/newton/test_newton.cc
@@ -7,6 +7,7 @@
 #include <dune/common/exceptions.hh>
 #include <dune/common/float_cmp.hh>
 #include <dune/common/parallel/mpihelper.hh>
+#include <dune/istl/bvector.hh>
 #include <dumux/nonlinear/newtonsolver.hh>
 
 /*
@@ -27,40 +28,10 @@
 
 namespace Dumux {
 
-class MockScalarVariables
-{
-public:
-    static constexpr int dimension = 1;
-    MockScalarVariables() : var_(0.0) {}
-    explicit MockScalarVariables(double v) : var_(v) {}
-    MockScalarVariables& operator-=(const MockScalarVariables& other) { var_ -= other.var_; return *this; }
-    double& operator[] (int i) { return var_; }
-    const double& operator[] (int i) const { return var_; }
-private:
-    double var_;
-};
-
-class MockScalarResidual
-{
-public:
-    MockScalarResidual() : res_(0.0) {}
-    explicit MockScalarResidual(double r) : res_(r) {}
-    MockScalarResidual& operator=(double r) { res_[0] = r; return *this; }
-    MockScalarResidual& operator*=(double a) { res_[0] *= a; return *this; }
-    MockScalarResidual& operator+=(const MockScalarResidual& other) { res_[0] += other.res_[0]; return *this; }
-    MockScalarResidual& operator-=(const MockScalarResidual& other) { res_[0] -= other.res_[0]; return *this; }
-    constexpr std::size_t size() const { return 1; }
-    MockScalarVariables& operator[] (int i) { return res_; }
-    const MockScalarVariables& operator[] (int i) const { return res_; }
-    double two_norm2() const { return res_[0]*res_[0]; }
-private:
-    MockScalarVariables res_;
-};
-
 class MockScalarAssembler
 {
 public:
-    using ResidualType = MockScalarResidual;
+    using ResidualType = Dune::BlockVector<double>;
     using Scalar = double;
     using JacobianMatrix = double;
 
@@ -74,13 +45,14 @@ public:
 
     void assembleResidual(const ResidualType& sol)
     {
-        res_[0][0] = sol[0][0]*sol[0][0] - 5.0;
+        res_.resize(1);
+        res_[0] = sol[0]*sol[0] - 5.0;
     }
 
     void assembleJacobianAndResidual (const ResidualType& sol)
     {
         assembleResidual(sol);
-        jac_ = 2.0*sol[0][0];
+        jac_ = 2.0*sol[0];
     }
 
     JacobianMatrix& jacobian() { return jac_; }
@@ -90,7 +62,7 @@ public:
     double residualNorm(const ResidualType& sol)
     {
         assembleResidual(sol);
-        return res_[0][0];
+        return res_[0];
     }
 
     void updateGridVariables(const ResidualType& sol) {}
@@ -108,7 +80,7 @@ public:
     template<class Vector>
     bool solve (const double& A, Vector& x, const Vector& b) const
     {
-        x[0][0] = b[0][0]/A;
+        x[0] = b[0]/A;
         return true;
     }
 };
@@ -137,16 +109,17 @@ int main(int argc, char* argv[]) try
     auto solver = std::make_shared<Solver>(assembler, linearSolver);
 
     double initialGuess = 0.1;
-    MockScalarResidual x(initialGuess);
+    Dune::BlockVector<double> x(1);
+    x = initialGuess;
 
     std::cout << "Solving: x^2 - 5 = 0" << std::endl;
     solver->solve(x);
-    std::cout << "Solution: " << std::setprecision(15) << x[0][0]
+    std::cout << "Solution: " << std::setprecision(15) << x[0]
               << ", exact: " << std::sqrt(5.0)
-              << ", error: " << std::abs(x[0][0]-std::sqrt(5.0))/std::sqrt(5.0)*100 << "%" << std::endl;
+              << ", error: " << std::abs(x[0]-std::sqrt(5.0))/std::sqrt(5.0)*100 << "%" << std::endl;
 
-    if (Dune::FloatCmp::ne(x[0][0], std::sqrt(5.0), 1e-13))
-        DUNE_THROW(Dune::Exception, "Didn't find correct root: " << std::setprecision(15) << x[0][0] << ", exact: " << std::sqrt(5.0));
+    if (Dune::FloatCmp::ne(x[0], std::sqrt(5.0), 1e-13))
+        DUNE_THROW(Dune::Exception, "Didn't find correct root: " << std::setprecision(15) << x[0] << ", exact: " << std::sqrt(5.0));
 
     return 0;