diff --git a/pages/docs/using.md b/pages/docs/using.md
index 87d91925f1fdd176faa57f724193dd24a1f5accc..5854d4708679dd27af9036336d039b0d2bb3bf8f 100644
--- a/pages/docs/using.md
+++ b/pages/docs/using.md
@@ -244,3 +244,14 @@ assertEquals(receiverRoot.getAList(), list("1", "other"));
 // after receiving "new" on topic "some/topic/one"
 assertEquals(receiverRoot.getAList(), list("1", "other", "new"));
 ```
+
+## Using attributes as endpoint targets
+
+As described in the [DSL specification](/dsl), attributes can be used as endpoint targets.
+They can only be used in send endpoints, and the return type of the attribute must be specified in the connect specification (because aspect files are not handled completely yet).
+
+Currently, synthesized, inherited, collection, and circular attributes are supported.
+Nonterminal attributes are best used with the "legacy" notation `/Context:Type/` within the grammar.
+
+Please note, that serialization of Java collections of nonterminals is not supported, e.g., a `java.util.Set<ASTNode>`.
+Only list nodes as defined in the grammar `/Context:Type*/` are properly recognized.
diff --git a/ragconnect.base/.gitignore b/ragconnect.base/.gitignore
index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..4c0fcfac7f2f2c14ac6e81d173d3a2f7d9d99a55 100644
--- a/ragconnect.base/.gitignore
+++ b/ragconnect.base/.gitignore
@@ -3,3 +3,4 @@ src/gen-res/
 src/gen/
 out/
 *.class
+/parameters.txt
diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle
index 7dfe1e77b8599daad3915c6c94c0f995937f0edb..c138189d065693a908cd0e276dcddc3eb2320313 100644
--- a/ragconnect.base/build.gradle
+++ b/ragconnect.base/build.gradle
@@ -34,7 +34,7 @@ dependencies {
     relast group: 'org.jastadd', name: 'relast', version: "0.3.0-137"
     implementation group: 'org.jastadd', name: 'relast-preprocessor', version: "${preprocessor_version}"
     implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "${mustache_java_version}"
-    runtimeOnly group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden'
+    jastadd2 group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden'
     api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 }
 
@@ -187,6 +187,23 @@ ext {
 }
 application.mainClassName = "${mainClassName}"
 
+def parametersTxtFileName = 'ragconnect.base/parameters.txt'
+task runParameters(type: JavaExec) {
+    doFirst {
+        if (!new File(parametersTxtFileName).exists()) {
+            throw new GradleException("Please create '${parametersTxtFileName}' to use this task.")
+        }
+    }
+    group 'application'
+    description 'Run using parameters.txt (line-separated with comments)'
+    classpath sourceSets.main.runtimeClasspath
+    main = "org.jastadd.ragconnect.compiler.Compiler"
+    try {
+        args new File(parametersTxtFileName).text.strip().split("\n").dropWhile { it.startsWith("#") }
+    } catch (FileNotFoundException ignored) { /* empty */ }
+    standardInput = System.in
+}
+
 jar {
     manifest.attributes "Main-Class": "${mainClassName}"
 }
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
index 95f9b865b7217091537e954fa8ac98c292e45822..1a24fa97c1c561bd841944a82231fe2fd40aad69 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.jadd
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -506,13 +506,36 @@ aspect MustacheSendDefinition {
   syn String EndpointDefinition.forwardingNTA_Name() = getEndpointTarget().forwardingNTA_Name();
   syn String EndpointDefinition.forwardingNTA_Type() = getEndpointTarget().forwardingNTA_Type();
 
+  syn boolean EndpointDefinition.targetIsAttribute() = getEndpointTarget().isAttributeEndpointTarget();
+
+  syn boolean EndpointDefinition.indexBasedAccessAndTargetIsNTA() {
+    return typeIsList() && getIndexBasedListAccess() && !needForwardingNTA();
+  }
+
   syn boolean EndpointDefinition.relationEndpointWithListRole() = getEndpointTarget().relationEndpointWithListRole();
 
   syn String EndpointDefinition.senderName() = getEndpointTarget().senderName();
 
+  syn java.util.List<SendIncrementalObserverEntry> EndpointDefinition.sendIncrementalObserverEntries() {
+    // todo maybe getterMethodName needs to be change for indexed send
+    java.util.List<SendIncrementalObserverEntry> result = new java.util.ArrayList<>();
+    // "{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}"
+    result.add(SendIncrementalObserverEntry.of(getterMethodName() + (getIndexBasedListAccess() ? "_int" : ""),
+            getIndexBasedListAccess(), getIndexBasedListAccess() ? "index" : "null"));
+    if (indexBasedAccessAndTargetIsNTA()) {
+      // "{{getterMethodName}}List"
+      result.add(SendIncrementalObserverEntry.of(getterMethodName() + "List", false, "index"));
+    }
+    if (targetIsAttribute()) {
+      // "{{parentTypeName}}_{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}"
+      result.add(SendIncrementalObserverEntry.of(parentTypeName() + "_" + getterMethodName() + (getIndexBasedListAccess() ? "_int" : ""), getIndexBasedListAccess(), getIndexBasedListAccess() ? "index" : "null"));
+    }
+    return result;
+  }
+
   syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod();
 
-  syn String EndpointDefinition.tokenResetMethodName() = getterMethodName() + "_reset";
+  syn String EndpointDefinition.tokenResetMethodName() = getEndpointTarget().tokenResetMethodName();
 
   syn String EndpointDefinition.updateMethodName() = toMustache().updateMethodName();
 
@@ -552,6 +575,9 @@ containingEndpointDefinition().getIndexBasedListAccess());
   syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
   eq ContextFreeTypeEndpointTarget.senderName() = null;
 
+  syn String EndpointTarget.tokenResetMethodName() = getterMethodName() + (
+          typeIsList() && containingEndpointDefinition().getIndexBasedListAccess() ? "List" : "") + "_reset";
+
   syn String MEndpointDefinition.updateMethodName();
   syn String MEndpointDefinition.writeMethodName();
 
@@ -584,6 +610,13 @@ containingEndpointDefinition().getIndexBasedListAccess());
 
   syn String EndpointDefinition.typeName() = type().getName();
   syn String MEndpointDefinition.typeName() = getEndpointDefinition().typeName();
+
+  static SendIncrementalObserverEntry SendIncrementalObserverEntry.of(String attributeString, boolean compareParams, Object params) {
+    return new SendIncrementalObserverEntry()
+            .setParams(params)
+            .setCompareParams(compareParams)
+            .setAttributeString(attributeString);
+  }
 }
 
 aspect MustacheTokenComponent {
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.relast b/ragconnect.base/src/main/jastadd/Intermediate.relast
index 8fa32f92b7bc1fdc0b6fad196b82523a660dc6c2..0307d8d9a45fc40e2d6fcfa03ab06d25578687d6 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.relast
+++ b/ragconnect.base/src/main/jastadd/Intermediate.relast
@@ -15,3 +15,5 @@ MContextFreeTypeSendDefinition : MContextFreeTypeEndpointDefinition;
 
 MInnerMappingDefinition;
 rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+
+SendIncrementalObserverEntry ::= <Params:Object> <CompareParams:boolean> <AttributeString>;
diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
index f822bf01139341080c65f5026bd5661124cafd4e..55eac248e5f48d39aa38db6ac3cd20df61930220 100644
--- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
+++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
@@ -63,8 +63,8 @@ EndpointDefinition endpoint_definition_type
 
 EndpointTarget endpoint_target
   = ID.type_name DOT ID.child_name    {: return new UntypedEndpointTarget(type_name, child_name, false); :}
-  | ID.type_name DOT ID.child_name BRACKET_LEFT ID.attribute_type_name BRACKET_RIGHT
-     {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name, true); :}
+  | ID.type_name DOT ID.child_name BRACKET_LEFT java_type_use.attribute_type_name BRACKET_RIGHT
+     {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name.prettyPrint(), true); :}
   | ID.type_name                      {: return new UntypedEndpointTarget(type_name, "", false); :}
 ;
 
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index 7e054a4927b527e6d32a30e32a6bffa71d86b97f..c428021bf1eab32bf8baac8585a3f801ba8ad6ad 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -110,6 +110,7 @@ aspect RagConnectObserver {
       final boolean compareParams;
       final Object params;
       final Runnable attributeCall;
+      //final RagConnectToken connectToken;
       final java.util.List<RagConnectToken> connectList = new java.util.ArrayList<>();
 
       RagConnectObserverEntry(ASTNode node, String attributeString,
@@ -126,11 +127,11 @@ aspect RagConnectObserver {
       }
 
       boolean baseMembersEqualTo(ASTNode otherNode, String otherAttributeString,
-          boolean otherCompareParams, Object otherParams) {
+          boolean forceCompareParams, Object otherParams) {
         return this.node.equals(otherNode) &&
             this.attributeString.equals(otherAttributeString) &&
-            this.compareParams == otherCompareParams &&
-            (!this.compareParams || java.util.Objects.equals(this.params, otherParams));
+            //this.compareParams == otherCompareParams &&
+            (!(this.compareParams || forceCompareParams) || java.util.Objects.equals(this.params, otherParams));
       }
     }
 
@@ -162,23 +163,16 @@ aspect RagConnectObserver {
       node.trace().setReceiver(this);
     }
 
-    void add(RagConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
-      internal_add(connectToken, node, attributeString, false, null, attributeCall);
-    }
-    void add(RagConnectToken connectToken, ASTNode node, String attributeString, Object params, Runnable attributeCall) {
-      internal_add(connectToken, node, attributeString, true, params, attributeCall);
-    }
-
-    private void internal_add(RagConnectToken connectToken, ASTNode node, String attributeString,
-        boolean compareParams, Object params, Runnable attributeCall) {
+    void add(RagConnectToken connectToken, ASTNode node, boolean compareParams, Object params,
+             Runnable attributeCall, String attributeString) {
       {{#configLoggingEnabledForIncremental}}
       {{logDebug}}("** observer add: {{log_}} on {{log_}}{{log_}}",
         node, attributeString, (compareParams ? " (parameterized)" : ""));
       {{/configLoggingEnabledForIncremental}}
-      // either add to an existing entry (with same node, attribute) or create new entry
+      // either add to an existing entry (with same node, attribute, params) or create new entry
       boolean needNewEntry = true;
       for (RagConnectObserverEntry entry : observedNodes) {
-        if (entry.baseMembersEqualTo(node, attributeString, compareParams, params)) {
+        if (entry.baseMembersEqualTo(node, attributeString, true, params)) {
           entry.connectList.add(connectToken);
           needNewEntry = false;
           break;
@@ -193,16 +187,14 @@ aspect RagConnectObserver {
     }
 
     void remove(RagConnectToken connectToken) {
-      RagConnectObserverEntry entryToDelete = null;
+      java.util.List<RagConnectObserverEntry> entriesToDelete = new java.util.ArrayList<>();
       for (RagConnectObserverEntry entry : observedNodes) {
         entry.connectList.remove(connectToken);
         if (entry.connectList.isEmpty()) {
-          entryToDelete = entry;
+          entriesToDelete.add(entry);
         }
       }
-      if (entryToDelete != null) {
-        observedNodes.remove(entryToDelete);
-      }
+      observedNodes.removeAll(entriesToDelete);
     }
 
     @Override
@@ -256,7 +248,7 @@ aspect RagConnectObserver {
       {{/configLoggingEnabledForIncremental}}
       // iterate through list, if matching pair. could maybe be more efficient.
       for (RagConnectObserverEntry entry : observedNodes) {
-        if (entry.node.equals(node) && entry.attributeString.equals(attribute) && (!entry.compareParams || java.util.Objects.equals(entry.params, params))) {
+        if (entry.baseMembersEqualTo(node, attribute, false, params)) {
           // hit. call the attribute/nta-token
           {{#configLoggingEnabledForIncremental}}
           {{logDebug}}("** observer hit: {{log_}} on {{log_}}", entry.node, entry.attributeString);
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index abcbdffe2d4f7aaff5dfce8700cb11e72ec8d83d..2b8a6eb3536e81d9621f56866a375462562cf299 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -28,6 +28,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
       final String topic = {{attributeName}}().extractTopic(uri);
       {{senderName}}.add(() -> {
         {{#configLoggingEnabledForWrites}}
+        {{!FIXME circular computation for collection attributes!!}}
         {{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}});
         {{/configLoggingEnabledForWrites}}
         if ({{lastValueGetterCall}} != null) {
@@ -63,18 +64,20 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
   if (success) {
     connectTokenMap.add(this, false, connectToken);
     {{#configIncrementalOptionActive}}
-        {{!todo maybe getterMethodName needs to be change for indexed send}}
-      {{observerInstanceSingletonMethodName}}().add(
+    {{#sendIncrementalObserverEntries}}
+    {{observerInstanceSingletonMethodName}}().add(
       connectToken,
       this,
-      "{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}",
-      {{#IndexBasedListAccess}}index,{{/IndexBasedListAccess}}
+      {{CompareParams}},
+      {{Params}},
       () -> {
         if (this.{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}})) {
           this.{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
         }
-      }
+      },
+      "{{AttributeString}}"
     );
+    {{/sendIncrementalObserverEntries}}
     {{/configIncrementalOptionActive}}
   }
   return success;
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index ad9cff1725069ac97ba81bf008bbdaa712f18b09..5068a66dbc306052951b3b076879dc1ca3edcd83 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -46,7 +46,7 @@ dependencies {
     ragconnect project(':ragconnect.base')
     testImplementation project(':ragconnect.base')
 
-    implementation group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden-5'
+    implementation group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden-7'
     relast group: 'org.jastadd', name: 'relast', version: "0.3.0-137"
 
     testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0'
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.connect b/ragconnect.tests/src/test/01-input/attribute/Test.connect
index 03688a96bdb327076e974498d3ce62c9497f89be..a463af6c3ae24241a2dd7bc019bbbb21479a4d09 100644
--- a/ragconnect.tests/src/test/01-input/attribute/Test.connect
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.connect
@@ -3,6 +3,8 @@ send SenderRoot.simple(String) ;
 send SenderRoot.transformed(int) ;
 send SenderRoot.toReferenceType(A) ;
 send SenderRoot.toNTA(A) ;
+send SenderRoot.circularAttribute(int);
+send SenderRoot.collectionAttribute(Set<String>) using SetToString;
 
 AddSuffix maps A a to A {:
   A result = new A();
@@ -20,6 +22,10 @@ AddPlusOne maps int i to int {:
   return i + 1;
 :}
 
+SetToString maps Set<String> set to String {:
+  return set.toString();
+:}
+
 receive ReceiverRoot.FromBasic;
 receive ReceiverRoot.FromSimpleNoMapping;
 receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix;
@@ -29,3 +35,7 @@ receive ReceiverRoot.FromReferenceTypeNoMapping;
 receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix;
 receive ReceiverRoot.FromNTANoMapping;
 receive ReceiverRoot.FromNTAWithMapping using AddSuffix;
+receive ReceiverRoot.FromCircularNoMapping;
+receive ReceiverRoot.FromCircularWithMapping using AddPlusOne;
+receive ReceiverRoot.FromCollectionNoMapping;
+receive ReceiverRoot.FromCollectionWithMapping using AddStringSuffix;
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.jadd b/ragconnect.tests/src/test/01-input/attribute/Test.jadd
index 34b1b45e7a181d0368df2da491c25c197cc009b8..4ad766889588ea23c2abbda5d938359c7c62d4f8 100644
--- a/ragconnect.tests/src/test/01-input/attribute/Test.jadd
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.jadd
@@ -1,3 +1,5 @@
+import java.util.Set;
+import java.util.HashSet;
 aspect Computation {
   syn String SenderRoot.basic() = getInput();
   syn String SenderRoot.simple() = getInput() + "Post";
@@ -18,6 +20,12 @@ aspect Computation {
     result.setInner(inner);
     return result;
   }
+  syn int SenderRoot.circularAttribute() circular [0] {
+    return Integer.parseInt(getInput()) + 2;
+  }
+  coll Set<String> SenderRoot.collectionAttribute() [new HashSet<>()] root SenderRoot ;
+  A contributes getValue() to SenderRoot.collectionAttribute();
+  SenderRoot contributes nta toNTA() to SenderRoot.collectionAttribute();
 }
 aspect MakeCodeCompile {
 
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.relast b/ragconnect.tests/src/test/01-input/attribute/Test.relast
index 7afc9b7745c0da1cab4af5140fdddb2e5981f3b1..e3a3a67123589a40446651ed27bab5e6494e90f8 100644
--- a/ragconnect.tests/src/test/01-input/attribute/Test.relast
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.relast
@@ -1,5 +1,5 @@
 Root ::= SenderRoot* ReceiverRoot;
-SenderRoot ::= <Input> ;
+SenderRoot ::= <Input> A* ;
 ReceiverRoot ::=
     <FromBasic>
     <FromSimpleNoMapping>
@@ -9,6 +9,10 @@ ReceiverRoot ::=
     FromReferenceTypeNoMapping:A
     FromReferenceTypeWithMapping:A
     FromNTANoMapping:A
-    FromNTAWithMapping:A ;
+    FromNTAWithMapping:A
+    <FromCircularNoMapping:int>
+    <FromCircularWithMapping:int>
+    <FromCollectionNoMapping>
+    <FromCollectionWithMapping> ;
 A ::= <Value> Inner ;
 Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.connect b/ragconnect.tests/src/test/01-input/indexedSend/Test.connect
index 3ff49e174a5afddac148e0a99374255ec0b61646..9e390cd63028cc0708969285792222a42f11b2fd 100644
--- a/ragconnect.tests/src/test/01-input/indexedSend/Test.connect
+++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.connect
@@ -1,6 +1,7 @@
 send indexed SenderRoot.MultipleA;
-
 send indexed SenderRoot.MultipleAWithSuffix using AddSuffix;
+send indexed SenderRoot.A;
+send indexed SenderRoot.ComputedA using AddSuffix;
 
 AddSuffix maps A a to A {:
   A result = new A();
@@ -12,3 +13,5 @@ AddSuffix maps A a to A {:
 
 receive indexed ReceiverRoot.ManyA;
 receive indexed ReceiverRoot.ManyAWithSuffix;
+receive indexed ReceiverRoot.FromNTA;
+receive indexed ReceiverRoot.FromNTAWithSuffix;
diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd b/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd
index d37dd045dbf11754fbd9066faae1613ea2a95e97..fc8893efc9579eeebc415c2b51450f66092a2055 100644
--- a/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd
+++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd
@@ -1,3 +1,21 @@
+aspect Computation {
+  syn JastAddList<A> SenderRoot.getAList() {
+    JastAddList<A> result = new JastAddList<>();
+    getMultipleAList().forEach(a -> result.add(a.touchedTerminals()));
+    return result;
+  }
+  syn JastAddList<A> SenderRoot.getComputedAList() {
+    JastAddList<A> result = new JastAddList<>();
+    getMultipleAWithSuffixList().forEach(a -> result.add(a.touchedTerminals()));
+    return result;
+  }
+  A A.touchedTerminals() {
+    getValue();
+    getInner().getInnerValue();
+    return this;
+  }
+}
+
 aspect NameResolution {
   // overriding customID guarantees to produce the same JSON representation for equal lists
   // otherwise, the value for id is different each time
diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.relast b/ragconnect.tests/src/test/01-input/indexedSend/Test.relast
index 9480f18d9efcd27452e548044f4caca335d4312a..2e9924b0316578cb221e9778cc28fec83cfddc03 100644
--- a/ragconnect.tests/src/test/01-input/indexedSend/Test.relast
+++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.relast
@@ -1,5 +1,5 @@
 Root ::= SenderRoot ReceiverRoot;
-SenderRoot ::= MultipleA:A* MultipleAWithSuffix:A* ;
-ReceiverRoot ::= ManyA:A* ManyAWithSuffix:A* ;
+SenderRoot ::= MultipleA:A* MultipleAWithSuffix:A* /A*/ /ComputedA:A*/ ;
+ReceiverRoot ::= ManyA:A* ManyAWithSuffix:A* FromNTA:A* FromNTAWithSuffix:A* ;
 A ::= <Value> Inner ;
 Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
index 31335e5482eee4a3ef8737d28694a06a2430aba3..b8f31b89aced9f362f6b6961fd476ccea6ab7660 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
@@ -25,7 +25,7 @@ public abstract class AbstractMqttTest extends RagConnectTest {
   /**
    * if the initial/current value shall be sent upon connecting
    */
-  private boolean writeCurrentValue;
+  protected boolean writeCurrentValue;
 
   public boolean isWriteCurrentValue() {
     return writeCurrentValue;
@@ -58,7 +58,7 @@ public abstract class AbstractMqttTest extends RagConnectTest {
 
   @Tag("mqtt")
   @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
-  public final void testCommunicateSendInitialValue() throws IOException, InterruptedException {
+  public void testCommunicateSendInitialValue() throws IOException, InterruptedException {
     this.writeCurrentValue = true;
 
     createModel();
@@ -76,7 +76,7 @@ public abstract class AbstractMqttTest extends RagConnectTest {
 
   @Tag("mqtt")
   @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
-  public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
+  public void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
     this.writeCurrentValue = false;
 
     createModel();
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java
index 1c10bd428a8e47087e9081516a6e4e56e3eeaf77..20ff9f3f791c35b47ac24ead95990076edd41a93 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java
@@ -9,8 +9,10 @@ import java.util.Objects;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 import static java.util.function.Predicate.isEqual;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.jastadd.ragconnect.tests.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -32,15 +34,22 @@ public class AttributeTest extends AbstractMqttTest {
   private static final String TOPIC_REFERENCE_TYPE_WITH_MAPPING = "attr/a/ref/mapped";
   private static final String TOPIC_NTA_NO_MAPPING = "attr/a/nta/plain";
   private static final String TOPIC_NTA_WITH_MAPPING = "attr/a/nta/mapped";
+  private static final String TOPIC_CIRCULAR_NO_MAPPING = "attr/a/circular/plain";
+  private static final String TOPIC_CIRCULAR_WITH_MAPPING = "attr/a/circular/mapped";
+  private static final String TOPIC_COLLECTION_NO_MAPPING = "attr/a/collection/plain";
+  private static final String TOPIC_COLLECTION_WITH_MAPPING = "attr/a/collection/mapped";
 
   private static final String INITIAL_STRING = "initial";
   private static final String INITIAL_STRING_FOR_INT = "1";
+  private static final String INITIAL_STRING_FOR_INT_PLUS_2 = Integer.toString(Integer.parseInt(INITIAL_STRING_FOR_INT) + 2);
 
   private static final String CHECK_BASIC = "basic";
   private static final String CHECK_SIMPLE = "simple";
   private static final String CHECK_TRANSFORMED = "transformed";
   private static final String CHECK_A = "a";
   private static final String CHECK_NTA = "nta";
+  private static final String CHECK_CIRCULAR = "circular";
+  private static final String CHECK_COLLECTION = "collection";
 
   private MqttHandler handler;
   private ReceiverData data;
@@ -82,6 +91,8 @@ public class AttributeTest extends AbstractMqttTest {
             .setCheckForString(CHECK_TRANSFORMED, this::checkTransformed)
             .setCheckForString(CHECK_A, this::checkA)
             .setCheckForString(CHECK_NTA, this::checkNta)
+            .setCheckForString(CHECK_CIRCULAR, this::checkCircular)
+            .setCheckForString(CHECK_COLLECTION, this::checkCollection)
     ;
 
     // connect receive
@@ -94,14 +105,22 @@ public class AttributeTest extends AbstractMqttTest {
     assertTrue(receiverRoot.connectFromReferenceTypeWithMapping(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING)));
     assertTrue(receiverRoot.connectFromNTANoMapping(mqttUri(TOPIC_NTA_NO_MAPPING)));
     assertTrue(receiverRoot.connectFromNTAWithMapping(mqttUri(TOPIC_NTA_WITH_MAPPING)));
+    assertTrue(receiverRoot.connectFromCircularNoMapping(mqttUri(TOPIC_CIRCULAR_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromCircularWithMapping(mqttUri(TOPIC_CIRCULAR_WITH_MAPPING)));
+    assertTrue(receiverRoot.connectFromCollectionNoMapping(mqttUri(TOPIC_COLLECTION_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromCollectionWithMapping(mqttUri(TOPIC_COLLECTION_WITH_MAPPING)));
 
     // connect send, and wait to receive (if writeCurrentValue is set)
     assertTrue(senderString.connectBasic(mqttUri(TOPIC_BASIC), isWriteCurrentValue()));
     assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING), isWriteCurrentValue()));
     assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderString.connectCollectionAttribute(mqttUri(TOPIC_COLLECTION_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderString.connectCollectionAttribute(mqttUri(TOPIC_COLLECTION_WITH_MAPPING), isWriteCurrentValue()));
 
     assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_NO_MAPPING), isWriteCurrentValue()));
     assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_WITH_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderInt.connectCircularAttribute(mqttUri(TOPIC_CIRCULAR_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderInt.connectCircularAttribute(mqttUri(TOPIC_CIRCULAR_WITH_MAPPING), isWriteCurrentValue()));
 
     assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_NO_MAPPING), isWriteCurrentValue()));
     assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING), isWriteCurrentValue()));
@@ -111,58 +130,68 @@ public class AttributeTest extends AbstractMqttTest {
     waitForValue(senderString.basic(), receiverRoot::getFromBasic);
     waitForValue(senderString.simple(), receiverRoot::getFromSimpleNoMapping);
     waitForValue(senderInt.transformed(), receiverRoot::getFromTransformedNoMapping);
+    waitForNonNull(receiverRoot::getFromCollectionNoMapping);
     waitForNonNull(receiverRoot::getFromReferenceTypeNoMapping);
     waitForNonNull(receiverRoot::getFromNTANoMapping);
   }
 
   @Override
   protected void communicateSendInitialValue() throws IOException, InterruptedException {
-    // basic, simple(2)    <-- senderString
-    // transformed(2)      <-- senderInt
-    // ref-type(2), nta(2) <-- senderA
-    checker.addToNumberOfValues(9)
+    // 13 = basic, simple(2), collection(2), transformed(2), circular(2), ref-type(2), nta(2)
+    checker.addToNumberOfValues(13)
             .put(CHECK_BASIC, INITIAL_STRING)
             .put(CHECK_SIMPLE, INITIAL_STRING + "Post")
             .put(CHECK_TRANSFORMED, INITIAL_STRING_FOR_INT)
             .put(CHECK_A, INITIAL_STRING)
-            .put(CHECK_NTA, INITIAL_STRING);
+            .put(CHECK_NTA, INITIAL_STRING)
+            .put(CHECK_CIRCULAR, INITIAL_STRING_FOR_INT_PLUS_2)
+            .put(CHECK_COLLECTION, "[" + INITIAL_STRING + "]");
 
     communicateBoth();
   }
 
   @Override
   protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
-    // basic, simple(2)    <-- senderString
-    // transformed(2)      <-- senderInt
-    // ref-type(2), nta(2) <-- senderA
     checker.put(CHECK_BASIC, (String) null)
             .put(CHECK_SIMPLE, (String) null)
             .put(CHECK_TRANSFORMED, (String) null)
             .put(CHECK_A, (String) null)
-            .put(CHECK_NTA, (String) null);
+            .put(CHECK_NTA, (String) null)
+            .put(CHECK_COLLECTION, (String) null);
 
     communicateBoth();
   }
 
   private void communicateBoth() throws IOException {
+    // basic, simple(2), collection(2) <-- senderString
+    // transformed(2), circular(2)     <-- senderInt
+    // ref-type(2), nta(2)             <-- senderA
     checker.check();
 
     senderString.setInput("test-01");
-    checker.addToNumberOfValues(3)
+    checker.addToNumberOfValues(5) // basic, simple(2), collection(2)
             .put(CHECK_BASIC, "test-01")
             .put(CHECK_SIMPLE, "test-01Post")
+            .put(CHECK_COLLECTION, "[test-01]")
             .check();
 
+    // no change for same value
     senderString.setInput("test-01");
     checker.check();
 
+    senderString.addA(new A().setValue("test-02").setInner(new Inner().setInnerValue("inner")));
+    checker.addToNumberOfValues(2) // collection(2)
+            .put(CHECK_COLLECTION, "[test-01, test-02]")
+            .check();
+
     senderInt.setInput("20");
-    checker.addToNumberOfValues(2)
+    checker.addToNumberOfValues(4) // transformed(2), circular(2)
             .put(CHECK_TRANSFORMED, "20")
+            .put(CHECK_CIRCULAR, "22")
             .check();
 
     senderA.setInput("test-03");
-    checker.addToNumberOfValues(4)
+    checker.addToNumberOfValues(4) // ref-type(2), nta(2)
             .put(CHECK_A, "test-03")
             .put(CHECK_NTA, "test-03")
             .check();
@@ -170,14 +199,22 @@ public class AttributeTest extends AbstractMqttTest {
     assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING)));
     assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING)));
     senderString.setInput("test-04");
-    checker.incNumberOfValues()
+    checker.addToNumberOfValues(3) // basic, collection(2)
             .put(CHECK_BASIC, "test-04")
+            .put(CHECK_COLLECTION, "[test-02, test-04]")
+            .check();
+
+    assertTrue(senderString.disconnectCollectionAttribute(mqttUri(TOPIC_COLLECTION_NO_MAPPING)));
+    assertTrue(senderString.disconnectCollectionAttribute(mqttUri(TOPIC_COLLECTION_WITH_MAPPING)));
+    senderString.setInput("test-05");
+    checker.incNumberOfValues() // basic
+            .put(CHECK_BASIC, "test-05")
             .check();
 
     assertTrue(senderA.disconnectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING)));
-    senderA.setInput("test-05");
+    senderA.setInput("test-06");
     checker.addToNumberOfValues(3)
-            .put(CHECK_A, "test-05")
+            .put(CHECK_A, "test-06")
             .check();
   }
 
@@ -208,18 +245,17 @@ public class AttributeTest extends AbstractMqttTest {
   }
 
   private void checkTransformed(String name, String expected) {
+    _checkInt(name, expected, receiverRoot::getFromTransformedNoMapping, receiverRoot::getFromTransformedWithMapping);
+  }
+
+  private void _checkInt(String name, String expected, Supplier<Integer> noMapping, Supplier<Integer> withMapping) {
     if (expected != null) {
-      assertEquals(Integer.parseInt(expected),
-              receiverRoot.getFromTransformedNoMapping(), "transformed");
-      assertEquals(Integer.parseInt(expected) + 1,
-              receiverRoot.getFromTransformedWithMapping(), "transformed mapped");
+      assertEquals(Integer.parseInt(expected), noMapping.get(), name);
+      assertEquals(Integer.parseInt(expected) + 1, withMapping.get(), name + " mapped");
     } else {
-      assertEquals(0,
-              receiverRoot.getFromTransformedNoMapping(), "transformed null");
-      assertEquals(0,
-              receiverRoot.getFromTransformedWithMapping(), "transformed mapped null");
+      assertEquals(0, noMapping.get(), name + " null");
+      assertEquals(0, withMapping.get(), name + " mapped null");
     }
-
   }
 
   private void checkA(String name, String expected) {
@@ -245,6 +281,30 @@ public class AttributeTest extends AbstractMqttTest {
     }
   }
 
+  private void checkCircular(String name, String expected) {
+    _checkInt(name, expected, receiverRoot::getFromCircularNoMapping, receiverRoot::getFromCircularWithMapping);
+  }
+
+  private void checkCollection(String name, String expected) {
+    if (expected != null) {
+      assertThat(receiverRoot.getFromCollectionWithMapping()).hasSizeGreaterThan(4).endsWith("post");
+      checkCollectionContent(name, expected, receiverRoot.getFromCollectionNoMapping());
+      checkCollectionContent(name + " mapped", expected, receiverRoot.getFromCollectionWithMapping().substring(0, receiverRoot.getFromCollectionWithMapping().length() - 4));
+    } else {
+      assertEquals("", receiverRoot.getFromCollectionNoMapping(), "collection null");
+      assertEquals("", receiverRoot.getFromCollectionWithMapping(), "collection mapped null");
+    }
+  }
+
+  private void checkCollectionContent(String name, String expected, String actual) {
+    assertThat(actual).as(name)
+            .startsWith("[")
+            .endsWith("]");
+    String[] actualValues = actual.substring(1, actual.length() - 1).split(", ");
+    String[] expectedValues = expected.substring(1, expected.length() - 1).split(", ");
+    assertThat(actualValues).containsExactlyInAnyOrder(expectedValues);
+  }
+
   private void assertA(String expectedValue, String expectedInner, A actual, String message) {
     assertEquals(expectedValue, actual.getValue(), message + " value");
     assertEquals(expectedInner, actual.getInner().getInnerValue(), message + " inner");
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java
index a15d9990c44d0856c2f1c7af3f022e422d745c71..dc1bdda4a03c1b228a1c0214811ebb00b417f004 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java
@@ -1,13 +1,16 @@
 package org.jastadd.ragconnect.tests;
 
 import indexedSendInc.ast.*;
+import io.github.artsok.RepeatedIfExceptionsTest;
 import org.assertj.core.api.Assertions;
+import org.assertj.core.groups.Tuple;
 import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 import static org.assertj.core.groups.Tuple.tuple;
 import static org.jastadd.ragconnect.tests.TestUtils.*;
@@ -33,6 +36,8 @@ public class IndexedSendTest extends AbstractMqttTest {
   private static final String CHECK_MANY_A = "many-a";
   private static final String CHECK_WITH_SUFFIX = "many-a-with-suffix";
 
+  private boolean connectNTAsInstead;
+
   private MqttHandler handler;
   private ReceiverData data;
   private TestUtils.TestChecker checker;
@@ -83,70 +88,99 @@ public class IndexedSendTest extends AbstractMqttTest {
     checker = new TestChecker();
     checker.setActualNumberOfValues(() -> data.numberOfValues)
             .setCheckForTuple(CHECK_MANY_A, (name, expected) ->
-                    Assertions.assertThat(receiverRoot.getManyAList()).extracting("Value")
-                            .as(name)
-                            .containsExactlyElementsOf(expected.toList()))
+                    checkList(name, expected, receiverRoot::getManyAList))
             .setCheckForTuple(CHECK_WITH_SUFFIX, (name, expected) ->
-                    Assertions.assertThat(receiverRoot.getManyAWithSuffixList()).extracting("Value")
-                            .as(name)
-                            .containsExactlyElementsOf(expected.toList()));
+                    checkList(name, expected, receiverRoot::getManyAWithSuffixList));
 
     // connect receive
     assertTrue(receiverRoot.connectManyA(mqttUri(TOPIC_A_MANY_NORMAL_WILDCARD)));
     assertTrue(receiverRoot.connectManyAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_WILDCARD)));
 
     // connect send, and wait to receive (if writeCurrentValue is set)
-    assertTrue(senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_0), 0, isWriteCurrentValue()));
+    assertConnectAOrMultipleA(TOPIC_A_MANY_NORMAL_0, 0);
     waitForValue(receiverRoot::getNumManyA, 1);
 
-    assertTrue(senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_1), 1, isWriteCurrentValue()));
+    assertConnectAOrMultipleA(TOPIC_A_MANY_NORMAL_1, 1);
     waitForValue(receiverRoot::getNumManyA, 2);
 
-    assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0), 0, isWriteCurrentValue()));
+    assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0, 0);
     waitForValue(receiverRoot::getNumManyAWithSuffix, 1);
 
-    assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_1), 1, isWriteCurrentValue()));
+    assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_1, 1);
     waitForValue(receiverRoot::getNumManyAWithSuffix, 2);
   }
 
+  private void assertConnectAOrMultipleA(String topic, int index) throws IOException {
+    assertTrue(connectNTAsInstead ?
+            senderRoot.connectA(mqttUri(topic), index, isWriteCurrentValue()) :
+            senderRoot.connectMultipleA(mqttUri(topic), index, isWriteCurrentValue()));
+  }
+
+  private void assertConnectComputedAOrMultipleAWithSuffix(String topic, int index) throws IOException {
+    assertTrue(connectNTAsInstead ?
+            senderRoot.connectComputedA(mqttUri(topic), index, isWriteCurrentValue()) :
+            senderRoot.connectMultipleAWithSuffix(mqttUri(topic), index, isWriteCurrentValue()));
+  }
+
+  private void assertDisconnectComputedAOrMultipleAWithSuffix(String topic) throws IOException {
+    assertTrue(connectNTAsInstead ?
+            senderRoot.disconnectComputedA(mqttUri(topic)) :
+            senderRoot.disconnectMultipleAWithSuffix(mqttUri(topic)));
+  }
+
+  private void checkList(String name, Tuple expected, Supplier<JastAddList<A>> actual) {
+    Assertions.assertThat(actual.get()).extracting("Value")
+            .as(name)
+            .containsExactlyElementsOf(expected.toList());
+  }
+
   private void waitForValue(Callable<Integer> callable, int expectedValue) {
     if (isWriteCurrentValue()) {
       awaitMqtt().until(callable, Predicate.isEqual(expectedValue));
     }
   }
 
-  @Override
-  protected void communicateSendInitialValue() throws IOException, InterruptedException {
-    checker.addToNumberOfValues(4)
-            .put(CHECK_MANY_A, tuple("am0", "am1"))
-            .put(CHECK_WITH_SUFFIX, tuple("am0post", "am1post"));
+  @Tag("mqtt")
+  @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
+  public void testCommunicateSendInitialValueWithNTAs() throws IOException, InterruptedException {
+    this.writeCurrentValue = true;
+    this.connectNTAsInstead = true;
 
-    listA0.setValue("changedValue");
-    checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "am1")).check();
+    try {
+      createModel();
+      setupReceiverAndConnect();
 
-    // setting same value must not change data, and must not trigger a new sent message
-    listA0.setValue("changedValue");
-    checker.check();
+      logger.info("Calling communicateSendInitialValue");
+      communicateSendInitialValue();
+    } finally {
+      this.connectNTAsInstead = false;
+    }
+  }
 
-    listA1.setValue("");
-    checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "")).check();
+  @Tag("mqtt")
+  @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
+  public void testCommunicateOnlyUpdatedValueWithNTAs() throws IOException, InterruptedException {
+    this.writeCurrentValue = false;
+    this.connectNTAsInstead = true;
 
-    listA1InSuffix.setValue("re");
-    checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("am0post", "repost")).check();
+    try {
+      createModel();
+      setupReceiverAndConnect();
 
-    // adding a new element does not automatically send it
-    A listA3InSuffix = createA("out");
-    senderRoot.addMultipleAWithSuffix(listA3InSuffix);
-    checker.check();
+      logger.info("Calling communicateOnlyUpdatedValue");
+      communicateOnlyUpdatedValue();
+    } finally {
+      this.connectNTAsInstead = false;
+    }
+  }
 
-    // only after connecting it, the element gets sent
-    assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_2), 2, true));
-    checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("am0post", "repost", "outpost")).check();
+  @Override
+  protected void communicateSendInitialValue() throws IOException, InterruptedException {
+    checker.addToNumberOfValues(4)
+            .put(CHECK_MANY_A, tuple("am0", "am1"))
+            .put(CHECK_WITH_SUFFIX, tuple("am0post", "am1post"));
 
-    // after successful disconnect, no messages will be sent
-    assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0)));
-    listA0InSuffix.setValue("willBeIgnored");
-    checker.check();
+    communicateBoth("am1", "am0post");
   }
 
   @Override
@@ -154,10 +188,10 @@ public class IndexedSendTest extends AbstractMqttTest {
     checker.put(CHECK_MANY_A, tuple())
             .put(CHECK_WITH_SUFFIX, tuple());
 
-    communicateBoth();
+    communicateBoth(null, null);
   }
 
-  private void communicateBoth() throws IOException {
+  private void communicateBoth(String manyAtIndex1, String suffixAtIndex0) throws IOException {
     // Sink.ManyA           <-- Root.MultipleA
     // Sink.ManyAWithSuffix <-- Root.MultipleAWithSuffix
     checker.check();
@@ -165,7 +199,10 @@ public class IndexedSendTest extends AbstractMqttTest {
     assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue());
     listA0.setValue("changedValue");
     assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue());
-    checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue")).check();
+
+    checker.incNumberOfValues()
+            .put(CHECK_MANY_A, manyAtIndex1 != null ? tuple("changedValue", manyAtIndex1) : tuple("changedValue"))
+            .check();
 
     // setting same value must not change data, and must not trigger a new sent message
     listA0.setValue("changedValue");
@@ -176,21 +213,44 @@ public class IndexedSendTest extends AbstractMqttTest {
 
     // first element in suffix-list
     listA1InSuffix.setValue("re");
-    checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("repost")).check();
+    checker.incNumberOfValues()
+            .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost") : tuple("repost"))
+            .check();
 
     // adding a new element does not automatically send it
-    A listA3InSuffix = createA("out");
-    senderRoot.addMultipleAWithSuffix(listA3InSuffix);
+    A listA2InSuffix = createA("out");
+    senderRoot.addMultipleAWithSuffix(listA2InSuffix);
+    checker.check();
+
+    // only after connecting it, the element gets sent (for SendInitialValue case)
+    assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_2, 2);
+    if (isWriteCurrentValue()) {
+      checker.incNumberOfValues()
+              .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "outpost") : tuple("repost", "outpost"));
+    }
     checker.check();
 
-    // only after connecting it, the element gets sent
-    assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_2), 2, true));
-    checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("repost", "outpost")).check();
+    // changing the value of the newly added element will send it
+    listA2InSuffix.setValue("goal");
+    checker.incNumberOfValues()
+            .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "goalpost") : tuple("repost", "goalpost"));
+    checker.check();
 
-    // after successful disconnect, no messages will be sent
-    assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0)));
+    // after successful disconnect for index 0, no messages will be sent
+    assertDisconnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0);
     listA0InSuffix.setValue("willBeIgnored");
     checker.check();
+
+    // for index 1 (not disconnected), messages will be sent still
+    listA1InSuffix.setValue("sign");
+    checker.incNumberOfValues()
+            .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "signpost", "goalpost") : tuple("signpost", "goalpost"))
+            .check();
+
+    // after successful disconnect for index 1, no messages will be sent anymore
+    assertDisconnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_1);
+    listA1InSuffix.setValue("willBeIgnored");
+    checker.check();
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
index 5e6c94ff596909e6f70a0b84e6562ab4ec171d7d..4a74e6b3a205f092a53b73897656151464f8e8e1 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
@@ -22,13 +22,12 @@ import java.util.*;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
-import static java.util.Collections.addAll;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.util.Lists.newArrayList;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -98,6 +97,12 @@ public class TestUtils {
     return outPath;
   }
 
+  public static <T> String prettyPrint(Iterable<T> aList, Function<T, String> elementPrinter) {
+    StringJoiner sj = new StringJoiner(", ", "[", "]");
+    aList.forEach(element -> sj.add(elementPrinter.apply(element)));
+    return sj.toString();
+  }
+
   public static void assertLinesMatch(String directory, String expectedName, String out) throws IOException {
     Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX)
             .resolve(directory)
@@ -226,20 +231,30 @@ public class TestUtils {
       }
 
       public TestChecker setActual(String name, Callable<T> actual) {
-        values.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
+        _computeIfAbsent(name).actual = actual;
         return parent;
       }
 
       public TestChecker setCheck(String name, BiConsumer<String, T> check) {
-        values.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
+        _computeIfAbsent(name).customCheck = check;
         return parent;
       }
 
       public TestChecker put(String name, T expected) {
-        values.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
+        _computeIfAbsent(name).expected = expected;
         return parent;
       }
 
+      public TestChecker updateExpected(String name, Function<T, T> updater) {
+        ActualAndExpected<T> aae = _computeIfAbsent(name);
+        aae.expected = updater.apply(aae.expected);
+        return parent;
+      }
+
+      private ActualAndExpected<T> _computeIfAbsent(String name) {
+        return values.computeIfAbsent(name, ActualAndExpected::new);
+      }
+
       ActualAndExpected<T> get(String name) {
         return values.get(name);
       }