diff --git a/dumux/experimental/assembly/multistagefvassembler.hh b/dumux/experimental/assembly/multistagefvassembler.hh
index 41b383b4eed678d01059c28fa94bae27cc3dab4c..7de0cb8166b5f32e6db4df58c25acd1cbff3d984 100644
--- a/dumux/experimental/assembly/multistagefvassembler.hh
+++ b/dumux/experimental/assembly/multistagefvassembler.hh
@@ -275,30 +275,55 @@ public:
         stageParams_ = std::move(params);
         const auto curStage = stageParams_->size() - 1;
 
-        // in the first stage, also assemble the old residual
+        // in the first stage, also assemble the residual
+        // at the previous time level (stage 0 residual)
         if (curStage == 1)
         {
             // update time in variables?
-            setProblemTime_(*problem_, stageParams_->timeAtStage(curStage));
+            setProblemTime_(*problem_, stageParams_->timeAtStage(0));
 
             resetResidual_(); // residual resized and zero
-            spatialOperatorEvaluations_.push_back(*residual_);
-            temporalOperatorEvaluations_.push_back(*residual_);
 
-            // assemble stage 0 residuals
-            assemble_([&](const auto& element)
+            assert(spatialOperatorEvaluations_.size() >= 0);
+            if (spatialOperatorEvaluations_.size() == 0)
             {
-                LocalAssembler localAssembler(*this, element, *prevSol_);
-                localAssembler.localResidual().spatialWeight(1.0);
-                localAssembler.localResidual().temporalWeight(1.0);
-                localAssembler.assembleCurrentResidual(spatialOperatorEvaluations_.back(), temporalOperatorEvaluations_.back());
-            });
+                spatialOperatorEvaluations_.push_back(*residual_);
+                temporalOperatorEvaluations_.push_back(*residual_);
+
+                // assemble stage 0 residuals
+                assemble_([&](const auto& element)
+                {
+                    LocalAssembler localAssembler(*this, element, *prevSol_);
+                    localAssembler.localResidual().spatialWeight(1.0);
+                    localAssembler.localResidual().temporalWeight(1.0);
+                    localAssembler.assembleCurrentResidual(spatialOperatorEvaluations_.back(), temporalOperatorEvaluations_.back());
+                });
+            }
+
+            // we don't delete the first stage so it can be reused in a restarted
+            // time integration step. The evaluations are only deleted
+            // when explicitly requested by calling clearStages().
+            // So if here the vector is non-empty, we don't need to evaluate again
+            // (this should only occur if we are restarting time integration, e.g.
+            // with a different time step size)
+            else if (spatialOperatorEvaluations_.size() > 0)
+            {
+                updateGridVariables(x);
+                spatialOperatorEvaluations_.resize(1);
+                temporalOperatorEvaluations_.resize(1);
+            }
         }
 
         // update time in variables?
         setProblemTime_(*problem_, stageParams_->timeAtStage(curStage));
 
         resetResidual_(); // residual resized and zero
+
+        if (spatialOperatorEvaluations_.size() != curStage)
+            DUNE_THROW(Dune::InvalidStateException,
+                "Invalid state. Maybe you forgot to call clearStages()");
+
+        // allocate memory for this stage
         spatialOperatorEvaluations_.push_back(*residual_);
         temporalOperatorEvaluations_.push_back(*residual_);
     }
diff --git a/dumux/experimental/timestepping/multistagetimestepper.hh b/dumux/experimental/timestepping/multistagetimestepper.hh
index 9ac5d13800efe7204386807985a21bb8c773281c..be0113b5ce03285a72a73d492b2e52293b8985c2 100644
--- a/dumux/experimental/timestepping/multistagetimestepper.hh
+++ b/dumux/experimental/timestepping/multistagetimestepper.hh
@@ -19,6 +19,8 @@
 
 #include <dumux/io/format.hh>
 
+#include <dumux/common/variablesbackend.hh>
+#include <dumux/common/timeloop.hh>
 #include <dumux/experimental/timestepping/multistagemethods.hh>
 
 namespace Dumux::Experimental {
@@ -40,7 +42,7 @@ public:
         for (std::size_t k = 0; k < size_; ++k)
         {
             auto& p = params_[k];
-            p.alpha = m.temporalWeight(i, k);///dt;
+            p.alpha = m.temporalWeight(i, k);
             p.betaDt = m.spatialWeight(i, k)*dt;
             p.timeAtStage = t + m.timeStepWeight(k)*dt;
             p.dtFraction = m.timeStepWeight(k);
@@ -94,6 +96,7 @@ class MultiStageTimeStepper
 {
     using Variables = typename PDESolver::Variables;
     using StageParams = MultiStageParams<Scalar>;
+    using Backend = VariablesBackend<Variables>;
 
 public:
 
@@ -101,16 +104,20 @@ public:
      * \brief The constructor
      * \param pdeSolver Solver class for solving a PDE in each stage
      * \param msMethod The multi-stage method which is to be used for time integration
-     * \todo TODO: Add time step control if the pde solver doesn't converge
+     * \param paramGroup A parameter group in which we look for parameters
      */
     MultiStageTimeStepper(std::shared_ptr<PDESolver> pdeSolver,
-                          std::shared_ptr<const MultiStageMethod<Scalar>> msMethod)
+                          std::shared_ptr<const MultiStageMethod<Scalar>> msMethod,
+                          const std::string& paramGroup = "")
     : pdeSolver_(pdeSolver)
     , msMethod_(msMethod)
     {
-        std::cout << "Initialize time stepper with method " << msMethod_->name()
-                  << Fmt::format(" ({} stage{})", msMethod_->numStages(), (msMethod_->numStages() > 1 ? "s" : ""))
-                  << std::endl;
+        initParams_(paramGroup);
+
+        if (pdeSolver_->verbosity() >= 1)
+            std::cout << "Initialize time stepper with method " << msMethod_->name()
+                    << Fmt::format(" ({} stage{})", msMethod_->numStages(), (msMethod_->numStages() > 1 ? "s" : ""))
+                    << std::endl;
     }
 
     /*!
@@ -119,13 +126,78 @@ public:
      * \param t The current time level
      * \param dt The time step size to be used
      * \note We expect the time level in vars to correspond to the given time `t`
-     * \todo: TODO: Add time step control if the pde solver doesn't converge
      */
     void step(Variables& vars, const Scalar t, const Scalar dt)
     {
         // make sure there are no traces of previous stages
         pdeSolver_->assembler().clearStages();
 
+        // do time integration
+        bool converged = step_(vars, t, dt);
+
+        // clear traces of previously registered stages
+        pdeSolver_->assembler().clearStages();
+
+        // if the solver didn't converge we can't recover
+        if (!converged)
+            DUNE_THROW(NumericalProblem, "Solver did not converge!");
+    }
+
+    /*!
+     * \brief Advance one time step of the given time loop (adaptive time stepping on solver failure)
+     * \param vars The variables object at the current time level.
+     * \param timeLoop An instance of a time loop
+     * \note We expect the time level in vars to correspond to the given time `t`
+     */
+    void step(Variables& vars, TimeLoopBase<Scalar>& timeLoop)
+    {
+        // make sure there are no traces of previous stages
+        pdeSolver_->assembler().clearStages();
+
+        // try solving the non-linear system
+        for (std::size_t i = 0; i <= maxTimeStepDivisions_; ++i)
+        {
+            // try solving the non-linear system
+            bool converged = step_(vars, timeLoop.time(), timeLoop.timeStepSize());
+            if (converged)
+            {
+                // clear traces of previously registered stages
+                pdeSolver_->assembler().clearStages();
+                return;
+            }
+
+            else if (!converged && i < maxTimeStepDivisions_)
+            {
+                // set solution to previous solution & reset time step
+                Backend::update(vars, pdeSolver_->assembler().prevSol());
+                pdeSolver_->assembler().resetTimeStep(Backend::dofs(vars));
+
+                if (pdeSolver_->verbosity() >= 1)
+                {
+                    const auto dt = timeLoop.timeStepSize();
+                    std::cout << Fmt::format("Solver did not converge with dt = {} seconds. ", dt)
+                            << Fmt::format("Retrying with time step of dt = {} seconds.\n", dt*retryTimeStepReductionFactor_);
+                }
+
+                // try again with dt = dt * retryTimeStepReductionFactor_
+                timeLoop.setTimeStepSize(timeLoop.timeStepSize() * retryTimeStepReductionFactor_);
+            }
+
+            else
+            {
+                pdeSolver_->assembler().clearStages();
+                DUNE_THROW(NumericalProblem,
+                    Fmt::format("Solver didn't converge after {} time-step divisions; dt = {}.\n",
+                                maxTimeStepDivisions_, timeLoop.timeStepSize()));
+            }
+        }
+
+        DUNE_THROW(Dune::InvalidStateException, "Unreachable");
+    }
+
+private:
+    bool step_(Variables& vars, const Scalar t, const Scalar dt)
+    {
         for (auto stageIdx = 1UL; stageIdx <= msMethod_->numStages(); ++stageIdx)
         {
             // prepare the assembler for this stage
@@ -135,16 +207,25 @@ public:
             );
 
             // assemble & solve
-            pdeSolver_->solve(vars);
+            bool converged = pdeSolver_->apply(vars);
+            if (!converged)
+                return false;
         }
 
-        // clear traces of previously registered stages
-        pdeSolver_->assembler().clearStages();
+        return true;
+    }
+
+    void initParams_(const std::string& group = "")
+    {
+        maxTimeStepDivisions_ = getParamFromGroup<std::size_t>(group, "TimeStepper.MaxTimeStepDivisions", 10);
+        retryTimeStepReductionFactor_ = getParamFromGroup<Scalar>(group, "TimeStepper.RetryTimeStepReductionFactor", 0.5);
     }
 
-private:
     std::shared_ptr<PDESolver> pdeSolver_;
     std::shared_ptr<const MultiStageMethod<Scalar>> msMethod_;
+
+    Scalar maxTimeStepDivisions_;
+    Scalar retryTimeStepReductionFactor_;
 };
 
 } // end namespace Dumux::Experimental