diff --git a/src/amdis/utility/CMakeLists.txt b/src/amdis/utility/CMakeLists.txt
index 870230af8d6cc5e42061d2bffb6e135918017f11..33add2585317639ad95105ba90b78d6bf0df0ed5 100644
--- a/src/amdis/utility/CMakeLists.txt
+++ b/src/amdis/utility/CMakeLists.txt
@@ -4,4 +4,5 @@ install(FILES
     LocalToGlobalAdapter.hpp
     MacroGridFactory.hpp
     QuadratureFactory.hpp
+    UniqueBorderPartition.hpp
 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/amdis/utility)
diff --git a/src/amdis/utility/UniqueBorderPartition.hpp b/src/amdis/utility/UniqueBorderPartition.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6a3000b2f02dfef5e59e7d14f44ee53b43b275e9
--- /dev/null
+++ b/src/amdis/utility/UniqueBorderPartition.hpp
@@ -0,0 +1,86 @@
+#pragma once
+
+#include <cassert>
+#include <set>
+
+#include <dune/common/unused.hh>
+#include <dune/grid/common/datahandleif.hh>
+
+namespace AMDiS
+{
+  /// \brief Determine for each border entity which processor owns it
+  /**
+   * All entities must be uniquely owned by exactly one processor, but they can
+   * exist on multiple processors. For interior, overlap, and ghost entities the
+   * assignment is trivial: interior: owner, otherwise not owner.
+   *
+   * For border entities (codim != 0) the ownership is not known a priori and must
+   * be communicated. Here we assign the entity to the processor with the lowest rank.
+   **/
+  template <class Grid>
+  class UniqueBorderPartitionDataHandle
+      : public Dune::CommDataHandleIF<UniqueBorderPartitionDataHandle<Grid>, int>
+  {
+    using IdSet = typename Grid::GlobalIdSet;
+    using IdType = typename IdSet::IdType;
+
+  public:
+    using EntitySet = std::set<IdType>;
+
+  public:
+    /// \brief Construct a UniqueBorderPartition DataHandle to be used in a GridView
+    /// communicator.
+    /**
+     * \param rank            The own processor rank
+     * \param borderEntities  A set of entity ids filled with all border entities
+     *                        owned by this processor after communication.
+     * \param idSet           The id set of entity ids to store in borderEntities,
+     *                        typically the grid globalIdSet.
+     *
+     * NOTE: Since idSet is stored by reference it must not go out of scope
+     * until all calls to \ref gather and \ref scatter are finished.
+     **/
+    UniqueBorderPartitionDataHandle(int rank, EntitySet& borderEntities, IdSet const& idSet)
+      : myrank_(rank)
+      , borderEntities_(&borderEntities)
+      , idSet_(idSet)
+    {}
+
+    // Communicate all entities except for grid elements
+    bool contains(int /*dim*/, int codim) const { return codim != 0; }
+
+    // communicate exactly one integer, the rank
+    bool fixedSize(int /*dim*/, int /*codim*/) const { return true; }
+
+    // Always contains one int, the rank
+    template <class Entity>
+    std::size_t size(Entity const& e) const { return 1; }
+
+    template <class MessageBuffer, class Entity>
+    void gather(MessageBuffer& buff, Entity const& e) const
+    {
+      buff.write(myrank_);
+      // insert all border entities
+      borderEntities_->insert(idSet_.id(e));
+    }
+
+    template <class MessageBuffer, class Entity>
+    void scatter(MessageBuffer& buff, Entity const& e, std::size_t n)
+    {
+      DUNE_UNUSED_PARAMETER(n); // n == 1
+      assert(n == 1);
+
+      int rank = 0;
+      buff.read(rank);
+      // remove all border entities with rank < myrank_
+      if (rank < myrank_)
+        borderEntities_->erase(idSet_.id(e));
+    }
+
+  private:
+    int myrank_;
+    EntitySet* borderEntities_;
+    IdSet const& idSet_;
+  };
+
+} // end namespace AMDiS
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 282214b3a68816de41ce0f583939d8f86a0bc0bf..da50325e8666af00621bab9d5615d6e69a0e74a1 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -97,3 +97,9 @@ dune_add_test(SOURCES TreeContainerTest.cpp
 
 dune_add_test(SOURCES TypeTraitsTest.cpp
   LINK_LIBRARIES amdis)
+
+dune_add_test(SOURCES UniqueBorderPartitionTest.cpp
+  LINK_LIBRARIES amdis
+  MPI_RANKS 2
+  TIMEOUT 300
+  CMAKE_GUARD MPI_FOUND)
diff --git a/test/UniqueBorderPartitionTest.cpp b/test/UniqueBorderPartitionTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e23f95225e2aaf4f16c1122ea17f4439fc77fd12
--- /dev/null
+++ b/test/UniqueBorderPartitionTest.cpp
@@ -0,0 +1,40 @@
+#include <amdis/AMDiS.hpp>
+#include <amdis/utility/UniqueBorderPartition.hpp>
+
+#include <dune/grid/yaspgrid.hh>
+
+#include "Tests.hpp"
+
+using namespace AMDiS;
+
+template <class GridView>
+void test(GridView const& gv)
+{
+  using Grid = typename GridView::Grid;
+  using DataHandle = UniqueBorderPartitionDataHandle<Grid>;
+
+  using EntitySet = typename DataHandle::EntitySet;
+  EntitySet borderEntities;
+
+  DataHandle handle(gv.comm().rank(), borderEntities, gv.grid().globalIdSet());
+  gv.communicate(handle,
+    Dune::InterfaceType::InteriorBorder_InteriorBorder_Interface,
+    Dune::CommunicationDirection::ForwardCommunication);
+
+  msg("#borderEntities = {}", borderEntities.size());
+}
+
+int main(int argc, char** argv)
+{
+  Environment env(argc, argv);
+
+  Dune::YaspGrid<2> grid1({1.0, 1.0}, {8,8}, 0, 0); // no overlap
+  Dune::YaspGrid<2> grid2({1.0, 1.0}, {8,8}, 0, 1); // overlap = 1
+  Dune::YaspGrid<3> grid3({1.0, 1.0, 1.0}, {8,8,8}, 0, 1); // overlap = 1
+
+  test(grid1.leafGridView());
+  test(grid2.leafGridView());
+  test(grid3.leafGridView());
+
+  return report_errors();
+}