diff --git a/python/mesa_pd.py b/python/mesa_pd.py
index c80bcf232a49016c87a03030b790ffb72ea426fb..42572d41ffaaf44f7edb932c918678ae8d311b33 100755
--- a/python/mesa_pd.py
+++ b/python/mesa_pd.py
@@ -108,6 +108,11 @@ if __name__ == '__main__':
     mpd.add(mpi.ClearGhostOwnerSync())
     mpd.add(mpi.ClearNextNeighborSync())
     mpd.add(mpi.Notifications(ps))
+    ftn = mpd.add(mpi.PropertyNotification('ForceTorqueNotification'))
+    ftn.add_property('force', 'mesa_pd::Vec3', 'Vec3(real_t(0))')
+    ftn.add_property('torque', 'mesa_pd::Vec3', 'Vec3(real_t(0))')
+    hfn = mpd.add(mpi.PropertyNotification('HeatFluxNotification'))
+    hfn.add_property('heatFlux', 'real_t', 'real_t(0)')
     mpd.add(mpi.ReduceContactHistory())
     mpd.add(mpi.ReduceProperty())
     mpd.add(mpi.ShapePackUnpack(ps))
diff --git a/python/mesa_pd/mpi/Notifications.py b/python/mesa_pd/mpi/Notifications.py
index 4a87ee426c931d7c6627ddea582584a511e74061..5be06cbf48583a1b677ef0dcad0612102112b46c 100644
--- a/python/mesa_pd/mpi/Notifications.py
+++ b/python/mesa_pd/mpi/Notifications.py
@@ -9,8 +9,6 @@ class Notifications:
    def generate(self, module):
       ctx = {'module': module, 'particle' : self.ps.get_context()}
 
-      generate_file(module['module_path'], 'mpi/notifications/ForceTorqueNotification.templ.h', ctx)
-      generate_file(module['module_path'], 'mpi/notifications/HeatFluxNotification.templ.h', ctx)
       generate_file(module['module_path'], 'mpi/notifications/ParseMessage.templ.h', ctx)
       generate_file(module['module_path'], 'mpi/notifications/ParticleCopyNotification.templ.h', ctx)
       generate_file(module['module_path'], 'mpi/notifications/ParticleGhostCopyNotification.templ.h', ctx)
diff --git a/python/mesa_pd/mpi/PropertyNotification.py b/python/mesa_pd/mpi/PropertyNotification.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7bced94f832e9b2f9ecde2b5136e22b7af0d554
--- /dev/null
+++ b/python/mesa_pd/mpi/PropertyNotification.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from ..utility import find, generate_file, TerminalColor
+
+
+class PropertyNotification:
+    def __init__(self, name):
+        self.context = {'name': name, 'properties': []}
+
+    def add_property(self, name, type, reset_value):
+        prop = find(lambda x: x['name'] == name, self.context['properties'])
+        if (prop == None):
+            self.context['properties'].append({'name': name, 'type': type, 'resetValue': reset_value})
+        else:
+            if not (prop['type'] == type and prop['name'] == name and prop['resetValue'] == reset_value):
+                new_prop = {'name': name, 'type': type, 'resetValue': reset_value}
+                raise RuntimeError(
+                    f"{TerminalColor.RED} property definition differs from previous one:\nPREVIOUS {prop}\nNEW {new_prop} {TerminalColor.DEFAULT}")
+            print(f"{TerminalColor.YELLOW} reusing property: {name} {TerminalColor.DEFAULT}")
+
+    def generate(self, module):
+        ctx = {'module': module, **self.context}
+        generate_file(module['module_path'], 'mpi/notifications/PropertyNotification.templ.h', ctx, f'mpi/notifications/{self.context["name"]}.h')
diff --git a/python/mesa_pd/mpi/__init__.py b/python/mesa_pd/mpi/__init__.py
index 37f0854ad44a0da7adb8420cf7f11578028d8d5c..e74f708e8376e0db1f56a421f37a2ab9bcf45017 100644
--- a/python/mesa_pd/mpi/__init__.py
+++ b/python/mesa_pd/mpi/__init__.py
@@ -4,6 +4,7 @@ from .BroadcastProperty import BroadcastProperty
 from .ClearGhostOwnerSync import ClearGhostOwnerSync
 from .ClearNextNeighborSync import ClearNextNeighborSync
 from .Notifications import Notifications
+from .PropertyNotification import PropertyNotification
 from .ReduceContactHistory import ReduceContactHistory
 from .ReduceProperty import ReduceProperty
 from .ShapePackUnpack import ShapePackUnpack
@@ -14,6 +15,7 @@ from .SyncNextNeighborsNoGhosts import SyncNextNeighborsNoGhosts
 __all__ = ['BroadcastProperty',
            'ClearGhostOwnerSync',
            'ClearNextNeighborSync',
+           'PropertyNotification',
            'ReduceContactHistory',
            'ReduceProperty',
            'ShapePackUnpack',
diff --git a/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
deleted file mode 100644
index ce463a214544c48f460bed15997e1a3373e63484..0000000000000000000000000000000000000000
--- a/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
+++ /dev/null
@@ -1,114 +0,0 @@
-//======================================================================================================================
-//
-//  This file is part of waLBerla. waLBerla 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.
-//
-//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
-//
-//! \file HeatFluxNotification.h
-//! \author Sebastian Eibl <sebastian.eibl@fau.de>
-//
-//======================================================================================================================
-
-//======================================================================================================================
-//
-//  THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
-//
-//======================================================================================================================
-
-#pragma once
-
-#include <mesa_pd/data/DataTypes.h>
-#include <mesa_pd/data/ParticleStorage.h>
-#include <mesa_pd/mpi/notifications/NotificationType.h>
-#include <mesa_pd/mpi/notifications/reset.h>
-
-#include <core/mpi/Datatype.h>
-#include <core/mpi/RecvBuffer.h>
-#include <core/mpi/SendBuffer.h>
-
-namespace walberla {
-namespace mesa_pd {
-
-/**
- * Trasmits force and torque information.
- */
-class HeatFluxNotification
-{
-public:
-   struct Parameters
-   {
-      id_t uid_;
-      real_t heatFlux_;
-   };
-
-   inline explicit HeatFluxNotification( const data::Particle& p ) : p_(p) {}
-
-   const data::Particle& p_;
-};
-
-template <>
-void reset<HeatFluxNotification>(data::Particle& p)
-{
-   p.setHeatFlux( real_t(0) );
-}
-
-void reduce(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
-{
-   p.getHeatFluxRef()  += objparam.heatFlux_;
-}
-
-void update(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
-{
-   p.setHeatFlux( objparam.heatFlux_ );
-}
-
-}  // namespace mesa_pd
-}  // namespace walberla
-
-//======================================================================================================================
-//
-//  Send/Recv Buffer Serialization Specialization
-//
-//======================================================================================================================
-
-namespace walberla {
-namespace mpi {
-
-template< typename T,    // Element type of SendBuffer
-          typename G>    // Growth policy of SendBuffer
-mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::HeatFluxNotification& obj )
-{
-   buf.addDebugMarker( "hf" );
-   buf << obj.p_.getUid();
-   buf << obj.p_.getHeatFlux();
-   return buf;
-}
-
-template< typename T>    // Element type  of RecvBuffer
-mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::HeatFluxNotification::Parameters& objparam )
-{
-   buf.readDebugMarker( "hf" );
-   buf >> objparam.uid_;
-   buf >> objparam.heatFlux_;
-   return buf;
-}
-
-template< >
-struct BufferSizeTrait< mesa_pd::HeatFluxNotification > {
-   static const bool constantSize = true;
-   static const uint_t size = BufferSizeTrait<id_t>::size +
-                              BufferSizeTrait<real_t>::size +
-                              mpi::BUFFER_DEBUG_OVERHEAD;
-};
-
-} // mpi
-} // walberla
diff --git a/python/mesa_pd/templates/mpi/notifications/ForceTorqueNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/PropertyNotification.templ.h
similarity index 69%
rename from python/mesa_pd/templates/mpi/notifications/ForceTorqueNotification.templ.h
rename to python/mesa_pd/templates/mpi/notifications/PropertyNotification.templ.h
index 6bfa6412b1a7d5e1d8661c36868f14b76192f69a..701f21ea910476a3ac9e9449be91a6a9c5b4ed46 100644
--- a/python/mesa_pd/templates/mpi/notifications/ForceTorqueNotification.templ.h
+++ b/python/mesa_pd/templates/mpi/notifications/PropertyNotification.templ.h
@@ -13,7 +13,7 @@
 //  You should have received a copy of the GNU General Public License along
 //  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
 //
-//! \file ParticlePropertyNotification.h
+//! \file {{name}}.h
 //! \author Sebastian Eibl <sebastian.eibl@fau.de>
 //
 //======================================================================================================================
@@ -41,38 +41,42 @@ namespace mesa_pd {
 /**
  * Trasmits force and torque information.
  */
-class ForceTorqueNotification
+class {{name}}
 {
 public:
    struct Parameters
    {
       id_t uid_;
-      Vec3 force_;
-      Vec3 torque_;
+      {%- for prop in properties %}
+      {{prop.type}} {{prop.name}}_;
+      {%- endfor %}
    };
 
-   inline explicit ForceTorqueNotification( const data::Particle& p ) : p_(p) {}
+   inline explicit {{name}}( const data::Particle& p ) : p_(p) {}
 
    const data::Particle& p_;
 };
 
 template <>
-void reset<ForceTorqueNotification>(data::Particle& p)
+void reset<{{name}}>(data::Particle& p)
 {
-   p.setForce(  Vec3(real_t(0)) );
-   p.setTorque( Vec3(real_t(0)) );
+   {%- for prop in properties %}
+   p.set{{prop.name | capFirst}}( {{prop.resetValue}} );
+   {%- endfor %}
 }
 
-void reduce(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
+void reduce(data::Particle&& p, const {{name}}::Parameters& objparam)
 {
-   p.getForceRef()  += objparam.force_;
-   p.getTorqueRef() += objparam.torque_;
+   {%- for prop in properties %}
+   p.get{{prop.name | capFirst}}Ref() += objparam.{{prop.name}}_;
+   {%- endfor %}
 }
 
-void update(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
+void update(data::Particle&& p, const {{name}}::Parameters& objparam)
 {
-   p.setForce(  objparam.force_  );
-   p.setTorque( objparam.torque_ );
+   {%- for prop in properties %}
+   p.set{{prop.name | capFirst}}( objparam.{{prop.name}}_ );
+   {%- endfor %}
 }
 
 }  // namespace mesa_pd
@@ -89,31 +93,34 @@ namespace mpi {
 
 template< typename T,    // Element type of SendBuffer
           typename G>    // Growth policy of SendBuffer
-mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ForceTorqueNotification& obj )
+mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::{{name}}& obj )
 {
-   buf.addDebugMarker( "ft" );
+   buf.addDebugMarker( "pn" );
    buf << obj.p_.getUid();
-   buf << obj.p_.getForce();
-   buf << obj.p_.getTorque();
+   {%- for prop in properties %}
+   buf << obj.p_.get{{prop.name | capFirst}}();
+   {%- endfor %}
    return buf;
 }
 
 template< typename T>    // Element type  of RecvBuffer
-mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ForceTorqueNotification::Parameters& objparam )
+mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::{{name}}::Parameters& objparam )
 {
-   buf.readDebugMarker( "ft" );
+   buf.readDebugMarker( "pn" );
    buf >> objparam.uid_;
-   buf >> objparam.force_;
-   buf >> objparam.torque_;
+   {%- for prop in properties %}
+   buf >> objparam.{{prop.name}}_;
+   {%- endfor %}
    return buf;
 }
 
 template< >
-struct BufferSizeTrait< mesa_pd::ForceTorqueNotification > {
+struct BufferSizeTrait< mesa_pd::{{name}} > {
    static const bool constantSize = true;
    static const uint_t size = BufferSizeTrait<id_t>::size +
-                              BufferSizeTrait<mesa_pd::Vec3>::size +
-                              BufferSizeTrait<mesa_pd::Vec3>::size +
+   {%- for prop in properties %}
+                              BufferSizeTrait<{{prop.type}}>::size +
+   {%- endfor %}
                               mpi::BUFFER_DEBUG_OVERHEAD;
 };
 
diff --git a/src/mesa_pd/mpi/notifications/ForceTorqueNotification.h b/src/mesa_pd/mpi/notifications/ForceTorqueNotification.h
index d19d0d084635528194d048820d9d70785d03940a..2a41c9d4f34fa786c54fd75cf86b750aaf0abfdc 100644
--- a/src/mesa_pd/mpi/notifications/ForceTorqueNotification.h
+++ b/src/mesa_pd/mpi/notifications/ForceTorqueNotification.h
@@ -13,7 +13,7 @@
 //  You should have received a copy of the GNU General Public License along
 //  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
 //
-//! \file ParticlePropertyNotification.h
+//! \file ForceTorqueNotification.h
 //! \author Sebastian Eibl <sebastian.eibl@fau.de>
 //
 //======================================================================================================================
@@ -47,8 +47,8 @@ public:
    struct Parameters
    {
       id_t uid_;
-      Vec3 force_;
-      Vec3 torque_;
+      mesa_pd::Vec3 force_;
+      mesa_pd::Vec3 torque_;
    };
 
    inline explicit ForceTorqueNotification( const data::Particle& p ) : p_(p) {}
@@ -59,19 +59,19 @@ public:
 template <>
 void reset<ForceTorqueNotification>(data::Particle& p)
 {
-   p.setForce(  Vec3(real_t(0)) );
+   p.setForce( Vec3(real_t(0)) );
    p.setTorque( Vec3(real_t(0)) );
 }
 
 void reduce(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
 {
-   p.getForceRef()  += objparam.force_;
+   p.getForceRef() += objparam.force_;
    p.getTorqueRef() += objparam.torque_;
 }
 
 void update(data::Particle&& p, const ForceTorqueNotification::Parameters& objparam)
 {
-   p.setForce(  objparam.force_  );
+   p.setForce( objparam.force_ );
    p.setTorque( objparam.torque_ );
 }
 
@@ -91,7 +91,7 @@ template< typename T,    // Element type of SendBuffer
           typename G>    // Growth policy of SendBuffer
 mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::ForceTorqueNotification& obj )
 {
-   buf.addDebugMarker( "ft" );
+   buf.addDebugMarker( "pn" );
    buf << obj.p_.getUid();
    buf << obj.p_.getForce();
    buf << obj.p_.getTorque();
@@ -101,7 +101,7 @@ mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, cons
 template< typename T>    // Element type  of RecvBuffer
 mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::ForceTorqueNotification::Parameters& objparam )
 {
-   buf.readDebugMarker( "ft" );
+   buf.readDebugMarker( "pn" );
    buf >> objparam.uid_;
    buf >> objparam.force_;
    buf >> objparam.torque_;
diff --git a/src/mesa_pd/mpi/notifications/HeatFluxNotification.h b/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
index 884a684e9aadf477c9e33a1c2cb9905aeefc5322..a2ab05d07154697dc5c38d1ddf3a94767599a3db 100644
--- a/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
+++ b/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
@@ -63,7 +63,7 @@ void reset<HeatFluxNotification>(data::Particle& p)
 
 void reduce(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
 {
-   p.getHeatFluxRef()  += objparam.heatFlux_;
+   p.getHeatFluxRef() += objparam.heatFlux_;
 }
 
 void update(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
@@ -87,7 +87,7 @@ template< typename T,    // Element type of SendBuffer
           typename G>    // Growth policy of SendBuffer
 mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, const mesa_pd::HeatFluxNotification& obj )
 {
-   buf.addDebugMarker( "hf" );
+   buf.addDebugMarker( "pn" );
    buf << obj.p_.getUid();
    buf << obj.p_.getHeatFlux();
    return buf;
@@ -96,7 +96,7 @@ mpi::GenericSendBuffer<T,G>& operator<<( mpi::GenericSendBuffer<T,G> & buf, cons
 template< typename T>    // Element type  of RecvBuffer
 mpi::GenericRecvBuffer<T>& operator>>( mpi::GenericRecvBuffer<T> & buf, mesa_pd::HeatFluxNotification::Parameters& objparam )
 {
-   buf.readDebugMarker( "hf" );
+   buf.readDebugMarker( "pn" );
    buf >> objparam.uid_;
    buf >> objparam.heatFlux_;
    return buf;
diff --git a/tests/mesa_pd/mpi/ReduceProperty.cpp b/tests/mesa_pd/mpi/ReduceProperty.cpp
index 50c5b77836e842f6ba88cb4695c0bccf87c5389d..3f8138ac942f553831adf3854aeca0539822540b 100644
--- a/tests/mesa_pd/mpi/ReduceProperty.cpp
+++ b/tests/mesa_pd/mpi/ReduceProperty.cpp
@@ -21,6 +21,7 @@
 #include <mesa_pd/data/ParticleStorage.h>
 #include <mesa_pd/domain/BlockForestDomain.h>
 #include <mesa_pd/mpi/notifications/ForceTorqueNotification.h>
+#include <mesa_pd/mpi/notifications/HeatFluxNotification.h>
 #include <mesa_pd/mpi/ReduceProperty.h>
 #include <mesa_pd/mpi/SyncNextNeighbors.h>
 
@@ -86,16 +87,23 @@ void main( int argc, char ** argv )
    if (pIt != ps.end())
    {
       pIt->getForceRef() += Vec3(real_c(walberla::mpi::MPIManager::instance()->rank()));
+      pIt->getTorqueRef() += Vec3(real_c(walberla::mpi::MPIManager::instance()->rank()));
+      pIt->getHeatFluxRef() += real_c(walberla::mpi::MPIManager::instance()->rank());
    }
 
    RP.operator()<ForceTorqueNotification>(ps);
+   RP.operator()<HeatFluxNotification>(ps);
 
    if (walberla::mpi::MPIManager::instance()->rank() == 0)
    {
       WALBERLA_CHECK_FLOAT_EQUAL( pIt->getForce(), Vec3(real_t(28)) );
+      WALBERLA_CHECK_FLOAT_EQUAL( pIt->getTorque(), Vec3(real_t(28)) );
+      WALBERLA_CHECK_FLOAT_EQUAL( pIt->getHeatFlux(), real_t(28) );
    } else
    {
       WALBERLA_CHECK_FLOAT_EQUAL( pIt->getForce(), Vec3(0) );
+      WALBERLA_CHECK_FLOAT_EQUAL( pIt->getTorque(), Vec3(0) );
+      WALBERLA_CHECK_FLOAT_EQUAL( pIt->getHeatFlux(), real_t(0) );
    }
 }