diff --git a/dumux/discretization/facecentered/staggered/fvgridgeometry.hh b/dumux/discretization/facecentered/staggered/fvgridgeometry.hh
index c53cd6e1bad9de339b46b21f81cd461a707fd457..18af7159e96a1d63d70189f4e7ae20ffaff2c0fb 100644
--- a/dumux/discretization/facecentered/staggered/fvgridgeometry.hh
+++ b/dumux/discretization/facecentered/staggered/fvgridgeometry.hh
@@ -822,6 +822,9 @@ private:
             }
         }
 
+        // set number of subcontrolvolume faces
+        numScvf_ = scvfIdx;
+
         connectivityMap_.update(*this);
     }
 
diff --git a/dumux/freeflow/navierstokes/problem.hh b/dumux/freeflow/navierstokes/problem.hh
index 092603f0324a6b1c326f92ede9dd6bb86291e7b8..d6832349bd3de307d22f03878c7a455517d8c5d8 100644
--- a/dumux/freeflow/navierstokes/problem.hh
+++ b/dumux/freeflow/navierstokes/problem.hh
@@ -471,7 +471,7 @@ public:
     const VelocityVector beaversJosephVelocity(const FVElementGeometry& fvGeometry,
                                                const SubControlVolumeFace& scvf,
                                                const ElementVolumeVariables& elemVolVars,
-                                               const Scalar tangentialVelocityGradient) const
+                                               Scalar tangentialVelocityGradient) const
     {
         assert(scvf.isLateral());
         assert(scvf.boundary());
@@ -487,6 +487,10 @@ public:
         const Scalar betaBJ = asImp_().betaBJ(fvGeometry, scvf, orientation);
         const Scalar distanceNormalToBoundary = (scvf.ipGlobal() - scv.dofPosition()).two_norm();
 
+        static const bool onlyNormalGradient = getParamFromGroup<bool>(this->paramGroup(), "FreeFlow.EnableUnsymmetrizedVelocityGradientForBeaversJoseph", false);
+        if (onlyNormalGradient)
+            tangentialVelocityGradient = 0.0;
+
         const Scalar scalarSlipVelocity = (tangentialVelocityGradient*distanceNormalToBoundary
             + asImp_().porousMediumVelocity(fvGeometry, scvf) * orientation * betaBJ * distanceNormalToBoundary
             + elemVolVars[scv].velocity()) / (betaBJ*distanceNormalToBoundary + 1.0);
diff --git a/dumux/multidomain/boundary/CMakeLists.txt b/dumux/multidomain/boundary/CMakeLists.txt
index addd460feb4d2b502e6a92a0630ad445b11020d9..5ffc5873b26785265ec0c9ad64e4cc7aa93cce1b 100644
--- a/dumux/multidomain/boundary/CMakeLists.txt
+++ b/dumux/multidomain/boundary/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_subdirectory(darcydarcy)
+add_subdirectory(freeflowporousmedium)
 add_subdirectory(stokesdarcy)
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/CMakeLists.txt b/dumux/multidomain/boundary/freeflowporousmedium/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..063a18b3b6887225bcbd24d05c411231071873f7
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_subdirectory(ffmasspm)
+add_subdirectory(ffmomentumpm)
+
+file(GLOB DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOWPOROUSMEDIUM_HEADERS *.hh *.inc)
+install(FILES ${DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOWPOROUSMEDIUM_HEADERS}
+        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/multidomain/boundary/freeflowporousmedium)
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/couplingconditions.hh b/dumux/multidomain/boundary/freeflowporousmedium/couplingconditions.hh
new file mode 100644
index 0000000000000000000000000000000000000000..d4d9dceec66947ac3b9967ff2c4f1e60f671e476
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/couplingconditions.hh
@@ -0,0 +1,580 @@
+// -*- 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 3 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 FreeFlowPorousMediumCoupling
+ * \copydoc Dumux::FreeFlowPorousMediumCouplingData
+ */
+
+#ifndef DUMUX_MD_FREEFLOW_POROUSMEDIUM_COUPLINGCONDITIONS_HH
+#define DUMUX_MD_FREEFLOW_POROUSMEDIUM_COUPLINGCONDITIONS_HH
+
+#include <numeric>
+
+#include <dune/common/exceptions.hh>
+
+#include <dumux/common/properties.hh>
+#include <dumux/common/math.hh>
+
+#include <dumux/discretization/cellcentered/tpfa/computetransmissibility.hh>
+
+#include <dumux/flux/darcyslaw_fwd.hh>
+#include <dumux/flux/fickslaw_fwd.hh>
+#include <dumux/flux/forchheimerslaw_fwd.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief This structs holds a set of options which allow to modify the Stokes-Darcy
+ *        coupling mechanism during runtime.
+ */
+struct FreeFlowPorousMediumCouplingOptions
+{
+    /*!
+     * \brief Defines which kind of averanging of diffusion coefficients
+     *        (moleculat diffusion or thermal conductance) at the interface
+     *        between free flow and porous medium shall be used.
+     */
+    enum class DiffusionCoefficientAveragingType
+    {
+        harmonic, arithmethic, ffOnly, pmOnly
+    };
+
+    /*!
+     * \brief Convenience function to convert user input given as std::string to the corresponding enum class used for chosing the type
+     *        of averaging of the diffusion/conduction parameter at the interface between the two domains.
+     */
+    static DiffusionCoefficientAveragingType stringToEnum(DiffusionCoefficientAveragingType, const std::string& diffusionCoefficientAveragingType)
+    {
+        if (diffusionCoefficientAveragingType == "Harmonic")
+            return DiffusionCoefficientAveragingType::harmonic;
+        else if (diffusionCoefficientAveragingType == "Arithmethic")
+            return DiffusionCoefficientAveragingType::arithmethic;
+        else if (diffusionCoefficientAveragingType == "FreeFlowOnly")
+            return DiffusionCoefficientAveragingType::ffOnly;
+        else if (diffusionCoefficientAveragingType == "PorousMediumOnly")
+            return DiffusionCoefficientAveragingType::pmOnly;
+        else
+            DUNE_THROW(Dune::IOError, "Unknown DiffusionCoefficientAveragingType");
+    }
+
+};
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief This structs helps to check if the two sub models use the same fluidsystem.
+ *        Specialization for the case of using an adapter only for the free-flow model.
+ * \tparam FFFS The free-flow fluidsystem
+ * \tparam PMFS The porous-medium flow fluidsystem
+ */
+template<class FFFS, class PMFS>
+struct IsSameFluidSystem
+{
+    static_assert(FFFS::numPhases == 1, "Only single-phase fluidsystems may be used for free flow.");
+    static constexpr bool value = std::is_same<typename FFFS::MultiPhaseFluidSystem, PMFS>::value;
+};
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief This structs helps to check if the two sub models use the same fluidsystem.
+ * \tparam FS The fluidsystem
+ */
+template<class FS>
+struct IsSameFluidSystem<FS, FS>
+{
+    static_assert(FS::numPhases == 1, "Only single-phase fluidsystems may be used for free flow.");
+    static constexpr bool value = std::is_same<FS, FS>::value; // always true
+};
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief This structs indicates that Fick's law is not used for diffusion.
+ * \tparam DiffLaw The diffusion law.
+ */
+template<class DiffLaw>
+struct IsFicksLaw : public std::false_type {};
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief This structs indicates that Fick's law is used for diffusion.
+ * \tparam DiffLaw The diffusion law.
+ */
+template<class T>
+struct IsFicksLaw<Dumux::FicksLaw<T>> : public std::true_type {};
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief Helper struct to choose the correct index for phases and components. This is need if the porous-medium-flow model
+          features more fluid phases than the free-flow model.
+ * \tparam stokesIdx The domain index of the free-flow model.
+ * \tparam porousMediumIndex The domain index of the porous-medium-flow model.
+ * \tparam FFFS The free-flow fluidsystem.
+ * \tparam hasAdapter Specifies whether an adapter class for the fluidsystem is used.
+ */
+template<std::size_t stokesIdx, std::size_t porousMediumIndex, class FFFS, bool hasAdapter>
+struct IndexHelper;
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief Helper struct to choose the correct index for phases and components. This is need if the porous-medium-flow model
+          features more fluid phases than the free-flow model. Specialization for the case that no adapter is used.
+ * \tparam stokesIdx The domain index of the free-flow model.
+ * \tparam porousMediumIndex The domain index of the porous-medium-flow model.
+ * \tparam FFFS The free-flow fluidsystem.
+ */
+template<std::size_t stokesIdx, std::size_t porousMediumIndex, class FFFS>
+struct IndexHelper<stokesIdx, porousMediumIndex, FFFS, false>
+{
+    /*!
+     * \brief No adapter is used, just return the input index.
+     */
+    template<std::size_t i>
+    static constexpr auto couplingPhaseIdx(Dune::index_constant<i>, int coupledPhaseIdx = 0)
+    { return coupledPhaseIdx; }
+
+    /*!
+     * \brief No adapter is used, just return the input index.
+     */
+    template<std::size_t i>
+    static constexpr auto couplingCompIdx(Dune::index_constant<i>, int coupledCompdIdx)
+    { return coupledCompdIdx; }
+};
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief Helper struct to choose the correct index for phases and components. This is need if the porous-medium-flow model
+          features more fluid phases than the free-flow model. Specialization for the case that a adapter is used.
+ * \tparam stokesIdx The domain index of the free-flow model.
+ * \tparam porousMediumIndex The domain index of the porous-medium-flow model.
+ * \tparam FFFS The free-flow fluidsystem.
+ */
+template<std::size_t stokesIdx, std::size_t porousMediumIndex, class FFFS>
+struct IndexHelper<stokesIdx, porousMediumIndex, FFFS, true>
+{
+    /*!
+     * \brief The free-flow model always uses phase index 0.
+     */
+    static constexpr int couplingPhaseIdx(Dune::index_constant<stokesIdx>, int coupledPhaseIdx = 0)
+    { return 0; }
+
+    /*!
+     * \brief The phase index of the porous-medium-flow model is given by the adapter fluidsytem (i.e., user input).
+     */
+    static constexpr auto couplingPhaseIdx(Dune::index_constant<porousMediumIndex>, int coupledPhaseIdx = 0)
+    { return FFFS::multiphaseFluidsystemPhaseIdx; }
+
+    /*!
+     * \brief The free-flow model does not need any change of the component index.
+     */
+    static constexpr auto couplingCompIdx(Dune::index_constant<stokesIdx>, int coupledCompdIdx)
+    { return coupledCompdIdx; }
+
+    /*!
+     * \brief The component index of the porous-medium-flow model is mapped by the adapter fluidsytem.
+     */
+    static constexpr auto couplingCompIdx(Dune::index_constant<porousMediumIndex>, int coupledCompdIdx)
+    { return FFFS::compIdx(coupledCompdIdx); }
+};
+
+template<class MDTraits, class CouplingManager, bool enableEnergyBalance, bool isCompositional>
+class FreeFlowPorousMediumCouplingConditionsImplementation;
+
+/*!
+* \ingroup BoundaryCoupling
+* \brief Data for the coupling of a Darcy model (cell-centered finite volume)
+*        with a (Navier-)Stokes model (staggerd grid).
+*/
+template<class MDTraits, class CouplingManager>
+using FreeFlowPorousMediumCouplingConditions
+    = FreeFlowPorousMediumCouplingConditionsImplementation<
+        MDTraits, CouplingManager,
+        GetPropType<typename MDTraits::template SubDomain<0>::TypeTag, Properties::ModelTraits>::enableEnergyBalance(),
+        (GetPropType<typename MDTraits::template SubDomain<0>::TypeTag, Properties::ModelTraits>::numFluidComponents() > 1)
+    >;
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief A base class which provides some common methods used for Stokes-Darcy coupling.
+ */
+template<class MDTraits, class CouplingManager>
+class FreeFlowPorousMediumCouplingConditionsImplementationBase
+{
+    using Scalar = typename MDTraits::Scalar;
+
+    template<std::size_t id> using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+    template<std::size_t id> using GridGeometry = GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>;
+    template<std::size_t id> using Element = typename GridGeometry<id>::GridView::template Codim<0>::Entity;
+    template<std::size_t id> using FVElementGeometry = typename GridGeometry<id>::LocalView;
+    template<std::size_t id> using SubControlVolumeFace = typename GridGeometry<id>::LocalView::SubControlVolumeFace;
+    template<std::size_t id> using SubControlVolume = typename GridGeometry<id>::LocalView::SubControlVolume;
+    template<std::size_t id> using Indices = typename GetPropType<SubDomainTypeTag<id>, Properties::ModelTraits>::Indices;
+    template<std::size_t id> using ElementVolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::LocalView;
+    template<std::size_t id> using VolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::VolumeVariables;
+    template<std::size_t id> using Problem = GetPropType<SubDomainTypeTag<id>, Properties::Problem>;
+    template<std::size_t id> using FluidSystem = GetPropType<SubDomainTypeTag<id>, Properties::FluidSystem>;
+    template<std::size_t id> using ModelTraits = GetPropType<SubDomainTypeTag<id>, Properties::ModelTraits>;
+    template<std::size_t id> using GlobalPosition = typename Element<id>::Geometry::GlobalCoordinate;
+    template<std::size_t id> using NumEqVector = typename Problem<id>::Traits::NumEqVector;
+
+public:
+    static constexpr auto freeFlowMomentumIndex = CouplingManager::freeFlowMomentumIndex;
+    static constexpr auto freeFlowMassIndex = CouplingManager::freeFlowMassIndex;
+    static constexpr auto porousMediumIndex = CouplingManager::porousMediumIndex;
+private:
+
+    using AdvectionType = GetPropType<SubDomainTypeTag<porousMediumIndex>, Properties::AdvectionType>;
+    using DarcysLaw = Dumux::DarcysLaw<SubDomainTypeTag<porousMediumIndex>>;
+    using ForchheimersLaw = Dumux::ForchheimersLaw<SubDomainTypeTag<porousMediumIndex>>;
+
+    static constexpr bool adapterUsed = ModelTraits<porousMediumIndex>::numFluidPhases() > 1;
+    using IndexHelper = Dumux::IndexHelper<freeFlowMassIndex, porousMediumIndex, FluidSystem<freeFlowMassIndex>, adapterUsed>;
+
+    static constexpr int enableEnergyBalance = GetPropType<SubDomainTypeTag<freeFlowMassIndex>, Properties::ModelTraits>::enableEnergyBalance();
+    static_assert(GetPropType<SubDomainTypeTag<porousMediumIndex>, Properties::ModelTraits>::enableEnergyBalance() == enableEnergyBalance,
+                  "All submodels must both be either isothermal or non-isothermal");
+
+    static_assert(IsSameFluidSystem<FluidSystem<freeFlowMassIndex>,
+                                    FluidSystem<porousMediumIndex>>::value,
+                  "All submodels must use the same fluid system");
+
+    using DiffusionCoefficientAveragingType = typename FreeFlowPorousMediumCouplingOptions::DiffusionCoefficientAveragingType;
+
+public:
+    /*!
+     * \brief Returns the corresponding phase index needed for coupling.
+     */
+    template<std::size_t i>
+    static constexpr auto couplingPhaseIdx(Dune::index_constant<i> id, int coupledPhaseIdx = 0)
+    { return IndexHelper::couplingPhaseIdx(id, coupledPhaseIdx); }
+
+    /*!
+     * \brief Returns the corresponding component index needed for coupling.
+     */
+    template<std::size_t i>
+    static constexpr auto couplingCompIdx(Dune::index_constant<i> id, int coupledCompdIdx)
+    { return IndexHelper::couplingCompIdx(id, coupledCompdIdx); }
+
+    /*!
+     * \brief Returns the intrinsic permeability of the coupled Darcy element.
+     */
+    template<class Context>
+    static auto darcyPermeability(const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                                  const SubControlVolumeFace<freeFlowMomentumIndex>& scvf,
+                                  const Context& context)
+    { return context.volVars.permeability(); }
+
+    /*!
+     * \brief Returns the momentum flux across the coupling boundary.
+     *
+     * For the normal momentum coupling, the porous medium side of the coupling condition
+     * is evaluated, i.e. -[p n]^pm.
+     *
+     */
+    template<class Context>
+    static NumEqVector<freeFlowMomentumIndex> momentumCouplingCondition(const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                                                                        const SubControlVolumeFace<freeFlowMomentumIndex>& scvf,
+                                                                        const ElementVolumeVariables<freeFlowMomentumIndex>& elemVolVars,
+                                                                        const Context& context)
+    {
+        static constexpr auto numPhasesDarcy = GetPropType<SubDomainTypeTag<porousMediumIndex>, Properties::ModelTraits>::numFluidPhases();
+        NumEqVector<freeFlowMomentumIndex> momentumFlux(0.0);
+
+        if (!scvf.isFrontal())
+            return momentumFlux;
+
+        const auto pmPhaseIdx = couplingPhaseIdx(porousMediumIndex);
+
+        if(numPhasesDarcy > 1)
+            momentumFlux[scvf.normalAxis()] = context.volVars.pressure(pmPhaseIdx);
+        else // use pressure reconstruction for single phase models
+            momentumFlux[scvf.normalAxis()] = pressureAtInterface_(fvGeometry, scvf, elemVolVars, context);
+
+        // TODO: generalize for permeability tensors
+
+        // normalize pressure
+        momentumFlux[scvf.normalAxis()] -= elemVolVars.gridVolVars().problem().referencePressure(fvGeometry.element(), fvGeometry, scvf);
+
+        momentumFlux[scvf.normalAxis()] *= scvf.directionSign();
+
+        return momentumFlux;
+    }
+
+    /*!
+     * \brief Evaluate an advective flux across the interface and consider upwinding.
+     */
+    static Scalar advectiveFlux(const Scalar insideQuantity, const Scalar outsideQuantity, const Scalar volumeFlow, bool insideIsUpstream)
+    {
+        const Scalar upwindWeight = 1.0; //TODO use Flux.UpwindWeight or something like Coupling.UpwindWeight?
+
+        if(insideIsUpstream)
+            return (upwindWeight * insideQuantity + (1.0 - upwindWeight) * outsideQuantity) * volumeFlow;
+        else
+            return (upwindWeight * outsideQuantity + (1.0 - upwindWeight) * insideQuantity) * volumeFlow;
+    }
+
+protected:
+
+    /*!
+     * \brief Returns the transmissibility used for either molecular diffusion or thermal conductivity.
+     */
+    template<std::size_t i, std::size_t j>
+    Scalar transmissibility_(Dune::index_constant<i> domainI,
+                             Dune::index_constant<j> domainJ,
+                             const Scalar insideDistance,
+                             const Scalar outsideDistance,
+                             const Scalar avgQuantityI,
+                             const Scalar avgQuantityJ,
+                             const DiffusionCoefficientAveragingType diffCoeffAvgType) const
+    {
+        const Scalar totalDistance = insideDistance + outsideDistance;
+        if(diffCoeffAvgType == DiffusionCoefficientAveragingType::harmonic)
+        {
+            return harmonicMean(avgQuantityI, avgQuantityJ, insideDistance, outsideDistance)
+                   / totalDistance;
+        }
+        else if(diffCoeffAvgType == DiffusionCoefficientAveragingType::arithmethic)
+        {
+            return arithmeticMean(avgQuantityI, avgQuantityJ, insideDistance, outsideDistance)
+                   / totalDistance;
+        }
+        else if(diffCoeffAvgType == DiffusionCoefficientAveragingType::ffOnly)
+            return domainI == freeFlowMassIndex
+                            ? avgQuantityI / totalDistance
+                            : avgQuantityJ / totalDistance;
+
+        else // diffCoeffAvgType == DiffusionCoefficientAveragingType::pmOnly)
+            return domainI == porousMediumIndex
+                            ? avgQuantityI / totalDistance
+                            : avgQuantityJ / totalDistance;
+    }
+
+    /*!
+     * \brief Returns the distance between an scvf and the corresponding scv center.
+     */
+    template<class Scv, class Scvf>
+    Scalar getDistance_(const Scv& scv, const Scvf& scvf) const
+    {
+        return (scv.dofPosition() - scvf.ipGlobal()).two_norm();
+    }
+
+    /*!
+     * \brief Returns the conductive energy flux acorss the interface.
+     */
+    template<std::size_t i, std::size_t j, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    Scalar conductiveEnergyFlux_(Dune::index_constant<i> domainI,
+                                 Dune::index_constant<j> domainJ,
+                                 const FVElementGeometry<i>& fvGeometryI,
+                                 const FVElementGeometry<j>& fvGeometryJ,
+                                 const SubControlVolumeFace<i>& scvfI,
+                                 const SubControlVolume<i>& scvI,
+                                 const SubControlVolume<j>& scvJ,
+                                 const VolumeVariables<i>& volVarsI,
+                                 const VolumeVariables<j>& volVarsJ,
+                                 const DiffusionCoefficientAveragingType diffCoeffAvgType) const
+    {
+        const Scalar insideDistance = getDistance_(scvI, scvfI);
+        const Scalar outsideDistance = getDistance_(scvJ, scvfI);
+
+        const Scalar deltaT = volVarsJ.temperature() - volVarsI.temperature();
+        const Scalar tij = transmissibility_(
+            domainI, domainJ,
+            insideDistance, outsideDistance,
+            volVarsI.effectiveThermalConductivity(), volVarsJ.effectiveThermalConductivity(),
+            diffCoeffAvgType
+        );
+
+        return -tij * deltaT;
+    }
+
+    /*!
+     * \brief Returns the pressure at the interface
+     */
+    template<class CouplingContext>
+    static Scalar pressureAtInterface_(const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                                       const SubControlVolumeFace<freeFlowMomentumIndex>& scvf,
+                                       const ElementVolumeVariables<freeFlowMomentumIndex>& elemVolVars,
+                                       const CouplingContext& context)
+    {
+        GlobalPosition<freeFlowMomentumIndex> velocity(0.0);
+        const auto& scv = fvGeometry.scv(scvf.insideScvIdx());
+        velocity[scv.dofAxis()] = elemVolVars[scv].velocity();
+        const auto& pmScvf = context.fvGeometry.scvf(context.porousMediumScvfIdx);
+        return computeCouplingPhasePressureAtInterface_(context.fvGeometry, pmScvf, context.volVars, context.gravity, velocity, AdvectionType());
+    }
+
+    /*!
+     * \brief Returns the pressure at the interface using Forchheimers's law for reconstruction
+     */
+    static Scalar computeCouplingPhasePressureAtInterface_(const FVElementGeometry<porousMediumIndex>& fvGeometry,
+                                                           const SubControlVolumeFace<porousMediumIndex>& scvf,
+                                                           const VolumeVariables<porousMediumIndex>& volVars,
+                                                           const typename Element<freeFlowMomentumIndex>::Geometry::GlobalCoordinate& couplingPhaseVelocity,
+                                                           ForchheimersLaw)
+    {
+        DUNE_THROW(Dune::NotImplemented, "Forchheimer's law pressure reconstruction");
+    }
+
+    /*!
+     * \brief Returns the pressure at the interface using Darcy's law for reconstruction
+     */
+    static Scalar computeCouplingPhasePressureAtInterface_(const FVElementGeometry<porousMediumIndex>& fvGeometry,
+                                                           const SubControlVolumeFace<porousMediumIndex>& scvf,
+                                                           const VolumeVariables<porousMediumIndex>& volVars,
+                                                           const typename Element<freeFlowMomentumIndex>::Geometry::GlobalCoordinate& gravity,
+                                                           const typename Element<freeFlowMomentumIndex>::Geometry::GlobalCoordinate& couplingPhaseVelocity,
+                                                           DarcysLaw)
+    {
+        const auto pmPhaseIdx = couplingPhaseIdx(porousMediumIndex);
+        const Scalar couplingPhaseCellCenterPressure = volVars.pressure(pmPhaseIdx);
+        const Scalar couplingPhaseMobility = volVars.mobility(pmPhaseIdx);
+        const Scalar couplingPhaseDensity = volVars.density(pmPhaseIdx);
+        const auto K = volVars.permeability();
+
+        // A tpfa approximation yields (works if mobility != 0)
+        // v*n = -kr/mu*K * (gradP - rho*g)*n = mobility*(ti*(p_center - p_interface) + rho*n^TKg)
+        // -> p_interface = (1/mobility * (-v*n) + rho*n^TKg)/ti + p_center
+        // where v is the free-flow velocity (couplingPhaseVelocity)
+        const auto alpha = vtmv(scvf.unitOuterNormal(), K, gravity);
+
+        const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx());
+        const auto ti = computeTpfaTransmissibility(scvf, insideScv, K, 1.0);
+
+        return (-1/couplingPhaseMobility * (scvf.unitOuterNormal() * couplingPhaseVelocity) + couplingPhaseDensity * alpha)/ti
+               + couplingPhaseCellCenterPressure;
+    }
+};
+
+/*!
+ * \ingroup FreeFlowPorousMediumCoupling
+ * \brief Coupling data specialization for non-compositional models.
+ */
+template<class MDTraits, class CouplingManager, bool enableEnergyBalance>
+class FreeFlowPorousMediumCouplingConditionsImplementation<MDTraits, CouplingManager, enableEnergyBalance, false>
+: public FreeFlowPorousMediumCouplingConditionsImplementationBase<MDTraits, CouplingManager>
+{
+    using ParentType = FreeFlowPorousMediumCouplingConditionsImplementationBase<MDTraits, CouplingManager>;
+    using Scalar = typename MDTraits::Scalar;
+
+    // the sub domain type tags
+    template<std::size_t id>
+    using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+
+    template<std::size_t id> using GridGeometry = GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>;
+    template<std::size_t id> using Element = typename GridGeometry<id>::GridView::template Codim<0>::Entity;
+    template<std::size_t id> using FVElementGeometry = typename GridGeometry<id>::LocalView;
+    template<std::size_t id> using SubControlVolumeFace = typename GridGeometry<id>::LocalView::SubControlVolumeFace;
+    template<std::size_t id> using SubControlVolume = typename GridGeometry<id>::LocalView::SubControlVolume;
+    template<std::size_t id> using Indices = typename GetPropType<SubDomainTypeTag<id>, Properties::ModelTraits>::Indices;
+    template<std::size_t id> using ElementVolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::LocalView;
+    template<std::size_t id> using VolumeVariables  = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::VolumeVariables;
+
+    static_assert(GetPropType<SubDomainTypeTag<ParentType::porousMediumIndex>, Properties::ModelTraits>::numFluidComponents() == GetPropType<SubDomainTypeTag<ParentType::porousMediumIndex>, Properties::ModelTraits>::numFluidPhases(),
+                  "Darcy Model must not be compositional");
+
+    using DiffusionCoefficientAveragingType = typename FreeFlowPorousMediumCouplingOptions::DiffusionCoefficientAveragingType;
+
+public:
+    using ParentType::ParentType;
+    using ParentType::couplingPhaseIdx;
+
+    /*!
+     * \brief Returns the mass flux across the coupling boundary as seen from the Darcy domain.
+     */
+    template<std::size_t i, std::size_t j, class CouplingContext>
+    static Scalar massCouplingCondition(Dune::index_constant<i> domainI,
+                                        Dune::index_constant<j> domainJ,
+                                        const FVElementGeometry<i>& fvGeometry,
+                                        const SubControlVolumeFace<i>& scvf,
+                                        const ElementVolumeVariables<i>& insideVolVars,
+                                        const CouplingContext& context)
+    {
+        const Scalar normalVelocity = context.velocity * scvf.unitOuterNormal();
+        const Scalar darcyDensity = insideVolVars[scvf.insideScvIdx()].density(couplingPhaseIdx(ParentType::porousMediumIndex));
+        const Scalar stokesDensity = context.volVars.density();
+        const bool insideIsUpstream = normalVelocity > 0.0;
+        return ParentType::advectiveFlux(darcyDensity, stokesDensity, normalVelocity, insideIsUpstream);
+    }
+
+    /*!
+     * \brief Returns the energy flux across the coupling boundary as seen from the Darcy domain.
+     */
+    template<bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    Scalar energyCouplingCondition(const Element<ParentType::porousMediumIndex>& element,
+                                   const FVElementGeometry<ParentType::porousMediumIndex>& fvGeometry,
+                                   const ElementVolumeVariables<ParentType::porousMediumIndex>& darcyElemVolVars,
+                                   const SubControlVolumeFace<ParentType::porousMediumIndex>& scvf,
+                                   const DiffusionCoefficientAveragingType diffCoeffAvgType = DiffusionCoefficientAveragingType::ffOnly) const
+    {
+        DUNE_THROW(Dune::NotImplemented, "Energy coupling condition");
+    }
+
+    /*!
+     * \brief Returns the energy flux across the coupling boundary as seen from the free-flow domain.
+     */
+    template<bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    Scalar energyCouplingCondition(const Element<ParentType::freeFlowMassIndex>& element,
+                                   const FVElementGeometry<ParentType::freeFlowMassIndex>& fvGeometry,
+                                   const ElementVolumeVariables<ParentType::freeFlowMassIndex>& stokesElemVolVars,
+                                   const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                                   const DiffusionCoefficientAveragingType diffCoeffAvgType = DiffusionCoefficientAveragingType::ffOnly) const
+    {
+        DUNE_THROW(Dune::NotImplemented, "Energy coupling condition");
+    }
+
+private:
+
+
+
+    /*!
+     * \brief Evaluate the energy flux across the interface.
+     */
+    template<std::size_t i, std::size_t j, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    Scalar energyFlux_(Dune::index_constant<i> domainI,
+                       Dune::index_constant<j> domainJ,
+                       const FVElementGeometry<i>& insideFvGeometry,
+                       const FVElementGeometry<j>& outsideFvGeometry,
+                       const SubControlVolumeFace<i>& scvf,
+                       const VolumeVariables<i>& insideVolVars,
+                       const VolumeVariables<j>& outsideVolVars,
+                       const Scalar velocity,
+                       const bool insideIsUpstream,
+                       const DiffusionCoefficientAveragingType diffCoeffAvgType) const
+    {
+        Scalar flux(0.0);
+
+        const auto& insideScv = (*scvs(insideFvGeometry).begin());
+        const auto& outsideScv = (*scvs(outsideFvGeometry).begin());
+
+        // convective fluxes
+        const Scalar insideTerm = insideVolVars.density(couplingPhaseIdx(domainI)) * insideVolVars.enthalpy(couplingPhaseIdx(domainI));
+        const Scalar outsideTerm = outsideVolVars.density(couplingPhaseIdx(domainJ)) * outsideVolVars.enthalpy(couplingPhaseIdx(domainJ));
+
+        flux += this->advectiveFlux(insideTerm, outsideTerm, velocity, insideIsUpstream);
+
+        flux += this->conductiveEnergyFlux_(domainI, domainJ, insideFvGeometry, outsideFvGeometry, scvf, insideScv, outsideScv, insideVolVars, outsideVolVars, diffCoeffAvgType);
+
+        return flux;
+    }
+
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/couplingmanager.hh b/dumux/multidomain/boundary/freeflowporousmedium/couplingmanager.hh
new file mode 100644
index 0000000000000000000000000000000000000000..cb3b2b57ade83aa1c4952885cf3f9b9d1ad79bec
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/couplingmanager.hh
@@ -0,0 +1,477 @@
+// -*- 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 3 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 BoundaryCoupling
+ * \copydoc Dumux::FreeFlowPorousMediumCouplingManager
+ */
+
+#ifndef DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOW_POROUSMEDIUM_COUPLINGMANAGER_HH
+#define DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOW_POROUSMEDIUM_COUPLINGMANAGER_HH
+
+#include <utility>
+#include <memory>
+
+#include <dune/common/indices.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/multidomain/boundary/freeflowporousmedium/ffmasspm/couplingmanager.hh>
+#include <dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/couplingmanager.hh>
+#include <dumux/multidomain/staggeredfreeflow/couplingmanager.hh>
+#include <dumux/multidomain/multibinarycouplingmanager.hh>
+
+#include "couplingconditions.hh"
+
+namespace Dumux {
+
+namespace FreeFlowPorousMediumDetail {
+
+// global subdomain indices
+static constexpr auto freeFlowMomentumIndex = Dune::index_constant<0>();
+static constexpr auto freeFlowMassIndex = Dune::index_constant<1>();
+static constexpr auto porousMediumIndex = Dune::index_constant<2>();
+
+// coupling indices
+static constexpr auto freeFlowMassToFreeFlowMomentumIndex = Dune::index_constant<0>();
+static constexpr auto freeFlowMomentumToPorousMediumIndex = Dune::index_constant<1>();
+static constexpr auto freeFlowMassToPorousMediumIndex = Dune::index_constant<2>();
+static constexpr auto noCouplingIdx = Dune::index_constant<99>();
+
+constexpr auto makeCouplingManagerMap()
+{
+    auto map = std::array<std::array<std::size_t, 3>, 3>{};
+
+    // free flow (momentum-mass)
+    map[freeFlowMomentumIndex][freeFlowMassIndex] = freeFlowMassToFreeFlowMomentumIndex;
+    map[freeFlowMassIndex][freeFlowMomentumIndex] = freeFlowMassToFreeFlowMomentumIndex;
+
+    // free flow momentum - porous medium
+    map[freeFlowMomentumIndex][porousMediumIndex] = freeFlowMomentumToPorousMediumIndex;
+    map[porousMediumIndex][freeFlowMomentumIndex] = freeFlowMomentumToPorousMediumIndex;
+
+    // free flow mass - porous medium
+    map[freeFlowMassIndex][porousMediumIndex] = freeFlowMassToPorousMediumIndex;
+    map[porousMediumIndex][freeFlowMassIndex] = freeFlowMassToPorousMediumIndex;
+
+    return map;
+}
+
+template<std::size_t i>
+constexpr auto coupledDomains(Dune::index_constant<i> domainI)
+{
+    if constexpr (i == freeFlowMomentumIndex)
+        return std::make_tuple(freeFlowMassIndex, porousMediumIndex);
+    else if constexpr (i == freeFlowMassIndex)
+        return std::make_tuple(freeFlowMomentumIndex, porousMediumIndex);
+    else // i == porousMediumIndex
+        return std::make_tuple(freeFlowMomentumIndex, freeFlowMassIndex);
+}
+
+template<std::size_t i, std::size_t j>
+constexpr auto globalToLocalDomainIndices(Dune::index_constant<i>, Dune::index_constant<j>)
+{
+    static_assert(i <= 2 && j <= 2);
+    static_assert(i != j);
+
+    if constexpr (i < j)
+        return std::pair<Dune::index_constant<0>, Dune::index_constant<1>>{};
+    else
+        return std::pair<Dune::index_constant<1>, Dune::index_constant<0>>{};
+}
+
+struct CouplingMaps
+{
+    static constexpr auto managerMap()
+    {
+        return  FreeFlowPorousMediumDetail::makeCouplingManagerMap();
+    }
+
+    template<std::size_t i, std::size_t j>
+    static constexpr auto globalToLocal(Dune::index_constant<i> domainI, Dune::index_constant<j> domainJ)
+    {
+        return FreeFlowPorousMediumDetail::globalToLocalDomainIndices(domainI, domainJ);
+    }
+
+    template<std::size_t i>
+    static constexpr auto coupledDomains(Dune::index_constant<i> domainI)
+    {
+        return FreeFlowPorousMediumDetail::coupledDomains(domainI);
+    }
+};
+
+template<class MDTraits>
+struct CouplingManagers
+{
+    template<std::size_t id>
+    using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+
+    using FreeFlowTraits = MultiDomainTraits<
+        SubDomainTypeTag<freeFlowMomentumIndex>, SubDomainTypeTag<freeFlowMassIndex>
+    >;
+
+    using FreeFlowMomentumPorousMediumTraits = MultiDomainTraits<
+        SubDomainTypeTag<freeFlowMomentumIndex>, SubDomainTypeTag<porousMediumIndex>
+    >;
+
+    using FreeFlowMassPorousMediumTraits = MultiDomainTraits<
+        SubDomainTypeTag<freeFlowMassIndex>, SubDomainTypeTag<porousMediumIndex>
+    >;
+
+    using FreeFlowCouplingManager
+        = Dumux::StaggeredFreeFlowCouplingManager<FreeFlowTraits>;
+    using FreeFlowMomentumPorousMediumCouplingManager
+        = Dumux::FreeFlowMomentumPorousMediumCouplingManager<FreeFlowMomentumPorousMediumTraits>;
+    using FreeFlowMassPorousMediumCouplingManager
+        = Dumux::FreeFlowMassPorousMediumCouplingManager<FreeFlowMassPorousMediumTraits>;
+};
+
+} // end namespace Detail
+
+
+/*!
+ * \ingroup BoundaryCoupling
+ * \brief Coupling manager for coupling freeflow and porous medium flow models
+ */
+template<class MDTraits>
+class FreeFlowPorousMediumCouplingManager
+: public MultiBinaryCouplingManager<
+    MDTraits,
+    FreeFlowPorousMediumDetail::CouplingMaps,
+    typename FreeFlowPorousMediumDetail::CouplingManagers<MDTraits>::FreeFlowCouplingManager,
+    typename FreeFlowPorousMediumDetail::CouplingManagers<MDTraits>::FreeFlowMomentumPorousMediumCouplingManager,
+    typename FreeFlowPorousMediumDetail::CouplingManagers<MDTraits>::FreeFlowMassPorousMediumCouplingManager
+>
+{
+    using ParentType = MultiBinaryCouplingManager<
+        MDTraits,
+        FreeFlowPorousMediumDetail::CouplingMaps,
+        typename FreeFlowPorousMediumDetail::CouplingManagers<MDTraits>::FreeFlowCouplingManager,
+        typename FreeFlowPorousMediumDetail::CouplingManagers<MDTraits>::FreeFlowMomentumPorousMediumCouplingManager,
+        typename FreeFlowPorousMediumDetail::CouplingManagers<MDTraits>::FreeFlowMassPorousMediumCouplingManager
+    >;
+
+    using Scalar = typename MDTraits::Scalar;
+
+    // the sub domain type tags
+    template<std::size_t id>
+    using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+
+    template<std::size_t id> using Problem = GetPropType<SubDomainTypeTag<id>, Properties::Problem>;
+    template<std::size_t id> using GridGeometry = GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>;
+    template<std::size_t id> using FVElementGeometry = typename GridGeometry<id>::LocalView;
+    template<std::size_t id> using SubControlVolumeFace = typename FVElementGeometry<id>::SubControlVolumeFace;
+    template<std::size_t id> using SubControlVolume = typename FVElementGeometry<id>::SubControlVolume;
+    template<std::size_t id> using ElementVolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::LocalView;
+    template<std::size_t id> using NumEqVector = typename Problem<id>::Traits::NumEqVector;
+
+    template<std::size_t id> using GridView = typename GridGeometry<id>::GridView;
+    template<std::size_t id> using Element = typename GridView<id>::template Codim<0>::Entity;
+    using SolutionVector = typename MDTraits::SolutionVector;
+
+    using CouplingConditions = FreeFlowPorousMediumCouplingConditions<MDTraits, FreeFlowPorousMediumCouplingManager<MDTraits>>;
+
+public:
+
+    template<std::size_t i, std::size_t j>
+    using SubCouplingManager = typename ParentType::template SubCouplingManager<i, j>;
+
+    static constexpr auto freeFlowMomentumIndex = FreeFlowPorousMediumDetail::freeFlowMomentumIndex;
+    static constexpr auto freeFlowMassIndex = FreeFlowPorousMediumDetail::freeFlowMassIndex;
+    static constexpr auto porousMediumIndex = FreeFlowPorousMediumDetail::porousMediumIndex;
+
+public:
+    using ParentType::ParentType;
+
+    template<class GridVarsTuple>
+    void init(std::shared_ptr<Problem<freeFlowMomentumIndex>> freeFlowMomentumProblem,
+              std::shared_ptr<Problem<freeFlowMassIndex>> freeFlowMassProblem,
+              std::shared_ptr<Problem<porousMediumIndex>> porousMediumProblem,
+              GridVarsTuple&& gridVarsTuple,
+              const SolutionVector& curSol)
+    {
+        this->updateSolution(curSol); // generic coupling manager stores tuple of shared_ptr
+
+        // initialize the binary sub coupling managers
+        typename SubCouplingManager<freeFlowMomentumIndex, freeFlowMassIndex>::SolutionVectorStorage ffSolVecTuple;
+        std::get<0>(ffSolVecTuple) = std::get<freeFlowMomentumIndex>(this->curSol());
+        std::get<1>(ffSolVecTuple) = std::get<freeFlowMassIndex>(this->curSol());
+        this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).init(
+            freeFlowMomentumProblem, freeFlowMassProblem,
+            std::make_tuple(std::get<freeFlowMomentumIndex>(gridVarsTuple), std::get<freeFlowMassIndex>(gridVarsTuple)),
+            ffSolVecTuple
+        );
+
+        typename SubCouplingManager<freeFlowMassIndex, porousMediumIndex>::SolutionVectorStorage ffMassPmSolVecTuple;
+        std::get<0>(ffMassPmSolVecTuple) = std::get<freeFlowMassIndex>(this->curSol());
+        std::get<1>(ffMassPmSolVecTuple) = std::get<porousMediumIndex>(this->curSol());
+        this->subCouplingManager(freeFlowMassIndex, porousMediumIndex).init(
+            freeFlowMassProblem, porousMediumProblem, ffMassPmSolVecTuple
+        );
+
+        typename SubCouplingManager<freeFlowMomentumIndex, porousMediumIndex>::SolutionVectorStorage ffMomentumPmSolVecTuple;
+        std::get<0>(ffMomentumPmSolVecTuple) = std::get<freeFlowMomentumIndex>(this->curSol());
+        std::get<1>(ffMomentumPmSolVecTuple) = std::get<porousMediumIndex>(this->curSol());
+        this->subCouplingManager(freeFlowMomentumIndex, porousMediumIndex).init(
+            freeFlowMomentumProblem, porousMediumProblem, ffMomentumPmSolVecTuple
+        );
+    }
+
+    /*!
+     * \brief Returns the mass flux across the coupling boundary.
+     */
+    template<std::size_t i, std::size_t j>
+    auto massCouplingCondition(Dune::index_constant<i> domainI, Dune::index_constant<j> domainJ,
+                               const FVElementGeometry<i>& fvGeometry,
+                               const typename FVElementGeometry<i>::SubControlVolumeFace& scvf,
+                               const ElementVolumeVariables<i>& elemVolVars) const
+    {
+        static_assert(domainI != freeFlowMomentumIndex && domainJ != freeFlowMomentumIndex);
+
+        const auto& couplingContext = this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
+            return cm.couplingContext(ii, fvGeometry, scvf);
+        });
+
+        const auto& freeFlowElement = [&]
+        {
+            if constexpr (domainI == freeFlowMassIndex)
+                return fvGeometry.element();
+            else
+                return couplingContext.fvGeometry.element();
+        }();
+
+        const auto& freeFlowScvf = [&]
+        {
+            if constexpr (domainI == freeFlowMassIndex)
+                return scvf;
+            else
+                return couplingContext.fvGeometry.scvf(couplingContext.freeFlowMassScvfIdx);
+
+        }();
+
+        // todo revise velocity (see ff mom pm mgr)
+
+        couplingContext.velocity = this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).faceVelocity(freeFlowElement, freeFlowScvf);
+        return CouplingConditions::massCouplingCondition(domainI, domainJ, fvGeometry, scvf, elemVolVars, couplingContext);
+    }
+
+
+    //////////////////////// Conditions for FreeFlowMomentum - PorousMedium coupling //////////
+    ///////////////////////////////////////////////////////////////////////////////////////////
+
+    NumEqVector<freeFlowMomentumIndex> momentumCouplingCondition(Dune::index_constant<freeFlowMomentumIndex> domainI,
+                                                                 Dune::index_constant<porousMediumIndex> domainJ,
+                                                                 const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                                                                 const typename FVElementGeometry<freeFlowMomentumIndex>::SubControlVolumeFace& scvf,
+                                                                 const ElementVolumeVariables<freeFlowMomentumIndex>& elemVolVars) const
+    {
+        if (scvf.isLateral())
+            return NumEqVector<freeFlowMomentumIndex>(0.0);
+
+        const auto& context = this->subCouplingManager(freeFlowMomentumIndex, porousMediumIndex).couplingContext(
+            domainI, fvGeometry, scvf
+        );
+
+        return CouplingConditions::momentumCouplingCondition(fvGeometry, scvf, elemVolVars, context);
+    }
+
+    /*!
+     * \brief Returns the intrinsic permeability of the coupled Darcy element.
+     */
+    auto darcyPermeability(const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                           const SubControlVolumeFace<freeFlowMomentumIndex>& scvf) const
+    {
+        if (scvf.isFrontal())
+        {
+            const auto& context = this->subCouplingManager(freeFlowMomentumIndex, porousMediumIndex).couplingContext(
+                Dune::index_constant<freeFlowMomentumIndex>(), fvGeometry, scvf
+            );
+
+            return CouplingConditions::darcyPermeability(fvGeometry, scvf, context);
+        }
+        else
+        {
+            const auto& orthogonalScvf = fvGeometry.lateralOrthogonalScvf(scvf);
+            const auto& orthogonalScv = fvGeometry.scv(orthogonalScvf.insideScvIdx());
+            const auto& frontalScvfOnBoundary = fvGeometry.frontalScvfOnBoundary(orthogonalScv);
+            const auto& context = this->subCouplingManager(freeFlowMomentumIndex, porousMediumIndex).couplingContext(
+                Dune::index_constant<freeFlowMomentumIndex>(), fvGeometry, frontalScvfOnBoundary
+            );
+
+            return CouplingConditions::darcyPermeability(fvGeometry, frontalScvfOnBoundary, context);
+        }
+    }
+
+    //////////////////////// Conditions for FreeFlowMomentum - FreeFlowMass coupling //////////
+    ///////////////////////////////////////////////////////////////////////////////////////////
+
+    /*!
+     * \brief Returns the pressure at a given sub control volume face.
+     */
+    Scalar pressure(const Element<freeFlowMomentumIndex>& element,
+                    const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                    const SubControlVolumeFace<freeFlowMomentumIndex>& scvf) const
+    {
+        return this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).pressure(
+            element, fvGeometry, scvf
+        );
+    }
+
+    /*!
+     * \brief Returns the pressure at the center of a sub control volume corresponding to a given sub control volume face.
+     *        This is used for setting a Dirichlet pressure for the mass model when a fixed pressure for the momentum balance is set at another
+     *        boundary. Since the the pressure at the given scvf is solution-dependent and thus unknown a priori, we just use the value
+     *        of the interior cell here.
+     */
+    Scalar cellPressure(const Element<freeFlowMomentumIndex>& element,
+                        const SubControlVolumeFace<freeFlowMomentumIndex>& scvf) const
+    {
+        return this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).cellPressure(
+            element, scvf
+        );
+    }
+
+    /*!
+     * \brief Returns the density at a given sub control volume face.
+     */
+    Scalar density(const Element<freeFlowMomentumIndex>& element,
+                   const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                   const SubControlVolumeFace<freeFlowMomentumIndex>& scvf,
+                   const bool considerPreviousTimeStep = false) const
+    {
+        return this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).density(
+            element, fvGeometry, scvf, considerPreviousTimeStep
+        );
+    }
+
+    auto insideAndOutsideDensity(const Element<freeFlowMomentumIndex>& element,
+                                 const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                                 const SubControlVolumeFace<freeFlowMomentumIndex>& scvf,
+                                 const bool considerPreviousTimeStep = false) const
+    {
+        return this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).insideAndOutsideDensity(
+            element, fvGeometry, scvf, considerPreviousTimeStep
+        );
+    }
+
+    /*!
+     * \brief Returns the density at a given sub control volume.
+     */
+    Scalar density(const Element<freeFlowMomentumIndex>& element,
+                   const SubControlVolume<freeFlowMomentumIndex>& scv,
+                   const bool considerPreviousTimeStep = false) const
+    {
+        return this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).density(
+            element, scv, considerPreviousTimeStep
+        );
+    }
+
+    /*!
+     * \brief Returns the pressure at a given sub control volume face.
+     */
+    Scalar effectiveViscosity(const Element<freeFlowMomentumIndex>& element,
+                              const FVElementGeometry<freeFlowMomentumIndex>& fvGeometry,
+                              const SubControlVolumeFace<freeFlowMomentumIndex>& scvf) const
+    {
+        return this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).effectiveViscosity(
+            element, fvGeometry, scvf
+        );
+    }
+
+    /*!
+     * \brief Returns the velocity at a given sub control volume face.
+     */
+    auto faceVelocity(const Element<freeFlowMassIndex>& element,
+                      const SubControlVolumeFace<freeFlowMassIndex>& scvf) const
+    {
+        return this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).faceVelocity(
+            element, scvf
+        );
+    }
+
+    template<std::size_t i>
+    const Problem<i>& problem(Dune::index_constant<i> domainI) const
+    {
+        return this->subApply(domainI, [&](const auto& cm, auto&& ii) -> const auto& {
+            return cm.problem(ii);
+        });
+    }
+
+    template<std::size_t i, std::size_t j>
+    bool isCoupled(Dune::index_constant<i> domainI,
+                   Dune::index_constant<j> domainJ,
+                   const SubControlVolumeFace<i>& scvf) const
+    {
+        return this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj){
+            return cm.isCoupled(ii, scvf);
+        });
+    }
+
+    /*!
+     * \brief If the boundary entity is on a coupling boundary
+     * \param domainI the domain index for which to compute the flux
+     * \param scv the sub control volume
+     */
+    template<std::size_t i, std::size_t j>
+    bool isCoupled(Dune::index_constant<i> domainI,
+                   Dune::index_constant<j> domainJ,
+                   const SubControlVolume<i>& scv) const
+    {
+        return this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj){
+            return cm.isCoupled(ii, scv);
+        });
+    }
+
+    /*!
+     * \brief Returns whether a given scvf is coupled to the other domain
+     */
+    bool isCoupledLateralScvf(Dune::index_constant<freeFlowMomentumIndex> domainI,
+                              Dune::index_constant<porousMediumIndex> domainJ,
+                              const SubControlVolumeFace<freeFlowMomentumIndex>& scvf) const
+    {
+        return this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj){
+            return cm.isCoupledLateralScvf(ii, scvf);
+        });
+    }
+
+
+    using ParentType::couplingStencil;
+    /*!
+     * \brief returns an iteratable container of all indices of degrees of freedom of domain j
+     *        that couple with / influence the residual of the given sub-control volume of domain i
+     *
+     * \param domainI the domain index of domain i
+     * \param elementI the coupled element of domain í
+     * \param scvI the sub-control volume of domain i
+     * \param domainJ the domain index of domain j
+     */
+    template<std::size_t j>
+    const auto& couplingStencil(Dune::index_constant<freeFlowMomentumIndex> domainI,
+                                const Element<freeFlowMomentumIndex>& elementI,
+                                const SubControlVolume<freeFlowMomentumIndex>& scvI,
+                                Dune::index_constant<j> domainJ) const
+    {
+        static_assert(freeFlowMomentumIndex != j);
+        return this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
+            return cm.couplingStencil(ii, elementI, scvI, jj);
+        });
+    }
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/ffmasspm/CMakeLists.txt b/dumux/multidomain/boundary/freeflowporousmedium/ffmasspm/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2c7f3e04baf43525e71f8e78ad20d4eb23e64c53
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/ffmasspm/CMakeLists.txt
@@ -0,0 +1,3 @@
+file(GLOB DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOWPOROUSMEDIUM_FFMASSPM_HEADERS *.hh *.inc)
+install(FILES ${DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOWPOROUSMEDIUM_FFMASSPM_HEADERS}
+        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/multidomain/boundary/freeflowporousmedium/ffmasspm)
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/ffmasspm/couplingmanager.hh b/dumux/multidomain/boundary/freeflowporousmedium/ffmasspm/couplingmanager.hh
new file mode 100644
index 0000000000000000000000000000000000000000..e8156b97747aff358f6f33891fcb029db0f2380d
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/ffmasspm/couplingmanager.hh
@@ -0,0 +1,304 @@
+// -*- 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 3 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 FreeFlowPorousMediumCoupling
+ * \copydoc Dumux::FreeFlowMassPorousMediumCouplingManager
+ */
+
+#ifndef DUMUX_MULTIDOMAIN_BOUNDARY_FFPM_FFMASSPM_COUPLINGMANAGER_HH
+#define DUMUX_MULTIDOMAIN_BOUNDARY_FFPM_FFMASSPM_COUPLINGMANAGER_HH
+
+#include <utility>
+#include <memory>
+
+#include <dune/common/float_cmp.hh>
+#include <dune/common/exceptions.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/discretization/staggered/elementsolution.hh>
+#include <dumux/multidomain/couplingmanager.hh>
+#include <dumux/multidomain/boundary/darcydarcy/couplingmapper.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup StokesDarcyCoupling
+ * \brief Coupling manager for Stokes and Darcy domains with equal dimension.
+ */
+template<class MDTraits>
+class FreeFlowMassPorousMediumCouplingManager
+: public CouplingManager<MDTraits>
+{
+    using Scalar = typename MDTraits::Scalar;
+    using ParentType = CouplingManager<MDTraits>;
+
+public:
+    static constexpr auto freeFlowMassIndex = typename MDTraits::template SubDomain<0>::Index();
+    static constexpr auto porousMediumIndex = typename MDTraits::template SubDomain<1>::Index();
+
+    using SolutionVectorStorage = typename ParentType::SolutionVectorStorage;
+private:
+
+    // obtain the type tags of the sub problems
+    using FreeFlowMassTypeTag = typename MDTraits::template SubDomain<freeFlowMassIndex>::TypeTag;
+    using PorousMediumTypeTag = typename MDTraits::template SubDomain<porousMediumIndex>::TypeTag;
+
+    using CouplingStencils = std::unordered_map<std::size_t, std::vector<std::size_t> >;
+    using CouplingStencil = CouplingStencils::mapped_type;
+
+    // the sub domain type tags
+    template<std::size_t id>
+    using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+
+    template<std::size_t id> using GridView = typename GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>::GridView;
+    template<std::size_t id> using Problem = GetPropType<SubDomainTypeTag<id>, Properties::Problem>;
+    template<std::size_t id> using ElementVolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::LocalView;
+    template<std::size_t id> using GridVolumeVariables = GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>;
+    template<std::size_t id> using VolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::VolumeVariables;
+    template<std::size_t id> using GridGeometry = GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>;
+    template<std::size_t id> using FVElementGeometry = typename GridGeometry<id>::LocalView;
+    template<std::size_t id> using GridVariables = GetPropType<SubDomainTypeTag<id>, Properties::GridVariables>;
+    template<std::size_t id> using Element = typename GridView<id>::template Codim<0>::Entity;
+    template<std::size_t id> using PrimaryVariables = GetPropType<SubDomainTypeTag<id>, Properties::PrimaryVariables>;
+    template<std::size_t id> using SubControlVolumeFace = typename FVElementGeometry<id>::SubControlVolumeFace;
+    template<std::size_t id> using SubControlVolume = typename FVElementGeometry<id>::SubControlVolume;
+
+    using VelocityVector = typename Element<freeFlowMassIndex>::Geometry::GlobalCoordinate;
+
+    struct FreeFlowMassCouplingContext
+    {
+        Element<porousMediumIndex> element;
+        VolumeVariables<porousMediumIndex> volVars;
+        FVElementGeometry<porousMediumIndex> fvGeometry;
+        std::size_t dofIdx;
+        std::size_t freeFlowMassScvfIdx;
+        std::size_t porousMediumScvfIdx;
+        mutable VelocityVector velocity; // velocity needs to be set externally, not availabe in this class
+    };
+
+    struct PorousMediumCouplingContext
+    {
+        Element<freeFlowMassIndex> element;
+        VolumeVariables<freeFlowMassIndex> volVars;
+        FVElementGeometry<freeFlowMassIndex> fvGeometry;
+        std::size_t dofIdx;
+        std::size_t porousMediumScvfIdx;
+        std::size_t freeFlowMassScvfIdx;
+        mutable VelocityVector velocity; // velocity needs to be set externally, not availabe in this class
+    };
+
+    using CouplingMapper = DarcyDarcyBoundaryCouplingMapper<MDTraits>; // TODO rename/generalize class
+
+public:
+
+    /*!
+     * \brief Methods to be accessed by main
+     */
+    // \{
+
+    //! Initialize the coupling manager
+    void init(std::shared_ptr<Problem<freeFlowMassIndex>> freeFlowMassProblem,
+              std::shared_ptr<Problem<porousMediumIndex>> darcyProblem,
+              SolutionVectorStorage& curSol)
+    {
+        this->setSubProblems(std::make_tuple(freeFlowMassProblem, darcyProblem));
+        this->attachSolution(curSol);
+
+        couplingMapper_.update(*this);
+    }
+
+    // \}
+
+    /*!
+     * \brief Methods to be accessed by the assembly
+     */
+    // \{
+
+    /*!
+     * \brief prepares all data and variables that are necessary to evaluate the residual (called from the local assembler)
+     */
+    template<std::size_t i, class Assembler>
+    void bindCouplingContext(Dune::index_constant<i> domainI, const Element<i>& element, const Assembler& assembler) const
+    {
+        bindCouplingContext_(domainI, element);
+    }
+
+    /*!
+     * \brief Update the coupling context
+     */
+    template<std::size_t i, std::size_t j, class LocalAssemblerI>
+    void updateCouplingContext(Dune::index_constant<i> domainI,
+                               const LocalAssemblerI& localAssemblerI,
+                               Dune::index_constant<j> domainJ,
+                               std::size_t dofIdxGlobalJ,
+                               const PrimaryVariables<j>& priVarsJ,
+                               int pvIdxJ)
+    {
+        this->curSol(domainJ)[dofIdxGlobalJ][pvIdxJ] = priVarsJ[pvIdxJ];
+
+        // we need to update all solution-depenent components of the coupling context
+        // the dof of domain J has been deflected
+        // if domainJ == freeFlowMassIndex: update volvars in the PorousMediumCouplingContext
+        // if domainJ == porousMediumIndex: update volvars in the FreeFlowMassCouplingContext
+        // as the update is symmetric we only need to write this once
+        auto& context = std::get<1-j>(couplingContext_);
+        for (auto& c : context)
+        {
+            if (c.dofIdx == dofIdxGlobalJ)
+            {
+                const auto elemSol = elementSolution(c.element, this->curSol(domainJ), this->problem(domainJ).gridGeometry());
+                const auto& scv = *scvs(c.fvGeometry).begin();
+                c.volVars.update(elemSol, this->problem(domainJ), c.element, scv);
+            }
+        }
+    }
+
+    // \}
+
+    /*!
+     * \brief Access the coupling context needed for the Stokes domain
+     */
+    template<std::size_t i>
+    const auto& couplingContext(Dune::index_constant<i> domainI,
+                                const FVElementGeometry<i>& fvGeometry,
+                                const SubControlVolumeFace<i> scvf) const
+    {
+        auto& contexts = std::get<i>(couplingContext_);
+
+        if (contexts.empty() || couplingContextBoundForElement_[i] != scvf.insideScvIdx())
+            bindCouplingContext_(domainI, fvGeometry);
+
+        for (const auto& context : contexts)
+        {
+            const auto expectedScvfIdx = domainI == freeFlowMassIndex ? context.freeFlowMassScvfIdx : context.porousMediumScvfIdx;
+            if (scvf.index() == expectedScvfIdx)
+                return context;
+        }
+
+        DUNE_THROW(Dune::InvalidStateException, "No coupling context found at scvf " << scvf.center());
+    }
+
+    /*!
+     * \brief The coupling stencils
+     */
+    // \{
+
+    /*!
+     * \brief The Stokes cell center coupling stencil w.r.t. Darcy DOFs
+     */
+    template<std::size_t i, std::size_t j>
+    const CouplingStencil& couplingStencil(Dune::index_constant<i> domainI,
+                                           const Element<i>& element,
+                                           Dune::index_constant<j> domainJ) const
+    {
+        const auto eIdx = this->problem(domainI).gridGeometry().elementMapper().index(element);
+        return couplingMapper_.couplingStencil(domainI, eIdx, domainJ);
+    }
+
+    // \}
+
+    /*!
+     * \brief Returns whether a given scvf is coupled to the other domain
+     */
+    template<std::size_t i>
+    bool isCoupled(Dune::index_constant<i> domainI, const SubControlVolumeFace<i>& scvf) const
+    { return couplingMapper_.isCoupled(domainI, scvf); }
+
+private:
+    /*!
+     * \brief Returns whether a given scvf is coupled to the other domain
+     */
+    template<std::size_t i>
+    bool isCoupledElement_(Dune::index_constant<i> domainI, std::size_t eIdx) const
+    { return couplingMapper_.isCoupledElement(domainI, eIdx); }
+
+    //! Return the volume variables of domain i for a given element and scv
+    template<std::size_t i>
+    VolumeVariables<i> volVars_(Dune::index_constant<i> domainI,
+                                const Element<i>& element,
+                                const SubControlVolume<i>& scv) const
+    {
+        VolumeVariables<i> volVars;
+        const auto elemSol = elementSolution(
+            element, this->curSol(domainI), this->problem(domainI).gridGeometry()
+        );
+        volVars.update(elemSol, this->problem(domainI), element, scv);
+        return volVars;
+    }
+
+    /*!
+     * \brief prepares all data and variables that are necessary to evaluate the residual of an Darcy element (i.e. Stokes information)
+     */
+    template<std::size_t i>
+    void bindCouplingContext_(Dune::index_constant<i> domainI, const Element<i>& element) const
+    {
+        const auto fvGeometry = localView(this->problem(domainI).gridGeometry()).bindElement(element);
+        bindCouplingContext_(domainI, fvGeometry);
+    }
+
+    /*!
+     * \brief prepares all data and variables that are necessary to evaluate the residual of an Darcy element (i.e. Stokes information)
+     */
+    template<std::size_t i>
+    void bindCouplingContext_(Dune::index_constant<i> domainI, const FVElementGeometry<i>& fvGeometry) const
+    {
+        auto& context = std::get<domainI>(couplingContext_);
+        context.clear();
+
+        const auto eIdx = this->problem(domainI).gridGeometry().elementMapper().index(fvGeometry.element());
+
+        // do nothing if the element is not coupled to the other domain
+        if (!isCoupledElement_(domainI, eIdx))
+            return;
+
+        couplingContextBoundForElement_[domainI] = eIdx;
+
+        for (const auto& scvf : scvfs(fvGeometry))
+        {
+            if (isCoupled(domainI, scvf))
+            {
+                const auto otherElementIdx = couplingMapper_.outsideElementIndex(domainI, scvf);
+                constexpr auto domainJ = Dune::index_constant<1-domainI>();
+                const auto& otherGridGeometry = this->problem(domainJ).gridGeometry();
+                const auto& otherElement = otherGridGeometry.element(otherElementIdx);
+                auto otherFvGeometry = localView(otherGridGeometry).bindElement(otherElement);
+
+                // there is only one scv for TPFA
+                context.push_back({
+                    otherElement,
+                    volVars_(domainJ, otherElement, *std::begin(scvs(otherFvGeometry))),
+                    std::move(otherFvGeometry),
+                    otherElementIdx,
+                    scvf.index(),
+                    couplingMapper_.flipScvfIndex(domainI, scvf),
+                    VelocityVector{}
+                });
+            }
+        }
+    }
+
+    mutable std::tuple<std::vector<FreeFlowMassCouplingContext>, std::vector<PorousMediumCouplingContext>> couplingContext_;
+    mutable std::array<std::size_t, 2> couplingContextBoundForElement_;
+
+    CouplingMapper couplingMapper_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/CMakeLists.txt b/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b79ca4a8b025f3fdad01e0495885236acbc424a0
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/CMakeLists.txt
@@ -0,0 +1,3 @@
+file(GLOB DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOWPOROUSMEDIUM_FFMOMENTUMPM_HEADERS *.hh *.inc)
+install(FILES ${DUMUX_MULTIDOMAIN_BOUNDARY_FREEFLOWPOROUSMEDIUM_FFMOMENTUMPM_HEADERS}
+        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm)
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/couplingmanager.hh b/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/couplingmanager.hh
new file mode 100644
index 0000000000000000000000000000000000000000..f957e9f65f15d9c92dac5e867681b8f63f7d14f3
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/couplingmanager.hh
@@ -0,0 +1,422 @@
+// -*- 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 3 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 FreeFlowPorousMediumCoupling
+ * \copydoc Dumux::FreeFlowMomentumPorousMediumCouplingManager
+ */
+
+#ifndef DUMUX_MULTIDOMAIN_BOUNDARY_FFPM_FFMMOMENTUMPM_COUPLINGMANAGER_HH
+#define DUMUX_MULTIDOMAIN_BOUNDARY_FFPM_FFMMOMENTUMPM_COUPLINGMANAGER_HH
+
+#include <utility>
+#include <memory>
+
+#include <dune/common/float_cmp.hh>
+#include <dune/common/exceptions.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/discretization/staggered/elementsolution.hh>
+#include <dumux/multidomain/couplingmanager.hh>
+
+#include "couplingmapper.hh"
+
+namespace Dumux {
+
+/*!
+ * \ingroup StokesDarcyCoupling
+ * \brief Coupling manager for Stokes and Darcy domains with equal dimension.
+ */
+template<class MDTraits>
+class FreeFlowMomentumPorousMediumCouplingManager
+: public CouplingManager<MDTraits>
+{
+    using Scalar = typename MDTraits::Scalar;
+    using ParentType = CouplingManager<MDTraits>;
+
+public:
+    static constexpr auto freeFlowMomentumIndex = typename MDTraits::template SubDomain<0>::Index();
+    static constexpr auto porousMediumIndex = typename MDTraits::template SubDomain<1>::Index();
+
+    using SolutionVectorStorage = typename ParentType::SolutionVectorStorage;
+private:
+    // obtain the type tags of the sub problems
+    using FreeFlowMomentumTypeTag = typename MDTraits::template SubDomain<freeFlowMomentumIndex>::TypeTag;
+    using PorousMediumTypeTag = typename MDTraits::template SubDomain<porousMediumIndex>::TypeTag;
+
+    using CouplingStencils = std::unordered_map<std::size_t, std::vector<std::size_t> >;
+    using CouplingStencil = CouplingStencils::mapped_type;
+
+    // the sub domain type tags
+    template<std::size_t id>
+    using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+
+    template<std::size_t id> using GridView = typename GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>::GridView;
+    template<std::size_t id> using Problem = GetPropType<SubDomainTypeTag<id>, Properties::Problem>;
+    template<std::size_t id> using ElementVolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::LocalView;
+    template<std::size_t id> using GridVolumeVariables = GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>;
+    template<std::size_t id> using VolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::VolumeVariables;
+    template<std::size_t id> using GridGeometry = GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>;
+    template<std::size_t id> using FVElementGeometry = typename GridGeometry<id>::LocalView;
+    template<std::size_t id> using GridVariables = GetPropType<SubDomainTypeTag<id>, Properties::GridVariables>;
+    template<std::size_t id> using Element = typename GridView<id>::template Codim<0>::Entity;
+    template<std::size_t id> using PrimaryVariables = GetPropType<SubDomainTypeTag<id>, Properties::PrimaryVariables>;
+    template<std::size_t id> using SubControlVolumeFace = typename FVElementGeometry<id>::SubControlVolumeFace;
+    template<std::size_t id> using SubControlVolume = typename FVElementGeometry<id>::SubControlVolume;
+
+    using VelocityVector = typename Element<freeFlowMomentumIndex>::Geometry::GlobalCoordinate;
+
+    struct FreeFlowMomentumCouplingContext
+    {
+        Element<porousMediumIndex> element;
+        VolumeVariables<porousMediumIndex> volVars;
+        FVElementGeometry<porousMediumIndex> fvGeometry;
+        std::size_t freeFlowMomentumScvfIdx;
+        std::size_t porousMediumScvfIdx;
+        std::size_t porousMediumDofIdx;
+        VelocityVector gravity;
+    };
+
+    struct PorousMediumCouplingContext
+    {
+        Element<freeFlowMomentumIndex> element;
+        FVElementGeometry<freeFlowMomentumIndex> fvGeometry;
+        std::size_t porousMediumScvfIdx;
+        std::size_t freeFlowMomentumScvfIdx;
+        std::size_t freeFlowMomentumDofIdx;
+        VelocityVector faceVelocity;
+    };
+
+    using CouplingMapper = FreeFlowMomentumPorousMediumCouplingMapper<MDTraits, FreeFlowMomentumPorousMediumCouplingManager<MDTraits>>;
+
+public:
+
+    /*!
+     * \brief Methods to be accessed by main
+     */
+    // \{
+
+    //! Initialize the coupling manager
+    void init(std::shared_ptr<Problem<freeFlowMomentumIndex>> freeFlowMomentumProblem,
+              std::shared_ptr<Problem<porousMediumIndex>> porousMediumProblem,
+              SolutionVectorStorage& curSol)
+    {
+        this->setSubProblems(std::make_tuple(freeFlowMomentumProblem, porousMediumProblem));
+        this->attachSolution(curSol);
+
+        couplingMapper_.update(*this);
+    }
+
+    // \}
+
+
+    /*!
+     * \brief Methods to be accessed by the assembly
+     */
+    // \{
+
+    /*!
+     * \brief prepares all data and variables that are necessary to evaluate the residual (called from the local assembler)
+     */
+    template<std::size_t i, class Assembler>
+    void bindCouplingContext(Dune::index_constant<i> domainI, const Element<i>& element, const Assembler& assembler) const
+    {
+        bindCouplingContext_(domainI, element);
+    }
+
+    /*!
+     * \brief Update the coupling context
+     */
+    template<std::size_t i, std::size_t j, class LocalAssemblerI>
+    void updateCouplingContext(Dune::index_constant<i> domainI,
+                               const LocalAssemblerI& localAssemblerI,
+                               Dune::index_constant<j> domainJ,
+                               std::size_t dofIdxGlobalJ,
+                               const PrimaryVariables<j>& priVarsJ,
+                               int pvIdxJ)
+    {
+        this->curSol(domainJ)[dofIdxGlobalJ][pvIdxJ] = priVarsJ[pvIdxJ];
+
+        // we need to update all solution-depenent components of the coupling context
+        // the dof of domain J has been deflected
+
+        // update the faceVelocity in the PorousMediumCouplingContext
+        if constexpr (domainJ == freeFlowMomentumIndex)
+        {
+            // we only need to update if we are assembling the porous medium domain
+            // since the freeflow domain will not use the velocity from the context
+            if constexpr (domainI == porousMediumIndex)
+            {
+                auto& context = std::get<porousMediumIndex>(couplingContext_);
+                for (auto& c : context)
+                {
+                    if (c.freeFlowMomentumDofIdx == dofIdxGlobalJ)
+                    {
+                        const auto& scvf = localAssemblerI.fvGeometry().scvf(c.porousMediumScvfIdx);
+                        c.faceVelocity = faceVelocity(localAssemblerI.element(), scvf);
+                    }
+                }
+            }
+        }
+
+        // update volVars in the FreeFlowMomentumCouplingContext
+        else if (domainJ == porousMediumIndex)
+        {
+            auto& context = std::get<freeFlowMomentumIndex>(couplingContext_);
+            for (auto& c : context)
+            {
+                if (c.porousMediumDofIdx == dofIdxGlobalJ)
+                {
+                    const auto& ggJ = c.fvGeometry.gridGeometry();
+                    const auto& scv = *scvs(c.fvGeometry).begin();
+                    const auto elemSol = elementSolution(c.element, this->curSol(domainJ), ggJ);
+                    c.volVars.update(elemSol, this->problem(domainJ), c.element, scv);
+                }
+            }
+        }
+    }
+
+    // \}
+
+    /*!
+     * \brief Access the coupling context needed for the Stokes domain
+     */
+    template<std::size_t i>
+    const auto& couplingContext(Dune::index_constant<i> domainI,
+                                const FVElementGeometry<i>& fvGeometry,
+                                const SubControlVolumeFace<i> scvf) const
+    {
+        auto& contexts = std::get<i>(couplingContext_);
+
+        if (contexts.empty() || couplingContextBoundForElement_[i] != scvf.insideScvIdx())
+            bindCouplingContext_(domainI, fvGeometry);
+
+        for (const auto& context : contexts)
+        {
+            const auto expectedScvfIdx = domainI == freeFlowMomentumIndex ? context.freeFlowMomentumScvfIdx : context.porousMediumScvfIdx;
+            if (scvf.index() == expectedScvfIdx)
+                return context;
+        }
+
+        DUNE_THROW(Dune::InvalidStateException, "No coupling context found at scvf " << scvf.center());
+    }
+
+    /*!
+     * \brief The coupling stencils
+     */
+    // \{
+
+    /*!
+     * \brief The Stokes cell center coupling stencil w.r.t. Darcy DOFs
+     */
+    const CouplingStencil& couplingStencil(Dune::index_constant<porousMediumIndex> domainI,
+                                           const Element<porousMediumIndex>& element,
+                                           Dune::index_constant<freeFlowMomentumIndex> domainJ) const
+    {
+        const auto eIdx = this->problem(domainI).gridGeometry().elementMapper().index(element);
+        return couplingMapper_.couplingStencil(domainI, eIdx, domainJ);
+    }
+
+    /*!
+     * \brief returns an iteratable container of all indices of degrees of freedom of domain j
+     *        that couple with / influence the residual of the given sub-control volume of domain i
+     *
+     * \param domainI the domain index of domain i
+     * \param elementI the coupled element of domain í
+     * \param scvI the sub-control volume of domain i
+     * \param domainJ the domain index of domain j
+     */
+    const CouplingStencil& couplingStencil(Dune::index_constant<freeFlowMomentumIndex> domainI,
+                                           const Element<freeFlowMomentumIndex>& elementI,
+                                           const SubControlVolume<freeFlowMomentumIndex>& scvI,
+                                           Dune::index_constant<porousMediumIndex> domainJ) const
+    {
+        return couplingMapper_.couplingStencil(domainI, elementI, scvI, domainJ);
+    }
+
+    using ParentType::evalCouplingResidual;
+
+    /*!
+     * \brief evaluate the coupling residual
+     * special interface for fcstaggered methods
+     */
+    template<class LocalAssemblerI, std::size_t j>
+    decltype(auto) evalCouplingResidual(Dune::index_constant<freeFlowMomentumIndex> domainI,
+                                        const LocalAssemblerI& localAssemblerI,
+                                        const SubControlVolume<freeFlowMomentumIndex>& scvI,
+                                        Dune::index_constant<j> domainJ,
+                                        std::size_t dofIdxGlobalJ) const
+    {
+        return localAssemblerI.evalLocalResidual();
+    }
+
+    // \}
+
+    /*!
+     * \brief Returns whether a given scvf is coupled to the other domain
+     */
+    bool isCoupledLateralScvf(Dune::index_constant<freeFlowMomentumIndex> domainI, const SubControlVolumeFace<freeFlowMomentumIndex>& scvf) const
+    { return  couplingMapper_.isCoupledLateralScvf(domainI, scvf); }
+
+    /*!
+     * \brief Returns whether a given scvf is coupled to the other domain
+     */
+    template<std::size_t i>
+    bool isCoupled(Dune::index_constant<i> domainI, const SubControlVolumeFace<i>& scvf) const
+    {
+        if constexpr (i == freeFlowMomentumIndex)
+            return couplingMapper_.isCoupled(domainI, scvf) || couplingMapper_.isCoupledLateralScvf(domainI, scvf);
+        else
+            return couplingMapper_.isCoupled(domainI, scvf);
+    }
+
+    /*!
+     * \brief If the boundary entity is on a coupling boundary
+     * \param domainI the domain index for which to compute the flux
+     * \param scv the sub control volume
+     */
+    bool isCoupled(Dune::index_constant<freeFlowMomentumIndex> domainI,
+                   const SubControlVolume<freeFlowMomentumIndex>& scv) const
+    { return couplingMapper_.isCoupled(domainI, scv); }
+
+    /*!
+     * \brief Returns the velocity at a given sub control volume face.
+     */
+    auto faceVelocity(const Element<porousMediumIndex>& element,
+                      const SubControlVolumeFace<porousMediumIndex>& scvf) const
+    {
+        // create a unit normal vector oriented in positive coordinate direction
+        auto velocity = scvf.unitOuterNormal();
+        using std::abs;
+        std::for_each(velocity.begin(), velocity.end(), [](auto& v){ v = abs(v); });
+
+        // create the actual velocity vector
+        velocity *= this->curSol(freeFlowMomentumIndex)[
+            couplingMapper_.outsideDofIndex(Dune::index_constant<porousMediumIndex>(), scvf)
+        ];
+
+        return velocity;
+    }
+
+private:
+    //! Return the volume variables of domain i for a given element and scv
+    template<std::size_t i>
+    VolumeVariables<i> volVars_(Dune::index_constant<i> domainI,
+                                const Element<i>& element,
+                                const SubControlVolume<i>& scv) const
+    {
+        VolumeVariables<i> volVars;
+        const auto elemSol = elementSolution(
+            element, this->curSol(domainI), this->problem(domainI).gridGeometry()
+        );
+        volVars.update(elemSol, this->problem(domainI), element, scv);
+        return volVars;
+    }
+
+    /*!
+     * \brief Returns whether a given scvf is coupled to the other domain
+     */
+    template<std::size_t i>
+    bool isCoupledElement_(Dune::index_constant<i> domainI, std::size_t eIdx) const
+    { return couplingMapper_.isCoupledElement(domainI, eIdx); }
+
+    /*!
+     * \brief prepares all data and variables that are necessary to evaluate the residual of an Darcy element (i.e. Stokes information)
+     */
+    template<std::size_t i>
+    void bindCouplingContext_(Dune::index_constant<i> domainI, const Element<i>& element) const
+    {
+        const auto fvGeometry = localView(this->problem(domainI).gridGeometry()).bindElement(element);
+        bindCouplingContext_(domainI, fvGeometry);
+    }
+
+    /*!
+     * \brief prepares all data and variables that are necessary to evaluate the residual of an Darcy element (i.e. Stokes information)
+     */
+    template<std::size_t i>
+    void bindCouplingContext_(Dune::index_constant<i> domainI, const FVElementGeometry<i>& fvGeometry) const
+    {
+        auto& context = std::get<domainI>(couplingContext_);
+        context.clear();
+
+        const auto eIdx = this->problem(domainI).gridGeometry().elementMapper().index(fvGeometry.element());
+
+        // do nothing if the element is not coupled to the other domain
+        if (!isCoupledElement_(domainI, eIdx))
+            return;
+
+        couplingContextBoundForElement_[domainI] = eIdx;
+
+        for (const auto& scvf : scvfs(fvGeometry))
+        {
+            if (couplingMapper_.isCoupled(domainI, scvf))
+            {
+                if constexpr (domainI == freeFlowMomentumIndex)
+                {
+                    const auto otherElementIdx = couplingMapper_.outsideElementIndex(domainI, scvf);
+                    constexpr auto domainJ = Dune::index_constant<1-i>();
+                    const auto& otherGridGeometry = this->problem(domainJ).gridGeometry();
+                    const auto& otherElement = otherGridGeometry.element(otherElementIdx);
+                    auto otherFvGeometry = localView(otherGridGeometry).bindElement(otherElement);
+
+                    // there is only one scv for TPFA
+                    context.push_back({
+                        otherElement,
+                        volVars_(domainJ, otherElement, *std::begin(scvs(otherFvGeometry))),
+                        std::move(otherFvGeometry),
+                        scvf.index(),
+                        couplingMapper_.flipScvfIndex(domainI, scvf),
+                        otherElementIdx,
+                        this->problem(domainJ).spatialParams().gravity(scvf.center())
+                    });
+                }
+
+                else if constexpr (domainI == porousMediumIndex)
+                {
+                    const auto otherElementIdx = couplingMapper_.outsideElementIndex(domainI, scvf);
+                    constexpr auto domainJ = Dune::index_constant<1-i>();
+                    const auto& otherGridGeometry = this->problem(domainJ).gridGeometry();
+                    const auto& otherElement = otherGridGeometry.element(otherElementIdx);
+                    auto otherFvGeometry = localView(otherGridGeometry).bindElement(otherElement);
+
+                    const auto otherScvfIdx = couplingMapper_.flipScvfIndex(domainI, scvf);
+                    const auto& otherScvf = otherFvGeometry.scvf(otherScvfIdx);
+                    const auto& otherScv = otherFvGeometry.scv(otherScvf.insideScvIdx());
+
+                    context.push_back({
+                        otherElement,
+                        std::move(otherFvGeometry),
+                        scvf.index(),
+                        otherScvfIdx,
+                        otherScv.dofIndex(),
+                        faceVelocity(fvGeometry.element(), scvf)
+                    });
+                }
+            }
+        }
+    }
+
+    mutable std::tuple<std::vector<FreeFlowMomentumCouplingContext>, std::vector<PorousMediumCouplingContext>> couplingContext_;
+    mutable std::array<std::size_t, 2> couplingContextBoundForElement_;
+
+    CouplingMapper couplingMapper_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/couplingmapper.hh b/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/couplingmapper.hh
new file mode 100644
index 0000000000000000000000000000000000000000..59f15ea1385dbc547c749a1c8bb77f76b9d6c162
--- /dev/null
+++ b/dumux/multidomain/boundary/freeflowporousmedium/ffmomentumpm/couplingmapper.hh
@@ -0,0 +1,347 @@
+// -*- 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 3 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 DarcyDarcyCoupling
+ * \copydoc Dumux::FreeFlowMomentumPorousMediumCouplingMapper
+ */
+
+#ifndef DUMUX_MULTIDOMAIN_FREEFLOWMOMENTUM_POROUSMEDIUM_COUPLINGMAPPER_HH
+#define DUMUX_MULTIDOMAIN_FREEFLOWMOMENTUM_POROUSMEDIUM_COUPLINGMAPPER_HH
+
+#include <iostream>
+#include <unordered_map>
+#include <tuple>
+#include <vector>
+
+#include <dune/common/timer.hh>
+#include <dune/common/exceptions.hh>
+#include <dune/common/indices.hh>
+
+#include <dumux/geometry/intersectingentities.hh>
+#include <dumux/discretization/method.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup DarcyDarcyCoupling
+ * \brief the default mapper for conforming equal dimension boundary coupling between two domains (box or cc)
+ * \todo how to extend to arbitrary number of domains?
+ */
+template<class MDTraits, class CouplingManager>
+class FreeFlowMomentumPorousMediumCouplingMapper
+{
+    using Scalar = typename MDTraits::Scalar;
+
+    template<std::size_t i> using GridGeometry = typename MDTraits::template SubDomain<i>::GridGeometry;
+    template<std::size_t i> using SubControlVolume = typename GridGeometry<i>::SubControlVolume;
+    template<std::size_t i> using SubControlVolumeFace = typename GridGeometry<i>::SubControlVolumeFace;
+    template<std::size_t i> using GridView = typename GridGeometry<i>::GridView;
+    template<std::size_t i> using Element = typename GridView<i>::template Codim<0>::Entity;
+
+    template<std::size_t i>
+    static constexpr auto domainIdx()
+    { return typename MDTraits::template SubDomain<i>::Index{}; }
+
+    template<std::size_t i>
+    static constexpr bool isFcStaggered()
+    { return GridGeometry<i>::discMethod == DiscretizationMethod::fcstaggered; }
+
+    template<std::size_t i>
+    static constexpr bool isCCTpfa()
+    { return GridGeometry<i>::discMethod == DiscretizationMethod::cctpfa; }
+
+    struct ScvfInfoPM
+    {
+        std::size_t eIdxOutside;
+        std::size_t flipScvfIdx;
+    };
+
+    struct ScvfInfoFF
+    {
+        std::size_t eIdxOutside;
+        std::size_t flipScvfIdx;
+        std::size_t dofIdxOutside;
+    };
+
+    using FlipScvfMapTypePM = std::unordered_map<std::size_t, ScvfInfoPM>;
+    using FlipScvfMapTypeFF = std::unordered_map<std::size_t, ScvfInfoFF>;
+    using MapType = std::unordered_map<std::size_t, std::vector<std::size_t>>;
+
+    static constexpr std::size_t numSD = MDTraits::numSubDomains;
+
+public:
+    /*!
+     * \brief Main update routine
+     */
+    void update(const CouplingManager& couplingManager)
+    {
+        // TODO: Box and multiple domains
+        static_assert(numSD == 2, "More than two subdomains not implemented!");
+        static_assert(isFcStaggered<0>() && isCCTpfa<1>(), "Only coupling between fcstaggered and cctpfa implemented!");
+
+        Dune::Timer watch;
+        std::cout << "Initializing the coupling map..." << std::endl;
+
+        for (std::size_t domIdx = 0; domIdx < numSD; ++domIdx)
+            stencils_[domIdx].clear();
+
+        std::get<CouplingManager::freeFlowMomentumIndex>(scvfInfo_).clear();
+        std::get<CouplingManager::porousMediumIndex>(scvfInfo_).clear();
+
+        const auto& freeFlowMomentumProblem = couplingManager.problem(CouplingManager::freeFlowMomentumIndex);
+        const auto& porousMediumProblem = couplingManager.problem(CouplingManager::porousMediumIndex);
+        const auto& freeFlowMomentumGG = freeFlowMomentumProblem.gridGeometry();
+        const auto& porousMediumGG = porousMediumProblem.gridGeometry();
+
+        isCoupledFFDof_.resize(freeFlowMomentumGG.numScvf(), false);
+        isCoupledFFElement_.resize(freeFlowMomentumGG.gridView().size(0), false);
+        isCoupledScvf_[CouplingManager::freeFlowMomentumIndex].resize(freeFlowMomentumGG.numScvf(), false);
+        isCoupledScvf_[CouplingManager::porousMediumIndex].resize(porousMediumGG.numScvf(), false);
+
+        auto pmFvGeometry = localView(porousMediumGG);
+        auto ffFvGeometry = localView(freeFlowMomentumGG);
+
+        for (const auto& pmElement : elements(porousMediumGG.gridView()))
+        {
+            pmFvGeometry.bindElement(pmElement);
+
+            for (const auto& pmScvf : scvfs(pmFvGeometry))
+            {
+                // skip all non-boundaries
+                if (!pmScvf.boundary())
+                    continue;
+
+                // get elements intersecting with the scvf center
+                // for robustness add epsilon in unit outer normal direction
+                const auto eps = (pmScvf.ipGlobal() - pmScvf.geometry().corner(0)).two_norm()*1e-8;
+                auto globalPos = pmScvf.ipGlobal(); globalPos.axpy(eps, pmScvf.unitOuterNormal());
+                const auto indices = intersectingEntities(globalPos, freeFlowMomentumGG.boundingBoxTree());
+
+                // skip if no intersection was found
+                if (indices.empty())
+                    continue;
+
+                // sanity check
+                if (indices.size() > 1)
+                    DUNE_THROW(Dune::InvalidStateException, "Are you sure your sub-domain grids are conformingly discretized on the common interface?");
+
+                // add the pair to the multimap
+                const auto pmElemIdx = porousMediumGG.elementMapper().index(pmElement);
+                const auto ffElemIdx = indices[0];
+                const auto& ffElement = freeFlowMomentumGG.element(ffElemIdx);
+                ffFvGeometry.bindElement(ffElement);
+
+                for (const auto& ffScvf : scvfs(ffFvGeometry)) // TODO: maybe better iterate over all scvs
+                {
+                    // TODO this only takes the scv directly at the interface, maybe extend
+                    if (!ffScvf.boundary() || !ffScvf.isFrontal())
+                        continue;
+
+                    const auto dist = (pmScvf.ipGlobal() - ffScvf.ipGlobal()).two_norm();
+                    if (dist > eps)
+                        continue;
+
+                    const auto& ffScv = ffFvGeometry.scv(ffScvf.insideScvIdx());
+                    stencils_[CouplingManager::porousMediumIndex][pmElemIdx].push_back(ffScv.dofIndex());
+                    stencils_[CouplingManager::freeFlowMomentumIndex][ffScv.dofIndex()].push_back(pmElemIdx);
+
+                    // mark the scvf and find and mark the flip scvf
+                    isCoupledScvf_[CouplingManager::porousMediumIndex][pmScvf.index()] = true;
+                    isCoupledScvf_[CouplingManager::freeFlowMomentumIndex][ffScvf.index()] = true;
+
+                    // add all lateral free-flow scvfs touching the coupling interface ("standing" or "lying")
+                    for (const auto& otherFfScvf : scvfs(ffFvGeometry, ffScv))
+                    {
+                        if (otherFfScvf.isLateral())
+                        {
+                            // the orthogonal scvf has to lie on the coupling interface
+                            const auto& lateralOrthogonalScvf = ffFvGeometry.lateralOrthogonalScvf(otherFfScvf);
+                            isCoupledLateralScvf_[lateralOrthogonalScvf.index()] = true;
+
+                            // the "standing" scvf is marked as coupled if it does not lie on a boundary or if that boundary itself is a coupling interface
+                            if (!otherFfScvf.boundary())
+                                isCoupledLateralScvf_[otherFfScvf.index()] = true;
+                            else
+                            {
+                                // for robustness add epsilon in unit outer normal direction
+                                const auto otherScvfEps = (otherFfScvf.ipGlobal() - otherFfScvf.geometry().corner(0)).two_norm()*1e-8;
+                                auto otherScvfGlobalPos = otherFfScvf.center(); otherScvfGlobalPos.axpy(otherScvfEps, otherFfScvf.unitOuterNormal());
+
+                                if (!intersectingEntities(otherScvfGlobalPos, porousMediumGG.boundingBoxTree()).empty())
+                                    isCoupledLateralScvf_[otherFfScvf.index()] = true;
+                            }
+                        }
+                    }
+                    isCoupledFFDof_[ffScv.dofIndex()] = true;
+                    isCoupledFFElement_[ffElemIdx] = true;
+
+                    std::get<CouplingManager::porousMediumIndex>(scvfInfo_)[pmScvf.index()] = ScvfInfoPM{ffElemIdx, ffScvf.index()};
+                    std::get<CouplingManager::freeFlowMomentumIndex>(scvfInfo_)[ffScvf.index()] = ScvfInfoFF{pmElemIdx, pmScvf.index(), ffScv.dofIndex()};
+                }
+            }
+        }
+
+        std::cout << "took " << watch.elapsed() << " seconds." << std::endl;
+    }
+
+    /*!
+     * \brief returns an iteratable container of all indices of degrees of freedom of domain j
+     *        that couple with / influence the element residual of the given element of domain i
+     *
+     * \param domainI the domain index of domain i
+     * \param eIdxI the index of the coupled element of domain í
+     * \param domainJ the domain index of domain j
+     *
+     * \note  The element residual definition depends on the discretization scheme of domain i
+     *        box: a container of the residuals of all sub control volumes
+     *        cc : the residual of the (sub) control volume
+     *        fem: the residual of the element
+     * \note  This function has to be implemented by all coupling managers for all combinations of i and j
+     */
+    const std::vector<std::size_t>& couplingStencil(Dune::index_constant<CouplingManager::porousMediumIndex> domainI,
+                                                    const std::size_t eIdxI,
+                                                    Dune::index_constant<CouplingManager::freeFlowMomentumIndex> domainJ) const
+    {
+        if (isCoupledElement(domainI, eIdxI))
+            return stencils_[CouplingManager::porousMediumIndex].at(eIdxI);
+        else
+            return emptyStencil_;
+    }
+
+    /*!
+     * \brief returns an iteratable container of all indices of degrees of freedom of domain j
+     *        that couple with / influence the element residual of the given element of domain i
+     *
+     * \param domainI the domain index of domain i
+     * \param eIdxI the index of the coupled element of domain í
+     * \param domainJ the domain index of domain j
+     *
+     * \note  The element residual definition depends on the discretization scheme of domain i
+     *        box: a container of the residuals of all sub control volumes
+     *        cc : the residual of the (sub) control volume
+     *        fem: the residual of the element
+     * \note  This function has to be implemented by all coupling managers for all combinations of i and j
+     */
+    const std::vector<std::size_t>& couplingStencil(Dune::index_constant<CouplingManager::freeFlowMomentumIndex> domainI,
+                                                    const Element<CouplingManager::freeFlowMomentumIndex>& elementI,
+                                                    const SubControlVolume<CouplingManager::freeFlowMomentumIndex>& scvI,
+                                                    Dune::index_constant<CouplingManager::porousMediumIndex> domainJ) const
+    {
+        if (isCoupled(domainI, scvI))
+            return stencils_[CouplingManager::freeFlowMomentumIndex].at(scvI.dofIndex());
+        else
+            return emptyStencil_;
+    }
+
+    /*!
+     * \brief Return if an element residual with index eIdx of domain i is coupled to domain j
+     */
+    template<std::size_t i>
+    bool isCoupledElement(Dune::index_constant<i>, std::size_t eIdx) const
+    {
+        if constexpr (i == CouplingManager::porousMediumIndex)
+            return static_cast<bool>(stencils_[i].count(eIdx));
+        else
+            return isCoupledFFElement_[eIdx];
+    }
+
+    /*!
+     * \brief If the boundary entity is on a coupling boundary
+     * \param domainI the domain index for which to compute the flux
+     * \param scvf the sub control volume face
+     */
+    template<std::size_t i>
+    bool isCoupled(Dune::index_constant<i> domainI,
+                   const SubControlVolumeFace<i>& scvf) const
+    {
+        return isCoupledScvf_[i].at(scvf.index());
+    }
+
+    /*!
+     * \brief If the boundary entity is on a coupling boundary
+     * \param domainI the domain index for which to compute the flux
+     * \param scvf the sub control volume face
+     */
+    bool isCoupledLateralScvf(Dune::index_constant<CouplingManager::freeFlowMomentumIndex> domainI,
+                              const SubControlVolumeFace<CouplingManager::freeFlowMomentumIndex>& scvf) const
+    { return isCoupledLateralScvf_.count(scvf.index()); }
+
+    /*!
+     * \brief If the boundary entity is on a coupling boundary
+     * \param domainI the domain index for which to compute the flux
+     * \param scv the sub control volume
+     */
+    bool isCoupled(Dune::index_constant<CouplingManager::freeFlowMomentumIndex> domainI,
+                   const SubControlVolume<CouplingManager::freeFlowMomentumIndex>& scv) const
+    { return isCoupledFFDof_[scv.dofIndex()]; }
+
+    /*!
+     * \brief Return the scvf index of the flipped scvf in the other domain
+     * \param domainI the domain index for which to compute the flux
+     * \param scvf the sub control volume face
+     */
+    template<std::size_t i>
+    std::size_t flipScvfIndex(Dune::index_constant<i> domainI,
+                              const SubControlVolumeFace<i>& scvf) const
+    {
+        return std::get<i>(scvfInfo_).at(scvf.index()).flipScvfIdx;
+    }
+
+    /*!
+     * \brief Return the outside element index (the element index of the other domain)
+     * \param domainI the domain index for which to compute the flux
+     * \param scvf the sub control volume face
+     */
+    template<std::size_t i>
+    std::size_t outsideElementIndex(Dune::index_constant<i> domainI,
+                                    const SubControlVolumeFace<i>& scvf) const
+    {
+        return std::get<i>(scvfInfo_).at(scvf.index()).eIdxOutside;
+    }
+
+    /*!
+     * \brief Return the outside element index (the element index of the other domain)
+     * \param domainI the domain index for which to compute the flux
+     * \param scvf the sub control volume face
+     */
+    template<std::size_t i>
+    std::size_t outsideDofIndex(Dune::index_constant<i> domainI,
+                                const SubControlVolumeFace<i>& scvf) const
+    {
+        if constexpr (i == CouplingManager::porousMediumIndex)
+            return outsideElementIndex(domainI, scvf);
+        else
+            return std::get<i>(scvfInfo_).at(scvf.index()).dofIdxOutside;
+    }
+
+private:
+    std::array<MapType, numSD> stencils_;
+    std::vector<std::size_t> emptyStencil_;
+    std::array<std::vector<bool>, numSD> isCoupledScvf_;
+    std::unordered_map<std::size_t, bool> isCoupledLateralScvf_;
+    std::vector<bool> isCoupledFFDof_;
+    std::vector<bool> isCoupledFFElement_;
+    std::tuple<FlipScvfMapTypeFF, FlipScvfMapTypePM> scvfInfo_;
+
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/multidomain/multibinarycouplingmanager.hh b/dumux/multidomain/multibinarycouplingmanager.hh
new file mode 100644
index 0000000000000000000000000000000000000000..cd335ffb5bc48361b169ae87d43f27e9512f2df7
--- /dev/null
+++ b/dumux/multidomain/multibinarycouplingmanager.hh
@@ -0,0 +1,397 @@
+// -*- 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 3 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 MultiDomain
+ * \copydoc Dumux::MultiBinaryCouplingManager
+ */
+
+#ifndef DUMUX_MULTIDOMAIN_MULTIBINARY_COUPLINGMANAGER_HH
+#define DUMUX_MULTIDOMAIN_MULTIBINARY_COUPLINGMANAGER_HH
+
+#include <utility>
+#include <memory>
+#include <dune/common/hybridutilities.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/multidomain/traits.hh>
+
+namespace Dumux {
+
+namespace Detail {
+
+template <std::size_t, typename Tuple>
+struct HasIndex;
+
+template <std::size_t i, typename... Indices>
+struct HasIndex<i, std::tuple<Indices...>>
+: std::disjunction<std::is_same<Dune::index_constant<i>, Indices>...>
+{};
+
+} // end namespace Detail
+
+/*!
+ * \ingroup MultiDomain
+ * \brief Coupling manager that combines an arbitrary number of binary coupling manager (coupling two domains each)
+ * \tparam MDTraits the multidomain traits
+ * \tparam CouplingMap a coupling policy class
+ * \tparam CouplingMgrs the binary sub-coupling manager types
+ *
+ * The coupling policy has to provide the interfaces
+ * - CouplingMap::coupledDomains(i): returns a tuple of Dune::index_constants with the coupled domains
+ * - CouplingMap::globalToLocal(i, j): maps the indices i, j to the local index pair of the responsible sub coupling manager
+ * - CouplingMap::managerMap(): returns a two-dimensional array mapping two indices to the coupling manager index
+ */
+template<class MDTraits, class CouplingMap, class ...CouplingMgrs>
+class MultiBinaryCouplingManager
+{
+    // the sub domain type tags
+    template<std::size_t id>
+    using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+
+    template<std::size_t id> using GridGeometry = GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>;
+    template<std::size_t id> using GridView = typename GridGeometry<id>::GridView;
+    template<std::size_t id> using Element = typename GridView<id>::template Codim<0>::Entity;
+    template<std::size_t id> using FVElementGeometry = typename GridGeometry<id>::LocalView;
+    template<std::size_t id> using SubControlVolumeFace = typename FVElementGeometry<id>::SubControlVolumeFace;
+    template<std::size_t id> using SubControlVolume = typename FVElementGeometry<id>::SubControlVolume;
+    using CouplingStencil = std::vector<std::size_t>;
+
+    template<std::size_t id>
+    using SubCouplingManagerT = typename std::tuple_element_t<id, std::tuple<CouplingMgrs...>>;
+
+    using CMIndices = std::make_index_sequence<sizeof...(CouplingMgrs)>;
+    using CouplingManagers = typename Detail::MultiDomainTupleSharedPtr<SubCouplingManagerT, CMIndices>::type;
+
+    template<std::size_t id>
+    using SubSolutionVector = std::decay_t<decltype(std::declval<typename MDTraits::SolutionVector>()[Dune::index_constant<id>()])>;
+    using SolutionVectors = typename MDTraits::template TupleOfSharedPtr<SubSolutionVector>;
+
+    static constexpr auto couplingManagerMap_ = CouplingMap::managerMap();
+
+    //! Returns the local domain indices used within the binary sub coupling managers.
+    template<std::size_t i, std::size_t j>
+    static constexpr auto globalToLocal_(Dune::index_constant<i> domainI, Dune::index_constant<j> domainJ)
+    {
+        static_assert(i <= MDTraits::numSubDomains && j <= MDTraits::numSubDomains);
+        return CouplingMap::globalToLocal(domainI, domainJ);
+    }
+
+    //! If two domain are coupled
+    template<class Map, std::size_t i, std::size_t j>
+    static constexpr bool isCoupled_(Dune::index_constant<i> domainI, Dune::index_constant<j>)
+    { return Detail::HasIndex<j, std::decay_t<decltype(Map::coupledDomains(domainI))>>::value; }
+
+    //! Returns the coupling manager index for a given domain combination
+    template<std::size_t i, std::size_t j>
+    static constexpr auto subCouplingManagerIndex_(Dune::index_constant<i> domainI, Dune::index_constant<j> domainJ)
+    {
+        static_assert(
+            isCoupled_<CouplingMap>(domainI, domainJ),
+            "Sub-coupling manager only exists for coupled domains."
+        );
+        return couplingManagerMap_[i][j];
+    }
+
+public:
+    template<std::size_t i, std::size_t j>
+    using SubCouplingManager = SubCouplingManagerT<couplingManagerMap_[i][j]>;
+
+    MultiBinaryCouplingManager()
+    {
+        using namespace Dune::Hybrid;
+        forEach(couplingManagers_, [&](auto&& couplingManager)
+        {
+            couplingManager = std::make_shared<typename std::decay_t<decltype(couplingManager)>::element_type>();
+        });
+
+        forEach(solutionVectors_, [&](auto&& solutionVector)
+        {
+            solutionVector = std::make_shared<typename std::decay_t<decltype(solutionVector)>::element_type>();
+        });
+    }
+
+    //! return the binary sub-coupling manager
+    template<std::size_t i, std::size_t j>
+    auto& subCouplingManager(Dune::index_constant<i> domainI,
+                             Dune::index_constant<j> domainJ)
+    {
+        constexpr auto idx = subCouplingManagerIndex_(domainI, domainJ);
+        return *std::get<idx>(couplingManagers_);
+    }
+
+    //! return the binary sub-coupling manager
+    template<std::size_t i, std::size_t j>
+    const auto& subCouplingManager(Dune::index_constant<i> domainI,
+                                   Dune::index_constant<j> domainJ) const
+    {
+        constexpr auto idx = subCouplingManagerIndex_(domainI, domainJ);
+        return *std::get<idx>(couplingManagers_);
+    }
+
+    //! apply a function to the domainI-domainJ sub coupling manager using its local indices
+    template<std::size_t i, std::size_t j, class Apply>
+    decltype(auto) subApply(Dune::index_constant<i> domainI,
+                            Dune::index_constant<j> domainJ,
+                            Apply&& apply)
+    {
+        constexpr auto localIndices = globalToLocal_(domainI, domainJ);
+        return apply(subCouplingManager(domainI, domainJ), localIndices.first, localIndices.second);
+    }
+
+    //! apply a function to the domainI-domainJ sub coupling manager using its local indices
+    template<std::size_t i, std::size_t j, class Apply>
+    decltype(auto) subApply(Dune::index_constant<i> domainI,
+                            Dune::index_constant<j> domainJ,
+                            const Apply& apply) const
+    {
+        constexpr auto localIndices = globalToLocal_(domainI, domainJ);
+        return apply(subCouplingManager(domainI, domainJ), localIndices.first, localIndices.second);
+    }
+
+    //! apply a function to a sub coupling manager containing this domain
+    template<std::size_t i, class Apply>
+    decltype(auto) subApply(Dune::index_constant<i> domainI, Apply&& apply)
+    {
+        constexpr auto dm = CouplingMap::coupledDomains(domainI);
+        static_assert(std::tuple_size_v<std::decay_t<decltype(dm)>> != 0, "subApply on uncoupled domain!");
+        constexpr auto domainJ = std::get<0>(dm);
+        constexpr auto localIndices = globalToLocal_(domainI, domainJ);
+        return apply(subCouplingManager(domainI, domainJ), localIndices.first);
+    }
+
+    //! apply a function to a sub coupling manager containing this domain
+    template<std::size_t i, class Apply>
+    decltype(auto) subApply(Dune::index_constant<i> domainI, const Apply& apply) const
+    {
+        constexpr auto dm = CouplingMap::coupledDomains(domainI);
+        static_assert(std::tuple_size_v<std::decay_t<decltype(dm)>> != 0, "subApply on uncoupled domain!");
+        constexpr auto domainJ = std::get<0>(dm);
+        constexpr auto localIndices = globalToLocal_(domainI, domainJ);
+        return apply(subCouplingManager(domainI, domainJ), localIndices.first);
+    }
+
+    //! Update the solution vector before assembly
+    void updateSolution(const typename MDTraits::SolutionVector& curSol)
+    {
+        using namespace Dune::Hybrid;
+        forEach(integralRange(Dune::Hybrid::size(solutionVectors_)), [&](const auto id)
+        {
+            *std::get<id>(solutionVectors_) = curSol[id];
+        });
+    }
+
+    /*!
+     * \brief extend the jacobian pattern of the diagonal block of domain i
+     *        by those entries that are not already in the uncoupled pattern
+     * \note per default we do not add such additional dependencies
+     * \note Such additional dependencies can arise from the coupling, e.g. if a coupling source
+     *       term depends on a non-local average of a quantity of the same domain
+     * \warning if you overload this also implement evalAdditionalDomainDerivatives
+     */
+    template<std::size_t id, class JacobianPattern>
+    void extendJacobianPattern(Dune::index_constant<id> domainI, JacobianPattern& pattern) const
+    {}
+
+    /*!
+     * \brief Return the coupling element stencil for a given bulk domain element
+     */
+    template<std::size_t i, class Entity, std::size_t j>
+    const auto& couplingStencil(Dune::index_constant<i> domainI,
+                                const Entity& entity,
+                                Dune::index_constant<j> domainJ) const
+    {
+        // if the domains are coupled according to the map, forward to sub-coupling manager
+        if constexpr (isCoupled_<CouplingMap>(domainI, domainJ))
+            return subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
+                return cm.couplingStencil(ii, entity, jj);
+            });
+        else
+            return emptyStencil_;
+    }
+
+    // ! evaluate coupling residual for the derivative low dim DOF with respect to bulk DOF
+    // ! we only need to evaluate the part of the residual that will be influence by the bulk DOF
+    template<std::size_t i, class LocalAssemblerI, std::size_t j>
+    decltype(auto) evalCouplingResidual(Dune::index_constant<i> domainI,
+                                        const SubControlVolumeFace<i>& scvfI,
+                                        const LocalAssemblerI& localAssemblerI,
+                                        Dune::index_constant<j> domainJ,
+                                        std::size_t dofIdxGlobalJ) const
+    {
+        if constexpr (i == j || isCoupled_<CouplingMap>(domainI, domainJ))
+            return subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> decltype(auto) {
+                return cm.evalCouplingResidual(ii, scvfI, localAssemblerI, jj, dofIdxGlobalJ);
+            });
+        else
+        {
+            DUNE_THROW(Dune::InvalidStateException,
+                "Calling evalCouplingResidual for uncoupled domains " << i << " and " << j
+            );
+            return localAssemblerI.evalLocalResidual();
+        }
+    }
+
+    //! evaluate coupling residual for the derivative low dim DOF with respect to bulk DOF
+    //! we only need to evaluate the part of the residual that will be influence by the bulk DOF
+    template<std::size_t i, class LocalAssemblerI, std::size_t j>
+    decltype(auto) evalCouplingResidual(Dune::index_constant<i> domainI,
+                                        const LocalAssemblerI& localAssemblerI,
+                                        Dune::index_constant<j> domainJ,
+                                        std::size_t dofIdxGlobalJ) const
+    {
+        if constexpr (i == j || isCoupled_<CouplingMap>(domainI, domainJ))
+            return subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> decltype(auto) {
+                return cm.evalCouplingResidual(ii, localAssemblerI, jj, dofIdxGlobalJ);
+            });
+        else
+        {
+            DUNE_THROW(Dune::InvalidStateException,
+                "Calling evalCouplingResidual for uncoupled domains " << i << " and " << j
+            );
+            return localAssemblerI.evalLocalResidual();
+        }
+    }
+
+    //! evaluate coupling residual for the derivative low dim DOF with respect to bulk DOF
+    //! we only need to evaluate the part of the residual that will be influence by the bulk DOF
+    template<std::size_t i, class LocalAssemblerI, std::size_t j>
+    decltype(auto) evalCouplingResidual(Dune::index_constant<i> domainI,
+                                        const LocalAssemblerI& localAssemblerI,
+                                        const SubControlVolume<i>& scvI,
+                                        Dune::index_constant<j> domainJ,
+                                        std::size_t dofIdxGlobalJ) const
+    {
+        if constexpr (i == j || isCoupled_<CouplingMap>(domainI, domainJ))
+            return subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> decltype(auto) {
+                return cm.evalCouplingResidual(ii, localAssemblerI, scvI, jj, dofIdxGlobalJ);
+            });
+        else
+        {
+            DUNE_THROW(Dune::InvalidStateException,
+                "Calling evalCouplingResidual for uncoupled domains " << i << " and " << j
+            );
+            return localAssemblerI.evalLocalResidual();
+        }
+    }
+
+    /*!
+     * \brief Update the coupling context for the bulk face residual w.r.t to the lowDim dofs
+     */
+    template<std::size_t i, class LocalAssemblerI, std::size_t j, class PrimaryVariables>
+    void updateCouplingContext(Dune::index_constant<i> domainI,
+                               const LocalAssemblerI& localAssemblerI,
+                               Dune::index_constant<j> domainJ,
+                               const std::size_t dofIdxGlobalJ,
+                               const PrimaryVariables& priVars,
+                               int pvIdxJ)
+    {
+        // only one other manager needs to take action if i != j
+        if constexpr (i != j)
+        {
+            if constexpr (isCoupled_<CouplingMap>(domainI, domainJ))
+                subApply(domainI, domainJ, [&](auto& cm, auto&& ii, auto&& jj){
+                    cm.updateCouplingContext(ii, localAssemblerI, jj, dofIdxGlobalJ, priVars, pvIdxJ);
+                });
+        }
+        else
+        {
+            // for i == j, we need to call all relevant managers where domainI is involved and make sure that the
+            // context is updated with respect to its own domain (I-I coupling context)
+            using namespace Dune::Hybrid;
+            forEach(CouplingMap::coupledDomains(domainI), [&](const auto domainJ){
+                static_assert(domainI != domainJ);
+                subApply(domainI, domainJ, [&](auto& cm, auto&& ii, auto&& jj){
+                    cm.updateCouplingContext(ii, localAssemblerI, ii, dofIdxGlobalJ, priVars, pvIdxJ);
+                });
+            });
+        }
+    }
+
+    //! Bind the coupling context for a low dim element TODO remove Assembler
+    template<std::size_t i, class Assembler = int>
+    void bindCouplingContext(Dune::index_constant<i> domainI, const Element<i>& element, const Assembler& assembler = 0)
+    {
+        // for the coupling blocks
+        using namespace Dune::Hybrid;
+        forEach(CouplingMap::coupledDomains(domainI), [&](const auto domainJ){
+            subApply(domainI, domainJ, [&](auto& cm, auto&& ii, auto&& jj) -> void {
+                cm.bindCouplingContext(ii, element, assembler);
+            });
+        });
+    }
+
+    /*!
+     * \brief return the numeric epsilon used for deflecting primary variables of coupled domain i.
+     * \note  specialization for free-flow schemes
+     */
+    template<std::size_t i>
+    decltype(auto) numericEpsilon(Dune::index_constant<i> domainI,
+                                  const std::string& paramGroup) const
+    {
+        return subApply(domainI, [&](const auto& cm, auto&& ii) -> decltype(auto) {
+            return cm.numericEpsilon(ii, paramGroup);
+        });
+    }
+
+    /*!
+     * \brief evaluate additional derivatives of the element residual of a domain with respect
+     *        to dofs in the same domain that are not in the regular stencil (see CouplingManager::extendJacobianPattern)
+     * \note Such additional dependencies can arise from the coupling, e.g. if a coupling source
+     *       term depends on a non-local average of a quantity of the same domain
+     */
+    template<std::size_t i, class LocalAssemblerI, class JacobianMatrixDiagBlock, class GridVariables>
+    void evalAdditionalDomainDerivatives(Dune::index_constant<i> domainI,
+                                         const LocalAssemblerI& localAssemblerI,
+                                         const typename LocalAssemblerI::LocalResidual::ElementResidualVector& origResiduals,
+                                         JacobianMatrixDiagBlock& A,
+                                         GridVariables& gridVariables)
+    {}
+
+    template<std::size_t i, class LocalAssemblerI, class UpdatableElementVolVars, class UpdatableFluxVarCache>
+    void updateCoupledVariables(Dune::index_constant<i> domainI,
+                                const LocalAssemblerI& localAssemblerI,
+                                UpdatableElementVolVars& elemVolVars,
+                                UpdatableFluxVarCache& elemFluxVarsCache)
+    {
+        // for the coupling blocks
+        using namespace Dune::Hybrid;
+        forEach(CouplingMap::coupledDomains(domainI), [&](const auto domainJ){
+            subApply(domainI, domainJ, [&](auto& cm, auto&& ii, auto&& jj){
+                cm.updateCoupledVariables(ii, localAssemblerI, elemVolVars, elemFluxVarsCache);
+            });
+        });
+    }
+
+protected:
+    SolutionVectors& curSol()
+    { return solutionVectors_; }
+
+    const SolutionVectors& curSol() const
+    { return solutionVectors_; }
+
+private:
+    CouplingManagers couplingManagers_;
+    SolutionVectors solutionVectors_;
+
+    CouplingStencil emptyStencil_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/multidomain/staggeredfreeflow/couplingmanager.hh b/dumux/multidomain/staggeredfreeflow/couplingmanager.hh
index d04276a6e468bf2f991ee5d6214b8d3cbe1ff5ce..08fc15548a45d3e381cb067f36f78ef4a344ed07 100644
--- a/dumux/multidomain/staggeredfreeflow/couplingmanager.hh
+++ b/dumux/multidomain/staggeredfreeflow/couplingmanager.hh
@@ -48,9 +48,14 @@ template<class Traits>
 class StaggeredFreeFlowCouplingManager
 : public CouplingManager<Traits>
 {
+    using ParentType = CouplingManager<Traits>;
 public:
     static constexpr auto freeFlowMomentumIndex = typename Traits::template SubDomain<0>::Index();
     static constexpr auto freeFlowMassIndex = typename Traits::template SubDomain<1>::Index();
+
+    // this can be used if the coupling manager is used inside a meta-coupling manager (e.g. multi-binary)
+    // to manager the solution vector storage outside this class
+    using SolutionVectorStorage = typename ParentType::SolutionVectorStorage;
 private:
     template<std::size_t id> using SubDomainTypeTag = typename Traits::template SubDomain<id>::TypeTag;
     template<std::size_t id> using PrimaryVariables = GetPropType<SubDomainTypeTag<id>, Properties::PrimaryVariables>;
@@ -69,8 +74,6 @@ private:
     using Scalar = typename Traits::Scalar;
     using SolutionVector = typename Traits::SolutionVector;
 
-    using ParentType = CouplingManager<Traits>;
-
     using CouplingStencilType = std::vector<std::size_t>;
 
     using GridVariablesTuple = typename Traits::template TupleOfSharedPtr<GridVariables>;
diff --git a/test/multidomain/boundary/CMakeLists.txt b/test/multidomain/boundary/CMakeLists.txt
index addd460feb4d2b502e6a92a0630ad445b11020d9..5ffc5873b26785265ec0c9ad64e4cc7aa93cce1b 100644
--- a/test/multidomain/boundary/CMakeLists.txt
+++ b/test/multidomain/boundary/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_subdirectory(darcydarcy)
+add_subdirectory(freeflowporousmedium)
 add_subdirectory(stokesdarcy)
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/CMakeLists.txt b/test/multidomain/boundary/freeflowporousmedium/1p_1p/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..617ade1705be78321c072af769414efc7b66d337
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/CMakeLists.txt
@@ -0,0 +1,32 @@
+add_subdirectory(convergence)
+
+add_input_file_links()
+
+add_executable(test_md_boundary_ff1p_pm1p EXCLUDE_FROM_ALL main.cc)
+
+dumux_add_test(NAME test_md_boundary_ff1p_pm1p_horizontal
+              LABELS multidomain multidomain_boundary stokesdarcy 1p navierstokes
+              TARGET test_md_boundary_ff1p_pm1p
+              CMAKE_GUARD HAVE_UMFPACK
+              COMMAND ${CMAKE_SOURCE_DIR}/bin/testing/runtest.py
+              CMD_ARGS  --script fuzzy
+                        --files ${CMAKE_SOURCE_DIR}/test/references/test_md_boundary_darcy1p_stokes1p_horizontal_stokes-reference.vtu
+                                ${CMAKE_CURRENT_BINARY_DIR}/test_md_boundary_ff1p_pm1p_horizontal_freeflow-00000.vtu
+                                ${CMAKE_SOURCE_DIR}/test/references/test_md_boundary_darcy1p_stokes1p_horizontal_darcy-reference.vtu
+                                ${CMAKE_CURRENT_BINARY_DIR}/test_md_boundary_ff1p_pm1p_horizontal_darcy-00000.vtu
+                        --command "${CMAKE_CURRENT_BINARY_DIR}/test_md_boundary_ff1p_pm1p params_horizontalflow.input
+                                   -Vtk.OutputName test_md_boundary_ff1p_pm1p_horizontal")
+
+dumux_add_test(NAME test_md_boundary_ff1p_pm1p_vertical
+              LABELS multidomain multidomain_boundary stokesdarcy 1p navierstokes
+              TARGET test_md_boundary_ff1p_pm1p
+              CMAKE_GUARD HAVE_UMFPACK
+              COMMAND ${CMAKE_SOURCE_DIR}/bin/testing/runtest.py
+              CMD_ARGS  --script fuzzy
+                        --files ${CMAKE_SOURCE_DIR}/test/references/test_md_boundary_darcy1p_stokes1p_vertical_stokes-reference.vtu
+                                ${CMAKE_CURRENT_BINARY_DIR}/test_md_boundary_ff1p_pm1p_vertical_freeflow-00000.vtu
+                                ${CMAKE_SOURCE_DIR}/test/references/test_md_boundary_darcy1p_stokes1p_vertical_darcy-reference.vtu
+                                ${CMAKE_CURRENT_BINARY_DIR}/test_md_boundary_ff1p_pm1p_vertical_darcy-00000.vtu
+                        --command "${CMAKE_CURRENT_BINARY_DIR}/test_md_boundary_ff1p_pm1p params_verticalflow.input
+                                   -Vtk.OutputName test_md_boundary_ff1p_pm1p_vertical"
+                        --zeroThreshold {"velocity_liq \(m/s\)_0":6e-17})
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/CMakeLists.txt b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8336a0b30df4dc11d70415c1f3e81e6be49475f5
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/CMakeLists.txt
@@ -0,0 +1,50 @@
+add_input_file_links()
+dune_symlink_to_source_files(FILES "convergencetest.py")
+
+add_executable(test_md_boundary_ff1p_pm1p_convtest EXCLUDE_FROM_ALL main.cc)
+
+dumux_add_test(NAME test_md_boundary_ff1p_pm1p_convtest_shiue1
+              TARGET test_md_boundary_ff1p_pm1p_convtest
+              LABELS multidomain multidomain_boundary stokesdarcy
+              TIMEOUT 1000
+              CMAKE_GUARD HAVE_UMFPACK
+              COMMAND ./convergencetest.py
+              CMD_ARGS  test_md_boundary_ff1p_pm1p_convtest params.input
+                        -Problem.TestCase ShiueExampleOne
+                        -Darcy.Problem.BoundaryConditions Dirichlet
+                        -FreeFlow.Problem.BoundaryConditions Dirichlet)
+
+dumux_add_test(NAME test_md_boundary_ff1p_pm1p_convtest_shiue2
+              TARGET test_md_boundary_ff1p_pm1p_convtest
+              LABELS multidomain multidomain_boundary stokesdarcy
+              TIMEOUT 1000
+              CMAKE_GUARD HAVE_UMFPACK
+              COMMAND ./convergencetest.py
+              CMD_ARGS  test_md_boundary_ff1p_pm1p_convtest params.input
+                        -Problem.TestCase ShiueExampleTwo
+                        -Darcy.Problem.BoundaryConditions Mixed
+                        -FreeFlow.Problem.BoundaryConditions Mixed)
+
+dumux_add_test(NAME test_md_boundary_ff1p_pm1p_convtest_rybak
+              TARGET test_md_boundary_ff1p_pm1p_convtest
+              LABELS multidomain multidomain_boundary stokesdarcy
+              TIMEOUT 1000
+              CMAKE_GUARD HAVE_UMFPACK
+              COMMAND ./convergencetest.py
+              CMD_ARGS  test_md_boundary_ff1p_pm1p_convtest params.input
+                        -Problem.TestCase Rybak
+                        -Darcy.Problem.BoundaryConditions Neumann
+                        -FreeFlow.Problem.BoundaryConditions Stress)
+
+dumux_add_test(NAME test_md_boundary_ff1p_pm1p_convtest_navierstokes
+              TARGET test_md_boundary_ff1p_pm1p_convtest
+              LABELS multidomain multidomain_boundary stokesdarcy
+              TIMEOUT 1000
+              CMAKE_GUARD HAVE_UMFPACK
+              COMMAND ./convergencetest.py
+              CMD_ARGS  test_md_boundary_ff1p_pm1p_convtest params.input
+                        -Problem.TestCase Schneider
+                        -FreeFlow.Problem.EnableInertiaTerms true
+                        -FreeFlow.EnableUnsymmetrizedVelocityGradientForBeaversJoseph true
+                        -Darcy.Problem.BoundaryConditions Mixed
+                        -FreeFlow.Problem.BoundaryConditions Mixed)
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/analyticalsolutions.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/analyticalsolutions.hh
new file mode 100644
index 0000000000000000000000000000000000000000..6df9efadd3fba9d9581d38606c584d73af590fe9
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/analyticalsolutions.hh
@@ -0,0 +1,340 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief Analytical solutions and rhs for the different test cases
+ */
+
+#ifndef DUMUX_CONVERGENCE_TEST_ANALYTICAL_SOLUTIONS_HH
+#define DUMUX_CONVERGENCE_TEST_ANALYTICAL_SOLUTIONS_HH
+
+#include <cmath>
+#include <dune/common/fvector.hh>
+#include <dune/common/fmatrix.hh>
+
+namespace Dumux::Solution::DarcyStokes {
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// see Rybak et al., 2015: "Multirate time integration for coupled
+//   saturated/unsaturated porous medium and free flow systems"
+/////////////////////////////////////////////////////////////////////////////////////////////////
+namespace Rybak {
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> darcy(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    using std::exp; using std::sin; using std::cos;
+    sol[0] = -0.5*M_PI*y*y*cos(M_PI*x);
+    sol[1] = -1.0*y*sin(M_PI*x);
+    sol[2] = 0.5*y*y*sin(M_PI*x);
+    return sol;
+}
+
+// 0: mass
+Dune::FieldVector<double, 1> darcyRHS(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    using std::sin;
+    return { (0.5*M_PI*y*M_PI*y - 1)*sin(M_PI*x) };
+}
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> stokes(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    using std::sin; using std::cos;
+    sol[0] = -cos(M_PI*x)*sin(M_PI*y);
+    sol[1] = sin(M_PI*x)*cos(M_PI*y);
+    sol[2] = 0.5*y*sin(M_PI*x);
+    return sol;
+}
+
+// 0: mom-x | 1: mom-y | 2: mass
+Dune::FieldVector<double, 3> stokesRHS(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    Dune::FieldVector<double, 3> source(0.0);
+    using std::sin; using std::cos;
+    source[0] = 0.5*M_PI*y*cos(M_PI*x) - 2*M_PI*M_PI*cos(M_PI*x)*sin(M_PI*y);
+    source[1] = 0.5*sin(M_PI*x) + 2*M_PI*M_PI*sin(M_PI*x)*cos(M_PI*y);
+    return source;
+}
+
+// sigma_ff = -sym(grad(v)) + pI
+Dune::FieldMatrix<double, 2, 2> stokesStress(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    using std::exp; using std::sin; using std::cos;
+
+    Dune::FieldMatrix<double, 2, 2> stress(0.0);
+    stress[0][0] = (0.5*y - 2*M_PI*sin(M_PI*y))*sin(M_PI*x);
+    stress[1][0] = 0.0;
+    stress[0][1] = stress[1][0]; // symmetric
+    stress[1][1] = (0.5*y - 2*M_PI*sin(M_PI*y))*sin(M_PI*x);
+    return stress;
+}
+
+} // end namespace Rybak
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem"
+// Example 1
+/////////////////////////////////////////////////////////////////////////////////////////////////
+namespace ShiueOne {
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> darcy(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    using std::exp; using std::sin; using std::cos;
+    sol[0] = M_PI*(-exp(1)*y + exp(y))*sin(M_PI*x);
+    sol[1] = (exp(1) - exp(y))*cos(M_PI*x);
+    sol[2] = (exp(y) - y*exp(1)) * cos(M_PI*x);
+
+    return sol;
+}
+
+// 0: mass
+Dune::FieldVector<double, 1> darcyRHS(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    using std::exp; using std::sin; using std::cos;
+    return { cos(M_PI*x) * (M_PI*M_PI*(exp(y) - y*exp(1)) - exp(y)) };
+}
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> stokes(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    using std::exp; using std::sin; using std::cos;
+    sol[0] = -1/M_PI * exp(y) * sin(M_PI*x);
+    sol[1] = (exp(y) - exp(1)) * cos(M_PI*x);
+    sol[2] = 2*exp(y) * cos(M_PI*x);
+    return sol;
+}
+
+// 0: mom-x | 1: mom-y | 2: mass
+Dune::FieldVector<double, 3> stokesRHS(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    using std::exp; using std::sin; using std::cos;
+    Dune::FieldVector<double, 3> source(0.0);
+    source[0] = exp(y)*sin(M_PI*x) * (1/M_PI -3*M_PI);
+    source[1] = cos(M_PI*x) * (M_PI*M_PI*(exp(y)- exp(1)) + exp(y));
+    return source;
+}
+
+// sigma_ff = -sym(grad(v)) + pI
+Dune::FieldMatrix<double, 2, 2> stokesStress(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    using std::exp; using std::sin; using std::cos;
+
+    Dune::FieldMatrix<double, 2, 2> stress(0.0);
+    stress[0][0] = 4.0*exp(y)*cos(M_PI*x);
+    stress[1][0] = (M_PI*M_PI*(exp(y) - exp(1)) + 1.0*exp(y))*sin(M_PI*x)/M_PI;
+    stress[0][1] = stress[1][0]; // symmetric
+    stress[1][1] = 0.0;
+    return stress;
+}
+
+} // end namespace ShiueExOne
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem"
+// Example 2
+/////////////////////////////////////////////////////////////////////////////////////////////////
+namespace ShiueTwo {
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> darcy(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    sol[0] = x*(y - 1.0) + (x - 1.0)*(y - 1.0) - 2.0;
+    sol[1] = x*(x - 1.0) - 1.0*(y - 1.0)*(y - 1.0) - 2.0;
+    sol[2] = x*(1.0-x)*(y-1.0) + (y-1.0)*(y-1.0)*(y-1.0)/3.0 + 2.0*x + 2.0*y + 4.0;
+
+    return sol;
+}
+
+// 0: mass
+Dune::FieldVector<double, 1> darcyRHS(const Dune::FieldVector<double, 2>& globalPos)
+{ return { 0.0 }; }
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> stokes(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    sol[0] = (y-1.0)*(y-1.0) + x*(y-1.0) + 3.0*x - 1.0;
+    sol[1] = x*(x-1.0) - 0.5*(y-1.0)*(y-1.0) - 3.0*y + 1.0;
+    sol[2] = 2.0*x + y - 1.0;
+    return sol;
+}
+
+// 0: mom-x | 1: mom-y | 2: mass
+Dune::FieldVector<double, 3> stokesRHS(const Dune::FieldVector<double, 2>& globalPos)
+{ return { 0.0, 0.0, 0.0 }; }
+
+// sigma_ff = -sym(grad(v)) + pI
+Dune::FieldMatrix<double, 2, 2> stokesStress(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+
+    Dune::FieldMatrix<double, 2, 2> stress(0.0);
+    stress[0][0] = 2.0*x - y - 5.0;
+    stress[1][0] = -3*x - 2*y + 3.0;
+    stress[0][1] = stress[1][0]; // symmetric
+    stress[1][1] = 2.0*x + 3.0*y + 3.0;
+    return stress;
+}
+
+} // end namespace ShiueTwo
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// see Schneider et al., 2019: "Coupling staggered-grid and MPFA finite volume methods for
+//   free flow/porous-medium flow problems (with c = 0)"
+/////////////////////////////////////////////////////////////////////////////////////////////////
+namespace Schneider {
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> darcy(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    static constexpr double omega = M_PI;
+    static constexpr double c = 0.0;
+    using std::exp; using std::sin; using std::cos;
+    const double sinOmegaX = sin(omega*x);
+    const double cosOmegaX = cos(omega*x);
+    static const double expTwo = exp(2);
+    const double expYPlusOne = exp(y+1);
+
+    sol[0] = c/(2*omega)*expYPlusOne*sinOmegaX*sinOmegaX
+              -omega*(expYPlusOne + 2 - expTwo)*cosOmegaX;
+    sol[1] = (0.5*c*(expYPlusOne + 2 - expTwo)*cosOmegaX
+              -(c*cosOmegaX + 1)*exp(y-1))*sinOmegaX;
+    sol[2] = (expYPlusOne + 2 - expTwo)*sinOmegaX;
+
+    return sol;
+}
+
+// 0: mass
+Dune::FieldVector<double, 1> darcyRHS(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    using std::exp; using std::sin; using std::cos;
+    static constexpr double omega = M_PI;
+    static constexpr double c = 0.0;
+    const double cosOmegaX = cos(omega*x);
+    static const double expTwo = exp(2);
+    const double expYPlusOne = exp(y+1);
+
+    return { sin(omega*x)*(-(c*cosOmegaX + 1)*exp(y - 1)
+            + 1.5*c*expYPlusOne*cosOmegaX
+            + omega*omega*(expYPlusOne - expTwo + 2)) };
+}
+
+// 0: velocity-x | 1: velocity-y | 2: pressure
+Dune::FieldVector<double, 3> stokes(const Dune::FieldVector<double, 2>& globalPos)
+{
+    Dune::FieldVector<double, 3> sol(0.0);
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    using std::sin;
+    static constexpr double omega = M_PI;
+    const double sinOmegaX = sin(omega*x);
+
+    sol[0] = y;
+    sol[1] = -y*sinOmegaX;
+    sol[2] = -y*y*sinOmegaX*sinOmegaX;
+    return sol;
+}
+
+// 0: mom-x | 1: mom-y | 2: mass
+Dune::FieldVector<double, 3> stokesRHS(const Dune::FieldVector<double, 2>& globalPos)
+{
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    using std::exp; using std::sin; using std::cos;
+    static constexpr double omega = M_PI;
+    const double sinOmegaX = sin(omega*x);
+    const double cosOmegaX = cos(omega*x);
+
+    Dune::FieldVector<double, 3> source(0.0);
+    source[0] = -2*omega*y*y*sinOmegaX*cosOmegaX
+                                            -2*y*sinOmegaX + omega*cosOmegaX;
+    source[1] = -omega*y*y*cosOmegaX - omega*omega*y*sinOmegaX;
+    source[2] = -sinOmegaX;
+    return source;
+}
+
+// sigma_ff = vvT + sym(grad(v)) + pI
+Dune::FieldMatrix<double, 2, 2> stokesStress(const Dune::FieldVector<double, 2>& globalPos)
+{
+    using std::sin; using std::cos;
+    const double x = globalPos[0];
+    const double y = globalPos[1];
+    static constexpr double omega = M_PI;
+    const double sinOmegaX = sin(omega*x);
+    const double cosOmegaX = cos(omega*x);
+
+    Dune::FieldMatrix<double, 2, 2> stress(0.0);
+    stress[0][0] = y*y * cosOmegaX*cosOmegaX;
+    stress[0][1] = -y*y*sinOmegaX + omega*y*cosOmegaX - 1.0;
+    stress[1][0] = stress[0][1]; // symmetric
+    stress[1][1] = 2*sinOmegaX;
+    return stress;
+}
+
+} // end namespace Schneider
+
+} // end namespace Dumux::Solution::DarcyStokes
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/convergencetest.py b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/convergencetest.py
new file mode 100755
index 0000000000000000000000000000000000000000..b52826c3c48cc713f2f526dffe2bfb551e720cb2
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/convergencetest.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+from math import *
+import subprocess
+import sys
+
+if len(sys.argv) < 2:
+    sys.stderr.write('Please provide a single argument <testname> to the script\n')
+    sys.exit(1)
+
+executableName = str(sys.argv[1])
+testargs = [str(i) for i in sys.argv][2:]
+testname = testargs[testargs.index('-Problem.TestCase')+1]
+
+# remove the old log files
+subprocess.call(['rm', testname + '_freeFlow.log'])
+print("Removed old log file ({})!".format(testname + '_freeFlow.log'))
+subprocess.call(['rm', testname + '_darcy.log'])
+print("Removed old log file ({})!".format(testname + '_darcy.log'))
+
+# do the runs with different refinement
+for i in [0, 1, 2]:
+    subprocess.call(['./' + executableName] + testargs + ['-Grid.Refinement', str(i)])
+
+def checkRatesFreeFlow():
+    # check the rates and append them to the log file
+    with open(testname + '_freeFlow.log', "r+") as logfile:
+
+        errorP, errorVx, errorVy = [], [], []
+        for line in logfile:
+            line = line.strip("\n").strip("\[ConvergenceTest\]").split()
+            errorP.append(float(line[2]))
+            errorVx.append(float(line[5]))
+            errorVy.append(float(line[8]))
+
+        resultsP, resultsVx, resultsVy = [], [], []
+        logfile.truncate(0)
+        logfile.write("n\terrorP\t\trateP\t\terrorVx\t\trateVx\t\terrorVy\t\trateVy\n")
+        logfile.write("-"*50 + "\n")
+        for i in range(len(errorP)-1):
+            if isnan(errorP[i]) or isinf(errorP[i]):
+                continue
+            if not ((errorP[i] < 1e-12 or errorP[i+1] < 1e-12) and (errorVx[i] < 1e-12 or errorVx[i+1] < 1e-12) and (errorVy[i] < 1e-12 or errorVy[i+1] < 1e-12)):
+                rateP = (log(errorP[i])-log(errorP[i+1]))/log(2)
+                rateVx = (log(errorVx[i])-log(errorVx[i+1]))/log(2)
+                rateVy = (log(errorVy[i])-log(errorVy[i+1]))/log(2)
+                message = "{}\t{:0.4e}\t{:0.4e}\t{:0.4e}\t{:0.4e}\t{:0.4e}\t{:0.4e}\n".format(i, errorP[i], rateP,  errorVx[i], rateVx, errorVy[i], rateVy)
+                logfile.write(message)
+                resultsP.append(rateP)
+                resultsVx.append(rateVx)
+                resultsVy.append(rateVy)
+            else:
+                logfile.write("error: exact solution!?")
+        i = len(errorP)-1
+        message = "{}\t{:0.4e}\t\t{}\t{:0.4e}\t\t{}\t{:0.4e}\t\t{}\n".format(i, errorP[i], "",  errorVx[i], "", errorVy[i], "")
+        logfile.write(message)
+
+    print("\nComputed the following convergence rates for {}:\n".format(testname))
+
+    subprocess.call(['cat', testname + '_freeFlow.log'])
+
+    return {"p" : resultsP, "v_x" : resultsVx, "v_y" : resultsVy}
+
+def checkRatesDarcy():
+    # check the rates and append them to the log file
+    with open(testname + '_darcy.log', "r+") as logfile:
+
+        errorP = []
+        for line in logfile:
+            line = line.strip("\n")
+            line = line.strip("\[ConvergenceTest\]")
+            line = line.split()
+            errorP.append(float(line[2]))
+
+        resultsP = []
+        logfile.truncate(0)
+        logfile.write("n\terrorP\t\trateP\n")
+        logfile.write("-"*50 + "\n")
+        for i in range(len(errorP)-1):
+            if isnan(errorP[i]) or isinf(errorP[i]):
+                continue
+            if not ((errorP[i] < 1e-12 or errorP[i+1] < 1e-12)):
+                rateP = (log(errorP[i])-log(errorP[i+1]))/log(2)
+                message = "{}\t{:0.4e}\t{:0.4e}\n".format(i, errorP[i], rateP)
+                logfile.write(message)
+                resultsP.append(rateP)
+            else:
+                logfile.write("error: exact solution!?")
+        i = len(errorP)-1
+        message = "{}\t{:0.4e}\n".format(i, errorP[i], "")
+        logfile.write(message)
+
+    print("\nComputed the following convergence rates for {}:\n".format(testname))
+
+    subprocess.call(['cat', testname + '_darcy.log'])
+
+    return {"p" : resultsP}
+
+def checkRatesFreeFlowAndDarcy():
+    resultsFreeFlow = checkRatesFreeFlow()
+    resultsDarcy = checkRatesDarcy()
+
+    def mean(numbers):
+        return float(sum(numbers)) / len(numbers)
+
+    # check the rates, we expect rates around 2
+    if mean(resultsFreeFlow["p"]) < 2.05 and mean(resultsFreeFlow["p"]) < 1.7:
+        sys.stderr.write("*"*70 + "\n" + "The convergence rates for pressure were not close enough to 2! Test failed.\n" + "*"*70 + "\n")
+        sys.exit(1)
+
+    if mean(resultsFreeFlow["v_x"]) < 2.05 and mean(resultsFreeFlow["v_x"]) < 1.95:
+        sys.stderr.write("*"*70 + "\n" + "The convergence rates for x-velocity were not close enough to 2! Test failed.\n" + "*"*70 + "\n")
+        sys.exit(1)
+
+    if mean(resultsFreeFlow["v_y"]) < 2.05 and mean(resultsFreeFlow["v_y"]) < 1.95:
+        sys.stderr.write("*"*70 + "\n" + "The convergence rates for y-velocity were not close enough to 2! Test failed.\n" + "*"*70 + "\n")
+        sys.exit(1)
+
+    if mean(resultsDarcy["p"]) < 2.05 and mean(resultsDarcy["p"]) < 1.95:
+        sys.stderr.write("*"*70 + "\n" + "The convergence rates for pressure were not close enough to 2! Test failed.\n" + "*"*70 + "\n")
+        sys.exit(1)
+
+
+checkRatesFreeFlowAndDarcy()
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/main.cc b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/main.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8a9a0044d80d02a2b5b3d8d08ee2301cf9edc6ae
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/main.cc
@@ -0,0 +1,320 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief A convergence test for the coupled FreeFlow/Darcy problem (1p).
+ */
+
+#include <config.h>
+
+#include <iostream>
+
+#include <dune/common/parallel/mpihelper.hh>
+#include <dune/common/timer.hh>
+
+#include <dumux/common/properties.hh>
+#include <dumux/common/parameters.hh>
+#include <dumux/common/dumuxmessage.hh>
+#include <dumux/common/partial.hh>
+#include <dumux/linear/seqsolverbackend.hh>
+#include <dumux/assembly/fvassembler.hh>
+#include <dumux/discretization/method.hh>
+#include <dumux/io/format.hh>
+#include <dumux/io/vtkoutputmodule.hh>
+#include <dumux/io/grid/gridmanager_yasp.hh>
+
+#include <dumux/multidomain/staggeredtraits.hh>
+#include <dumux/multidomain/fvassembler.hh>
+#include <dumux/multidomain/newtonsolver.hh>
+#include <dumux/freeflow/navierstokes/velocityoutput.hh>
+
+#include <test/freeflow/navierstokes/analyticalsolutionvectors.hh>
+#include <test/freeflow/navierstokes/errors.hh>
+
+#include "testcase.hh"
+#include "properties.hh"
+
+/*!
+* \brief Creates analytical solution vector for output
+* Returns a tuple of the analytical solution for the pressure and velocity at cell centers
+* \param problem the problem for which to evaluate the analytical solution
+*/
+template<class Scalar, class Problem>
+auto createDarcyAnalyticalSolution(const Problem& problem)
+{
+    const auto& gridGeometry = problem.gridGeometry();
+    using GridView = typename std::decay_t<decltype(gridGeometry)>::GridView;
+
+    static constexpr auto dim = GridView::dimension;
+    static constexpr auto dimWorld = GridView::dimensionworld;
+
+    using VelocityVector = Dune::FieldVector<Scalar, dimWorld>;
+
+    std::vector<Scalar> analyticalPressure;
+    std::vector<VelocityVector> analyticalVelocity;
+
+    analyticalPressure.resize(gridGeometry.numDofs());
+    analyticalVelocity.resize(gridGeometry.numDofs());
+
+    for (const auto& element : elements(gridGeometry.gridView()))
+    {
+        auto fvGeometry = localView(gridGeometry);
+        fvGeometry.bindElement(element);
+        for (auto&& scv : scvs(fvGeometry))
+        {
+            const auto ccDofIdx = scv.dofIndex();
+            const auto ccDofPosition = scv.dofPosition();
+            const auto analyticalSolutionAtCc = problem.fullAnalyticalSolution(ccDofPosition);
+            analyticalPressure[ccDofIdx] = analyticalSolutionAtCc[dim];
+
+            for (int dirIdx = 0; dirIdx < dim; ++dirIdx)
+                analyticalVelocity[ccDofIdx][dirIdx] = analyticalSolutionAtCc[dirIdx];
+        }
+    }
+
+    return std::make_tuple(analyticalPressure, analyticalVelocity);
+}
+
+template<class MassP, class MomP, class SolutionVector>
+void printFreeFlowL2Error(std::shared_ptr<MomP> momentumProblem,
+                          std::shared_ptr<MassP> massProblem,
+                          const SolutionVector& sol)
+{
+    using namespace Dumux;
+
+    NavierStokesTest::Errors errors(momentumProblem, massProblem, sol);
+    const int numCellCenterDofs = massProblem->gridGeometry().numDofs();
+    const int numFaceDofs = momentumProblem->gridGeometry().numDofs();
+    const auto absL2 = errors.l2Absolute();
+    const auto relL2 = errors.l2Relative();
+
+    std::cout << Fmt::format("** L2 error (abs/rel) for {} cc dofs and {} face dofs (total: {}): ",
+                             numCellCenterDofs, numFaceDofs, numCellCenterDofs + numFaceDofs)
+              << Fmt::format("L2(p) = {:.8e} / {:.8e}", absL2[0], relL2[0])
+              << Fmt::format(", L2(vx) = {:.8e} / {:.8e}", absL2[1], relL2[1])
+              << Fmt::format(", L2(vy) = {:.8e} / {:.8e}", absL2[2], relL2[2])
+              << std::endl;
+
+    // write the norm into a log file
+    std::ofstream logFile(massProblem->name() + ".log", std::ios::app);
+    logFile << "[ConvergenceTest] L2(p) = " << absL2[0]
+            << " L2(vx) = " << absL2[1]
+            << " L2(vy) = " << absL2[2]
+            << std::endl;
+}
+
+template<class Problem, class SolutionVector>
+void printDarcyL2Error(std::shared_ptr<Problem> problem, const SolutionVector& x)
+{
+    using namespace Dumux;
+    using Scalar = double;
+
+    Scalar l2error = 0.0;
+    for (const auto& element : elements(problem->gridGeometry().gridView()))
+    {
+        auto fvGeometry = localView(problem->gridGeometry());
+        fvGeometry.bindElement(element);
+
+        for (auto&& scv : scvs(fvGeometry))
+        {
+            const auto dofIdx = scv.dofIndex();
+            const Scalar delta = x[dofIdx] - problem->fullAnalyticalSolution(scv.center())[2/*pressureIdx*/];
+            l2error += scv.volume()*(delta*delta);
+        }
+    }
+    using std::sqrt;
+    l2error = sqrt(l2error);
+
+    const auto numDofs = problem->gridGeometry().numDofs();
+    std::cout << Fmt::format("** L2 error (abs) for {} cc dofs", numDofs)
+              << Fmt::format("L2 error = {:.8e}", l2error)
+              << std::endl;
+
+    // write the norm into a log file
+    std::ofstream logFile(problem->name() + ".log", std::ios::app);
+    logFile << "[ConvergenceTest] L2(p) = " << l2error << std::endl;
+}
+
+int main(int argc, char** argv)
+{
+    using namespace Dumux;
+
+    // initialize MPI, finalize is done automatically on exit
+    const auto& mpiHelper = Dune::MPIHelper::instance(argc, argv);
+
+    // print dumux start message
+    if (mpiHelper.rank() == 0)
+        DumuxMessage::print(/*firstCall=*/true);
+
+    // parse command line arguments and input file
+    Parameters::init(argc, argv);
+
+    // Define the sub problem type tags
+    using FreeFlowMomentumTypeTag = Properties::TTag::FreeFlowOnePMomentum;
+    using FreeFlowMassTypeTag = Properties::TTag::FreeFlowOnePMass;
+    using DarcyTypeTag = Properties::TTag::DarcyOneP;
+
+    // try to create a grid (from the given grid file or the input file)
+    // for both sub-domains
+    using DarcyGridManager = Dumux::GridManager<GetPropType<DarcyTypeTag, Properties::Grid>>;
+    DarcyGridManager darcyGridManager;
+    darcyGridManager.init("Darcy"); // pass parameter group
+
+    using FreeFlowGridManager = Dumux::GridManager<GetPropType<FreeFlowMomentumTypeTag, Properties::Grid>>;
+    FreeFlowGridManager freeFlowGridManager;
+    freeFlowGridManager.init("FreeFlow"); // pass parameter group
+
+    // we compute on the leaf grid view
+    const auto& darcyGridView = darcyGridManager.grid().leafGridView();
+    const auto& freeFlowGridView = freeFlowGridManager.grid().leafGridView();
+
+    // create the finite volume grid geometry
+    using FreeFlowMomentumGridGeometry = GetPropType<FreeFlowMomentumTypeTag, Properties::GridGeometry>;
+    auto freeFlowMomentumGridGeometry = std::make_shared<FreeFlowMomentumGridGeometry>(freeFlowGridView);
+    using FreeFlowMassGridGeometry = GetPropType<FreeFlowMassTypeTag, Properties::GridGeometry>;
+    auto freeFlowMassGridGeometry = std::make_shared<FreeFlowMassGridGeometry>(freeFlowGridView);
+    using DarcyFVGridGeometry = GetPropType<DarcyTypeTag, Properties::GridGeometry>;
+    auto darcyGridGeometry = std::make_shared<DarcyFVGridGeometry>(darcyGridView);
+
+    using Traits = MultiDomainTraits<FreeFlowMomentumTypeTag, FreeFlowMassTypeTag, DarcyTypeTag>;
+    using CouplingManager = FreeFlowPorousMediumCouplingManager<Traits>;
+    auto couplingManager = std::make_shared<CouplingManager>();
+
+    // the indices
+    constexpr auto freeFlowMomentumIndex = CouplingManager::freeFlowMomentumIndex;
+    constexpr auto freeFlowMassIndex = CouplingManager::freeFlowMassIndex;
+    constexpr auto porousMediumIndex = CouplingManager::porousMediumIndex;
+
+    // the problem (initial and boundary conditions)
+    const auto testCaseName = getParam<std::string>("Problem.TestCase");
+    const auto testCase = [&]()
+    {
+        if (testCaseName == "ShiueExampleOne")
+            return DarcyStokesTestCase::ShiueExampleOne;
+        else if (testCaseName == "ShiueExampleTwo")
+            return DarcyStokesTestCase::ShiueExampleTwo;
+        else if (testCaseName == "Rybak")
+            return DarcyStokesTestCase::Rybak;
+        else if (testCaseName == "Schneider")
+            return DarcyStokesTestCase::Schneider;
+        else
+            DUNE_THROW(Dune::InvalidStateException, testCaseName + " is not a valid test case");
+    }();
+
+    // the problem (initial and boundary conditions)
+    using FreeFlowMomentumProblem = GetPropType<FreeFlowMomentumTypeTag, Properties::Problem>;
+    auto freeFlowMomentumProblem = std::make_shared<FreeFlowMomentumProblem>(freeFlowMomentumGridGeometry, couplingManager, testCase, testCaseName);
+    using FreeFlowMassProblem = GetPropType<FreeFlowMassTypeTag, Properties::Problem>;
+    auto freeFlowMassProblem = std::make_shared<FreeFlowMassProblem>(freeFlowMassGridGeometry, couplingManager, testCase, testCaseName);
+    using DarcyProblem = GetPropType<DarcyTypeTag, Properties::Problem>;
+    auto spatialParams = std::make_shared<typename DarcyProblem::SpatialParams>(darcyGridGeometry, testCase);
+    auto darcyProblem = std::make_shared<DarcyProblem>(darcyGridGeometry, couplingManager, spatialParams, testCase, testCaseName);
+
+    // the solution vector
+    Traits::SolutionVector sol;
+    sol[freeFlowMomentumIndex].resize(freeFlowMomentumGridGeometry->numDofs());
+    sol[freeFlowMassIndex].resize(freeFlowMassGridGeometry->numDofs());
+    sol[porousMediumIndex].resize(darcyGridGeometry->numDofs());
+
+    // the grid variables
+    using FreeFlowMomentumGridVariables = GetPropType<FreeFlowMomentumTypeTag, Properties::GridVariables>;
+    auto freeFlowMomentumGridVariables = std::make_shared<FreeFlowMomentumGridVariables>(freeFlowMomentumProblem, freeFlowMomentumGridGeometry);
+    using DarcyGridVariables = GetPropType<DarcyTypeTag, Properties::GridVariables>;
+    using FreeFlowMassGridVariables = GetPropType<FreeFlowMassTypeTag, Properties::GridVariables>;
+    auto freeFlowMassGridVariables = std::make_shared<FreeFlowMassGridVariables>(freeFlowMassProblem, freeFlowMassGridGeometry);
+    using DarcyGridVariables = GetPropType<DarcyTypeTag, Properties::GridVariables>;
+    auto darcyGridVariables = std::make_shared<DarcyGridVariables>(darcyProblem, darcyGridGeometry);
+
+    couplingManager->init(freeFlowMomentumProblem, freeFlowMassProblem, darcyProblem,
+                          std::make_tuple(freeFlowMomentumGridVariables, freeFlowMassGridVariables, darcyGridVariables),
+                          sol);
+
+    freeFlowMomentumGridVariables->init(sol[freeFlowMomentumIndex]);
+    freeFlowMassGridVariables->init(sol[freeFlowMassIndex]);
+    darcyGridVariables->init(sol[porousMediumIndex]);
+
+    // initialize the vtk output module
+    VtkOutputModule freeflowVtkWriter(*freeFlowMassGridVariables, sol[freeFlowMassIndex], freeFlowMassProblem->name());
+    GetPropType<FreeFlowMassTypeTag, Properties::IOFields>::initOutputModule(freeflowVtkWriter); // Add model specific output fields
+    freeflowVtkWriter.addVelocityOutput(std::make_shared<NavierStokesVelocityOutput<FreeFlowMassGridVariables>>());
+    NavierStokesTest::AnalyticalSolutionVectors freeFlowAnalyticSol(freeFlowMomentumProblem, freeFlowMassProblem);
+    freeflowVtkWriter.addField(freeFlowAnalyticSol.analyticalPressureSolution(), "pressureExact");
+    freeflowVtkWriter.addField(freeFlowAnalyticSol.analyticalVelocitySolution(), "velocityExact");
+    freeflowVtkWriter.write(0.0);
+
+    VtkOutputModule darcyVtkWriter(*darcyGridVariables, sol[porousMediumIndex],  darcyProblem->name());
+    GetPropType<DarcyTypeTag, Properties::IOFields>::initOutputModule(darcyVtkWriter);
+    darcyVtkWriter.addVelocityOutput(std::make_shared<GetPropType<DarcyTypeTag, Properties::VelocityOutput>>(*darcyGridVariables));
+    const auto darcyAnalyticalSolution = createDarcyAnalyticalSolution<double>(*darcyProblem);
+    darcyVtkWriter.addField(std::get<0>(darcyAnalyticalSolution), "pressureExact");
+    darcyVtkWriter.addField(std::get<1>(darcyAnalyticalSolution), "velocityExact");
+    darcyVtkWriter.write(0.0);
+
+    // the assembler for a stationary problem
+    using Assembler = MultiDomainFVAssembler<Traits, CouplingManager, DiffMethod::numeric>;
+    auto assembler = std::make_shared<Assembler>(std::make_tuple(freeFlowMomentumProblem, freeFlowMassProblem, darcyProblem),
+                                                 std::make_tuple(freeFlowMomentumGridGeometry,
+                                                                 freeFlowMassGridGeometry,
+                                                                 darcyGridGeometry),
+                                                 std::make_tuple(freeFlowMomentumGridVariables,
+                                                                 freeFlowMassGridVariables,
+                                                                 darcyGridVariables),
+                                                 couplingManager);
+
+    // the linear solver
+    using LinearSolver = UMFPackBackend;
+    auto linearSolver = std::make_shared<LinearSolver>();
+
+    // the non-linear solver
+    using NewtonSolver = MultiDomainNewtonSolver<Assembler, LinearSolver, CouplingManager>;
+    NewtonSolver nonLinearSolver(assembler, linearSolver, couplingManager);
+
+    const auto dofsVS = freeFlowMomentumGridGeometry->numDofs();
+    const auto dofsPS = freeFlowMassGridGeometry->numDofs();
+    const auto dofsPD = darcyGridGeometry->numDofs();
+    std::cout << "Stokes velocity dofs: " << dofsVS << std::endl;
+    std::cout << "Stokes pressure dofs: " << dofsPS << std::endl;
+    std::cout << "Darcy pressure dofs: " << dofsPD << std::endl;
+    std::cout << "Total number of dofs: " << dofsVS + dofsPS + dofsPD << std::endl;
+
+    // solve the non-linear system
+    nonLinearSolver.solve(sol);
+
+    // write vtk output
+    freeflowVtkWriter.write(1.0);
+    darcyVtkWriter.write(1.0);
+
+    // print L2 errors
+    printFreeFlowL2Error(freeFlowMomentumProblem, freeFlowMassProblem, partial(sol, freeFlowMomentumIndex, freeFlowMassIndex));
+    printDarcyL2Error(darcyProblem, sol[porousMediumIndex]);
+
+    ////////////////////////////////////////////////////////////
+    // finalize, print dumux message to say goodbye
+    ////////////////////////////////////////////////////////////
+
+    // print dumux end message
+    if (mpiHelper.rank() == 0)
+    {
+        Parameters::print();
+        DumuxMessage::print(/*firstCall=*/false);
+    }
+
+    return 0;
+}
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/manufactured_solution.py b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/manufactured_solution.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bc744363ba71de33bd5c423e56dbab10e873221
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/manufactured_solution.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+
+"""
+This script determines the source terms needed for analytical solutions for coupled Stokes/Darcy problems.
+Given an analytical solution, it evaluates the momentum and mass balance equations and outputs source terms
+such that the balance equations are fulfilled. It furthermore calculates the Darcy velocity from a given pressure solution.
+"""
+
+import sympy as sp
+import numpy as np
+from sympy import *
+
+# case = "Rybak"
+#case = "Shiue_1"
+case = "Schneider"
+
+# divergence of a matrix
+def div(M):
+    return np.array([sp.diff(M[0,0],x) + sp.diff(M[0,1],y) , sp.diff(M[1,0],x) + sp.diff(M[1,1],y) ])
+
+# gradient of a vector
+def grad(v):
+    return np.array([[sp.diff(v[0],x),  sp.diff(v[0],y)], [sp.diff(v[1],x),  sp.diff(v[1],y)]])
+
+# coordinates
+x, y = sp.symbols("x y")
+
+# analytical solutions for v and p in free-flow domain (see reference given in problems)
+def analyticalSolutionStokes(case):
+    if case == "Rybak":
+        vFF = np.array([-sp.cos(sp.pi*x)*sp.sin(sp.pi*y), sp.sin(sp.pi*x)*sp.cos(sp.pi*y)])
+        pFF = 0.5*y*sp.sin(sp.pi*x)
+    elif case == "Shiue_1":
+        vFF = np.array([-1.0/sp.pi * sp.exp(y) * sp.sin(sp.pi*x), (sp.exp(y) - sp.exp(1)) * sp.cos(sp.pi*x)])
+        pFF = 2.0*sp.exp(y) * sp.cos(sp.pi*x)
+    elif case == "Shiue_2":
+        vFF = np.array([(y-1.0)*(y-1.0) + x*(y-1.0) + 3.0*x - 1.0, x*(x-1.0) - 0.5*(y-1.0)*(y-1.0) - 3.0*y + 1.0])
+        pFF = 2.0*x + y - 1.0
+    else: # Schneider
+        vFF = np.array([y, -y*sp.sin(sp.pi*x)])
+        pFF = -y*y*sp.sin(sp.pi*x)*sp.sin(sp.pi*x)
+    return [vFF, pFF]
+
+vFF, pFF = analyticalSolutionStokes(case)
+
+# individual terms of the Navier-Stokes eq.
+vvT = np.outer(vFF, vFF)
+gradV = grad(vFF)
+gradVT = grad(vFF).T
+pI = np.array([[pFF,  0], [0,  pFF]])
+
+# complete momentum flux and its divergence
+if case == "Schneider":
+    momentumFlux = vvT - (gradV + gradVT) +pI
+else:
+    momentumFlux = - (gradV + gradVT) +pI # only Stokes
+divMomentumFlux = div(momentumFlux)
+
+print("\n\nStress tensor flux for case", case)
+print("-- [0][0]: ", sp.simplify(momentumFlux[0][0]))
+print("-- [1][1]: ", sp.simplify(momentumFlux[1][1]))
+print("-- [0][1] == [1][0]: ", sp.simplify(momentumFlux[0][1]))
+
+print("\n\nSource terms for case", case)
+
+# solution for source term
+print(" \nStokes:")
+print("-- Source term mass:", sp.diff(vFF[0],x) + sp.diff(vFF[1], y))
+print("-- Source term momentum x:", divMomentumFlux[0])
+print("-- Source term momentum y:", divMomentumFlux[1])
+
+# analytical solutions for p in Darcy domain (see reference given in problems)
+def analyticalSolutionDarcy(case):
+    if case == "Rybak":
+        pD = 0.5*y*y*sp.sin(sp.pi*x)
+    elif case == "Shiue_1":
+        pD = (sp.exp(y) - y*sp.exp(1)) * sp.cos(sp.pi*x)
+    elif case == "Shiue_2":
+        pD = x*(1.0-x)*(y-1.0) + pow(y-1.0, 3)/3.0 + 2.0*x + 2.0*y + 4.0
+    else: # Schneider
+        pD = (sp.exp(y+1) + 2.0 - sp.exp(2))*sp.sin(sp.pi*x)
+    return pD
+
+pD = analyticalSolutionDarcy(case)
+
+gradPdK = np.array([sp.diff(pD,x), sp.diff(pD,y)])
+vD = -gradPdK
+divVd = sp.diff(vD[0],x) + sp.diff(vD[1],y)
+
+print("\nDarcy:")
+print("-- Source term mass:", simplify(divVd))
+print("-- v x:", simplify(vD[0]))
+print("-- v_y", simplify(vD[1]))
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/params.input b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/params.input
new file mode 100644
index 0000000000000000000000000000000000000000..6d656742ed3c074b442d523783964a7dc81bf8b9
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/params.input
@@ -0,0 +1,34 @@
+[Darcy.Grid]
+UpperRight = 1 1
+Cells = 40 40
+
+[FreeFlow.Grid]
+LowerLeft = 0 1
+UpperRight = 1 2
+Cells = 40 40
+
+[FreeFlow.Problem]
+Name = freeFlow
+EnableInertiaTerms = false
+
+[Darcy.Problem]
+Name = darcy
+
+[Darcy.SpatialParams]
+AlphaBeaversJoseph = 1.0
+
+[Problem]
+EnableGravity = false
+
+[Vtk]
+AddVelocity = 1
+
+[Component]
+LiquidDensity = 1.0
+LiquidKinematicViscosity = 1.0
+
+[Assembly]
+NumericDifference.BaseEpsilon = 1e-4
+
+[FreeFlow.Flux]
+UpwindWeight = 0.5
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/problem_darcy.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/problem_darcy.hh
new file mode 100644
index 0000000000000000000000000000000000000000..a575666dbe796d2a1c744c75bdfbd6d1362813d0
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/problem_darcy.hh
@@ -0,0 +1,271 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief The Darcy sub-problem of coupled Stokes-Darcy convergence test
+ */
+
+#ifndef DUMUX_DARCY_SUBPROBLEM_HH
+#define DUMUX_DARCY_SUBPROBLEM_HH
+
+#include <dumux/common/boundarytypes.hh>
+#include <dumux/common/numeqvector.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/common/parameters.hh>
+
+#include <dumux/porousmediumflow/problem.hh>
+
+#include "testcase.hh"
+#include "analyticalsolutions.hh"
+
+namespace Dumux {
+
+/*!
+ * \ingroup BoundaryTests
+ * \brief The Darcy sub-problem of coupled Stokes-Darcy convergence test
+ */
+template <class TypeTag>
+class DarcySubProblem : public PorousMediumFlowProblem<TypeTag>
+{
+    using ParentType = PorousMediumFlowProblem<TypeTag>;
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using PrimaryVariables = GetPropType<TypeTag, Properties::PrimaryVariables>;
+    using NumEqVector = Dumux::NumEqVector<PrimaryVariables>;
+    using BoundaryTypes = Dumux::BoundaryTypes<GetPropType<TypeTag, Properties::ModelTraits>::numEq()>;
+    using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>;
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
+    using GridView = typename GridGeometry::GridView;
+    using FVElementGeometry = typename GridGeometry::LocalView;
+    using SubControlVolume = typename GridGeometry::SubControlVolume;
+    using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace;
+    using Element = typename GridView::template Codim<0>::Entity;
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+
+    using CouplingManager = GetPropType<TypeTag, Properties::CouplingManager>;
+
+    enum class BC {
+        dirichlet, neumann, mixed
+    };
+
+public:
+    //! export the Indices
+    using Indices = typename GetPropType<TypeTag, Properties::ModelTraits>::Indices;
+
+    DarcySubProblem(std::shared_ptr<const GridGeometry> gridGeometry,
+                    std::shared_ptr<CouplingManager> couplingManager,
+                    std::shared_ptr<typename ParentType::SpatialParams> spatialParams,
+                    const DarcyStokesTestCase testCase,
+                    const std::string& name)
+    : ParentType(gridGeometry, spatialParams, "Darcy")
+    , couplingManager_(couplingManager)
+    , testCase_(testCase)
+    {
+        problemName_ = name + "_"
+            + getParamFromGroup<std::string>(this->paramGroup(), "Problem.Name");
+
+        auto bc = getParamFromGroup<std::string>(this->paramGroup(), "Problem.BoundaryConditions", "Dirichlet");
+        if (bc == "Dirichlet")
+            boundaryConditions_ = BC::dirichlet;
+        else if (bc == "Neumann")
+            boundaryConditions_ = BC::neumann;
+        else if (bc == "Mixed")
+            boundaryConditions_ = BC::mixed;
+        else
+            DUNE_THROW(Dune::Exception, "Wrong BC type choose: Dirichlet, Neumann or Mixed");
+
+        std::cout << "Porous medium domain: Using " << bc << " boundary conditions" << std::endl;
+    }
+
+    const std::string& name() const
+    { return problemName_; }
+
+    /*!
+     * \name Problem parameters
+     */
+    // \{
+
+    Scalar temperature() const
+    { return 273.15 + 10; } // 10°C
+    // \}
+
+    /*!
+     * \name Boundary conditions
+     */
+    // \{
+
+    /*!
+      * \brief Specifies which kind of boundary condition should be
+      *        used for which equation on a given boundary control volume.
+      *
+      * \param element The element
+      * \param scvf The boundary sub control volume face
+      */
+    BoundaryTypes boundaryTypes(const Element &element, const SubControlVolumeFace &scvf) const
+    {
+        BoundaryTypes values;
+
+        if (couplingManager().isCoupled(CouplingManager::porousMediumIndex, CouplingManager::freeFlowMassIndex, scvf))
+            values.setAllCouplingNeumann();
+        else
+        {
+            if (boundaryConditions_ == BC::dirichlet)
+                values.setAllDirichlet();
+            else if (boundaryConditions_ == BC::neumann)
+                values.setAllNeumann();
+            else
+            {
+                if (onLeftBoundary_(scvf.center()))
+                    values.setAllNeumann();
+                else
+                    values.setAllDirichlet();
+            }
+        }
+
+        return values;
+    }
+
+    /*!
+     * \brief Evaluates the boundary conditions for a Dirichlet control volume.
+     *
+     * \param element The element for which the Dirichlet boundary condition is set
+     * \param scvf The boundary sub-control-volume-face
+     */
+    PrimaryVariables dirichlet(const Element &element, const SubControlVolumeFace &scvf) const
+    {
+        const auto p = fullAnalyticalSolution(scvf.center())[2];
+        return PrimaryVariables(p);
+    }
+
+    /*!
+     * \brief Evaluates the boundary conditions for a Neumann control volume.
+     *
+     * \param element The element for which the Neumann boundary condition is set
+     * \param fvGeometry The fvGeometry
+     * \param elemVolVars The element volume variables
+     * \param elemFluxVarsCache Flux variables caches for all faces in stencil
+     * \param scvf The boundary sub control volume face
+     */
+    template<class ElementVolumeVariables, class ElementFluxVarsCache>
+    NumEqVector neumann(const Element& element,
+                        const FVElementGeometry& fvGeometry,
+                        const ElementVolumeVariables& elemVolVars,
+                        const ElementFluxVarsCache& elemFluxVarsCache,
+                        const SubControlVolumeFace& scvf) const
+    {
+        NumEqVector values(0.0);
+
+        if (couplingManager().isCoupled(CouplingManager::porousMediumIndex, CouplingManager::freeFlowMassIndex, scvf))
+            values[Indices::conti0EqIdx] = couplingManager().massCouplingCondition(
+                CouplingManager::porousMediumIndex, CouplingManager::freeFlowMassIndex,
+                fvGeometry, scvf, elemVolVars
+            );
+
+        else
+        {
+            const auto sol = fullAnalyticalSolution(scvf.center());
+            const auto n = scvf.unitOuterNormal();
+            auto v = n; v[0] = sol[0]; v[1] = sol[1];
+            values[Indices::conti0EqIdx] = v*n;
+        }
+
+        return values;
+    }
+
+    // \}
+
+    /*!
+     * \name Volume terms
+     */
+    // \{
+    /*!
+     * \brief Evaluates the source term for all phases within a given
+     *        sub control volume.
+     * \param globalPos The global position
+     */
+    NumEqVector sourceAtPos(const GlobalPosition& globalPos) const
+    {
+        using namespace Solution::DarcyStokes;
+        switch (testCase_)
+        {
+            case DarcyStokesTestCase::ShiueExampleOne:
+                return ShiueOne::darcyRHS(globalPos);
+            case DarcyStokesTestCase::ShiueExampleTwo:
+                return ShiueTwo::darcyRHS(globalPos);
+            case DarcyStokesTestCase::Rybak:
+                return Rybak::darcyRHS(globalPos);
+            case DarcyStokesTestCase::Schneider:
+                return Schneider::darcyRHS(globalPos);
+            default:
+                DUNE_THROW(Dune::InvalidStateException, "Invalid test case");
+        }
+    }
+
+    // \}
+
+    /*!
+     * \brief Evaluates the initial value for a control volume.
+     * \param element The element
+     */
+    PrimaryVariables initial(const Element &element) const
+    {  return PrimaryVariables(0.0); }
+
+    /*!
+     * \brief Returns the analytical solution of the problem at a given position.
+     * \param globalPos The global position
+     * Returns vector with entries: (velocity-x | velocity-y | pressure)
+     */
+    Dune::FieldVector<Scalar, 3> fullAnalyticalSolution(const GlobalPosition& globalPos) const
+    {
+        using namespace Solution::DarcyStokes;
+        switch (testCase_)
+        {
+            case DarcyStokesTestCase::ShiueExampleOne:
+                return ShiueOne::darcy(globalPos);
+            case DarcyStokesTestCase::ShiueExampleTwo:
+                return ShiueTwo::darcy(globalPos);
+            case DarcyStokesTestCase::Rybak:
+                return Rybak::darcy(globalPos);
+            case DarcyStokesTestCase::Schneider:
+                return Schneider::darcy(globalPos);
+            default:
+                DUNE_THROW(Dune::InvalidStateException, "Invalid test case");
+        }
+    }
+
+    // \}
+
+    //! Get the coupling manager
+    const CouplingManager& couplingManager() const
+    { return *couplingManager_; }
+
+private:
+    bool onLeftBoundary_(const GlobalPosition& globalPos) const
+    { return globalPos[0] < this->gridGeometry().bBoxMin()[0] + eps_; }
+
+    static constexpr Scalar eps_ = 1e-7;
+    std::shared_ptr<CouplingManager> couplingManager_;
+    std::string problemName_;
+    DarcyStokesTestCase testCase_;
+    BC boundaryConditions_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/problem_freeflow.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/problem_freeflow.hh
new file mode 100644
index 0000000000000000000000000000000000000000..9ef9fd3db9a051d18c4c65faf43449b7cc663c7a
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/problem_freeflow.hh
@@ -0,0 +1,391 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief The free-flow sub-problem of coupled FreeFlow/Darcy convergence test
+ */
+
+#ifndef DUMUX_FREEFLOW_SUBPROBLEM_HH
+#define DUMUX_FREEFLOW_SUBPROBLEM_HH
+
+#include <dumux/freeflow/navierstokes/problem.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/freeflow/navierstokes/momentum/fluxhelper.hh>
+#include <dumux/freeflow/navierstokes/scalarfluxhelper.hh>
+#include <dune/common/fmatrix.hh>
+
+#include "testcase.hh"
+#include "analyticalsolutions.hh"
+
+namespace Dumux {
+
+/*!
+ * \ingroup BoundaryTests
+ * \brief The Stokes sub-problem of coupled Stokes-Darcy convergence test
+ */
+template <class TypeTag>
+class FreeFlowSubProblem : public NavierStokesProblem<TypeTag>
+{
+    using ParentType = NavierStokesProblem<TypeTag>;
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
+    using GridView = typename GridGeometry::GridView;
+    using FVElementGeometry = typename GridGeometry::LocalView;
+    using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace;
+    using Element = typename GridView::template Codim<0>::Entity;
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+    using PrimaryVariables = typename ParentType::PrimaryVariables;
+    using NumEqVector = typename ParentType::NumEqVector;
+    using BoundaryTypes = typename ParentType::BoundaryTypes;
+    using ModelTraits = GetPropType<TypeTag, Properties::ModelTraits>;
+
+    using CouplingManager = GetPropType<TypeTag, Properties::CouplingManager>;
+
+    enum class BC {
+        dirichlet, stress, mixed
+    };
+
+public:
+    //! export the Indices
+    using Indices = typename ModelTraits::Indices;
+
+    FreeFlowSubProblem(std::shared_ptr<const GridGeometry> gridGeometry,
+                       std::shared_ptr<CouplingManager> couplingManager,
+                       const DarcyStokesTestCase testCase,
+                       const std::string& name)
+    : ParentType(gridGeometry, couplingManager, "FreeFlow")
+    , couplingManager_(couplingManager)
+    , testCase_(testCase)
+    {
+        problemName_ = name + "_"
+            + getParamFromGroup<std::string>(this->paramGroup(), "Problem.Name");
+
+        auto bc = getParamFromGroup<std::string>(this->paramGroup(), "Problem.BoundaryConditions", "Dirichlet");
+        if (bc == "Dirichlet")
+            boundaryConditions_ = BC::dirichlet;
+        else if (bc == "Stress")
+            boundaryConditions_ = BC::stress;
+        else if (bc == "Mixed")
+            boundaryConditions_ = BC::mixed;
+        else
+            DUNE_THROW(Dune::Exception, "Wrong BC type choose: Dirichlet, Stress or Mixed");
+
+        std::cout << "Free flow domain: Using " << bc << " boundary conditions" << std::endl;
+    }
+
+    /*!
+     * \name Problem parameters
+     */
+    // \{
+
+    const std::string& name() const
+    { return problemName_; }
+
+    /*!
+     * \brief Returns the temperature within the domain in [K].
+     *
+     * This problem assumes a temperature of 10 degrees Celsius.
+     */
+    Scalar temperature() const
+    { return 273.15 + 10; } // 10°C
+
+    // \}
+
+   /*!
+     * \name Boundary conditions
+     */
+    // \{
+
+    /*!
+     * \brief Specifies which kind of boundary condition should be
+     *        used for which equation on a given boundary segment.
+     *
+     * \param element The finite element
+     * \param scvf The sub control volume face
+     */
+    BoundaryTypes boundaryTypes(const Element& element,
+                                const SubControlVolumeFace& scvf) const
+    {
+        BoundaryTypes values;
+
+        if constexpr (ParentType::isMomentumProblem())
+        {
+            if (couplingManager().isCoupled(CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex, scvf))
+            {
+                values.setCouplingNeumann(Indices::momentumYBalanceIdx);
+                values.setCouplingNeumann(Indices::momentumXBalanceIdx);
+            }
+            else
+            {
+                if (boundaryConditions_ == BC::dirichlet)
+                    values.setAllDirichlet();
+                else if (boundaryConditions_ == BC::stress)
+                    values.setAllNeumann();
+                else
+                {
+                    if (onLeftBoundary_(scvf.center()))
+                        values.setAllNeumann();
+                    else
+                        values.setAllDirichlet();
+                }
+            }
+        }
+        else
+        {
+            if (couplingManager().isCoupled(CouplingManager::freeFlowMassIndex, CouplingManager::porousMediumIndex, scvf))
+                values.setAllCouplingNeumann();
+            else
+                values.setAllNeumann();
+        }
+
+        return values;
+    }
+
+    /*!
+     * \brief Evaluates the boundary conditions for a Dirichlet control volume.
+     */
+    PrimaryVariables dirichletAtPos(const GlobalPosition& globalPos) const
+    { return analyticalSolution(globalPos); }
+
+    /*!
+     * \brief Evaluates the boundary conditions for a Neumann control volume.
+     *
+     * \param element The element for which the Neumann boundary condition is set
+     * \param fvGeometry The fvGeometry
+     * \param elemVolVars The element volume variables
+     * \param elemFaceVars The element face variables
+     * \param scvf The boundary sub control volume face
+     */
+    template<class ElementVolumeVariables, class ElementFluxVariablesCache>
+    NumEqVector neumann(const Element& element,
+                        const FVElementGeometry& fvGeometry,
+                        const ElementVolumeVariables& elemVolVars,
+                        const ElementFluxVariablesCache& elemFluxVarsCache,
+                        const SubControlVolumeFace& scvf) const
+    {
+        NumEqVector values(0.0);
+        const auto& globalPos = scvf.ipGlobal();
+        using FluxHelper = NavierStokesMomentumBoundaryFluxHelper;
+
+        // momentum boundary conditions
+        if constexpr (ParentType::isMomentumProblem())
+        {
+            // We need to take care here: If the integration point of an scvf lies both on the coupling interface and the
+            // domain boundary (i.e., the leftmost and rightmost points of the interface), do not evaluate the coupling or slip condition
+            // because they would require data from the boundary not available in this case (velocities for evaluating gradients,
+            // those would only be available for Dirichlet BCs). Instead, directly use the given Neumann boundary stress.
+            // TODO: Maybe couplingManager().isCoupled(...) could return false for these scvfs.
+            //
+            //                       | <-- left Neumann boundary
+            //                       |
+            // integration point --> o##### <-- scvf lying on coupling interface with integration point touching left Neumann boundary
+            //
+            if (couplingManager().isCoupled(CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex, scvf)
+                && scvf.ipGlobal()[0] > this->gridGeometry().bBoxMin()[0] + eps_
+                && scvf.ipGlobal()[0] < this->gridGeometry().bBoxMax()[0] - eps_)
+            {
+                values += couplingManager().momentumCouplingCondition(
+                    CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex,
+                    fvGeometry, scvf, elemVolVars
+                );
+
+                values += FluxHelper::slipVelocityMomentumFlux(
+                    *this, fvGeometry, scvf, elemVolVars, elemFluxVarsCache
+                );
+            }
+            else
+            {
+                const auto stress = stressTensorAtPos(globalPos);
+                const auto n = scvf.unitOuterNormal();
+                stress.mv(n, values);
+            }
+        }
+
+        // mass boundary conditions
+        else
+        {
+            if (couplingManager().isCoupled(CouplingManager::freeFlowMassIndex, CouplingManager::porousMediumIndex, scvf))
+            {
+                values = couplingManager().massCouplingCondition(
+                    CouplingManager::freeFlowMassIndex, CouplingManager::porousMediumIndex,
+                    fvGeometry, scvf, elemVolVars
+                );
+            }
+            else
+            {
+                using FluxHelper = NavierStokesScalarBoundaryFluxHelper<AdvectiveFlux<ModelTraits>>;
+                values = FluxHelper::scalarOutflowFlux(
+                    *this, element, fvGeometry, scvf, elemVolVars
+                );
+            }
+        }
+
+        return values;
+    }
+
+    /*!
+     * \brief Returns true if the scvf lies on a porous slip boundary
+     */
+    bool onSlipBoundary(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const
+    {
+        return scvf.boundary()
+            && couplingManager().isCoupled(
+                CouplingManager::freeFlowMomentumIndex,
+                CouplingManager::porousMediumIndex,
+                scvf
+            );
+    }
+
+    // \}
+
+    //! Get the coupling manager
+    const CouplingManager& couplingManager() const
+    { return *couplingManager_; }
+
+    /*!
+     * \name Volume terms
+     */
+    // \{
+
+    /*!
+     * \brief Returns the sources within the domain.
+     * \param globalPos The global position
+     */
+    NumEqVector sourceAtPos(const GlobalPosition &globalPos) const
+    {
+        const auto select = [&](const auto& rhs) -> NumEqVector {
+            if constexpr (ParentType::isMomentumProblem())
+                return { rhs[0], rhs[1] };
+            else
+                return { rhs[2] };
+        };
+
+        using namespace Solution::DarcyStokes;
+        switch (testCase_)
+        {
+            case DarcyStokesTestCase::ShiueExampleOne:
+                return select(ShiueOne::stokesRHS(globalPos));
+            case DarcyStokesTestCase::ShiueExampleTwo:
+                return select(ShiueTwo::stokesRHS(globalPos));
+            case DarcyStokesTestCase::Rybak:
+                return select(Rybak::stokesRHS(globalPos));
+            case DarcyStokesTestCase::Schneider:
+                return select(Schneider::stokesRHS(globalPos));
+            default:
+                DUNE_THROW(Dune::InvalidStateException, "Invalid test case");
+        }
+    }
+
+    /*!
+     * \brief Evaluates the initial value for a control volume.
+     * \param globalPos The global position
+     */
+    PrimaryVariables initialAtPos(const GlobalPosition& globalPos) const
+    { return PrimaryVariables(0.0); }
+
+    /*!
+     * \brief Returns the intrinsic permeability of required as input parameter
+              for the Beavers-Joseph-Saffman boundary condition
+     */
+    auto permeability(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const
+    { return couplingManager().darcyPermeability(fvGeometry, scvf); }
+
+    /*!
+     * \brief Returns the alpha value required as input parameter for the
+              Beavers-Joseph-Saffman boundary condition.
+     */
+    Scalar alphaBJ(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const
+    { return couplingManager().problem(CouplingManager::porousMediumIndex).spatialParams().beaversJosephCoeffAtPos(scvf.ipGlobal()); }
+
+    /*!
+     * \brief Returns the analytical solution of the problem at a given position.
+     * \param globalPos The global position
+     * Returns vector with entries: (velocity-x | velocity-y | pressure)
+     */
+    Dune::FieldVector<Scalar, 3> fullAnalyticalSolution(const GlobalPosition& globalPos) const
+    {
+        using namespace Solution::DarcyStokes;
+        switch (testCase_)
+        {
+            case DarcyStokesTestCase::ShiueExampleOne:
+                return ShiueOne::stokes(globalPos);
+            case DarcyStokesTestCase::ShiueExampleTwo:
+                return ShiueTwo::stokes(globalPos);
+            case DarcyStokesTestCase::Rybak:
+                return Rybak::stokes(globalPos);
+            case DarcyStokesTestCase::Schneider:
+                return Schneider::stokes(globalPos);
+            default:
+                DUNE_THROW(Dune::InvalidStateException, "Invalid test case");
+        }
+    }
+
+    /*!
+     * \brief Returns the analytical solution of the problem at a given position.
+     * \param globalPos The global position
+     * Returns analytical solution depending on the type of sub-problem
+     */
+    PrimaryVariables analyticalSolution(const GlobalPosition& globalPos, Scalar time = 0.0) const
+    {
+        using namespace Solution::DarcyStokes;
+        const auto sol = fullAnalyticalSolution(globalPos);
+        if constexpr (ParentType::isMomentumProblem())
+            return { sol[0], sol[1] };
+        else
+            return { sol[2] };
+    }
+
+    // \}
+
+private:
+    /*!
+     * \brief Returns the stress tensor within the domain.
+     * \param globalPos The global position
+     */
+    Dune::FieldMatrix<double, 2, 2> stressTensorAtPos(const GlobalPosition &globalPos) const
+    {
+        using namespace Solution::DarcyStokes;
+        switch (testCase_)
+        {
+            case DarcyStokesTestCase::ShiueExampleOne:
+                return ShiueOne::stokesStress(globalPos);
+            case DarcyStokesTestCase::ShiueExampleTwo:
+                return ShiueTwo::stokesStress(globalPos);
+            case DarcyStokesTestCase::Rybak:
+                return Rybak::stokesStress(globalPos);
+            case DarcyStokesTestCase::Schneider:
+                return Schneider::stokesStress(globalPos);
+            default:
+                DUNE_THROW(Dune::InvalidStateException, "Invalid test case");
+        }
+    }
+
+    bool onLeftBoundary_(const GlobalPosition& globalPos) const
+    { return globalPos[0] < this->gridGeometry().bBoxMin()[0] + eps_; }
+
+    static constexpr Scalar eps_ = 1e-7;
+    std::string problemName_;
+    std::shared_ptr<CouplingManager> couplingManager_;
+    DarcyStokesTestCase testCase_;
+    BC boundaryConditions_;
+};
+} // end namespace Dumux
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/properties.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/properties.hh
new file mode 100644
index 0000000000000000000000000000000000000000..70c62ded2fe14d3e218baeaee6951bc20f4f72a7
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/properties.hh
@@ -0,0 +1,129 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief The properties for a simple Darcy test (cell-centered finite volume method)
+ */
+#ifndef DUMUX_DARCYSTOKES_PROPERTIES_HH
+#define DUMUX_DARCYSTOKES_PROPERTIES_HH
+
+#include <dune/grid/yaspgrid.hh>
+
+#include <dumux/material/fluidsystems/1pliquid.hh>
+#include <dumux/material/components/constant.hh>
+
+#include <dumux/porousmediumflow/1p/model.hh>
+#include <dumux/freeflow/navierstokes/mass/1p/model.hh>
+#include <dumux/freeflow/navierstokes/momentum/model.hh>
+#include <dumux/discretization/cctpfa.hh>
+#include <dumux/discretization/fcstaggered.hh>
+
+#include <dumux/multidomain/boundary/freeflowporousmedium/couplingmanager.hh>
+#include <dumux/multidomain/traits.hh>
+
+#include "spatialparams.hh"
+#include "problem_darcy.hh"
+#include "problem_freeflow.hh"
+
+namespace Dumux::Properties {
+
+// Create new type tags
+namespace TTag {
+struct DarcyOneP { using InheritsFrom = std::tuple<OneP, CCTpfaModel>; };
+} // end namespace TTag
+
+// Set the problem property
+template<class TypeTag>
+struct Problem<TypeTag, TTag::DarcyOneP>
+{ using type = DarcySubProblem<TypeTag>; };
+
+// the fluid system
+template<class TypeTag>
+struct FluidSystem<TypeTag, TTag::DarcyOneP>
+{
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using type = FluidSystems::OnePLiquid<Scalar, Components::Constant<1, Scalar> > ;
+};
+
+// Set the grid type
+template<class TypeTag>
+struct Grid<TypeTag, TTag::DarcyOneP>
+{ using type = Dune::YaspGrid<2>; };
+
+template<class TypeTag>
+struct SpatialParams<TypeTag, TTag::DarcyOneP>
+{
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using type = ConvergenceTestSpatialParams<GridGeometry, Scalar>;
+};
+
+// Create new type tags
+namespace TTag {
+struct FreeFlowOneP {};
+struct FreeFlowOnePMass { using InheritsFrom = std::tuple<FreeFlowOneP, NavierStokesMassOneP, CCTpfaModel>; };
+struct FreeFlowOnePMomentum { using InheritsFrom = std::tuple<FreeFlowOneP, NavierStokesMomentum, FaceCenteredStaggeredModel>; };
+} // end namespace TTag
+
+// the fluid system
+template<class TypeTag>
+struct FluidSystem<TypeTag, TTag::FreeFlowOneP>
+{
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using type = FluidSystems::OnePLiquid<Scalar, Components::Constant<1, Scalar> > ;
+};
+
+// Set the grid type
+template<class TypeTag>
+struct Grid<TypeTag, TTag::FreeFlowOneP>
+{
+    using Coords = Dune::EquidistantOffsetCoordinates<GetPropType<TypeTag, Properties::Scalar>, 2>;
+    using type = Dune::YaspGrid<2, Coords>;
+};
+
+// Set the problem property
+template<class TypeTag>
+struct Problem<TypeTag, TTag::FreeFlowOneP>
+{ using type = FreeFlowSubProblem<TypeTag>; };
+
+template<class TypeTag>
+struct EnableGridGeometryCache<TypeTag, TTag::FreeFlowOneP> { static constexpr bool value = true; };
+template<class TypeTag>
+struct EnableGridFluxVariablesCache<TypeTag, TTag::FreeFlowOneP> { static constexpr bool value = true; };
+template<class TypeTag>
+struct EnableGridVolumeVariablesCache<TypeTag, TTag::FreeFlowOneP> { static constexpr bool value = true; };
+
+template<class TypeTag>
+struct CouplingManager<TypeTag, TTag::DarcyOneP>
+{
+    using Traits = MultiDomainTraits<TTag::FreeFlowOnePMomentum, TTag::FreeFlowOnePMass, TTag::DarcyOneP>;
+    using type = FreeFlowPorousMediumCouplingManager<Traits>;
+};
+
+template<class TypeTag>
+struct CouplingManager<TypeTag, TTag::FreeFlowOneP>
+{
+    using Traits = MultiDomainTraits<TTag::FreeFlowOnePMomentum, TTag::FreeFlowOnePMass, TTag::DarcyOneP>;
+    using type = FreeFlowPorousMediumCouplingManager<Traits>;
+};
+
+} // end namespace Dumux::Properties
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/spatialparams.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/spatialparams.hh
new file mode 100644
index 0000000000000000000000000000000000000000..82eb797c14cf2134c53b701a9524a32a39056cd3
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/spatialparams.hh
@@ -0,0 +1,129 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief The spatial parameters class for the test problem using the 1p cc model.
+ */
+
+#ifndef DUMUX_1P_TEST_SPATIALPARAMS_HH
+#define DUMUX_1P_TEST_SPATIALPARAMS_HH
+
+#include <dumux/material/spatialparams/fv1p.hh>
+#include "testcase.hh"
+
+namespace Dumux {
+
+/*!
+ * \ingroup BoundaryTests
+ * \brief The spatial parameters class for the test problem using the
+ *        1p cc model.
+ */
+template<class GridGeometry, class Scalar>
+class ConvergenceTestSpatialParams
+: public FVSpatialParamsOneP<GridGeometry, Scalar,
+                             ConvergenceTestSpatialParams<GridGeometry, Scalar>>
+{
+    using GridView = typename GridGeometry::GridView;
+    using ParentType = FVSpatialParamsOneP<
+        GridGeometry, Scalar, ConvergenceTestSpatialParams<GridGeometry, Scalar>
+    >;
+
+    using Element = typename GridView::template Codim<0>::Entity;
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+
+    static constexpr int dimWorld = GridView::dimensionworld;
+    using DimWorldMatrix = Dune::FieldMatrix<Scalar, dimWorld, dimWorld>;
+
+
+public:
+    // export permeability type
+    using PermeabilityType = DimWorldMatrix;
+
+    ConvergenceTestSpatialParams(std::shared_ptr<const GridGeometry> gridGeometry,
+                                 const DarcyStokesTestCase testCase)
+    : ParentType(gridGeometry)
+    , testCase_(testCase)
+    {
+        alphaBJ_ = getParam<Scalar>("Darcy.SpatialParams.AlphaBeaversJoseph");
+    }
+
+   /*!
+     * \brief Function for defining the (intrinsic) permeability \f$[m^2]\f$.
+     *
+     * \param element The element
+     * \param scv The sub control volume
+     * \param elemSol The element solution vector
+     * \return the intrinsic permeability
+     */
+    template<class SubControlVolume, class ElementSolution>
+    PermeabilityType permeability(const Element& element,
+                                  const SubControlVolume& scv,
+                                  const ElementSolution& elemSol) const
+    {
+        PermeabilityType K(0.0);
+
+        if (testCase_ == DarcyStokesTestCase::Schneider)
+        {
+            using std::cos;
+            using std::sin;
+            using std::exp;
+
+            static constexpr Scalar c = 0.0;
+            static constexpr Scalar omega = M_PI;
+
+            const Scalar x = scv.center()[0];
+            K[0][0] = 1.0;
+            K[0][1] = -c/(2*omega) * sin(omega*x);
+            K[1][0] = K[0][1];
+            K[1][1] = exp(-2)*(1 + c*cos(omega*x));
+        }
+        else
+        {
+            const static Scalar permeability = getParam<Scalar>("Darcy.SpatialParams.Permeability", 1.0);
+            K[0][0] = permeability;
+            K[1][1] = permeability;
+        }
+
+        return K;
+    }
+
+    /*! \brief Defines the porosity in [-].
+     *
+     * \param globalPos The global position
+     */
+    Scalar porosityAtPos(const GlobalPosition& globalPos) const
+    { return 0.4; }
+
+    /*! \brief Defines the Beavers-Joseph coefficient in [-].
+     *
+     * \param globalPos The global position
+     */
+    Scalar beaversJosephCoeffAtPos(const GlobalPosition& globalPos) const
+    { return alphaBJ_; }
+
+private:
+    DarcyStokesTestCase testCase_;
+    Scalar permeability_;
+    Scalar alphaBJ_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/testcase.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/testcase.hh
new file mode 100644
index 0000000000000000000000000000000000000000..9496ffe6ae773a194afc28d6ef1d6ca0e070d4b2
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/convergence/testcase.hh
@@ -0,0 +1,37 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief The different test cases
+ */
+
+#ifndef DUMUX_CONVERGENCE_TEST_TESTCASE_HH
+#define DUMUX_CONVERGENCE_TEST_TESTCASE_HH
+
+namespace Dumux {
+
+enum class DarcyStokesTestCase
+{
+    ShiueExampleOne, ShiueExampleTwo, Rybak, Schneider
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/main.cc b/test/multidomain/boundary/freeflowporousmedium/1p_1p/main.cc
new file mode 100644
index 0000000000000000000000000000000000000000..17abbaac06ee5cc9eb75793adddd40c1e1433c10
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/main.cc
@@ -0,0 +1,213 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief A test problem for the coupled Stokes/Darcy problem (1p).
+ */
+
+#include <config.h>
+
+#include <ctime>
+#include <iostream>
+
+#include <dune/common/parallel/mpihelper.hh>
+#include <dune/common/timer.hh>
+
+#include <dumux/common/properties.hh>
+#include <dumux/common/parameters.hh>
+#include <dumux/common/dumuxmessage.hh>
+#include <dumux/linear/seqsolverbackend.hh>
+#include <dumux/assembly/fvassembler.hh>
+#include <dumux/assembly/diffmethod.hh>
+#include <dumux/discretization/method.hh>
+#include <dumux/io/vtkoutputmodule.hh>
+#include <dumux/io/grid/gridmanager_yasp.hh>
+
+#include <dumux/multidomain/fvassembler.hh>
+#include <dumux/multidomain/newtonsolver.hh>
+#include <dumux/freeflow/navierstokes/velocityoutput.hh>
+
+#include "properties.hh"
+
+int main(int argc, char** argv)
+{
+    using namespace Dumux;
+
+    // initialize MPI, finalize is done automatically on exit
+    const auto& mpiHelper = Dune::MPIHelper::instance(argc, argv);
+
+    // print dumux start message
+    if (mpiHelper.rank() == 0)
+        DumuxMessage::print(/*firstCall=*/true);
+
+    // parse command line arguments and input file
+    Parameters::init(argc, argv);
+
+    // Define the sub problem type tags
+    using FreeFlowMomentumTypeTag = Properties::TTag::FreeFlowOnePMomentum;
+    using FreeFlowMassTypeTag = Properties::TTag::FreeFlowOnePMass;
+    using DarcyTypeTag = Properties::TTag::DarcyOneP;
+
+    // try to create a grid (from the given grid file or the input file)
+    // for both sub-domains
+    using DarcyGridManager = Dumux::GridManager<GetPropType<DarcyTypeTag, Properties::Grid>>;
+    DarcyGridManager darcyGridManager;
+    darcyGridManager.init("Darcy"); // pass parameter group
+
+    using FreeFlowGridManager = Dumux::GridManager<GetPropType<FreeFlowMomentumTypeTag, Properties::Grid>>;
+    FreeFlowGridManager freeFlowGridManager;
+    freeFlowGridManager.init("FreeFlow"); // pass parameter group
+
+    // we compute on the leaf grid view
+    const auto& darcyGridView = darcyGridManager.grid().leafGridView();
+    const auto& freeFlowGridView = freeFlowGridManager.grid().leafGridView();
+
+    // create the finite volume grid geometry
+    using FreeFlowMomentumGridGeometry = GetPropType<FreeFlowMomentumTypeTag, Properties::GridGeometry>;
+    auto freeFlowMomentumGridGeometry = std::make_shared<FreeFlowMomentumGridGeometry>(freeFlowGridView);
+    using FreeFlowMassGridGeometry = GetPropType<FreeFlowMassTypeTag, Properties::GridGeometry>;
+    auto freeFlowMassGridGeometry = std::make_shared<FreeFlowMassGridGeometry>(freeFlowGridView);
+    using DarcyGridGeometry = GetPropType<DarcyTypeTag, Properties::GridGeometry>;
+    auto darcyGridGeometry = std::make_shared<DarcyGridGeometry>(darcyGridView);
+
+    using Traits = MultiDomainTraits<FreeFlowMomentumTypeTag, FreeFlowMassTypeTag, DarcyTypeTag>;
+    using CouplingManager = FreeFlowPorousMediumCouplingManager<Traits>;
+    auto couplingManager = std::make_shared<CouplingManager>();
+
+    // the indices
+    constexpr auto freeFlowMomentumIndex = CouplingManager::freeFlowMomentumIndex;
+    constexpr auto freeFlowMassIndex = CouplingManager::freeFlowMassIndex;
+    constexpr auto porousMediumIndex = CouplingManager::porousMediumIndex;
+
+    // the problem (initial and boundary conditions)
+    using FreeFlowMomentumProblem = GetPropType<FreeFlowMomentumTypeTag, Properties::Problem>;
+    auto freeFlowMomentumProblem = std::make_shared<FreeFlowMomentumProblem>(freeFlowMomentumGridGeometry, couplingManager);
+    using FreeFlowMassProblem = GetPropType<FreeFlowMassTypeTag, Properties::Problem>;
+    auto freeFlowMassProblem = std::make_shared<FreeFlowMassProblem>(freeFlowMassGridGeometry, couplingManager);
+    using DarcyProblem = GetPropType<DarcyTypeTag, Properties::Problem>;
+    auto darcyProblem = std::make_shared<DarcyProblem>(darcyGridGeometry, couplingManager);
+
+    // the solution vector
+    Traits::SolutionVector sol;
+    sol[freeFlowMomentumIndex].resize(freeFlowMomentumGridGeometry->numDofs());
+    sol[freeFlowMassIndex].resize(freeFlowMassGridGeometry->numDofs());
+    sol[porousMediumIndex].resize(darcyGridGeometry->numDofs());
+
+    // the grid variables
+    using FreeFlowMomentumGridVariables = GetPropType<FreeFlowMomentumTypeTag, Properties::GridVariables>;
+    auto freeFlowMomentumGridVariables = std::make_shared<FreeFlowMomentumGridVariables>(freeFlowMomentumProblem, freeFlowMomentumGridGeometry);
+    using DarcyGridVariables = GetPropType<DarcyTypeTag, Properties::GridVariables>;
+    using FreeFlowMassGridVariables = GetPropType<FreeFlowMassTypeTag, Properties::GridVariables>;
+    auto freeFlowMassGridVariables = std::make_shared<FreeFlowMassGridVariables>(freeFlowMassProblem, freeFlowMassGridGeometry);
+    using DarcyGridVariables = GetPropType<DarcyTypeTag, Properties::GridVariables>;
+    auto darcyGridVariables = std::make_shared<DarcyGridVariables>(darcyProblem, darcyGridGeometry);
+
+    couplingManager->init(freeFlowMomentumProblem, freeFlowMassProblem, darcyProblem,
+                          std::make_tuple(freeFlowMomentumGridVariables, freeFlowMassGridVariables, darcyGridVariables),
+                          sol);
+
+    freeFlowMomentumGridVariables->init(sol[freeFlowMomentumIndex]);
+    freeFlowMassGridVariables->init(sol[freeFlowMassIndex]);
+    darcyGridVariables->init(sol[porousMediumIndex]);
+
+    // initialize the vtk output module
+    VtkOutputModule vtkWriterFF(*freeFlowMassGridVariables, sol[freeFlowMassIndex], freeFlowMassProblem->name());
+    GetPropType<FreeFlowMassTypeTag, Properties::IOFields>::initOutputModule(vtkWriterFF); // Add model specific output fields
+    vtkWriterFF.addVelocityOutput(std::make_shared<NavierStokesVelocityOutput<FreeFlowMassGridVariables>>());
+
+    const bool addAnalyticSolutionForV = getParamFromGroup<bool>(freeFlowMassProblem->paramGroup(), "Problem.AddAnalyticSolutionForV", false);
+
+    if (!freeFlowMassProblem->verticalFlow() && addAnalyticSolutionForV)
+    {
+        using Scalar = typename Traits::Scalar;
+        using std::sqrt;
+        std::vector<Scalar> analyticalVelocityX(freeFlowMassGridGeometry->gridView().size(0));
+        const Scalar dPdX = -freeFlowMomentumProblem->pressureDifference() / (freeFlowMassGridGeometry->bBoxMax()[0] - freeFlowMassGridGeometry->bBoxMin()[0]);
+        static const Scalar mu = getParam<Scalar>("Component.LiquidKinematicViscosity") * getParam<Scalar>("Component.LiquidDensity");
+        static const Scalar alpha = getParam<Scalar>("Darcy.SpatialParams.AlphaBeaversJoseph");
+        static const Scalar K = getParam<Scalar>("Darcy.SpatialParams.Permeability");
+        static const Scalar sqrtK = sqrt(K);
+        const Scalar sigma = (freeFlowMassGridGeometry->bBoxMax()[1] - freeFlowMassGridGeometry->bBoxMin()[1])/sqrtK;
+
+        const Scalar uB =  -K/(2.0*mu) * ((sigma*sigma + 2.0*alpha*sigma) / (1.0 + alpha*sigma)) * dPdX;
+
+        for (const auto& element : elements(freeFlowMassGridGeometry->gridView()))
+        {
+            const auto eIdx = freeFlowMassGridGeometry->gridView().indexSet().index(element);
+            const Scalar y = element.geometry().center()[1] - freeFlowMassGridGeometry->bBoxMin()[1];
+
+            const Scalar u = uB*(1.0 + alpha/sqrtK*y) + 1.0/(2.0*mu) * (y*y + 2*alpha*y*sqrtK) * dPdX;
+            analyticalVelocityX[eIdx] = u;
+        }
+
+        vtkWriterFF.addField(analyticalVelocityX, "analyticalV_x");
+    }
+
+    VtkOutputModule pmVtkWriter(*darcyGridVariables, sol[porousMediumIndex],  darcyProblem->name());
+    GetPropType<DarcyTypeTag, Properties::IOFields>::initOutputModule(pmVtkWriter);
+    pmVtkWriter.addVelocityOutput(std::make_shared<GetPropType<DarcyTypeTag, Properties::VelocityOutput>>(*darcyGridVariables));
+
+    // the assembler for a stationary problem
+    using Assembler = MultiDomainFVAssembler<Traits, CouplingManager, DiffMethod::numeric>;
+    auto assembler = std::make_shared<Assembler>(std::make_tuple(freeFlowMomentumProblem, freeFlowMassProblem, darcyProblem),
+                                                 std::make_tuple(freeFlowMomentumGridGeometry,
+                                                                 freeFlowMassGridGeometry,
+                                                                 darcyGridGeometry),
+                                                 std::make_tuple(freeFlowMomentumGridVariables,
+                                                                 freeFlowMassGridVariables,
+                                                                 darcyGridVariables),
+                                                 couplingManager);
+
+    // the linear solver
+    using LinearSolver = UMFPackBackend;
+    auto linearSolver = std::make_shared<LinearSolver>();
+
+    // the non-linear solver
+    using NewtonSolver = MultiDomainNewtonSolver<Assembler, LinearSolver, CouplingManager>;
+    NewtonSolver nonLinearSolver(assembler, linearSolver, couplingManager);
+
+    const auto dofsVS = freeFlowMomentumGridGeometry->numDofs();
+    const auto dofsPS = freeFlowMassGridGeometry->numDofs();
+    const auto dofsPD = darcyGridGeometry->numDofs();
+    std::cout << "Stokes velocity dofs: " << dofsVS << std::endl;
+    std::cout << "Stokes pressure dofs: " << dofsPS << std::endl;
+    std::cout << "Darcy pressure dofs: " << dofsPD << std::endl;
+    std::cout << "Total number of dofs: " << dofsVS + dofsPS + dofsPD << std::endl;
+
+    // solve the non-linear system
+    nonLinearSolver.solve(sol);
+
+    // write vtk output
+    vtkWriterFF.write(1.0);
+    pmVtkWriter.write(1.0);
+
+    ////////////////////////////////////////////////////////////
+    // finalize, print dumux message to say goodbye
+    ////////////////////////////////////////////////////////////
+
+    // print dumux end message
+    if (mpiHelper.rank() == 0)
+    {
+        Parameters::print();
+        DumuxMessage::print(/*firstCall=*/false);
+    }
+
+    return 0;
+} // end main
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/params_horizontalflow.input b/test/multidomain/boundary/freeflowporousmedium/1p_1p/params_horizontalflow.input
new file mode 100644
index 0000000000000000000000000000000000000000..3da8b5649dfa203a9fc834ded0109be31b547c6c
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/params_horizontalflow.input
@@ -0,0 +1,33 @@
+[Darcy.Grid]
+UpperRight = 1 1
+Cells = 20 20
+
+[FreeFlow.Grid]
+LowerLeft = 0 1
+UpperRight = 1 2
+Cells = 20 20
+
+[FreeFlow.Problem]
+Name = freeflow
+PressureDifference = 1e-9
+
+[Darcy.Problem]
+Name = darcy
+
+[Darcy.SpatialParams]
+Permeability = 1e-6 # m^2
+AlphaBeaversJoseph = 1.0
+
+[Component]
+LiquidKinematicViscosity = 1e-6
+LiquidDensity = 1e3
+
+[Vtk]
+OutputName = test_md_boundary_freeflow1p_darcy1p_horizontal
+
+[Problem]
+EnableGravity = false
+EnableInertiaTerms = false
+
+[Vtk]
+AddVelocity = 1
\ No newline at end of file
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/params_verticalflow.input b/test/multidomain/boundary/freeflowporousmedium/1p_1p/params_verticalflow.input
new file mode 100644
index 0000000000000000000000000000000000000000..54358b805bc91bb253dab9fdf5e0e3b6d783cd6a
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/params_verticalflow.input
@@ -0,0 +1,37 @@
+[Darcy.Grid]
+UpperRight = 2.0 2.0
+Cells = 20 20
+
+[FreeFlow.Grid]
+LowerLeft = 0.0 2.0
+UpperRight = 2.0 4.0
+Cells = 20 20
+
+[FreeFlow.Problem]
+Name = freeflow
+Velocity = -1e-6
+
+[Darcy.Problem]
+Name = darcy
+Pressure = 0.0
+
+[Darcy.SpatialParams]
+Permeability = 1e-10 # m^2
+AlphaBeaversJoseph = 1.0
+
+[Problem]
+EnableGravity = false
+EnableInertiaTerms = false
+
+[Component]
+LiquidKinematicViscosity = 1e-6
+LiquidDensity = 1e3
+
+[Vtk]
+OutputName = test_md_boundary_freeflow1p_darcy1p_vertical
+
+[Vtk]
+AddVelocity = 1
+
+[Assembly.NumericDifference]
+BaseEpsilon = 1e-6
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/problem_darcy.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/problem_darcy.hh
new file mode 100644
index 0000000000000000000000000000000000000000..28490814a2bca3dff391e65a0d61298f0dc7323c
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/problem_darcy.hh
@@ -0,0 +1,183 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief A simple Darcy test problem (cell-centered finite volume method).
+ */
+
+#ifndef DUMUX_DARCY_SUBPROBLEM_HH
+#define DUMUX_DARCY_SUBPROBLEM_HH
+
+#include <dumux/common/boundarytypes.hh>
+#include <dumux/common/numeqvector.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/common/parameters.hh>
+
+#include <dumux/porousmediumflow/problem.hh>
+
+namespace Dumux {
+
+template <class TypeTag>
+class DarcySubProblem : public PorousMediumFlowProblem<TypeTag>
+{
+    using ParentType = PorousMediumFlowProblem<TypeTag>;
+    using GridView = typename GetPropType<TypeTag, Properties::GridGeometry>::GridView;
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using PrimaryVariables = GetPropType<TypeTag, Properties::PrimaryVariables>;
+    using NumEqVector = Dumux::NumEqVector<GetPropType<TypeTag, Properties::PrimaryVariables>>;
+    using BoundaryTypes = Dumux::BoundaryTypes<GetPropType<TypeTag, Properties::ModelTraits>::numEq()>;
+    using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>;
+    using FVElementGeometry = typename GetPropType<TypeTag, Properties::GridGeometry>::LocalView;
+    using SubControlVolume = typename FVElementGeometry::SubControlVolume;
+    using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace;
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
+
+    using Indices = typename GetPropType<TypeTag, Properties::ModelTraits>::Indices;
+
+    using Element = typename GridView::template Codim<0>::Entity;
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+
+    using CouplingManager = GetPropType<TypeTag, Properties::CouplingManager>;
+
+public:
+    DarcySubProblem(std::shared_ptr<const GridGeometry> gridGeometry,
+                   std::shared_ptr<CouplingManager> couplingManager)
+    : ParentType(gridGeometry, "Darcy"), eps_(1e-7), couplingManager_(couplingManager)
+    {
+        problemName_ = getParam<std::string>("Vtk.OutputName")
+            + "_" + getParamFromGroup<std::string>(this->paramGroup(), "Problem.Name");
+
+        // determine whether to simulate a vertical or horizontal flow configuration
+        verticalFlow_ = problemName_.find("vertical") != std::string::npos;
+    }
+
+    /*!
+     * \brief The problem name.
+     */
+    const std::string& name() const
+    { return problemName_; }
+
+    /*!
+     * \name Problem parameters
+     */
+    // \{
+
+    /*!
+     * \brief Returns the temperature within the domain in [K].
+     *
+     */
+    Scalar temperature() const
+    { return 273.15 + 10; } // 10°C
+    // \}
+
+    /*!
+     * \name Boundary conditions
+     */
+    // \{
+
+    /*!
+      * \brief Specifies which kind of boundary condition should be
+      *        used for which equation on a given boundary control volume.
+      *
+      * \param element The element
+      * \param scvf The boundary sub control volume face
+      */
+    BoundaryTypes boundaryTypes(const Element &element, const SubControlVolumeFace &scvf) const
+    {
+        BoundaryTypes values;
+        values.setAllNeumann();
+
+        if (couplingManager().isCoupled(CouplingManager::porousMediumIndex, CouplingManager::freeFlowMassIndex, scvf))
+            values.setAllCouplingNeumann();
+
+        if (verticalFlow_)
+        {
+            if (onLowerBoundary_(scvf.center()))
+            values.setAllDirichlet();
+        }
+
+        return values;
+    }
+
+    /*!
+     * \brief Evaluates the boundary conditions for a Dirichlet control volume.
+     *
+     * \param element The element for which the Dirichlet boundary condition is set
+     * \param scvf The boundary sub-control-volume-face
+     */
+    PrimaryVariables dirichlet(const Element &element, const SubControlVolumeFace &scvf) const
+    { return initial(element); }
+
+    /*!
+     * \brief Evaluates the boundary conditions for a Neumann control volume.
+     *
+     * \param element The element for which the Neumann boundary condition is set
+     * \param fvGeometry The fvGeometry
+     * \param elemVolVars The element volume variables
+     * \param elemFluxVarsCache Flux variables caches for all faces in stencil
+     * \param scvf The boundary sub control volume face
+     */
+    template<class ElementVolumeVariables, class ElementFluxVarsCache>
+    NumEqVector neumann(const Element& element,
+                        const FVElementGeometry& fvGeometry,
+                        const ElementVolumeVariables& elemVolVars,
+                        const ElementFluxVarsCache& elemFluxVarsCache,
+                        const SubControlVolumeFace& scvf) const
+    {
+        NumEqVector values(0.0);
+
+        if (couplingManager().isCoupled(CouplingManager::porousMediumIndex, CouplingManager::freeFlowMassIndex, scvf))
+            values[Indices::conti0EqIdx] = couplingManager().massCouplingCondition(
+                CouplingManager::porousMediumIndex, CouplingManager::freeFlowMassIndex,
+                fvGeometry, scvf, elemVolVars
+            );
+
+        return values;
+    }
+
+    // \}
+
+    /*!
+     * \brief Evaluates the initial value for a control volume.
+     * \param element The element
+     */
+    PrimaryVariables initial(const Element &element) const
+    { return PrimaryVariables(0.0); }
+
+    // \}
+
+    //! Get the coupling manager
+    const CouplingManager& couplingManager() const
+    { return *couplingManager_; }
+
+private:
+
+    bool onLowerBoundary_(const GlobalPosition &globalPos) const
+    { return globalPos[1] < this->gridGeometry().bBoxMin()[1] + eps_; }
+
+    Scalar eps_;
+    std::shared_ptr<CouplingManager> couplingManager_;
+    std::string problemName_;
+    bool verticalFlow_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/problem_freeflow.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/problem_freeflow.hh
new file mode 100644
index 0000000000000000000000000000000000000000..d184aa12247975602496061572dea14df8dbb729
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/problem_freeflow.hh
@@ -0,0 +1,307 @@
+// -*- 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 3 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 NavierStokesTests
+ * \brief Test for the staggered grid Navier-Stokes model with analytical solution (Kovasznay 1948, \cite Kovasznay1948)
+ */
+
+#ifndef DUMUX_KOVASZNAY_TEST_PROBLEM_NEW_HH
+#define DUMUX_KOVASZNAY_TEST_PROBLEM_NEW_HH
+
+
+#include <dumux/freeflow/navierstokes/problem.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/freeflow/navierstokes/momentum/fluxhelper.hh>
+#include <dumux/freeflow/navierstokes/scalarfluxhelper.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup NavierStokesTests
+ * \brief  Test problem for the staggered grid (Kovasznay 1948, \cite Kovasznay1948)
+ *
+ * A two-dimensional Navier-Stokes flow with a periodicity in one direction
+ * is considered. The set-up represents a wake behind a two-dimensional grid
+ * and is chosen in a way such that an exact solution is available.
+ */
+template <class TypeTag>
+class FreeFlowOnePTestProblem :  public NavierStokesProblem<TypeTag>
+{
+    using ParentType = NavierStokesProblem<TypeTag>;
+
+    using BoundaryTypes = typename ParentType::BoundaryTypes;
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
+    using FVElementGeometry = typename GridGeometry::LocalView;
+    using SubControlVolume = typename FVElementGeometry::SubControlVolume;
+    using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace;
+    using Indices = typename GetPropType<TypeTag, Properties::ModelTraits>::Indices;
+    using NumEqVector = typename ParentType::NumEqVector;
+    using ModelTraits = GetPropType<TypeTag, Properties::ModelTraits>;
+    using PrimaryVariables = typename ParentType::PrimaryVariables;
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>;
+
+    static constexpr auto dimWorld = GridGeometry::GridView::dimensionworld;
+    using Element = typename GridGeometry::GridView::template Codim<0>::Entity;
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+    using VelocityVector = Dune::FieldVector<Scalar, dimWorld>;
+
+    using CouplingManager = GetPropType<TypeTag, Properties::CouplingManager>;
+
+    // static constexpr auto upwindSchemeOrder = getPropValue<TypeTag, Properties::UpwindSchemeOrder>();
+
+public:
+    FreeFlowOnePTestProblem(std::shared_ptr<const GridGeometry> gridGeometry, std::shared_ptr<CouplingManager> couplingManager)
+    : ParentType(gridGeometry, couplingManager, "FreeFlow")
+    , couplingManager_(couplingManager)
+    {
+        problemName_ = getParam<std::string>("Vtk.OutputName") + "_" + getParamFromGroup<std::string>(this->paramGroup(), "Problem.Name");
+        deltaP_ = getParamFromGroup<Scalar>(this->paramGroup(), "Problem.PressureDifference", 0.0);
+
+        // determine whether to simulate a vertical or horizontal flow configuration
+        verticalFlow_ = problemName_.find("vertical") != std::string::npos;
+    }
+
+    /*!
+     * \name Problem parameters
+     */
+    // \{
+
+    const std::string& name() const
+    { return problemName_; }
+
+    /*!
+     * \brief Returns the temperature within the domain in [K].
+     *
+     * This problem assumes a temperature of 10 degrees Celsius.
+     */
+    Scalar temperature() const
+    { return 298.0; }
+
+    // \}
+    /*!
+     * \name Boundary conditions
+     */
+    // \{
+
+    /*!
+     * \brief Specifies which kind of boundary condition should be
+     *        used for which equation on a given boundary segment.
+     *
+     * \param element The finite element
+     * \param scvf The sub control volume face
+     */
+    BoundaryTypes boundaryTypes(const Element& element,
+                                const SubControlVolumeFace& scvf) const
+    {
+        BoundaryTypes values;
+        const auto& globalPos = scvf.center(); //avoid ambiguities at corners
+
+        if (verticalFlow_)
+        {
+            if constexpr (ParentType::isMomentumProblem())
+            {
+                if (couplingManager_->isCoupled(CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex, scvf))
+                {
+                    values.setCouplingNeumann(Indices::momentumYBalanceIdx);
+                    values.setCouplingNeumann(Indices::momentumXBalanceIdx);
+                }
+                else
+                    values.setAllDirichlet();
+            }
+            else
+            {
+                if (couplingManager_->isCoupled(CouplingManager::freeFlowMassIndex, CouplingManager::porousMediumIndex, scvf))
+                    values.setAllCouplingNeumann();
+                else
+                    values.setAllNeumann();
+            }
+        }
+        else // horizontal flow
+        {
+            if constexpr (ParentType::isMomentumProblem())
+            {
+                if (onLeftBoundary_(globalPos) || onRightBoundary_(globalPos))
+                    values.setAllNeumann();
+                else if (couplingManager_->isCoupled(CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex, scvf))
+                {
+                    values.setCouplingNeumann(Indices::momentumYBalanceIdx);
+                    values.setCouplingNeumann(Indices::momentumXBalanceIdx);
+                }
+                else
+                    values.setAllDirichlet();
+            }
+            else
+            {
+                if (couplingManager_->isCoupled(CouplingManager::freeFlowMassIndex, CouplingManager::porousMediumIndex, scvf))
+                    values.setAllCouplingNeumann();
+                else
+                    values.setAllNeumann();
+            }
+        }
+
+        return values;
+    }
+
+    /*!
+     * \brief Returns Dirichlet boundary values at a given position.
+     *
+     * \param globalPos The global position
+     */
+    PrimaryVariables dirichletAtPos(const GlobalPosition& globalPos) const
+    {
+        PrimaryVariables values(0.0);
+        if constexpr (ParentType::isMomentumProblem())
+        {
+            if (verticalFlow_)
+            {
+                static const Scalar inletVelocity = getParamFromGroup<Scalar>(this->paramGroup(), "Problem.Velocity");
+                values[Indices::velocityYIdx] = inletVelocity * globalPos[0] * (this->gridGeometry().bBoxMax()[0] - globalPos[0]);
+            }
+        }
+        return values;
+    }
+
+    /*!
+     * \brief Evaluates the boundary conditions for a Neumann control volume.
+     *
+     * \param element The element for which the Neumann boundary condition is set
+     * \param fvGeometry The fvGeometry
+     * \param elemVolVars The element volume variables
+     * \param elemFaceVars The element face variables
+     * \param scvf The boundary sub control volume face
+     */
+    template<class ElementVolumeVariables, class ElementFluxVariablesCache>
+    NumEqVector neumann(const Element& element,
+                        const FVElementGeometry& fvGeometry,
+                        const ElementVolumeVariables& elemVolVars,
+                        const ElementFluxVariablesCache& elemFluxVarsCache,
+                        const SubControlVolumeFace& scvf) const
+    {
+        NumEqVector values(0.0);
+        const auto& globalPos = scvf.ipGlobal();
+        using FluxHelper = NavierStokesMomentumBoundaryFluxHelper;
+
+        if constexpr (ParentType::isMomentumProblem())
+        {
+
+            if (couplingManager_->isCoupled(CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex, scvf))
+            {
+                values += couplingManager_->momentumCouplingCondition(
+                    CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex,
+                    fvGeometry, scvf, elemVolVars
+                );
+
+                values += FluxHelper::slipVelocityMomentumFlux(
+                    *this, fvGeometry, scvf, elemVolVars, elemFluxVarsCache
+                );
+            }
+            else
+            {
+                const Scalar pressure = onLeftBoundary_(globalPos) ? deltaP_ : 0.0;
+                values = FluxHelper::fixedPressureMomentumFlux(
+                    *this, fvGeometry, scvf, elemVolVars,
+                    elemFluxVarsCache, pressure, true /*zeroNormalVelocityGradient*/
+                );
+            }
+        }
+        else
+        {
+            if (couplingManager_->isCoupled(CouplingManager::freeFlowMassIndex, CouplingManager::porousMediumIndex, scvf))
+            {
+                values = couplingManager_->massCouplingCondition(
+                    CouplingManager::freeFlowMassIndex, CouplingManager::porousMediumIndex,
+                    fvGeometry, scvf, elemVolVars
+                );
+            }
+            else
+            {
+                using FluxHelper = NavierStokesScalarBoundaryFluxHelper<AdvectiveFlux<ModelTraits>>;
+                values = FluxHelper::scalarOutflowFlux(
+                    *this, element, fvGeometry, scvf, elemVolVars
+                );
+            }
+        }
+
+        return values;
+    }
+
+    /*!
+     * \brief Returns true if the scvf lies on a porous slip boundary
+     */
+    bool onSlipBoundary(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const
+    {
+        assert(scvf.isFrontal());
+        return scvf.boundary() && couplingManager_->isCoupled(CouplingManager::freeFlowMomentumIndex, CouplingManager::porousMediumIndex, scvf);
+    }
+
+    /*!
+     * \brief Returns the intrinsic permeability of required as input parameter for the Beavers-Joseph-Saffman boundary condition
+     */
+    Scalar permeability(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const
+    { return couplingManager_->darcyPermeability(fvGeometry, scvf); }
+
+    /*!
+     * \brief Returns the alpha value required as input parameter for the Beavers-Joseph-Saffman boundary condition
+     */
+    Scalar alphaBJ(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const
+    { return couplingManager_->problem(CouplingManager::porousMediumIndex).spatialParams().beaversJosephCoeffAtPos(scvf.ipGlobal()); }
+
+    /*!
+     * \brief Returns the pressure difference between inlet and outlet for the horizontal flow scenario
+     */
+    Scalar pressureDifference() const
+    {
+        assert(!verticalFlow_);
+        return deltaP_;
+    }
+
+    /*!
+     * \brief Returns true if a vertical flow scenario is considered
+     */
+    bool verticalFlow() const
+    { return verticalFlow_; }
+
+    // \}
+
+private:
+
+    bool onLeftBoundary_(const GlobalPosition &globalPos) const
+    { return globalPos[0] < this->gridGeometry().bBoxMin()[0] + eps_; }
+
+    bool onRightBoundary_(const GlobalPosition &globalPos) const
+    { return globalPos[0] > this->gridGeometry().bBoxMax()[0] - eps_; }
+
+    bool onLowerBoundary_(const GlobalPosition &globalPos) const
+    { return globalPos[1] < this->gridGeometry().bBoxMin()[1] + eps_; }
+
+    bool onUpperBoundary_(const GlobalPosition &globalPos) const
+    { return globalPos[1] > this->gridGeometry().bBoxMax()[1] - eps_; }
+
+    std::string problemName_;
+    static constexpr Scalar eps_ = 1e-6;
+    Scalar deltaP_;
+    bool verticalFlow_;
+
+    std::shared_ptr<CouplingManager> couplingManager_;
+};
+} // end namespace Dumux
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/properties.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/properties.hh
new file mode 100644
index 0000000000000000000000000000000000000000..4bf95acba7c79323df243bd349f4f2677dd736f0
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/properties.hh
@@ -0,0 +1,125 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief The properties for a simple Darcy test (cell-centered finite volume method)
+ */
+#ifndef DUMUX_DARCYSTOKES_PROPERTIES_HH
+#define DUMUX_DARCYSTOKES_PROPERTIES_HH
+
+#include <dune/grid/yaspgrid.hh>
+
+#include <dumux/material/fluidsystems/1pliquid.hh>
+#include <dumux/material/components/constant.hh>
+
+#include <dumux/porousmediumflow/1p/model.hh>
+#include <dumux/freeflow/navierstokes/mass/1p/model.hh>
+#include <dumux/freeflow/navierstokes/momentum/model.hh>
+#include <dumux/discretization/cctpfa.hh>
+#include <dumux/discretization/fcstaggered.hh>
+
+#include <dumux/multidomain/boundary/freeflowporousmedium/couplingmanager.hh>
+
+#include "spatialparams.hh"
+#include "problem_darcy.hh"
+#include "problem_freeflow.hh"
+
+namespace Dumux::Properties {
+
+// Create new type tags
+namespace TTag {
+struct DarcyOneP { using InheritsFrom = std::tuple<OneP, CCTpfaModel>; };
+} // end namespace TTag
+
+// Set the problem property
+template<class TypeTag>
+struct Problem<TypeTag, TTag::DarcyOneP> { using type = Dumux::DarcySubProblem<TypeTag>; };
+
+// the fluid system
+template<class TypeTag>
+struct FluidSystem<TypeTag, TTag::DarcyOneP>
+{
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using type = FluidSystems::OnePLiquid<Scalar, Dumux::Components::Constant<1, Scalar> > ;
+};
+
+// Set the grid type
+template<class TypeTag>
+struct Grid<TypeTag, TTag::DarcyOneP> { using type = Dune::YaspGrid<2>; };
+
+template<class TypeTag>
+struct SpatialParams<TypeTag, TTag::DarcyOneP>
+{
+    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using type = OnePSpatialParams<GridGeometry, Scalar>;
+};
+
+// Create new type tags
+namespace TTag {
+struct FreeFlowOneP {};
+struct FreeFlowOnePMass { using InheritsFrom = std::tuple<FreeFlowOneP, NavierStokesMassOneP, CCTpfaModel>; };
+struct FreeFlowOnePMomentum { using InheritsFrom = std::tuple<FreeFlowOneP, NavierStokesMomentum, FaceCenteredStaggeredModel>; };
+} // end namespace TTag
+
+// the fluid system
+template<class TypeTag>
+struct FluidSystem<TypeTag, TTag::FreeFlowOneP>
+{
+    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
+    using type = FluidSystems::OnePLiquid<Scalar, Dumux::Components::Constant<1, Scalar> > ;
+};
+
+// Set the grid type
+template<class TypeTag>
+struct Grid<TypeTag, TTag::FreeFlowOneP>
+{
+    using Coords = Dune::EquidistantOffsetCoordinates<GetPropType<TypeTag, Properties::Scalar>, 2>;
+    using type = Dune::YaspGrid<2, Coords>;
+};
+
+// Set the problem property
+template<class TypeTag>
+struct Problem<TypeTag, TTag::FreeFlowOneP> { using type = Dumux::FreeFlowOnePTestProblem<TypeTag> ; };
+
+template<class TypeTag>
+struct EnableGridGeometryCache<TypeTag, TTag::FreeFlowOneP> { static constexpr bool value = true; };
+template<class TypeTag>
+struct EnableGridFluxVariablesCache<TypeTag, TTag::FreeFlowOneP> { static constexpr bool value = true; };
+template<class TypeTag>
+struct EnableGridVolumeVariablesCache<TypeTag, TTag::FreeFlowOneP> { static constexpr bool value = true; };
+
+template<class TypeTag>
+struct CouplingManager<TypeTag, TTag::DarcyOneP>
+{
+    using Traits = MultiDomainTraits<Properties::TTag::FreeFlowOnePMomentum, Properties::TTag::FreeFlowOnePMass, TTag::DarcyOneP>;
+    using type = Dumux::FreeFlowPorousMediumCouplingManager<Traits>;
+};
+
+template<class TypeTag>
+struct CouplingManager<TypeTag, TTag::FreeFlowOneP>
+{
+    using Traits = MultiDomainTraits<Properties::TTag::FreeFlowOnePMomentum, Properties::TTag::FreeFlowOnePMass, TTag::DarcyOneP>;
+    using type = Dumux::FreeFlowPorousMediumCouplingManager<Traits>;
+};
+
+} // end namespace Dumux::Properties
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/1p_1p/spatialparams.hh b/test/multidomain/boundary/freeflowporousmedium/1p_1p/spatialparams.hh
new file mode 100644
index 0000000000000000000000000000000000000000..40ef1710e2229ddb9fc273f9ea636a80384b44e3
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/1p_1p/spatialparams.hh
@@ -0,0 +1,92 @@
+// -*- 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 3 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 BoundaryTests
+ * \brief The spatial parameters class for the test problem using the 1p cc model.
+ */
+
+#ifndef DUMUX_1P_TEST_SPATIALPARAMS_HH
+#define DUMUX_1P_TEST_SPATIALPARAMS_HH
+
+#include <dumux/material/spatialparams/fv1p.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup BoundaryTests
+ * \brief The spatial parameters class for the test problem using the
+ *        1p cc model.
+ */
+template<class GridGeometry, class Scalar>
+class OnePSpatialParams
+: public FVSpatialParamsOneP<GridGeometry, Scalar,
+                             OnePSpatialParams<GridGeometry, Scalar>>
+{
+    using GridView = typename GridGeometry::GridView;
+    using ParentType = FVSpatialParamsOneP<
+        GridGeometry, Scalar, OnePSpatialParams<GridGeometry, Scalar>
+    >;
+
+    using Element = typename GridView::template Codim<0>::Entity;
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+
+public:
+    // export permeability type
+    using PermeabilityType = Scalar;
+
+    OnePSpatialParams(std::shared_ptr<const GridGeometry> gridGeometry)
+    : ParentType(gridGeometry)
+    {
+        permeability_ = getParam<Scalar>("Darcy.SpatialParams.Permeability");
+        alphaBJ_ = getParam<Scalar>("Darcy.SpatialParams.AlphaBeaversJoseph");
+    }
+
+    /*!
+     * \brief Function for defining the (intrinsic) permeability \f$[m^2]\f$.
+     *
+     * \param globalPos The global position
+     * \return the intrinsic permeability
+     */
+    PermeabilityType permeabilityAtPos(const GlobalPosition& globalPos) const
+    { return permeability_; }
+
+    /*! \brief Defines the porosity in [-].
+     *
+     * \param globalPos The global position
+     */
+    Scalar porosityAtPos(const GlobalPosition& globalPos) const
+    { return 0.4; }
+
+    /*! \brief Defines the Beavers-Joseph coefficient in [-].
+     *
+     * \param globalPos The global position
+     */
+    Scalar beaversJosephCoeffAtPos(const GlobalPosition& globalPos) const
+    { return alphaBJ_; }
+
+
+private:
+    Scalar permeability_;
+    Scalar alphaBJ_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/test/multidomain/boundary/freeflowporousmedium/CMakeLists.txt b/test/multidomain/boundary/freeflowporousmedium/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f62f49df141e549022cf0cb7aac8d6f5c4d7f83f
--- /dev/null
+++ b/test/multidomain/boundary/freeflowporousmedium/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(1p_1p)