diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index eb8e73a01d9374f07b764d18b0e1dc9a5ece1bf1..c315ced7e506310c08c414d78b4df77aab646d4b 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -18,6 +18,7 @@ add_library(
   MeshBuilderBase.cpp
   MeshDataManager.cpp
   MeshFaceBoundary.cpp
+  MeshFlatFaceBoundary.cpp
   MeshFlatNodeBoundary.cpp
   MeshLineNodeBoundary.cpp
   MeshNodeBoundary.cpp
diff --git a/src/mesh/MeshFlatFaceBoundary.cpp b/src/mesh/MeshFlatFaceBoundary.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0deefac0a8e9fc768634d53bfd5afa6d2323f5ec
--- /dev/null
+++ b/src/mesh/MeshFlatFaceBoundary.cpp
@@ -0,0 +1,29 @@
+#include <mesh/MeshFlatFaceBoundary.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshFlatNodeBoundary.hpp>
+
+template <size_t Dimension>
+MeshFlatFaceBoundary<Dimension>
+getMeshFlatFaceBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDescriptor& boundary_descriptor)
+{
+  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
+       ++i_ref_face_list) {
+    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
+    const RefId& ref          = ref_face_list.refId();
+    if (ref == boundary_descriptor) {
+      MeshFlatNodeBoundary<Dimension> mesh_flat_node_boundary = getMeshFlatNodeBoundary(mesh, boundary_descriptor);
+
+      return MeshFlatFaceBoundary<Dimension>{mesh, ref_face_list, mesh_flat_node_boundary.outgoingNormal()};
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find surface with name " << rang::fgB::red << boundary_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshFlatFaceBoundary<2> getMeshFlatFaceBoundary(const Mesh<Connectivity<2>>&, const IBoundaryDescriptor&);
+template MeshFlatFaceBoundary<3> getMeshFlatFaceBoundary(const Mesh<Connectivity<3>>&, const IBoundaryDescriptor&);
diff --git a/src/mesh/MeshFlatFaceBoundary.hpp b/src/mesh/MeshFlatFaceBoundary.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f3d34e16c5d52af38033e82dcd75e6897f392a3d
--- /dev/null
+++ b/src/mesh/MeshFlatFaceBoundary.hpp
@@ -0,0 +1,46 @@
+#ifndef MESH_FLAT_FACE_BOUNDARY_HPP
+#define MESH_FLAT_FACE_BOUNDARY_HPP
+
+#include <mesh/MeshFaceBoundary.hpp>
+
+template <size_t Dimension>
+class MeshFlatFaceBoundary final : public MeshFaceBoundary<Dimension>   // clazy:exclude=copyable-polymorphic
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const Rd m_outgoing_normal;
+
+ public:
+  const Rd&
+  outgoingNormal() const
+  {
+    return m_outgoing_normal;
+  }
+
+  MeshFlatFaceBoundary& operator=(const MeshFlatFaceBoundary&) = default;
+  MeshFlatFaceBoundary& operator=(MeshFlatFaceBoundary&&) = default;
+
+  template <size_t MeshDimension>
+  friend MeshFlatFaceBoundary<MeshDimension> getMeshFlatFaceBoundary(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                                     const IBoundaryDescriptor& boundary_descriptor);
+
+ private:
+  template <typename MeshType>
+  MeshFlatFaceBoundary(const MeshType& mesh, const RefFaceList& ref_face_list, const Rd& outgoing_normal)
+    : MeshFaceBoundary<Dimension>(mesh, ref_face_list), m_outgoing_normal(outgoing_normal)
+  {}
+
+ public:
+  MeshFlatFaceBoundary()                            = default;
+  MeshFlatFaceBoundary(const MeshFlatFaceBoundary&) = default;
+  MeshFlatFaceBoundary(MeshFlatFaceBoundary&&)      = default;
+  ~MeshFlatFaceBoundary()                           = default;
+};
+
+template <size_t Dimension>
+MeshFlatFaceBoundary<Dimension> getMeshFlatFaceBoundary(const Mesh<Connectivity<Dimension>>& mesh,
+                                                        const IBoundaryDescriptor& boundary_descriptor);
+
+#endif   // MESH_FLAT_FACE_BOUNDARY_HPP