diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md
index eb0fe4d555048fb90f71cec631baafeaef35a4d7..b2607fdf120d0b44ee8dd4a560cc4ad4047f0524 100644
--- a/pages/docs/changelog.md
+++ b/pages/docs/changelog.md
@@ -15,3 +15,20 @@
 - Add methods to `disconnect` an endpoint
 - Internal: PoC for incremental dependency tracking and subtree endpoint definitions ([#14](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/14))
 - Bugfix [#17](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/17): Added missing support for `boolean`
+
+## 0.2.2
+
+- Allow normal tokens to be used in send definitions
+
+## 0.2.1
+
+- New communication protocol: REST
+- Selection of protocol when `connect` methods are called, by scheme of given URI
+- Development changes:
+    - Supported printing out YAML data used for mustache templates
+    - Moved string constants to `MRagConnect` structure
+
+## 0.2.0
+
+- Version submitted in paper "A Connection from ROS to RAG-Based Models" (2020)
+- Supported communication protocols: MQTT
diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag
index e8afb8ba2d4bb1485be30b543812b48e291dd4b3..ac891b0e819b1e57e135afced64495a76396db5e 100644
--- a/ragconnect.base/src/main/jastadd/Analysis.jrag
+++ b/ragconnect.base/src/main/jastadd/Analysis.jrag
@@ -95,4 +95,10 @@ aspect Analysis {
     to MappingDefinition.effectiveUsedAt()
     for each effectiveMappings();
 
+  // --- typeIsList ---
+  syn boolean EndpointDefinition.typeIsList() = false;
+  eq TypeEndpointDefinition.typeIsList() {
+    return getType().isListComponent();
+  }
+
 }
diff --git a/ragconnect.base/src/main/jastadd/Configuration.jadd b/ragconnect.base/src/main/jastadd/Configuration.jadd
index e7e1268df443a278496a499c59e896d1ee27c80a..8fd0e64b14bbd9a05c7cbf9b7de59ed0cd216d78 100644
--- a/ragconnect.base/src/main/jastadd/Configuration.jadd
+++ b/ragconnect.base/src/main/jastadd/Configuration.jadd
@@ -3,6 +3,7 @@ aspect Configuration {
   public static boolean ASTNode.loggingEnabledForWrites = false;
   public static boolean ASTNode.loggingEnabledForIncremental = false;
   public static TypeDecl ASTNode.rootNode;
+  public static String ASTNode.JastAddList = "List";
   public static boolean ASTNode.usesMqtt;
   public static boolean ASTNode.usesRest;
   public static boolean ASTNode.incrementalOptionActive;
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index 309c4776d923a6afd7b98b7bec190d96db74f28e..c1cbbb2445e55956f41ee780eab2e216175b6b6f 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -13,10 +13,10 @@ rel TokenEndpointDefinition.Token <-> TokenComponent.TokenEndpointDefinition*;
 ReceiveTokenEndpointDefinition : TokenEndpointDefinition;
 SendTokenEndpointDefinition : TokenEndpointDefinition;
 
-abstract TypeEndpointDefinition : EndpointDefinition;
+abstract TypeEndpointDefinition : EndpointDefinition ::= <UseList:boolean> ;
 rel TypeEndpointDefinition.Type <-> TypeComponent.TypeEndpointDefinition*;
 
-ReceiveTypeEndpointDefinition : TypeEndpointDefinition;
+ReceiveTypeEndpointDefinition : TypeEndpointDefinition ::= <WithAdd:boolean>;
 SendTypeEndpointDefinition : TypeEndpointDefinition;
 
 DependencyDefinition ::= <ID>;
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
index 3b63bddfd2079518793308e0320fe2311724828e..7e4bde916d565d16bdef34598d638d2080f2f5fa 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
+++ b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
@@ -4,6 +4,9 @@ Design considerations
 */
 
 aspect AttributesForMustache {
+  // --- EndpointDefinition ---
+  syn String EndpointDefinition.idTokenName() = "InternalRagconnectTopicInList";
+
   // --- MRagConnect ---
   eq MRagConnect.getRootTypeComponent(int i).isFirst() = i == 0;
 
@@ -15,6 +18,32 @@ aspect AttributesForMustache {
   syn String MRagConnect.restHandlerAttribute() = "_restHandler";
   syn String MRagConnect.restHandlerField() = "_restHandler";
 
+  syn boolean MRagConnect.hasTreeListEndpoints() = !sendingTreeListEndpoints().isEmpty() || !receivingTreeListEndpoints().isEmpty();
+  syn List<MTypeEndpointDefinition> MRagConnect.sendingTreeListEndpoints() {
+    List<MTypeEndpointDefinition> result = new ArrayList<>();
+    for (var mEndpointDef : getTypeSendDefinitionList()) {
+      if (mEndpointDef.typeIsList()) {
+        result.add(mEndpointDef);
+      }
+    }
+    return result;
+  }
+  syn List<MTypeEndpointDefinition> MRagConnect.receivingTreeListEndpoints() {
+    List<MTypeEndpointDefinition> result = new ArrayList<>();
+    for (var mEndpointDef : getTypeReceiveDefinitionList()) {
+      if (mEndpointDef.typeIsList()) {
+        result.add(mEndpointDef);
+      }
+    }
+    return result;
+  }
+  syn List<TypeDecl> MRagConnect.typesForReceivingListEndpoints() {
+    return receivingTreeListEndpoints().stream()
+      .map(mEndpointDef -> mEndpointDef.type().getTypeDecl())
+      .distinct()
+      .collect(java.util.stream.Collectors.toList());
+  }
+
   // --- MEndpointDefinition ---
   syn String MEndpointDefinition.preemptiveExpectedValue();
   syn String MEndpointDefinition.preemptiveReturn();
@@ -24,12 +53,14 @@ aspect AttributesForMustache {
   syn String MEndpointDefinition.entityName();
   syn String MEndpointDefinition.updateMethod();
   syn String MEndpointDefinition.writeMethod();
+  syn String MEndpointDefinition.getterMethod();
 
   eq MEndpointDefinition.getInnerMappingDefinition(int i).isLast() = i == getNumInnerMappingDefinition() - 1;
   eq MEndpointDefinition.getInnerMappingDefinition(int i).inputVarName() = i == 0 ? firstInputVarName() : getInnerMappingDefinition(i - 1).outputVarName();
 
   syn String MEndpointDefinition.connectParameterName() = "uriString";
   syn String MEndpointDefinition.connectMethod() = "connect" + entityName();
+  syn String MEndpointDefinition.internalConnectMethod() = "_internal_" + connectMethod();
   syn boolean MEndpointDefinition.isTypeEndpointDefinition() = endpointDef().isTypeEndpointDefinition();
 
   syn String MEndpointDefinition.disconnectMethod() {
@@ -55,6 +86,7 @@ aspect AttributesForMustache {
   syn TokenComponent MEndpointDefinition.token() = endpointDef().asTokenEndpointDefinition().getToken();
   syn TypeComponent MEndpointDefinition.type() = endpointDef().asTypeEndpointDefinition().getType();
   syn boolean MEndpointDefinition.alwaysApply() = endpointDef().getAlwaysApply();
+  syn boolean MEndpointDefinition.typeIsList() = endpointDef().typeIsList();
   syn String MEndpointDefinition.tokenName() = token().getName();
   syn String MEndpointDefinition.typeName() = type().getName();
   syn String MEndpointDefinition.typeDeclName() = type().getTypeDecl().getName();
@@ -69,6 +101,10 @@ aspect AttributesForMustache {
     if (endpointDef().isTokenEndpointDefinition() && token().isPrimitiveType() && lastDefinition().mappingDef().getToType().isPrimitiveType()) {
       return preemptiveExpectedValue() + " == " + lastResult();
     }
+    if (endpointDef().isReceiveTypeEndpointDefinition() && endpointDef().asReceiveTypeEndpointDefinition().getWithAdd()) {
+      // only check if received list is not null
+      return lastResult() + " == null";
+    }
     if (endpointDef().isTypeEndpointDefinition() && type().isOptComponent()) {
       // use "hasX()" instead of "getX() != null" for optionals
       return "has" + typeName() + "()" + " && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")";
@@ -80,12 +116,16 @@ aspect AttributesForMustache {
   }
 
   // --- MTokenEndpointDefinition ---
+  eq MTokenEndpointDefinition.getterMethod() = "get" + tokenName();
   eq MTokenEndpointDefinition.parentTypeName() = token().containingTypeDecl().getName();
   eq MTokenEndpointDefinition.entityName() = tokenName();
 
   // --- MTypeEndpointDefinition ---
+  syn boolean MTypeEndpointDefinition.isWithAdd() = endpointDef().isReceiveTypeEndpointDefinition() ? endpointDef().asReceiveTypeEndpointDefinition().getWithAdd() : false;
+  syn boolean MTypeEndpointDefinition.isUseList() = endpointDef().asTypeEndpointDefinition().getUseList();
+  eq MTypeEndpointDefinition.getterMethod() = "get" + typeName() + (typeIsList() ? "List" : "");
   eq MTypeEndpointDefinition.parentTypeName() = type().containingTypeDecl().getName();
-  eq MTypeEndpointDefinition.entityName() = typeName();
+  eq MTypeEndpointDefinition.entityName() = typeName() + (isUseList() ? "List" : "");
 
   // --- MInnerMappingDefinition ---
   inh boolean MInnerMappingDefinition.isLast();
@@ -96,7 +136,7 @@ aspect AttributesForMustache {
   syn String MInnerMappingDefinition.outputVarName() = "result" + methodName();  // we do not need "_" in between here, because methodName begins with one
 
   // --- MTokenReceiveDefinition ---
-  eq MTokenReceiveDefinition.preemptiveExpectedValue() = "get" + tokenName() + "()";
+  eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethod() + "()";
   eq MTokenReceiveDefinition.preemptiveReturn() = "return;";
   eq MTokenReceiveDefinition.endpointDef() = getReceiveTokenEndpointDefinition();
   eq MTokenReceiveDefinition.firstInputVarName() = "message";
@@ -107,34 +147,37 @@ aspect AttributesForMustache {
   eq MTokenSendDefinition.preemptiveExpectedValue() = lastValue();
   eq MTokenSendDefinition.preemptiveReturn() = "return false;";
   eq MTokenSendDefinition.endpointDef() = getSendTokenEndpointDefinition();
-  eq MTokenSendDefinition.firstInputVarName() = "get" + tokenName() + "()";
+  eq MTokenSendDefinition.firstInputVarName() = getterMethod() + "()";
   eq MTokenSendDefinition.updateMethod() = "_update_" + tokenName();
   eq MTokenSendDefinition.writeMethod() = "_writeLastValue_" + tokenName();
 
   syn String MTokenSendDefinition.sender() = "_sender_" + tokenName();
   syn String MTokenSendDefinition.lastValue() = "_lastValue" + tokenName();
-  syn String MTokenSendDefinition.tokenResetMethod() = "get" + tokenName() + "_reset";
+  syn String MTokenSendDefinition.tokenResetMethod() = getterMethod() + "_reset";
   syn boolean MTokenSendDefinition.shouldSendValue() = endpointDef().asTokenEndpointDefinition().shouldSendValue();
 
   // MTypeReceiveDefinition
-  eq MTypeReceiveDefinition.preemptiveExpectedValue() = "get" + typeName() + "()";
+  eq MTypeReceiveDefinition.preemptiveExpectedValue() = getterMethod() + "()";
   eq MTypeReceiveDefinition.preemptiveReturn() = "return;";
   eq MTypeReceiveDefinition.endpointDef() = getReceiveTypeEndpointDefinition();
   eq MTypeReceiveDefinition.firstInputVarName() = "message";
   eq MTypeReceiveDefinition.updateMethod() = null;
   eq MTypeReceiveDefinition.writeMethod() = null;
 
+  syn String MTypeReceiveDefinition.resolveInListAttributeName() = "resolve" + entityName() + "InList";
+  syn String MTypeReceiveDefinition.idTokenName() = endpointDef().idTokenName();
+
   // MTypeSendDefinition
   eq MTypeSendDefinition.preemptiveExpectedValue() = lastValue();
   eq MTypeSendDefinition.preemptiveReturn() = "return false;";
   eq MTypeSendDefinition.endpointDef() = getSendTypeEndpointDefinition();
-  eq MTypeSendDefinition.firstInputVarName() = "get" + typeName() + "()";
+  eq MTypeSendDefinition.firstInputVarName() = getterMethod() + "()";
   eq MTypeSendDefinition.updateMethod() = "_update_" + typeName();
   eq MTypeSendDefinition.writeMethod() = "_writeLastValue_" + typeName();
 
   syn String MTypeSendDefinition.sender() = "_sender_" + typeName();
   syn String MTypeSendDefinition.lastValue() = "_lastValue" + typeName();
-  syn String MTypeSendDefinition.tokenResetMethod() = "get" + typeName() + "_reset";
+  syn String MTypeSendDefinition.tokenResetMethod() = getterMethod() + "_reset";
   syn boolean MTypeSendDefinition.shouldSendValue() = endpointDef().asTypeEndpointDefinition().shouldSendValue();
 
   // --- MMappingDefinition ---
@@ -313,7 +356,7 @@ aspect AspectGeneration {
   }
 }
 
-aspect RelationGeneration {
+aspect GrammarGeneration {
   syn java.util.List<Relation> RagConnect.additionalRelations() {
     java.util.List<Relation> result = new java.util.ArrayList<>();
     for (DependencyDefinition dd : allDependencyDefinitionList()) {
@@ -334,6 +377,37 @@ aspect RelationGeneration {
     result.addComment(new WhitespaceComment("\n"));
     return result;
   }
+
+//  coll java.util.Map<TypeDecl, TokenComponent> RagConnect.additionalTokens() [new java.util.HashMap<>()] with put root RagConnect;
+
+//  TypeEndpointDefinition contributes getTokenToCreate()
+//    when typeIsList() && !getUseList()
+//    to RagConnect.additionalTokens()
+////    for ragconnect()
+//    ;
+
+  syn java.util.Map<TypeDecl, TokenComponent> RagConnect.additionalTokens() {
+    java.util.Map<TypeDecl, TokenComponent> result = new java.util.HashMap<>();
+    for (EndpointDefinition def : allEndpointDefinitionList()) {
+      if (def.isTypeEndpointDefinition() && def.getTokenToCreate() != null) {
+        result.put(def.asTypeEndpointDefinition().getType().getTypeDecl(), def.getTokenToCreate());
+      }
+    }
+    return result;
+  }
+
+  syn TokenComponent EndpointDefinition.getTokenToCreate() = null;
+  eq TypeEndpointDefinition.getTokenToCreate() {
+    if (typeIsList() && !getUseList()) {
+      TokenComponent result = new TokenComponent();
+      result.setName(idTokenName());
+      result.setNTA(false);
+      result.setJavaTypeUse(new SimpleJavaTypeUse("String"));
+      return result;
+    } else {
+      return null;
+    }
+  }
 }
 
 aspect GrammarExtension {
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
index 47379bd06921509023f686ab05621b63eb5729d9..3a7fce51404e43be679d7b8829127099fd78b683 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
+++ b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
@@ -1,7 +1,7 @@
 aspect DefaultMappings {
 
   private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) {
-    return capitalize(typeName).replace("[]", "s");
+    return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List");
   }
 
   private MappingDefinitionType RagConnect.baseDefaultMappingTypeFromName(String typeName) {
@@ -67,6 +67,28 @@ aspect DefaultMappings {
     );
   }
 
+  syn nta DefaultMappingDefinition RagConnect.defaultBytesToListTreeMapping(String typeName) {
+    return treeDefaultMappingDefinition("byte[]", JastAddList + "<" + typeName + ">",
+      "String content = new String(input);\n" +
+      "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" +
+      "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
+      "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(content);\n" +
+      JastAddList + "<" + typeName + ">" + " result = " + typeName + ".deserializeList((com.fasterxml.jackson.databind.node.ArrayNode)mapper.readTree(parser));\n" +
+      "parser.close();\n" +
+      "return result;"
+    );
+  }
+  syn nta DefaultMappingDefinition RagConnect.defaultListTreeToBytesMapping() {
+    return treeDefaultMappingDefinition(JastAddList, "byte[]",
+      "java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
+      "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
+      "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n"+
+      "input.serialize(generator);\n" +
+      "generator.flush();\n" +
+      "return outputStream.toString().getBytes();"
+    );
+  }
+
   syn nta DefaultMappingDefinition RagConnect.defaultBooleanToBytesMapping() = baseDefaultMappingDefinition(
         "boolean", "byte[]", "return java.nio.ByteBuffer.allocate(1).put((byte) (input ? 1 : 0)).array();");
   syn nta DefaultMappingDefinition RagConnect.defaultIntToBytesMapping() = baseDefaultMappingDefinition(
@@ -187,13 +209,22 @@ aspect Mappings {
       case "String": return ragconnect().defaultBytesToStringMapping();
       default:
         try {
-            TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
-            return ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
+          TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
+          // TODO: also support list-types, if list is first type
+          return ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
         } catch (Exception ignore) {}
         System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
+  eq TypeEndpointDefinition.suitableReceiveDefaultMapping() {
+    try {
+      TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
+      return typeIsList() && getUseList() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
+    } catch (Exception ignore) {}
+    return super.suitableReceiveDefaultMapping();
+  }
+
   // --- suitableSendDefaultMapping ---
   syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() {
     switch (targetTypeName()) {
@@ -214,13 +245,21 @@ aspect Mappings {
       case "String": return ragconnect().defaultStringToBytesMapping();
       default:
         try {
-            TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
-            return ragconnect().defaultTreeToBytesMapping(typeDecl.getName());
+          TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
+          // TODO: also support list-types, if list is last type
+          return ragconnect().defaultTreeToBytesMapping(typeDecl.getName());
         } catch (Exception ignore) {}
         System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
+  eq TypeEndpointDefinition.suitableSendDefaultMapping() {
+    try {
+      TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
+      return typeIsList() && getUseList() ? ragconnect().defaultListTreeToBytesMapping() : ragconnect().defaultTreeToBytesMapping(typeDecl.getName());
+    } catch (Exception ignore) {}
+    return super.suitableSendDefaultMapping();
+  }
 
   // --- targetTypeName ---
   syn String EndpointDefinition.targetTypeName();
@@ -320,7 +359,9 @@ aspect Mappings {
     for (TypeDecl typeDecl : getProgram().typeDecls()) {
       result.add(defaultBytesToTreeMapping(typeDecl.getName()));
       result.add(defaultTreeToBytesMapping(typeDecl.getName()));
+      result.add(defaultBytesToListTreeMapping(typeDecl.getName()));
     }
+    result.add(defaultListTreeToBytesMapping());
 //    // string conversion
 //    result.add(defaultStringToBooleanMapping());
 //    result.add(defaultStringToIntMapping());
diff --git a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
index ef54a74e6fd57f11f17530e53524c63446277ca3..04b9cf89c248c39740677c88835b56c5325493bf 100644
--- a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
+++ b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
@@ -121,12 +121,14 @@ aspect MustacheNodesToYAML {
 
   syn MappingElement MTypeReceiveDefinition.toYAML() {
     MappingElement result = super.toYAML();
+    result.put("typeIsList", typeIsList());
     result.put("loggingEnabledForReads", loggingEnabledForReads);
     return result;
   }
 
   syn MappingElement MTypeSendDefinition.toYAML() {
     MappingElement result = super.toYAML();
+    result.put("typeIsList", typeIsList());
     result.put("sender", sender());
     result.put("lastValue", lastValue());
     result.put("loggingEnabledForWrites", loggingEnabledForWrites);
diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
index eefb64e8ec713991b1c8d214025e71586ad4a645..a7ba289535a01938c1284ad67f46b2a9e81b9fb5 100644
--- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
+++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
@@ -46,7 +46,36 @@ EndpointDefinition endpoint_definition_type
   = RECEIVE token_ref           {: return new ReceiveTokenEndpointDefinition().setToken(token_ref); :}
   | SEND token_ref              {: return new SendTokenEndpointDefinition().setToken(token_ref); :}
   | RECEIVE TREE type_ref       {: return new ReceiveTypeEndpointDefinition().setType(type_ref); :}
+  | RECEIVE TREE WITH ADD type_ref
+    {:
+      ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition();
+      result.setType(type_ref);
+      result.setWithAdd(true);
+      return result;
+    :}
   | SEND TREE type_ref          {: return new SendTypeEndpointDefinition().setType(type_ref); :}
+  | RECEIVE LIST type_ref
+    {:
+      ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition();
+      result.setType(type_ref);
+      result.setUseList(true);
+      return result;
+    :}
+  | RECEIVE LIST WITH ADD type_ref
+    {:
+      ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition();
+      result.setType(type_ref);
+      result.setWithAdd(true);
+      result.setUseList(true);
+      return result;
+    :}
+  | SEND LIST type_ref
+    {:
+      SendTypeEndpointDefinition result = new SendTypeEndpointDefinition();
+      result.setType(type_ref);
+      result.setUseList(true);
+      return result;
+    :}
 ;
 
 TokenComponent token_ref
diff --git a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
index 40d751b37ffab7ffe63355f50e226f794de02c3b..69830108bfb0644cd75008b47b58ee149dc3b2ad 100644
--- a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
+++ b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
@@ -6,3 +6,6 @@
 "to"         { return sym(Terminals.TO); }
 "as"         { return sym(Terminals.AS); }
 "tree"       { return sym(Terminals.TREE); }
+"list"       { return sym(Terminals.LIST); }
+"with"       { return sym(Terminals.WITH); }
+"add"        { return sym(Terminals.ADD); }
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index 8035d76e424cd813e62c400ffd70f84a6167a98f..915d9f92d49cf3a847f05d0b23393bce1832fc1a 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -232,6 +232,7 @@ public class Compiler extends AbstractCompiler {
     ragConnect.treeResolveAll();
 
     ragConnect.additionalRelations().forEach(ragConnectGrammarPart::addDeclaration);
+    ragConnect.additionalTokens().forEach(TypeDecl::addComponent);
     ASTNode.loggingEnabledForReads = optionLogReads.value();
     ASTNode.loggingEnabledForWrites = optionLogWrites.value();
     ASTNode.loggingEnabledForIncremental = optionLogIncremental.value();
@@ -241,6 +242,9 @@ public class Compiler extends AbstractCompiler {
     ASTNode.incrementalOptionActive = getConfiguration().incremental() && getConfiguration().traceFlush();
     LOGGER.fine(() -> "ASTNode.incrementalOptionActive = " + ASTNode.incrementalOptionActive);
 
+    // reuse "--List" option of JastAdd
+    ASTNode.JastAddList = getConfiguration().listType();
+
     ASTNode.usesMqtt = optionProtocols.hasValue(OPTION_PROTOCOL_MQTT);
     ASTNode.usesRest = optionProtocols.hasValue(OPTION_PROTOCOL_REST);
     return ragConnect;
diff --git a/ragconnect.base/src/main/resources/ListAspect.mustache b/ragconnect.base/src/main/resources/ListAspect.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..764b5441862c96a010f6d07f86e2c1aad2dec81c
--- /dev/null
+++ b/ragconnect.base/src/main/resources/ListAspect.mustache
@@ -0,0 +1,24 @@
+{{#hasTreeListEndpoints}}
+public void {{JastAddList}}.serialize(com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {
+  try {
+    g.writeStartArray();
+    for (T child : this) {
+      child.serialize(g);
+    }
+    g.writeEndArray();
+  } catch (java.io.IOException e) {
+    throw new SerializationException("unable to serialize {{JastAddList}}", e);
+  }
+}
+
+{{#typesForReceivingListEndpoints}}
+public static {{JastAddList}}<{{Name}}> {{Name}}.deserializeList(com.fasterxml.jackson.databind.node.ArrayNode node) throws DeserializationException {
+  {{JastAddList}}<{{Name}}> result = new {{JastAddList}}<>();
+  for (java.util.Iterator<com.fasterxml.jackson.databind.JsonNode> it = node.elements(); it.hasNext();) {
+    com.fasterxml.jackson.databind.JsonNode element = it.next();
+    result.add(deserialize(element));
+  }
+  return result;
+}
+{{/typesForReceivingListEndpoints}}
+{{/hasTreeListEndpoints}}
diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.jadd
index f84d8657ca7db7be868205b11cd939f27c08965d..a8f065e818c32874703d7f2775c589492fb6e8ee 100644
--- a/ragconnect.base/src/main/resources/MqttHandler.jadd
+++ b/ragconnect.base/src/main/resources/MqttHandler.jadd
@@ -1,11 +1,10 @@
-import java.io.IOException;
-import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
-aspect MqttHandler {
+import java.util.function.BiConsumer;aspect MqttHandler {
 public class MqttServerHandler {
   private final java.util.Map<String, MqttHandler> handlers = new java.util.HashMap<>();
-  private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>();
+  private final java.util.Map<ConnectToken, java.util.function.BiConsumer<String, byte[]>> tokensForRemoval = new java.util.HashMap<>();
   private long time;
   private java.util.concurrent.TimeUnit unit;
   private String name;
@@ -16,7 +15,7 @@ public class MqttServerHandler {
 
   public MqttServerHandler(String name) {
     this.name = name;
-    setupWaitUntilReady(1, TimeUnit.SECONDS);
+    setupWaitUntilReady(1, java.util.concurrent.TimeUnit.SECONDS);
   }
 
   public void setupWaitUntilReady(long time, java.util.concurrent.TimeUnit unit) {
@@ -24,7 +23,7 @@ public class MqttServerHandler {
     this.unit = unit;
   }
 
-  public MqttHandler resolveHandler(java.net.URI uri) throws IOException {
+  public MqttHandler resolveHandler(java.net.URI uri) throws java.io.IOException {
     MqttHandler handler = handlers.get(uri.getHost());
     if (handler == null) {
       // first connect to that server
@@ -40,33 +39,37 @@ public class MqttServerHandler {
     return handler;
   }
 
-  public ConnectToken newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException {
+  public ConnectToken newConnection(java.net.URI uri, java.util.function.BiConsumer<String, byte[]> callback) throws java.io.IOException {
     ConnectToken connectToken = new ConnectToken(uri);
     resolveHandler(uri).newConnection(extractTopic(uri), callback);
     tokensForRemoval.put(connectToken, callback);
     return connectToken;
   }
 
-  public boolean disconnect(ConnectToken connectToken) throws IOException {
+  public boolean disconnect(ConnectToken connectToken) throws java.io.IOException {
     MqttHandler handler = resolveHandler(connectToken.uri);
     return handler != null ? handler.disconnect(extractTopic(connectToken.uri), tokensForRemoval.get(connectToken)) : false;
   }
 
-  public void publish(java.net.URI uri, byte[] bytes) throws IOException {
+  public void publish(java.net.URI uri, byte[] bytes) throws java.io.IOException {
     resolveHandler(uri).publish(extractTopic(uri), bytes);
   }
 
-  public void publish(java.net.URI uri, byte[] bytes, boolean retain) throws IOException {
+  public void publish(java.net.URI uri, byte[] bytes, boolean retain) throws java.io.IOException {
     resolveHandler(uri).publish(extractTopic(uri), bytes, retain);
   }
 
   public void publish(java.net.URI uri, byte[] bytes,
-                      org.fusesource.mqtt.client.QoS qos, boolean retain) throws IOException {
+                      org.fusesource.mqtt.client.QoS qos, boolean retain) throws java.io.IOException {
     resolveHandler(uri).publish(extractTopic(uri), bytes, qos, retain);
   }
 
   public static String extractTopic(java.net.URI uri) {
     String path = uri.getPath();
+    if (uri.getFragment() != null) {
+      // do not also append fragment, as it is illegal, that anything follows "#" in a mqtt topic anyway
+      path += "#";
+    }
     if (path.charAt(0) == '/') {
       path = path.substring(1);
     }
@@ -100,7 +103,8 @@ public class MqttHandler {
   private boolean sendWelcomeMessage = true;
   private org.fusesource.mqtt.client.QoS qos;
   /** Dispatch knowledge */
-  private final java.util.Map<String, java.util.List<java.util.function.Consumer<byte[]>>> callbacks;
+  private final java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> normalCallbacks;
+  private final java.util.Map<java.util.regex.Pattern, java.util.List<java.util.function.BiConsumer<String, byte[]>>> wildcardCallbacks;
 
   public MqttHandler() {
     this("RagConnect");
@@ -109,7 +113,8 @@ public class MqttHandler {
   public MqttHandler(String name) {
     this.name = java.util.Objects.requireNonNull(name, "Name must be set");
     this.logger = org.apache.logging.log4j.LogManager.getLogger(MqttHandler.class);
-    this.callbacks = new java.util.HashMap<>();
+    this.normalCallbacks = new java.util.HashMap<>();
+    this.wildcardCallbacks = new java.util.HashMap<>();
     this.readyLatch = new java.util.concurrent.CountDownLatch(1);
     this.qos = org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE;
   }
@@ -122,21 +127,21 @@ public class MqttHandler {
   /**
    * Sets the host to receive messages from, and connects to it.
    * @param host name of the host to connect to, format is either <code>"$name"</code> or <code>"$name:$port"</code>
-   * @throws IOException if could not connect, or could not subscribe to a topic
+   * @throws java.io.IOException if could not connect, or could not subscribe to a topic
    * @return self
    */
   public MqttHandler setHost(String host) throws java.io.IOException {
     if (host.contains(":")) {
       int colon_index = host.indexOf(":");
       return setHost(host.substring(0, colon_index),
-                     Integer.parseInt(host.substring(colon_index + 1)));
+          Integer.parseInt(host.substring(colon_index + 1)));
     }
     return setHost(host, DEFAULT_PORT);
   }
 
   /**
    * Sets the host to receive messages from, and connects to it.
-   * @throws IOException if could not connect, or could not subscribe to a topic
+   * @throws java.io.IOException if could not connect, or could not subscribe to a topic
    * @return self
    */
   public MqttHandler setHost(String host, int port) throws java.io.IOException {
@@ -167,14 +172,14 @@ public class MqttHandler {
                             org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) {
         // this method is called, whenever a MQTT message is received
         String topicString = topic.toString();
-        java.util.List<java.util.function.Consumer<byte[]>> callbackList = new java.util.ArrayList<>(callbacks.get(topicString));
-        if (callbackList == null || callbackList.isEmpty()) {
+        java.util.List<java.util.function.BiConsumer<String, byte[]>> callbackList = callbacksFor(topicString);
+        if (callbackList.isEmpty()) {
           logger.debug("Got a message at {}, but no callback to call. Forgot to subscribe?", topic);
         } else {
           byte[] message = body.toByteArray();
-          for (java.util.function.Consumer<byte[]> callback : callbackList) {
+          for (java.util.function.BiConsumer<String, byte[]> callback : callbackList) {
             try {
-              callback.accept(message);
+              callback.accept(topicString, message);
             } catch (Exception e) {
               logger.catching(e);
             }
@@ -199,20 +204,20 @@ public class MqttHandler {
     throwIf(error);
 
     // actually establish the connection
-    connection.connect(new org.fusesource.mqtt.client.Callback<Void>() {
+    connection.connect(new org.fusesource.mqtt.client.Callback<>() {
       @Override
       public void onSuccess(Void value) {
         if (MqttHandler.this.sendWelcomeMessage) {
           connection.publish("components",
-                             (name + " is connected").getBytes(),
-                             org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE,
-                             false,
-                             new org.fusesource.mqtt.client.Callback<Void>() {
-            @Override
-            public void onSuccess(Void value) {
-              logger.debug("success sending welcome message");
-              setReady();
-            }
+              (name + " is connected").getBytes(),
+              org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE,
+              false,
+              new org.fusesource.mqtt.client.Callback<>() {
+                @Override
+                public void onSuccess(Void value) {
+                  logger.debug("success sending welcome message");
+                  setReady();
+                }
 
             @Override
             public void onFailure(Throwable value) {
@@ -233,6 +238,20 @@ public class MqttHandler {
     return this;
   }
 
+  private java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksFor(String topicString) {
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> result = new java.util.ArrayList<>();
+    List<BiConsumer<String, byte[]>> normalCallbackList = normalCallbacks.get(topicString);
+    if (normalCallbackList != null) {
+      result.addAll(normalCallbackList);
+    }
+    wildcardCallbacks.forEach((topicPattern, callback) -> {
+      if (topicPattern.matcher(topicString).matches()) {
+        result.addAll(callback);
+      }
+    });
+    return result;
+  }
+
   public java.net.URI getHost() {
     return host;
   }
@@ -251,61 +270,147 @@ public class MqttHandler {
     this.qos = qos;
   }
 
+  /**
+   * Establish a new connection for some topic.
+   * @param topic    the topic to create a connection for, may contain the wildcards "*" and "#"
+   * @param callback the callback to run if a new message arrives for this topic
+   * @return true if successful stored this connection, false otherwise (e.g., on failed subscribe)
+   */
   public boolean newConnection(String topic, java.util.function.Consumer<byte[]> callback) {
+    return newConnection(topic, (ignoredTopicString, bytes) -> callback.accept(bytes));
+  }
+
+  /**
+   * Establish a new connection for some topic.
+   * @param topic    the topic to create a connection for, may contain the wildcards "*" and "#"
+   * @param callback the callback to run if a new message arrives for this topic
+   * @return true if successful stored this connection, false otherwise (e.g., on failed subscribe)
+   */
+  public boolean newConnection(String topic, java.util.function.BiConsumer<String, byte[]> callback) {
     if (readyLatch.getCount() > 0) {
       System.err.println("Handler not ready");
       return false;
     }
     // register callback
     logger.debug("new connection for {}", topic);
-    if (callbacks.get(topic) == null || callbacks.get(topic).isEmpty()) {
-      callbacks.put(topic, new java.util.ArrayList<>());
-
+    final boolean needSubscribe;
+    if (isWildcardTopic(topic)) {
+      String regexForTopic = topic.replace("*", "[^/]*").replace("#", ".*");
+      java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regexForTopic);
+      wildcardCallbacks.computeIfAbsent(pattern, p -> new java.util.ArrayList<>())
+                       .add(callback);
+      needSubscribe = true;
+    } else { // normal topic
+      java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksForTopic = normalCallbacks.get(topic);
+      if (callbacksForTopic == null || callbacksForTopic.isEmpty()) {
+        callbacksForTopic = new java.util.ArrayList<>();
+        normalCallbacks.put(topic, callbacksForTopic);
+        needSubscribe = true;
+      } else {
+        needSubscribe = false;
+      }
+      callbacksForTopic.add(callback);
+    }
+    if (needSubscribe) {
       // subscribe at broker
+      CountDownLatch operationFinished = new CountDownLatch(1);
+      java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(true);
       org.fusesource.mqtt.client.Topic[] topicArray = { new org.fusesource.mqtt.client.Topic(topic, this.qos) };
       connection.getDispatchQueue().execute(() -> {
-        connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() {
+        connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<>() {
           @Override
           public void onSuccess(byte[] qoses) {
             logger.debug("Subscribed to {}, qoses: {}", topic, qoses);
+            operationFinished.countDown();
           }
 
           @Override
           public void onFailure(Throwable cause) {
             logger.error("Could not subscribe to {}", topic, cause);
+            success.set(false);
+            operationFinished.countDown();
           }
         });
       });
+      try {
+        operationFinished.await(2, TimeUnit.SECONDS);
+        return success.get();
+      } catch (InterruptedException e) {
+        return false;
+      }
+    } else {
+      return true;
     }
-    callbacks.get(topic).add(callback);
-    return true;
   }
 
-  public boolean disconnect(String topic, Object callback) {
-    java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topic);
-    if (callbackList == null) {
+  private boolean isWildcardTopic(String topic) {
+    return topic.contains("*") || topic.contains("#");
+  }
+
+  public boolean disconnect(String topic, java.util.function.BiConsumer<String, byte[]> callback) {
+    boolean needUnsubscribe = false;
+    java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(true);
+
+    boolean foundTopicInCallbacks = false;
+
+    // check if wildcard is to be removed
+    if (isWildcardTopic(topic)) {
+      java.util.regex.Pattern wildcardPatternToRemove = null;
+      for (java.util.Map.Entry<java.util.regex.Pattern, java.util.List<java.util.function.BiConsumer<String, byte[]>>> entry : wildcardCallbacks.entrySet()) {
+        if (entry.getKey().pattern().equals(topic)) {
+          foundTopicInCallbacks = true;
+          // if still successful, update with whether callback could be removed
+          success.compareAndSet(true, (entry.getValue().remove(callback)));
+          if (entry.getValue().isEmpty()) {
+            wildcardPatternToRemove = entry.getKey();
+            needUnsubscribe = true;
+          }
+          break;
+        }
+      }
+      ;
+      if (wildcardPatternToRemove != null) {
+        wildcardCallbacks.remove(wildcardPatternToRemove);
+      }
+    } else if (normalCallbacks.containsKey(topic)) {
+      foundTopicInCallbacks = true;
+      // if still successful, update with whether callback could be removed
+      var normalCallbackList = normalCallbacks.get(topic);
+      success.compareAndSet(true, normalCallbackList.remove(callback));
+      needUnsubscribe |= normalCallbackList.isEmpty();
+    }
+
+    if (!foundTopicInCallbacks) {
       logger.warn("Disconnect for not connected topic '{}'", topic);
       return false;
     }
-    java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>();
-    success.set(callbackList.remove(callback));
-    if (callbackList.isEmpty()) {
+
+    if (needUnsubscribe) {
+      java.util.concurrent.CountDownLatch operationFinished = new java.util.concurrent.CountDownLatch(1);
       // no callbacks anymore for this topic, unsubscribe from mqtt
       connection.getDispatchQueue().execute(() -> {
         org.fusesource.hawtbuf.UTF8Buffer topicBuffer = org.fusesource.hawtbuf.Buffer.utf8(topic);
         org.fusesource.hawtbuf.UTF8Buffer[] topicArray = new org.fusesource.hawtbuf.UTF8Buffer[]{topicBuffer};
-        connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<Void>() {
+        connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<>() {
           @Override
           public void onSuccess(Void value) {
-            // empty, all good
+            operationFinished.countDown();
           }
 
           @Override
           public void onFailure(Throwable cause) {
             success.set(false);
+            logger.warn("Could not disconnect from {}", topic, cause);
+            operationFinished.countDown();
           }
         });
       });
+      try {
+        operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        logger.catching(e);
+        success.set(false);
+      }
     }
     return success.get();
   }
@@ -334,7 +439,7 @@ public class MqttHandler {
       return;
     }
     connection.getDispatchQueue().execute(() -> {
-      connection.disconnect(new org.fusesource.mqtt.client.Callback<Void>() {
+      connection.disconnect(new org.fusesource.mqtt.client.Callback<>() {
         @Override
         public void onSuccess(Void value) {
           logger.info("Disconnected {} from {}", name, host);
@@ -358,7 +463,7 @@ public class MqttHandler {
 
   public void publish(String topic, byte[] bytes, org.fusesource.mqtt.client.QoS qos, boolean retain) {
     connection.getDispatchQueue().execute(() -> {
-      connection.publish(topic, bytes, qos, retain, new org.fusesource.mqtt.client.Callback<Void>() {
+      connection.publish(topic, bytes, qos, retain, new org.fusesource.mqtt.client.Callback<>() {
         @Override
         public void onSuccess(Void value) {
           logger.debug("Published some bytes to {}", topic);
diff --git a/ragconnect.base/src/main/resources/handleUri.mustache b/ragconnect.base/src/main/resources/handleUri.mustache
index aa4176ef0b067bca3c54ca754096f633cadcfa71..ff0ea7af7f920ac09a14de75a4afe1882540ac94 100644
--- a/ragconnect.base/src/main/resources/handleUri.mustache
+++ b/ragconnect.base/src/main/resources/handleUri.mustache
@@ -4,7 +4,7 @@ try {
   uri = new java.net.URI({{connectParameterName}});
   scheme = uri.getScheme();
   host = uri.getHost();
-  path = uri.getPath();
+  path = uri.getPath() + (uri.getFragment() != null ? "#" : "");
 } catch (java.net.URISyntaxException e) {
   System.err.println(e.getMessage());  // Maybe re-throw error?
   return false;
diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache
index 47ec31ee58b27485a51e2b7e3a7de196cbe130f3..bc59e0b318dcd430b5f21fd0a67d157b35a4bb7c 100644
--- a/ragconnect.base/src/main/resources/mappingApplication.mustache
+++ b/ragconnect.base/src/main/resources/mappingApplication.mustache
@@ -1,7 +1,7 @@
-{{lastDefinitionToType}} {{lastResult}};
+{{{lastDefinitionToType}}} {{lastResult}};
 try {
   {{#InnerMappingDefinitions}}
-  {{^last}}{{toType}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}});
+  {{^last}}{{{toType}}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}});
   {{/InnerMappingDefinitions}}
 } catch (RagConnectRejectMappingException e) {
   // do not print message in case of rejection
diff --git a/ragconnect.base/src/main/resources/mappingDefinition.mustache b/ragconnect.base/src/main/resources/mappingDefinition.mustache
index 47a9381e2e017b8cc7b7264db6784f2bab1227d2..09f161f37c3742850b09969d4eb85c7798d2d346 100644
--- a/ragconnect.base/src/main/resources/mappingDefinition.mustache
+++ b/ragconnect.base/src/main/resources/mappingDefinition.mustache
@@ -1,3 +1,3 @@
-protected static {{toType}} ASTNode.{{methodName}}({{fromType}} {{fromVariableName}}) throws Exception {
+protected static {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{fromVariableName}}) throws Exception {
   {{{content}}}
 }
diff --git a/ragconnect.base/src/main/resources/ragConnectVersion.properties b/ragconnect.base/src/main/resources/ragConnectVersion.properties
index cc1f011163b2d40f2569f47f8c4151154d2fab63..7e24342af2954e314a43f78b900f8921a74678a8 100644
--- a/ragconnect.base/src/main/resources/ragConnectVersion.properties
+++ b/ragconnect.base/src/main/resources/ragConnectVersion.properties
@@ -1,2 +1,2 @@
-#Thu Jun 03 11:17:05 CEST 2021
-version=0.3.1
+#Thu Jun 24 17:13:44 CEST 2021
+version=0.3.2-alpha
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index 50cd5cece4429e3529c64f5896548e50bbe564b0..c08529a22bfacc7ec5caa3e6e6569e51bc5182a1 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -35,6 +35,9 @@ aspect RagConnect {
   {{#TokenComponents}}
   {{> tokenComponent}}
   {{/TokenComponents}}
+
+  {{> ListAspect}}
+
   public void {{rootNodeName}}.ragconnectCheckIncremental() {
   {{#incrementalOptionActive}}
     // check if --tracing is active
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index 10f519129c8dec8abe587a1bcb5876c4c0224745..4ae8a05c6f97e03f5522160a487f33a710186a2c 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -1,15 +1,104 @@
-public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
-  {{>handleUri}}
-  java.util.function.Consumer<byte[]> consumer = message -> {
+{{#typeIsList}}
+{{^UseList}}
+/* first try with resolve to type
+syn {{typeName}} {{parentTypeName}}.{{resolveInListAttributeName}}(String topic) {
+  for ({{typeName}} element : get{{entityName}}()) {
+    if (element.get{{idTokenName}}().equals(topic)) {
+      return element;
+    }
+  }
+  return null;
+}
+*/
+syn int {{parentTypeName}}.{{resolveInListAttributeName}}(String topic) {
+  for (int index = 0; index < getNum{{entityName}}(); index++) {
+    if (get{{entityName}}(index).get{{idTokenName}}().equals(topic)) {
+      return index;
+    }
+  }
+  return -1;
+}
+{{/UseList}}
+{{/typeIsList}}
+
+/**
+ * Connects the receive endpoint {{entityName}}.
+{{#typeIsList}}{{#isWithAdd}}
+ * New values are appended to the end of the list.
+{{/isWithAdd}}{{/typeIsList}}
+ * @param {{connectParameterName}} string describing protocol and path as an URI
+{{#typeIsList}}{{^UseList}}{{^isWithAdd}}
+ * @param index index of node in list to connect (the list is expected to have enough elements)
+{{/isWithAdd}}{{/UseList}}{{/typeIsList}}
+ * @return true if connect was successful, false otherwise
+ * @throws java.io.IOException if connect failed
+ */
+public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}{{#typeIsList}}{{^UseList}}{{^isWithAdd}}, int index{{/isWithAdd}}{{/UseList}}{{/typeIsList}}) throws java.io.IOException {
+  java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
     {{> mappingApplication}}
-    {{#loggingEnabledForReads}}
+{{#loggingEnabledForReads}}
     System.out.println("[Receive] " + {{connectParameterName}} + " -> {{entityName}} = " + {{lastResult}});
-    {{/loggingEnabledForReads}}
-    {{#isTypeEndpointDefinition}}
+{{/loggingEnabledForReads}}
+{{#isTypeEndpointDefinition}}
     {{lastResult}}.treeResolveAll();
-    {{/isTypeEndpointDefinition}}
+ {{#typeIsList}}
+  {{#UseList}}
+   {{#isWithAdd}}
+    {{getterMethod}}().addAll({{lastResult}});
+   {{/isWithAdd}}
+   {{^isWithAdd}}
+    set{{entityName}}({{lastResult}});
+   {{/isWithAdd}}
+  {{/UseList}}
+  {{^UseList}}
+    {{lastResult}}.set{{idTokenName}}(topic);
+   {{#isWithAdd}}
+    {{getterMethod}}().add({{lastResult}});
+   {{/isWithAdd}}
+   {{^isWithAdd}}
+    set{{entityName}}({{lastResult}}, index);
+   {{/isWithAdd}}
+  {{/UseList}}
+ {{/typeIsList}}
+ {{^typeIsList}}
+    set{{entityName}}({{lastResult}});
+ {{/typeIsList}}
+{{/isTypeEndpointDefinition}}
+{{^isTypeEndpointDefinition}}
     set{{entityName}}({{lastResult}});
+{{/isTypeEndpointDefinition}}
   };
+  return {{internalConnectMethod}}({{connectParameterName}}, consumer);
+}
+
+{{#typeIsList}}{{^UseList}}{{^isWithAdd}}
+/**
+ * Connects the receive endpoint {{entityName}} using a "wildcard" URI (if supported by the chosen protocol).
+ * @param {{connectParameterName}} string describing protocol and path as an URI
+ * @return true if connect was successful, false otherwise
+ * @throws java.io.IOException if connect failed
+*/
+public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
+  java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
+    {{> mappingApplication}}
+{{#loggingEnabledForReads}}
+    System.out.println("[Receive] " + {{connectParameterName}} + " (" + topic + ") -> {{entityName}} = " + {{lastResult}});
+{{/loggingEnabledForReads}}
+    {{lastResult}}.set{{idTokenName}}(topic);
+    int resolvedIndex = {{resolveInListAttributeName}}(topic);
+    if (resolvedIndex == -1) {
+      add{{entityName}}({{lastResult}});
+    } else {
+      set{{entityName}}({{lastResult}}, resolvedIndex);
+    }
+  };
+  return {{internalConnectMethod}}({{connectParameterName}}, consumer);
+}
+{{/isWithAdd}}{{/UseList}}{{/typeIsList}}
+
+private boolean {{parentTypeName}}.{{internalConnectMethod}}(String {{connectParameterName}},
+    java.util.function.BiConsumer<String, byte[]> consumer) throws java.io.IOException {
+  {{>handleUri}}
   ConnectToken connectToken;
   switch (scheme) {
   {{#usesMqtt}}
@@ -23,7 +112,8 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
   {{#usesRest}}
     case "rest":
       connectToken = {{restHandlerAttribute}}().newPUTConnection(uri, input -> {
-        consumer.accept(input.getBytes());
+        // TODO wildcard-topic not supported yet
+        consumer.accept("", input.getBytes());
       });
       if (connectToken == null) {
         return false;
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 62cfb7d5016fe5ed6ac2abadab45e9fdc3a45228..6fd97bf2cc7154cd0d053311f95166347cbccc6a 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -16,7 +16,7 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
       final String topic = {{mqttHandlerAttribute}}().extractTopic(uri);
       {{sender}} = () -> {
         {{#loggingEnabledForWrites}}
-        System.out.println("[Send] {{entityName}} = " + get{{entityName}}() + " -> " + {{connectParameterName}});
+        System.out.println("[Send] {{entityName}} = " + {{getterMethod}}() + " -> " + {{connectParameterName}});
         {{/loggingEnabledForWrites}}
         handler.publish(topic, {{lastValue}});
       };
@@ -45,7 +45,7 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
   connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
                .put(uri, connectToken);
   {{#incrementalOptionActive}}
-  _ragConnectObserver().add(connectToken, this, "get{{entityName}}", () -> {
+  _ragConnectObserver().add(connectToken, this, "{{getterMethod}}", () -> {
     if (this.{{updateMethod}}()) {
       this.{{writeMethod}}();
     }
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 485a59d61efefe9f77592c1f62ca485db087da98..61470f5292531d0ec7ca5d3691a3b248bcff1588 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -9,7 +9,7 @@ buildscript {
     }
     dependencies {
         classpath 'org.jastadd:jastaddgradle:1.13.3'
-        classpath 'org.jastadd.preprocessor:testing:0.2.8'
+        classpath 'org.jastadd.preprocessor:testing:0.2.10'
     }
 }
 
@@ -411,3 +411,154 @@ task compileTreeAllowedTokensIncremental(type: RagConnectTest) {
                         '--flush=full']
     }
 }
+
+// --- Test: list-manual ---
+task compileListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/list')
+        inputFiles = [file('src/test/01-input/list/Test.relast'),
+                      file('src/test/01-input/list/Test.connect'),
+                      file('src/test/01-input/list/TestDependencies.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/list/list'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'list.ast'
+        inputFiles = [file('src/test/01-input/list/Test.jadd')]
+    }
+}
+
+// --- Test: list-incremental ---
+task compileListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/listInc')
+        inputFiles = [file('src/test/01-input/list/Test.relast'),
+                      file('src/test/01-input/list/Test.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/listInc/listInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'listInc.ast'
+        inputFiles = [file('src/test/01-input/list/Test.jadd')]
+        extraOptions = ['--tracing=cache,flush',
+                        '--incremental=param',
+                        '--cache=all',
+                        '--rewrite=cnta',
+                        '--flush=full']
+    }
+}
+
+// --- Test: singleList-manual ---
+task compileSingleListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/singleList')
+        inputFiles = [file('src/test/01-input/singleList/Test.relast'),
+                      file('src/test/01-input/singleList/Test.connect'),
+                      file('src/test/01-input/singleList/TestDependencies.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleList/singleList'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'singleList.ast'
+        inputFiles = [file('src/test/01-input/singleList/Test.jadd')]
+    }
+}
+
+// --- Test: singleList-incremental ---
+task compileSingleListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/singleListInc')
+        inputFiles = [file('src/test/01-input/singleList/Test.relast'),
+                      file('src/test/01-input/singleList/Test.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleListInc/singleListInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'singleListInc.ast'
+        inputFiles = [file('src/test/01-input/singleList/Test.jadd')]
+        extraOptions = ['--tracing=cache,flush',
+                        '--incremental=param',
+                        '--cache=all',
+                        '--rewrite=cnta',
+                        '--flush=full']
+    }
+}
+
+// --- Test: singleListVariant-manual ---
+task compileSingleListVariantManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/singleListVariant')
+        inputFiles = [file('src/test/01-input/singleListVariant/Test.relast'),
+                      file('src/test/01-input/singleListVariant/Test.connect'),
+                      file('src/test/01-input/singleListVariant/TestDependencies.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleListVariant/singleListVariant'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'singleListVariant.ast'
+        inputFiles = [file('src/test/01-input/singleListVariant/Test.jadd')]
+    }
+}
+
+// --- Test: singleListVariant-incremental ---
+task compileSingleListVariantIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/singleListVariantInc')
+        inputFiles = [file('src/test/01-input/singleListVariant/Test.relast'),
+                      file('src/test/01-input/singleListVariant/Test.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleListVariantInc/singleListVariantInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'singleListVariantInc.ast'
+        inputFiles = [file('src/test/01-input/singleListVariant/Test.jadd')]
+        extraOptions = ['--tracing=cache,flush',
+                        '--incremental=param',
+                        '--cache=all',
+                        '--rewrite=cnta',
+                        '--flush=full']
+    }
+}
+
+//task cleanCurrentManualTest(type: Delete) {
+//    delete "src/test/02-after-ragconnect/singleListVariant"
+//    delete "src/test/03-after-relast/singleListVariant"
+//    delete "src/test/java-gen/singleListVariant/ast"
+//}
+//task cleanCurrentIncrementalTest(type: Delete) {
+//    delete "src/test/02-after-ragconnect/singleListVariantInc"
+//    delete "src/test/03-after-relast/singleListVariantInc"
+//    delete "src/test/java-gen/singleListVariantInc/ast"
+//}
+//compileSingleListVariantManual.dependsOn cleanCurrentManualTest
+//compileSingleListVariantIncremental.dependsOn cleanCurrentIncrementalTest
diff --git a/ragconnect.tests/src/test/01-input/list/README.md b/ragconnect.tests/src/test/01-input/list/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7634a3d2069a10b5c07bef272f757bd5ee2df8c9
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/README.md
@@ -0,0 +1,34 @@
+# List
+
+Idea: send and receive lists of subtrees.
+Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation
+
+## Execution-Model
+
+```
+SenderRoot                                    ReceiverRoot
+|- A* ---( mqtt: a ) ---+------> A* --------------------|
+|                       \------> WidthAddFromA:A* ------|   
+|- SingleA:A*                ,-> FromSingleA:A* --------|
+     \---( mqtt: single-a ) -+-> WithAddFromSingleA:A* -|
+```
+
+## Execution-Trace (SendInitialValue)
+
+| Input | # | A* | WidthAddFromA | FromSingleA | WithAddFromSingleA:A |
+|---|---|---|---|---|---|
+| 0 | 1 | [] | [0] | [] | [0] |
+| 1 | 2 | [1] | [1] | [1] | [0,1] |
+| 1 | 2 | [1] | [1] | [1] | [0,1] |
+| 2 | 3 | [1,2] | [2] | [1,1,2] | [0,1,2] |
+| 3 | 4 | [1,2,3] | [3] | [1,1,2,1,2,3] | [0,1,2,3] |
+
+## Execution-Trace (OnlyUpdate)
+
+| Input | # | A* | WidthAddFromA | FromSingleA | WithAddFromSingleA:A |
+|---|---|---|---|---|---|
+| - | 0 | [] | [] | [] | [] |
+| 1 | 1 | [1] | [1] | [1] | [1] |
+| 1 | 1 | [1] | [1] | [1] | [1] |
+| 2 | 2 | [1,2] | [2] | [1,1,2] | [1,2] |
+| 3 | 3 | [1,2,3] | [3] | [1,1,2,1,2,3] | [1,2,3] |
diff --git a/ragconnect.tests/src/test/01-input/list/Test.connect b/ragconnect.tests/src/test/01-input/list/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..c0efaea9d5ad307c0c43b7d577b1cff5884c0d8b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/Test.connect
@@ -0,0 +1,6 @@
+send list SenderRoot.A ;
+send list SenderRoot.SingleA ;
+receive list ReceiverRoot.A ;
+receive list ReceiverRoot.FromSingleA ;
+receive list with add ReceiverRoot.WithAddFromA ;
+receive list with add ReceiverRoot.WithAddFromSingleA ;
diff --git a/ragconnect.tests/src/test/01-input/list/Test.jadd b/ragconnect.tests/src/test/01-input/list/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..9d8ab5395a6c58e790023f902e53bae5763de76e
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/Test.jadd
@@ -0,0 +1,37 @@
+aspect Computation {
+  syn JastAddList<A> SenderRoot.getAList() {
+    var result = new JastAddList<A>();
+    for (int i = 1; i <= getInput(); i++) {
+      A a = new A().setID(i);
+      B b = new B().setID(i + 1);
+      a.addB(b);
+      result.addChild(a);
+    }
+    return result;
+  }
+  syn JastAddList<A> SenderRoot.getSingleAList() {
+    var result = new JastAddList<A>();
+    A a = new A().setID(getInput());
+    result.addChild(a);
+    return result;
+  }
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Testing {
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperReceiverRoot {}
+  class A implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperA {}
+  class B implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperB {}
+  class JastAddList<T> implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperJastAddList<T> {}
+}
+
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String Nameable.customID() {
+    return getClass().getSimpleName() + getID();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/list/Test.relast b/ragconnect.tests/src/test/01-input/list/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..2680b5cd43fc68d144386fc4494a52cc1ed5e002
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/Test.relast
@@ -0,0 +1,8 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Input:int> /A*/ /SingleA:A*/ ;
+
+ReceiverRoot : Nameable ::= A* FromSingleA:A* WithAddFromA:A* WithAddFromSingleA:A* ;
+A : Nameable ::= B* ;
+B : Nameable ;
diff --git a/ragconnect.tests/src/test/01-input/list/TestDependencies.connect b/ragconnect.tests/src/test/01-input/list/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..bce44247ea8db263a29ce66f08bb10346c3c4245
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/TestDependencies.connect
@@ -0,0 +1,2 @@
+SenderRoot.A canDependOn SenderRoot.Input as InputDependencyToA ;
+SenderRoot.SingleA canDependOn SenderRoot.Input as InputDependencyToSingleA ;
diff --git a/ragconnect.tests/src/test/01-input/singleList/README.md b/ragconnect.tests/src/test/01-input/singleList/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..6a76bb4d8457d1f246edaeb96d6d72048ef4ced1
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/README.md
@@ -0,0 +1,47 @@
+# Single List
+
+Idea: send and receive single values for lists of subtrees.
+Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation
+
+## Execution-Model
+
+```
+SenderRoot                         ReceiverRoot
+|- A1 --( a/1 ) --\                          |
+|- A2 --( a/2 ) --+=\  /--> A* --------------|
+|- A3 --( a/3 ) ----+==+--> WithAdd:A* ------|
+|- A4 --( a/4 ) --+=/                        |
+|- IO --( a/5 ) --/                          |
+                       /--> UsingWc:A* ------|
+              ( a/# ) -+--> UsingWcWithA:A* -|
+```
+
+## Computation
+
+A _n_ = Input _n_ + 1, e.g., A1 = Input1 + 1
+
+## Execution-Trace (SendInitialValue)
+
+| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A |
+|---|---|---|---|---|---|---|
+| * | [1,2,3,4,0] | 5 | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] |
+| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] |
+| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] |
+| I1:2 | [3,2,3,4,0] | 7 | [3,2,3,4,0] | [3,2,3,4,0] | [1,2,3,4,0,2,3] | [1,2,3,4,0,2,3] |
+| IO:5 | [3,2,3,4,5] | 8 | [3,2,3,4,5] | [3,2,3,4,5] | [1,2,3,4,0,2,3,5] | [1,2,3,4,0,2,3,5]
+| I3:4 | [3,2,7,4,5] | 9 | [3,2,7,4,5] | [3,2,7,4,5] | [1,2,3,4,0,2,3,5,7] | [1,2,3,4,0,2,3,5,7] |
+
+*: (1:0, 2:0, 3:0, 4:0, 5:0)
+
+## Execution-Trace (OnlyUpdate)
+
+| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A |
+|---|---|---|---|---|---|---|
+| * | [-,-,-,-,-] | 0 | [0,0,0,0,0] | [] | [] | [] |
+| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] |
+| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] |
+| I1:2 | [3,-,-,-,-] | 2 | [3,0,0,0,0] | [3] | [2,3] | [2,3] |
+| IO:5 | [2,-,-,-,5] | 3 | [3,0,0,0,5] | [3,5] | [2,3,5] | [2,3,5]
+| I3:4 | [2,-,7,-,5] | 4 | [3,0,7,0,5] | [3,5,7] | [2,3,5,7] | [2,3,5,7] |
+
+*: (1:0, 2:0, 3:0, 4:0, 5:0)
diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.connect b/ragconnect.tests/src/test/01-input/singleList/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..21b2796544ae65e96da5b03f088e5f7966620a7f
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/Test.connect
@@ -0,0 +1,14 @@
+send tree SenderRoot.A1 ;
+send tree SenderRoot.A2 ;
+send tree SenderRoot.A3 ;
+send tree SenderRoot.A4 ;
+send SenderRoot.InOutput using IntToA ;
+
+receive tree ReceiverRoot.A ;
+receive tree ReceiverRoot.UsingWildcardA ;
+receive tree with add ReceiverRoot.WithAddA ;
+receive tree with add ReceiverRoot.UsingWildcardWithAddA ;
+
+IntToA maps int i to A {:
+    return new A().setID(i);
+:}
diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.jadd b/ragconnect.tests/src/test/01-input/singleList/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..005b419bb1f625a460e219079dcd3a5361b5844d
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/Test.jadd
@@ -0,0 +1,25 @@
+aspect Computation {
+  syn A SenderRoot.getA1() = new A().setID(getInput1() + 1);
+  syn A SenderRoot.getA2() = new A().setID(getInput2() + 2);
+  syn A SenderRoot.getA3() = new A().setID(getInput3() + 3);
+  syn A SenderRoot.getA4() = new A().setID(getInput4() + 4);
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Testing {
+  class SenderRoot implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperSenderRoot {}
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperReceiverRoot {}
+  class A implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperA {}
+  class JastAddList<T> implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperJastAddList<T> {}
+}
+
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String Nameable.customID() {
+    return getClass().getSimpleName() + getID();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.relast b/ragconnect.tests/src/test/01-input/singleList/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..0d97bb6c6b2fa4be6a4628b5e7e45926817364d0
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/Test.relast
@@ -0,0 +1,11 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Input1:int> /A1:A/
+                          <Input2:int> /A2:A/
+                          <Input3:int> /A3:A/
+                          <Input4:int> /A4:A/
+                          <InOutput:int> ;
+
+ReceiverRoot : Nameable ::= A* UsingWildcardA:A* WithAddA:A* UsingWildcardWithAddA:A* ;
+A : Nameable ;
diff --git a/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect b/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..f1429b6aa1cee28ca3d2a1421060af74a28fbcf8
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect
@@ -0,0 +1,4 @@
+SenderRoot.A1 canDependOn SenderRoot.Input1 as InputDependencyToA1 ;
+SenderRoot.A2 canDependOn SenderRoot.Input2 as InputDependencyToA2 ;
+SenderRoot.A3 canDependOn SenderRoot.Input3 as InputDependencyToA3 ;
+SenderRoot.A4 canDependOn SenderRoot.Input4 as InputDependencyToA4 ;
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/README.md b/ragconnect.tests/src/test/01-input/singleListVariant/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4004ac42c136c25f2447efe5c1161c98754f32c1
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/README.md
@@ -0,0 +1,61 @@
+# Single List
+
+Idea: send and receive single values for lists of subtrees.
+Test different variants of the structure/shape of the send/received value.
+
+## Execution-Model
+
+TODO: check again (old model copied from `singleList`)
+
+```
+SenderRoot/ReceiverRoot
+  |- T_Empty       ::= /* empty */ ;
+  |- T_Token       ::= <Value:String> ;
+  |- T_OneChild    ::= Other ;
+  |- T_OneOpt      ::= [Other] ;
+  |- T_OneList     ::= Other* ;
+  |- T_TwoChildren ::= Left:Other Right:Other ;
+  |- T_OneOfEach   ::= First:Other [Second:Other] Third:Other* <Fourth:String> ;
+  |- abstract T_Abstract ::= <ValueAbstract> ;
+```
+
+## Computation
+
+```
+T.ID = Input
+T.token = Input
+T.Other.ID = Input + 1
+```
+
+## Execution-Trace (SendInitialValue)
+
+Inputs:
+
+- 1
+- 1
+- 2
+- 3
+
+| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A |
+|---|---|---|---|---|---|---|
+| * | [1,2,3,4,0] | 5 | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] |
+| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] |
+| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] |
+| I1:2 | [3,2,3,4,0] | 7 | [3,2,3,4,0] | [3,2,3,4,0] | [1,2,3,4,0,2,3] | [1,2,3,4,0,2,3] |
+| IO:5 | [3,2,3,4,5] | 8 | [3,2,3,4,5] | [3,2,3,4,5] | [1,2,3,4,0,2,3,5] | [1,2,3,4,0,2,3,5]
+| I3:4 | [3,2,7,4,5] | 9 | [3,2,7,4,5] | [3,2,7,4,5] | [1,2,3,4,0,2,3,5,7] | [1,2,3,4,0,2,3,5,7] |
+
+*: (1:0, 2:0, 3:0, 4:0, 5:0)
+
+## Execution-Trace (OnlyUpdate)
+
+| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A |
+|---|---|---|---|---|---|---|
+| * | [-,-,-,-,-] | 0 | [0,0,0,0,0] | [] | [] | [] |
+| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] |
+| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] |
+| I1:2 | [3,-,-,-,-] | 2 | [3,0,0,0,0] | [3] | [2,3] | [2,3] |
+| IO:5 | [2,-,-,-,5] | 3 | [3,0,0,0,5] | [3,5] | [2,3,5] | [2,3,5]
+| I3:4 | [2,-,7,-,5] | 4 | [3,0,7,0,5] | [3,5,7] | [2,3,5,7] | [2,3,5,7] |
+
+*: (1:0, 2:0, 3:0, 4:0, 5:0)
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..23365109901425e8a6060c3ef3c2d8fe4cfc7656
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect
@@ -0,0 +1,28 @@
+send tree SenderRoot.T_Empty ;
+send tree SenderRoot.T_Token ;
+send tree SenderRoot.T_OneChild ;
+send tree SenderRoot.T_OneOpt ;
+send tree SenderRoot.T_OneList ;
+send tree SenderRoot.T_TwoChildren ;
+send tree SenderRoot.T_OneOfEach ;
+send tree SenderRoot.T_Abstract ;
+
+receive tree ReceiverRoot.T_Empty ;
+receive tree ReceiverRoot.T_Token ;
+receive tree ReceiverRoot.T_OneChild ;
+receive tree ReceiverRoot.T_OneOpt ;
+receive tree ReceiverRoot.T_OneList ;
+receive tree ReceiverRoot.T_TwoChildren ;
+receive tree ReceiverRoot.T_OneOfEach ;
+receive tree ReceiverRoot.T_Abstract ;
+
+receive tree ReceiverRoot.MyEmpty ;
+
+receive tree with add ReceiverRoot.EmptyWithAdd ;
+receive tree with add ReceiverRoot.TokenWithAdd ;
+receive tree with add ReceiverRoot.OneChildWithAdd ;
+receive tree with add ReceiverRoot.OneOptWithAdd ;
+receive tree with add ReceiverRoot.OneListWithAdd ;
+receive tree with add ReceiverRoot.TwoChildrenWithAdd ;
+receive tree with add ReceiverRoot.OneOfEachWithAdd ;
+receive tree with add ReceiverRoot.AbstractWithAdd ;
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd b/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..10ed1174a4cbb99e46cd5ba582df829d547bd446
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd
@@ -0,0 +1,75 @@
+aspect Computation {
+  syn T_Empty SenderRoot.getT_Empty() = new T_Empty().setID(getInput());
+  syn T_Token SenderRoot.getT_Token() = new T_Token().setID(getInput())
+    .setValue(Integer.toString(getInput()));
+  syn T_OneChild SenderRoot.getT_OneChild() {
+    T_OneChild result = new T_OneChild().setID(getInput());
+    result.setOther(createOther());
+    return result;
+  }
+  syn T_OneOpt SenderRoot.getT_OneOpt() {
+    T_OneOpt result = new T_OneOpt().setID(getInput());
+    if (getShouldSetOptAndList()) {
+      result.setOther(createOther());
+    }
+    return result;
+  }
+  syn T_OneList SenderRoot.getT_OneList() {
+    T_OneList result = new T_OneList().setID(getInput());
+    if (getShouldSetOptAndList()) {
+      result.addOther(createOther());
+    }
+    return result;
+  }
+  syn T_TwoChildren SenderRoot.getT_TwoChildren() {
+    T_TwoChildren result = new T_TwoChildren().setID(getInput());
+    result.setLeft(createOther());
+    result.setRight(createOther());
+    return result;
+  }
+  syn T_OneOfEach SenderRoot.getT_OneOfEach() {
+    T_OneOfEach result = new T_OneOfEach().setID(getInput());
+    result.setFirst(createOther());
+    if (getShouldSetOptAndList()) {
+      result.setSecond(createOther());
+      result.addThird(createOther());
+    }
+    result.setFourth(Integer.toString(getInput()));
+    return result;
+  }
+  syn T_Abstract SenderRoot.getT_Abstract() = new T_SubClass()
+    .setValueSub(Integer.toString(getInput()))
+    .setID(getInput())
+    .setValueAbstract(Integer.toString(getInput()));
+
+  private Other SenderRoot.createOther() {
+    return new Other().setID(getInput() + 1);
+  }
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Testing {
+  class SenderRoot implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperSenderRoot {}
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperReceiverRoot {}
+  class Other implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperOther {}
+  class T_Empty  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Empty {}
+  class T_Token  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Token {}
+  class T_OneChild  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneChild {}
+  class T_OneOpt  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneOpt {}
+  class T_OneList  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneList {}
+  class T_TwoChildren  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_TwoChildren {}
+  class T_OneOfEach  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneOfEach {}
+  class T_Abstract  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Abstract {}
+  class JastAddList<T> implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperJastAddList<T> {}
+}
+
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String Nameable.customID() {
+    return getClass().getSimpleName() + getID();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast b/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..4ac99ae124e19d182d9ad23b4c4ae65980272013
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast
@@ -0,0 +1,44 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Input:int> <ShouldSetOptAndList:boolean>
+/T_Empty/
+/T_Token/
+/T_OneChild/
+/T_OneOpt/
+/T_OneList/
+/T_TwoChildren/
+/T_OneOfEach/
+/T_Abstract/
+;
+
+ReceiverRoot : Nameable ::=
+T_Empty*
+T_Token*
+T_OneChild*
+T_OneOpt*
+T_OneList*
+T_TwoChildren*
+T_OneOfEach*
+T_Abstract*
+MyEmpty:T_Empty*
+EmptyWithAdd:T_Empty*
+TokenWithAdd:T_Token*
+OneChildWithAdd:T_OneChild*
+OneOptWithAdd:T_OneOpt*
+OneListWithAdd:T_OneList*
+TwoChildrenWithAdd:T_TwoChildren*
+OneOfEachWithAdd:T_OneOfEach*
+AbstractWithAdd:T_Abstract*
+;
+
+T_Empty : Nameable       ::= /* empty */ ;
+T_Token : Nameable       ::= <Value:String> ;
+T_OneChild : Nameable    ::= Other ;
+T_OneOpt : Nameable      ::= [Other] ;
+T_OneList : Nameable     ::= Other* ;
+T_TwoChildren : Nameable ::= Left:Other Right:Other ;
+T_OneOfEach : Nameable   ::= First:Other [Second:Other] Third:Other* <Fourth:String> ;
+abstract T_Abstract : Nameable ::= <ValueAbstract>;
+T_SubClass : T_Abstract  ::= <ValueSub> ;
+Other : Nameable ;
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect b/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..2ac87895268cfa631fcfee65a8c2c90f4c13b3c7
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect
@@ -0,0 +1,16 @@
+SenderRoot.T_Empty canDependOn SenderRoot.Input as InputDependencyToT_Empty ;
+SenderRoot.T_Token canDependOn SenderRoot.Input as InputDependencyToT_Token ;
+SenderRoot.T_OneChild canDependOn SenderRoot.Input as InputDependencyToT_OneChild ;
+
+SenderRoot.T_OneOpt canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneOpt ;
+SenderRoot.T_OneOpt canDependOn SenderRoot.Input as InputDependencyToT_OneOpt ;
+
+SenderRoot.T_OneList canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneList ;
+SenderRoot.T_OneList canDependOn SenderRoot.Input as InputDependencyToT_OneList ;
+
+SenderRoot.T_TwoChildren canDependOn SenderRoot.Input as InputDependencyToT_TwoChildren ;
+
+SenderRoot.T_OneOfEach canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneOfEach ;
+SenderRoot.T_OneOfEach canDependOn SenderRoot.Input as InputDependencyToT_OneOfEach ;
+
+SenderRoot.T_Abstract canDependOn SenderRoot.Input as InputDependencyToT_Abstract ;
diff --git a/ragconnect.tests/src/test/01-input/tree/Test.jadd b/ragconnect.tests/src/test/01-input/tree/Test.jadd
index c9e47ed7d9c0402992882e3d60a78f08044bf063..475e20f88bbe93daadb59f153b49f4e8d5ac99b4 100644
--- a/ragconnect.tests/src/test/01-input/tree/Test.jadd
+++ b/ragconnect.tests/src/test/01-input/tree/Test.jadd
@@ -66,11 +66,11 @@ aspect Computation {
 }
 
 aspect Testing {
-  class ReceiverRoot implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperReceiverRoot {}
-  class Alfa implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperAlfa {}
-  class Bravo implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperBravo {}
-  class Charlie implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperCharlie {}
-  class Delta implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperDelta {}
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperReceiverRoot {}
+  class Alfa implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperAlfa {}
+  class Bravo implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperBravo {}
+  class Charlie implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperCharlie {}
+  class Delta implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperDelta {}
 }
 
 aspect NameResolution {
diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd
index e5f8750c50a076498fea4398899fbebb5999620e..5ac99ca749c7f962416f4c34bb8544a2fdd2e38f 100644
--- a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd
+++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd
@@ -35,8 +35,8 @@ aspect Enum {
   public enum MyEnum { FALSE, TRUE; }
 }
 aspect Testing {
-  class ReceiverRoot implements org.jastadd.ragconnect.tests.AbstractTreeAllowedTokensTest.TestWrapperReceiverRoot {}
-  class Alfa implements org.jastadd.ragconnect.tests.AbstractTreeAllowedTokensTest.TestWrapperAlfa {}
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.treeAllowedTokens.AbstractTreeAllowedTokensTest.TestWrapperReceiverRoot {}
+  class Alfa implements org.jastadd.ragconnect.tests.treeAllowedTokens.AbstractTreeAllowedTokensTest.TestWrapperAlfa {}
 }
 
 aspect NameResolution {
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 f2f5ddee691854ef40f8b3ede6820a38e0262c2b..330c950b8114cb1364ea30a86cd76c7dc81eb4a2 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
@@ -14,12 +14,12 @@ import java.util.concurrent.TimeUnit;
 @Tag("mqtt")
 public abstract class AbstractMqttTest {
 
-  static boolean checkDone = false;
-  static boolean checkResult;
-  static MqttHandler publisher;
+  private static boolean checkDone = false;
+  protected static MqttHandler publisher;
 
   @BeforeAll
   public static void createPublishAndOnceCheckMqttConnection() {
+    boolean checkResult;
     try {
       publisher = new MqttHandler("Publisher")
           .dontSendWelcomeMessage()
@@ -90,7 +90,7 @@ public abstract class AbstractMqttTest {
    * and finally call generated connect* methods on model elements.
    * @param writeCurrentValue if the initial/current value shall be sent upon connecting
    */
-  protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException;
+  protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException;
 
   @AfterEach
   public void alwaysCloseConnections() {
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 c09059c8a66ca1cba262843ce30d4ce8ad0bc663..ce79190fe184f5b777366c25e9eff07cc8fc3576 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
@@ -132,12 +132,12 @@ public class TestUtils {
     return new String(encoded, encoding);
   }
 
-  static void waitForMqtt() throws InterruptedException {
+  public static void waitForMqtt() throws InterruptedException {
     TimeUnit.MILLISECONDS.sleep(1500);
   }
 
   @SuppressWarnings({"unused", "rawtypes"})
-  static class DefaultMappings {
+  public static class DefaultMappings {
     static class ReadNode extends defaultOnlyRead.ast.ASTNode {
       public static boolean _apply__DefaultBytesToBooleanMapping(byte[] input) throws Exception {
         return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToBooleanMapping(input);
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..18a6e59bcc07ffc5c61006c671d15b8c55a8d501
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java
@@ -0,0 +1,179 @@
+package org.jastadd.ragconnect.tests.list;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.function.Function;
+
+import static java.util.Collections.addAll;
+import static org.assertj.core.util.Lists.newArrayList;
+import static org.jastadd.ragconnect.tests.list.AbstractListTest.IntList.list;
+import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Base class for test cases "list manual" and "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("List")
+public abstract class AbstractListTest extends AbstractMqttTest {
+
+  public interface TestWrapperJastAddList<T> extends Iterable<T> {
+    int getNumChild();
+  }
+  public interface TestWrapperReceiverRoot {
+    TestWrapperJastAddList<? extends TestWrapperA> getAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getAs();
+    int getNumA();
+    TestWrapperA getA(int index);
+
+    TestWrapperJastAddList<? extends TestWrapperA> getFromSingleAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getFromSingleAs();
+
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromAs();
+
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromSingleAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromSingleAs();
+
+    boolean connectAList(String mqttUri) throws IOException;
+    boolean connectFromSingleAList(String mqttUri) throws IOException;
+    boolean connectWithAddFromAList(String mqttUri) throws IOException;
+    boolean connectWithAddFromSingleAList(String mqttUri) throws IOException;
+  }
+  public interface TestWrapperA {
+    AbstractListTest.TestWrapperB getB(int i);
+    int getNumB();
+    int getID();
+  }
+  public interface TestWrapperB {
+    int getID();
+  }
+
+  AbstractListTest(String shortName) {
+    this.shortName = shortName;
+  }
+
+  protected static final String TOPIC_A = "a";
+  protected static final String TOPIC_SINGLE_A = "single-a";
+
+  protected TestWrapperReceiverRoot receiverRoot;
+  protected ReceiverData data;
+  protected ReceiverData dataSingle;
+
+  private final String shortName;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", shortName, "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    checkTree(1, list(), list(0), list(), list(0));
+
+    setInput(1);
+    checkTree(2, list(1), list(1), list(1), list(0, 1));
+
+    setInput(1);
+    checkTree(2, list(1), list(1), list(1), list(0, 1));
+
+    setInput(2);
+    checkTree(3, list(1, 2), list(2), list(1, 1, 2), list(0, 1, 2));
+
+    setInput(3);
+    checkTree(4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(0, 1, 2, 3));
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    checkTree(0, list(), list(), list(), list());
+
+    setInput(1);
+    checkTree(1, list(1), list(1), list(1), list(1));
+
+    setInput(1);
+    checkTree(1, list(1), list(1), list(1), list(1));
+
+    setInput(2);
+    checkTree(2, list(1, 2), list(2), list(1, 1, 2), list(1, 2));
+
+    setInput(3);
+    checkTree(3, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3));
+  }
+
+  protected abstract void setInput(int input);
+
+  private void checkTree(int expectedTransmissions, IntList normalA, IntList fromSingleA, IntList withAddA, IntList withAddFromSingleA) throws InterruptedException {
+    TestUtils.waitForMqtt();
+
+    assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for normal");
+    assertEquals(expectedTransmissions, dataSingle.numberOfElements, "transmissions for single");
+
+    checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA, true);
+    checkList(normalA.toList(), receiverRoot.getAList(), true);
+    checkList(normalA.toList(), receiverRoot.getAs(), true);
+
+    checkList(fromSingleA.toList(), receiverRoot.getFromSingleAList(), false);
+    checkList(fromSingleA.toList(), receiverRoot.getFromSingleAs(), false);
+
+    checkList(withAddA.toList(), receiverRoot.getWithAddFromAList(), true);
+    checkList(withAddA.toList(), receiverRoot.getWithAddFromAs(), true);
+
+    checkList(withAddFromSingleA.toList(), receiverRoot.getWithAddFromSingleAList(), false);
+    checkList(withAddFromSingleA.toList(), receiverRoot.getWithAddFromSingleAs(), false);
+  }
+
+  private void checkList(List<Integer> expectedList, int numChildren, Function<Integer, TestWrapperA> getA, boolean expectB) {
+    assertEquals(expectedList.size(), numChildren, "same list size");
+    for (int index = 0; index < expectedList.size(); index++) {
+      TestWrapperA a = getA.apply(index);
+      assertEquals(expectedList.get(index), a.getID(), "correct ID for A");
+      if (expectB) {
+        assertEquals(1, a.getNumB(), "one B child");
+        assertEquals(expectedList.get(index) + 1, a.getB(0).getID(), "correct ID for B child");
+      }
+    }
+  }
+
+  private void checkList(List<Integer> expectedList, TestWrapperJastAddList<? extends TestWrapperA> actualList, boolean expectB) {
+    assertEquals(expectedList.size(), actualList.getNumChild(), "same list size");
+    int index = 0;
+    for (TestWrapperA a : actualList) {
+      assertEquals(expectedList.get(index), a.getID(), "correct ID for A");
+      if (expectB) {
+        assertEquals(1, a.getNumB(), "one B child");
+        assertEquals(expectedList.get(index) + 1, a.getB(0).getID(), "correct ID for B child");
+      }
+      index++;
+    }
+  }
+
+  protected static class ReceiverData {
+    int numberOfElements = 0;
+  }
+
+  protected static class IntList {
+    private final List<Integer> integers = newArrayList();
+    public IntList(Integer... values) {
+      addAll(integers, values);
+    }
+
+    public List<Integer> toList() {
+      return integers;
+    }
+
+    public static IntList list(Integer... values) {
+      return new IntList(values);
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5fbae01e28d2c31eed1511c7ff9a0712cd5e3c4
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java
@@ -0,0 +1,85 @@
+package org.jastadd.ragconnect.tests.list;
+
+import listInc.ast.*;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class ListIncrementalTest extends AbstractListTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  ListIncrementalTest() {
+    super("listInc");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    senderRoot.setInput(0);
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // no dependencies
+
+    data = new ReceiverData();
+    dataSingle = new ReceiverData();
+    handler.newConnection(TOPIC_A, bytes -> data.numberOfElements += 1);
+    handler.newConnection(TOPIC_SINGLE_A, bytes -> dataSingle.numberOfElements += 1);
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+    assertTrue(receiverRoot.connectAList(mqttUri(TOPIC_A)));
+    assertTrue(receiverRoot.connectFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
+    assertTrue(receiverRoot.connectWithAddFromAList(mqttUri(TOPIC_A)));
+    assertTrue(receiverRoot.connectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
+
+    assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), writeCurrentValue));
+    assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), writeCurrentValue));
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @Override
+  protected void setInput(int input) {
+    senderRoot.setInput(input);
+    assertEquals(input, senderRoot.getNumA(), "size of normal NTA");
+    if (input > 0) {
+      assertEquals(input, senderRoot.getA(input - 1).getID(), "ID value of last A in normal list");
+    }
+    assertEquals(1, senderRoot.getNumSingleA(), "size of single NTA");
+    assertEquals(input, senderRoot.getSingleA(0).getID(), "ID value of single A");
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3e6eee5c93ea62e6edf939a1e6f5f3ef5419463
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java
@@ -0,0 +1,84 @@
+package org.jastadd.ragconnect.tests.list;
+
+import list.ast.MqttHandler;
+import list.ast.ReceiverRoot;
+import list.ast.Root;
+import list.ast.SenderRoot;
+import org.jastadd.ragconnect.tests.TestUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list manual".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class ListManualTest extends AbstractListTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  ListManualTest() {
+    super("list");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    senderRoot.setInput(0);
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // add dependencies
+    senderRoot.addInputDependencyToA(senderRoot);
+    senderRoot.addInputDependencyToSingleA(senderRoot);
+
+    data = new ReceiverData();
+    dataSingle = new ReceiverData();
+    handler.newConnection(TOPIC_A, bytes -> data.numberOfElements += 1);
+    handler.newConnection(TOPIC_SINGLE_A, bytes -> dataSingle.numberOfElements += 1);
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+    assertTrue(receiverRoot.connectAList(mqttUri(TOPIC_A)));
+    assertTrue(receiverRoot.connectFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
+    assertTrue(receiverRoot.connectWithAddFromAList(mqttUri(TOPIC_A)));
+    assertTrue(receiverRoot.connectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
+
+    assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), writeCurrentValue));
+    assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), writeCurrentValue));
+  }
+
+  @Override
+  protected void setInput(int input) {
+    senderRoot.setInput(input);
+    assertEquals(input, senderRoot.getNumA(), "size of normal NTA");
+    assertEquals(1, senderRoot.getNumSingleA(), "size of single NTA");
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c2779f8d4188c0f1fff7f8773d27401e94dd597
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java
@@ -0,0 +1,297 @@
+package org.jastadd.ragconnect.tests.singleList;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import singleList.ast.MqttHandler;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+import static java.util.Collections.addAll;
+import static org.assertj.core.util.Lists.newArrayList;
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.IntList.list;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Base class for test cases "singleList manual" and "singleList incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("List")
+@Tag("SingleList")
+public abstract class AbstractSingleListTest extends AbstractMqttTest {
+
+  public interface TestWrapperJastAddList<T> extends Iterable<T> {
+    int getNumChild();
+  }
+  public interface TestWrapperReceiverRoot {
+    TestWrapperJastAddList<? extends TestWrapperA> getAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getAs();
+    int getNumA();
+    int getNumWithAddA();
+    TestWrapperA getA(int index);
+
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getUsingWildcardAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getUsingWildcardWithAddAList();
+
+    @SuppressWarnings("unused") boolean connectA(String mqttUri) throws IOException;
+    boolean connectA(String mqttUri, int index) throws IOException;
+    boolean connectUsingWildcardA(String mqttUri) throws IOException;
+    @SuppressWarnings("unused") boolean connectUsingWildcardA(String mqttUri, int index) throws IOException;
+    boolean connectWithAddA(String mqttUri) throws IOException;
+    boolean connectUsingWildcardWithAddA(String mqttUri) throws IOException;
+  }
+  @SuppressWarnings("UnusedReturnValue")
+  public interface TestWrapperSenderRoot {
+    boolean connectA1(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectA2(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectA3(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectA4(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectInOutput(String mqttUri, boolean writeCurrentValue) throws IOException;
+
+    TestWrapperSenderRoot setInput1(int input);
+    TestWrapperSenderRoot setInput2(int input);
+    TestWrapperSenderRoot setInput3(int input);
+    TestWrapperSenderRoot setInput4(int input);
+    TestWrapperSenderRoot setInOutput(int input);
+
+    TestWrapperA getA1();
+    TestWrapperA getA2();
+    TestWrapperA getA3();
+    TestWrapperA getA4();
+  }
+  public interface TestWrapperA {
+    int getID();
+  }
+
+  AbstractSingleListTest(String shortName) {
+    this.shortName = shortName;
+  }
+
+  protected static final String TOPIC_A_1 = "a/first";
+  protected static final String TOPIC_A_2 = "a/second";
+  protected static final String TOPIC_A_3 = "a/third";
+  protected static final String TOPIC_A_4 = "a/fourth";
+  protected static final String TOPIC_A_5_INOUT = "a/special";
+  protected static final String TOPIC_A_WILDCARD = "a/#";
+
+  protected TestWrapperSenderRoot senderRoot;
+  protected TestWrapperReceiverRoot receiverRoot;
+  protected ReceiverData data;
+
+  private final String shortName;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", shortName, "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException {
+    // late model initialization
+    senderRoot.setInput1(0);
+    senderRoot.setInput2(0);
+    senderRoot.setInput3(0);
+    senderRoot.setInput4(0);
+    senderRoot.setInOutput(0);
+
+    setupReceiverAndConnectPart();
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+
+    // receive: explicit topic subscription
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_1), 0));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_2), 1));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_3), 2));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_4), 3));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_5_INOUT), 4));
+
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_1)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_2)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_3)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_4)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_5_INOUT)));
+
+    // receive: wildcard subscription
+    assertTrue(receiverRoot.connectUsingWildcardA(mqttUri(TOPIC_A_WILDCARD)));
+    assertTrue(receiverRoot.connectUsingWildcardWithAddA(mqttUri(TOPIC_A_WILDCARD)));
+
+    // send: explicit topics, wait between connections to ensure correct arrival at receiver
+    MqttHandler checkArrivalHandler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    Map<String, CountDownLatch> arrived = new HashMap<>() {{
+      put(TOPIC_A_1, new CountDownLatch(1));
+      put(TOPIC_A_2, new CountDownLatch(1));
+      put(TOPIC_A_3, new CountDownLatch(1));
+      put(TOPIC_A_4, new CountDownLatch(1));
+    }};
+    checkArrivalHandler.waitUntilReady(2, TimeUnit.SECONDS);
+    checkArrivalHandler.newConnection("#", (topic, bytes) ->
+        Optional.ofNullable(arrived.get(topic)).ifPresent(CountDownLatch::countDown));
+
+    assertTrue(senderRoot.connectA4(mqttUri(TOPIC_A_4), writeCurrentValue));
+    if (writeCurrentValue) {
+      assertTrue(arrived.get(TOPIC_A_4).await(2, TimeUnit.SECONDS));
+    }
+
+    assertTrue(senderRoot.connectA3(mqttUri(TOPIC_A_3), writeCurrentValue));
+    if (writeCurrentValue) {
+      assertTrue(arrived.get(TOPIC_A_3).await(2, TimeUnit.SECONDS));
+    }
+
+    assertTrue(senderRoot.connectA2(mqttUri(TOPIC_A_2), writeCurrentValue));
+    if (writeCurrentValue) {
+      assertTrue(arrived.get(TOPIC_A_2).await(2, TimeUnit.SECONDS));
+    }
+
+    assertTrue(senderRoot.connectA1(mqttUri(TOPIC_A_1), writeCurrentValue));
+    if (writeCurrentValue) {
+      assertTrue(arrived.get(TOPIC_A_1).await(2, TimeUnit.SECONDS));
+    }
+
+    assertTrue(senderRoot.connectInOutput(mqttUri(TOPIC_A_5_INOUT), writeCurrentValue));
+    // no need to wait here, because first "checkTree" will wait anyway
+    checkArrivalHandler.close();
+  }
+
+  abstract protected void setupReceiverAndConnectPart() throws IOException;
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    checkTree(5, list(1, 2, 3, 4, 0), list(4, 3, 2, 1, 0), // normal: (normal / wildcard)
+                                     list(4, 3, 2, 1, 0), list(4, 3, 2, 1, 0)); // withAdd: (normal / wildcard)
+
+    // A1 will be 2 (1+1, previously 1)
+    setInput(1, 1);
+    checkTree(6, list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard)
+                                     list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard)
+
+    // A1 should stay at 2
+    setInput(1, 1);
+    checkTree(6, list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard)
+                                     list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard)
+
+    // A1 will be 3 (2+1, previously 2)
+    setInput(1, 2);
+    checkTree(7, list(3, 2, 3, 4, 0), list(4, 3, 2, 3, 0), // normal: (normal / wildcard)
+                                     list(4, 3, 2, 1, 0, 2, 3), list(4, 3, 2, 1, 0, 2, 3)); // withAdd: (normal / wildcard)
+
+    // InOut will be 5 (previously 0)
+    setInput(5, 5);
+    checkTree(8, list(3, 2, 3, 4, 5), list(4, 3, 2, 3, 5), // normal: (normal / wildcard)
+                                     list(4, 3, 2, 1, 0, 2, 3, 5), list(4, 3, 2, 1, 0, 2, 3, 5)); // withAdd: (normal / wildcard)
+
+    // A3 will be 7 (4+3, previously 3)
+    setInput(3, 4);
+    checkTree(9, list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard)
+                                     list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard)
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    checkTree(0, list(0, 0, 0, 0, 0), list(), // normal
+                                     list(), list()); // withAdd
+
+    // A1 will be 2 (1+1, previously 1)
+    setInput(1, 1);
+    checkTree(1, list(2, 0, 0, 0, 0), list(2), // normal
+                                     list(2), list(2)); // withAdd
+
+    // A1 should stay at 2
+    setInput(1, 1);
+    checkTree(1, list(2, 0, 0, 0, 0), list(2), // normal
+                                     list(2), list(2)); // withAdd
+
+    // A1 will be 3 (2+1, previously 2)
+    setInput(1, 2);
+    checkTree(2, list(3, 0, 0, 0, 0), list(3), // normal
+                                     list(2, 3), list(2, 3)); // withAdd
+
+    // InOut will be 5 (previously 0)
+    setInput(5, 5);
+    checkTree(3, list(3, 0, 0, 0, 5), list(3, 5), // normal
+                                     list(2, 3, 5), list(2, 3, 5)); // withAdd
+
+    // A3 will be 7 (4+3, previously 3)
+    setInput(3, 4);
+    checkTree(4, list(3, 0, 7, 0, 5), list(3,5,7), // normal
+                                     list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd
+  }
+
+  protected void setInput(int index, int input) {
+    int actualComputedValue;
+    switch (index) {
+      case 1: senderRoot.setInput1(input); actualComputedValue = senderRoot.getA1().getID(); break;
+      case 2: senderRoot.setInput2(input); actualComputedValue = senderRoot.getA2().getID(); break;
+      case 3: senderRoot.setInput3(input); actualComputedValue = senderRoot.getA3().getID(); break;
+      case 4: senderRoot.setInput4(input); actualComputedValue = senderRoot.getA4().getID(); break;
+      case 5: senderRoot.setInOutput(input); return;
+      default: fail("Wrong index " + index); return;
+    }
+    assertEquals(input + index, actualComputedValue, "ID value of single A");
+  }
+
+  private void checkTree(int expectedTransmissions, IntList normalA, IntList usingWildcardA, IntList withAddA, IntList usingWildcardWithAddA) throws InterruptedException {
+    TestUtils.waitForMqtt();
+    assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for any A");
+
+    checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA);
+    checkList(normalA.toList(), receiverRoot.getAList());
+
+    checkList(usingWildcardA.toList(), receiverRoot.getUsingWildcardAList());
+
+    checkList(withAddA.toList(), receiverRoot.getWithAddAList());
+
+    checkList(usingWildcardWithAddA.toList(), receiverRoot.getUsingWildcardWithAddAList());
+  }
+
+  private void checkList(List<Integer> expectedList, int numChildren, Function<Integer, TestWrapperA> getA) {
+    assertEquals(expectedList.size(), numChildren, "same list size");
+    for (int index = 0; index < expectedList.size(); index++) {
+      TestWrapperA a = getA.apply(index);
+      assertEquals(expectedList.get(index), a.getID(), "correct ID for A");
+    }
+  }
+
+  private void checkList(List<Integer> expectedList, TestWrapperJastAddList<? extends TestWrapperA> actualList) {
+    assertEquals(expectedList.size(), actualList.getNumChild(), "same list size");
+    int index = 0;
+    for (TestWrapperA a : actualList) {
+      assertEquals(expectedList.get(index), a.getID(), "correct ID for A");
+      index++;
+    }
+  }
+
+  protected static class ReceiverData {
+    int numberOfElements = 0;
+  }
+
+  protected static class IntList {
+    private final List<Integer> integers = newArrayList();
+    public IntList(Integer... values) {
+      addAll(integers, values);
+    }
+
+    public List<Integer> toList() {
+      return integers;
+    }
+
+    public static IntList list(Integer... values) {
+      return new IntList(values);
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a946e44499f71dc0dfb92271ca1fc414275597b
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java
@@ -0,0 +1,67 @@
+package org.jastadd.ragconnect.tests.singleList;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import singleListInc.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class SingleListIncrementalTest extends AbstractSingleListTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListIncrementalTest() {
+    super("singleListInc");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+
+    // first prepare non-wildcard lists
+    for (int i = 0; i < 5; i++) {
+      localReceiverRoot.addA(new A());
+    }
+    receiverRoot = localReceiverRoot;
+    assertEquals(5, receiverRoot.getNumA());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // no dependencies
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_A_WILDCARD, bytes -> data.numberOfElements += 1);
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2896f633bc299159f0ab74913c3a9c0456d8370
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java
@@ -0,0 +1,69 @@
+package org.jastadd.ragconnect.tests.singleList;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import singleList.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list manual".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class SingleListManualTest extends AbstractSingleListTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListManualTest() {
+    super("singleList");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+
+    // first prepare non-wildcard lists
+    for (int i = 0; i < 5; i++) {
+      localReceiverRoot.addA(new A());
+    }
+    receiverRoot = localReceiverRoot;
+    assertEquals(5, receiverRoot.getNumA());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // add dependencies
+    ((SenderRoot) senderRoot).addInputDependencyToA1((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToA2((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToA3((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToA4((SenderRoot) senderRoot);
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_A_WILDCARD, bytes -> data.numberOfElements += 1);
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..465ad8ac024d595f29a87757c776db6c88e72fe4
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java
@@ -0,0 +1,406 @@
+package org.jastadd.ragconnect.tests.singleListVariant;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import static java.lang.Math.abs;
+import static java.util.Collections.addAll;
+import static org.assertj.core.util.Lists.newArrayList;
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.IntList.list;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Base class for test cases "singleList manual" and "singleList incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("List")
+@Tag("SingleList")
+public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
+
+  public interface TestWrapperJastAddList<T> extends Iterable<T> {
+    int getNumChild();
+  }
+  public interface TestWrapperReceiverRoot {
+    TestWrapperJastAddList<? extends TestWrapperT_Empty> getT_EmptyList();
+    TestWrapperJastAddList<? extends TestWrapperT_Token> getT_TokenList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneChild> getT_OneChildList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOpt> getT_OneOptList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneList> getT_OneListList();
+    TestWrapperJastAddList<? extends TestWrapperT_TwoChildren> getT_TwoChildrenList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOfEach> getT_OneOfEachList();
+    TestWrapperJastAddList<? extends TestWrapperT_Abstract> getT_AbstractList();
+
+    TestWrapperJastAddList<? extends TestWrapperT_Empty> getMyEmptyList();
+
+    TestWrapperJastAddList<? extends TestWrapperT_Empty> getEmptyWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_Token> getTokenWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneChild> getOneChildWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOpt> getOneOptWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneList> getOneListWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_TwoChildren> getTwoChildrenWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOfEach> getOneOfEachWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_Abstract> getAbstractWithAddList();
+
+    boolean connectT_Empty(String mqttUri) throws IOException;
+    boolean connectT_Token(String mqttUri) throws IOException;
+    boolean connectT_OneChild(String mqttUri) throws IOException;
+    boolean connectT_OneOpt(String mqttUri) throws IOException;
+    boolean connectT_OneList(String mqttUri) throws IOException;
+    boolean connectT_TwoChildren(String mqttUri) throws IOException;
+    boolean connectT_OneOfEach(String mqttUri) throws IOException;
+    boolean connectT_Abstract(String mqttUri) throws IOException;
+
+    boolean connectMyEmpty(String mqttUri) throws IOException;
+
+    boolean connectEmptyWithAdd(String mqttUri) throws IOException;
+    boolean connectTokenWithAdd(String mqttUri) throws IOException;
+    boolean connectOneChildWithAdd(String mqttUri) throws IOException;
+    boolean connectOneOptWithAdd(String mqttUri) throws IOException;
+    boolean connectOneListWithAdd(String mqttUri) throws IOException;
+    boolean connectTwoChildrenWithAdd(String mqttUri) throws IOException;
+    boolean connectOneOfEachWithAdd(String mqttUri) throws IOException;
+    boolean connectAbstractWithAdd(String mqttUri) throws IOException;
+  }
+  @SuppressWarnings("UnusedReturnValue")
+  public interface TestWrapperSenderRoot {
+    boolean connectT_Empty(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_Token(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneChild(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneOpt(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneList(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_TwoChildren(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneOfEach(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_Abstract(String mqttUri, boolean writeCurrentValue) throws IOException;
+
+    TestWrapperSenderRoot setInput(int input);
+    TestWrapperSenderRoot setShouldSetOptAndList(boolean shouldSetOptAndList);
+    TestWrapperT_Empty getT_Empty();
+    TestWrapperT_OneOpt getT_OneOpt();
+  }
+  public interface TestWrapperNameable {
+    int getID();
+  }
+  public interface TestWrapperOther extends TestWrapperNameable {}
+  public interface TestWrapperT_Empty extends TestWrapperNameable {}
+  public interface TestWrapperT_Token extends TestWrapperNameable {
+    String getValue();
+  }
+  public interface TestWrapperT_OneChild extends TestWrapperNameable {
+    TestWrapperNameable getOther();
+  }
+  public interface TestWrapperT_OneOpt extends TestWrapperNameable {
+    boolean hasOther();
+    TestWrapperNameable getOther();
+  }
+  public interface TestWrapperT_OneList extends TestWrapperNameable {
+    int getNumOther();
+    TestWrapperNameable getOther(int index);
+  }
+  public interface TestWrapperT_TwoChildren extends TestWrapperNameable {
+    TestWrapperNameable getLeft();
+    TestWrapperNameable getRight();
+  }
+  public interface TestWrapperT_OneOfEach extends TestWrapperNameable {
+    TestWrapperNameable getFirst();
+    boolean hasSecond();
+    TestWrapperNameable getSecond();
+    int getNumThird();
+    TestWrapperNameable getThird(int index);
+    String getFourth();
+  }
+  public interface TestWrapperT_Abstract extends TestWrapperNameable {
+    String getValueAbstract();
+    String getValueSub();
+  }
+
+  AbstractSingleListVariantTest(String shortName) {
+    this.shortName = shortName;
+  }
+
+  protected static final String TOPIC_T_Empty = "t/Empty";
+  protected static final String TOPIC_T_Token = "t/Token";
+  protected static final String TOPIC_T_OneChild = "t/OneChild";
+  protected static final String TOPIC_T_OneOpt = "t/OneOpt";
+  protected static final String TOPIC_T_OneList = "t/OneList";
+  protected static final String TOPIC_T_TwoChildren = "t/TwoChildren";
+  protected static final String TOPIC_T_OneOfEach = "t/OneOfEach";
+  protected static final String TOPIC_T_Abstract = "t/Abstract";
+  protected static final String TOPIC_T_all = "t/#";
+
+  protected TestWrapperSenderRoot senderRoot;
+  protected TestWrapperReceiverRoot receiverRoot;
+  protected ReceiverData data;
+
+  private final String shortName;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", shortName, "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException {
+    // late model initialization
+    setInput(0);
+    setShouldSetOptAndList(false);
+
+    setupReceiverAndConnectPart();
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+
+    // receive: unnamed
+    assertTrue(receiverRoot.connectT_Empty(mqttUri(TOPIC_T_Empty)));
+    assertTrue(receiverRoot.connectT_Token(mqttUri(TOPIC_T_Token)));
+    assertTrue(receiverRoot.connectT_OneChild(mqttUri(TOPIC_T_OneChild)));
+    assertTrue(receiverRoot.connectT_OneOpt(mqttUri(TOPIC_T_OneOpt)));
+    assertTrue(receiverRoot.connectT_OneList(mqttUri(TOPIC_T_OneList)));
+    assertTrue(receiverRoot.connectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren)));
+    assertTrue(receiverRoot.connectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach)));
+    assertTrue(receiverRoot.connectT_Abstract(mqttUri(TOPIC_T_Abstract)));
+
+    // receive: named
+    assertTrue(receiverRoot.connectMyEmpty(mqttUri(TOPIC_T_Empty)));
+
+    // receive: with add
+    assertTrue(receiverRoot.connectEmptyWithAdd(mqttUri(TOPIC_T_Empty)));
+    assertTrue(receiverRoot.connectTokenWithAdd(mqttUri(TOPIC_T_Token)));
+    assertTrue(receiverRoot.connectOneChildWithAdd(mqttUri(TOPIC_T_OneChild)));
+    assertTrue(receiverRoot.connectOneOptWithAdd(mqttUri(TOPIC_T_OneOpt)));
+    assertTrue(receiverRoot.connectOneListWithAdd(mqttUri(TOPIC_T_OneList)));
+    assertTrue(receiverRoot.connectTwoChildrenWithAdd(mqttUri(TOPIC_T_TwoChildren)));
+    assertTrue(receiverRoot.connectOneOfEachWithAdd(mqttUri(TOPIC_T_OneOfEach)));
+    assertTrue(receiverRoot.connectAbstractWithAdd(mqttUri(TOPIC_T_Abstract)));
+
+    // send
+    assertTrue(senderRoot.connectT_Empty(mqttUri(TOPIC_T_Empty), writeCurrentValue));
+    assertTrue(senderRoot.connectT_Token(mqttUri(TOPIC_T_Token), writeCurrentValue));
+    assertTrue(senderRoot.connectT_OneChild(mqttUri(TOPIC_T_OneChild), writeCurrentValue));
+    assertTrue(senderRoot.connectT_OneOpt(mqttUri(TOPIC_T_OneOpt), writeCurrentValue));
+    assertTrue(senderRoot.connectT_OneList(mqttUri(TOPIC_T_OneList), writeCurrentValue));
+    assertTrue(senderRoot.connectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren), writeCurrentValue));
+    assertTrue(senderRoot.connectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach), writeCurrentValue));
+    assertTrue(senderRoot.connectT_Abstract(mqttUri(TOPIC_T_Abstract), writeCurrentValue));
+  }
+
+  abstract protected void setupReceiverAndConnectPart() throws IOException;
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // transmissions: 8 * 1 = 8
+    checkTree(8, list(-0), list(0), list(-0));
+
+    setInput(1);
+    // transmissions: 8 + 8 = 16
+    checkTree(16, list(-1), list(0, 1), list(-0, -1));
+
+    setInput(1);
+    // transmissions: 16
+    checkTree(16, list(-1), list(0, 1), list(-0, -1));
+
+    setShouldSetOptAndList(true);
+    // transmissions: 16 + 3 = 19
+    checkTree(19, list(1), list(0, 1), list(-0, -1, 1));
+
+    setShouldSetOptAndList(true);
+    // transmissions: 19
+    checkTree(19, list(1), list(0, 1), list(-0, -1, 1));
+
+    setInput(2);
+    // transmissions: 19 + 8 = 27
+    checkTree(27, list(2), list(0, 1, 2), list(-0, -1, 1, 2));
+
+    setInput(5);
+    // transmissions: 27 + 8 = 35
+    checkTree(35, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    // transmissions: 0
+    checkTree(0, list(), list(), list());
+
+    setInput(1);
+    // transmissions: 8 * 1 = 0
+    checkTree(8, list(-1), list(1), list(-1));
+
+    setInput(1);
+    // transmissions: 8
+    checkTree(8, list(-1), list(1), list(-1));
+
+    setShouldSetOptAndList(true);
+    // transmissions: 8 + 3 = 11
+    checkTree(11, list(1), list(1), list(-1, 1));
+
+    setShouldSetOptAndList(true);
+    // transmissions: 11
+    checkTree(11, list(1), list(1), list(-1, 1));
+
+    setInput(2);
+    // transmissions: 11 + 8 = 19
+    checkTree(19, list(2), list(1, 2), list(-1, 1, 2));
+
+    setInput(5);
+    // transmissions: 19 + 8 = 27
+    checkTree(27, list(5), list(1, 2, 5), list(-1, 1, 2, 5));
+  }
+
+  protected void setInput(int input) {
+    senderRoot.setInput(input);
+    assertEquals(input, senderRoot.getT_Empty().getID(), "ID value of empty");
+  }
+
+  protected void setShouldSetOptAndList(boolean shouldSetOptAndList) {
+    senderRoot.setShouldSetOptAndList(shouldSetOptAndList);
+    assertEquals(shouldSetOptAndList, senderRoot.getT_OneOpt().hasOther(), "opt is filled or not");
+  }
+
+  /**
+   * Check against expected lists of IDs.
+   * If an ID is negative, do not check Opts and Lists, but use absolute value for comparison.
+   * The tests starts with ID 0 and does not use opts and lists at this point, so checking with > 0 is used.
+   * @param expectedTransmissions            expected number of total transmissions
+   * @param expectedList                     ids for unnamed and named endpoints without add
+   * @param expectedWithAddList              ids for endpoints with add, but not those with opts and lists
+   *                                         (only positive numbers can/need to be passed)
+   * @param expectedWithAddListForOptAndList ids for endpoints with add and with opts and lists
+   * @throws InterruptedException if interrupted in TestUtils.waitForMqtt
+   */
+  private void checkTree(int expectedTransmissions,
+                         IntList expectedList,
+                         IntList expectedWithAddList,
+                         IntList expectedWithAddListForOptAndList)
+      throws InterruptedException {
+    TestUtils.waitForMqtt();
+    assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for any element");
+
+    // check unnamed
+    checkList(expectedList.toList(), receiverRoot.getT_EmptyList(), (e, n) -> {});
+    checkList(expectedList.toList(), receiverRoot.getT_TokenList(),
+        (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue()));
+    checkList(expectedList.toList(), receiverRoot.getT_OneChildList(),
+        (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID()));
+    checkList(expectedList.toList(), receiverRoot.getT_OneOptList(),
+        (e, n) -> {
+          assertEquals(e > 0, n.hasOther());
+          if (n.hasOther()) {
+            assertEquals(abs(e) + 1, n.getOther().getID());
+          }
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_OneListList(),
+        (e, n) -> {
+          assertEquals(e > 0 ? 1 : 0, n.getNumOther());
+          if (n.getNumOther() > 0) {
+            assertEquals(abs(e) + 1, n.getOther(0).getID());
+          }
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_TwoChildrenList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getLeft().getID());
+          assertEquals(abs(e) + 1, n.getRight().getID());
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_OneOfEachList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getFirst().getID());
+          assertEquals(e > 0, n.hasSecond());
+          if (n.hasSecond()) {
+            assertEquals(abs(e) + 1, n.getSecond().getID());
+          }
+          assertEquals(e > 0 ? 1 : 0, n.getNumThird());
+          if (n.getNumThird() > 0) {
+            assertEquals(abs(e) + 1, n.getThird(0).getID());
+          }
+          assertEquals(Integer.toString(abs(e)), n.getFourth());
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_AbstractList(),
+        (e, n) -> {
+          assertEquals(Integer.toString(abs(e)), n.getValueAbstract());
+          assertEquals(Integer.toString(abs(e)), n.getValueSub());
+        });
+
+    // check named
+    checkList(expectedList.toList(), receiverRoot.getMyEmptyList(), (e, n) -> {});
+
+    // check with add
+    checkList(expectedWithAddList.toList(), receiverRoot.getEmptyWithAddList(), (e, n) -> {});
+    checkList(expectedWithAddList.toList(), receiverRoot.getTokenWithAddList(),
+        (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue()));
+    checkList(expectedWithAddList.toList(), receiverRoot.getOneChildWithAddList(),
+        (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID()));
+    checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneOptWithAddList(),
+        (e, n) -> {
+          if (n.hasOther()) {
+            assertEquals(abs(e) + 1, n.getOther().getID());
+          }
+        });
+    checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneListWithAddList(),
+        (e, n) -> {
+          if (n.getNumOther() > 0) {
+            assertEquals(abs(e) + 1, n.getOther(0).getID());
+          }
+        });
+    checkList(expectedWithAddList.toList(), receiverRoot.getTwoChildrenWithAddList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getLeft().getID());
+          assertEquals(abs(e) + 1, n.getRight().getID());
+        });
+    checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneOfEachWithAddList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getFirst().getID());
+          if (n.hasSecond()) {
+            assertEquals(abs(e) + 1, n.getSecond().getID());
+          }
+          if (n.getNumThird() > 0) {
+            assertEquals(abs(e) + 1, n.getThird(0).getID());
+          }
+          assertEquals(Integer.toString(abs(e)), n.getFourth());
+        });
+    checkList(expectedWithAddList.toList(), receiverRoot.getAbstractWithAddList(),
+        (e, n) -> {
+          assertEquals(Integer.toString(abs(e)), n.getValueAbstract());
+          assertEquals(Integer.toString(abs(e)), n.getValueSub());
+        });
+  }
+
+  private <T extends TestWrapperNameable> void checkList(List<Integer> expectedList, TestWrapperJastAddList<T> actualList, BiConsumer<Integer, T> additionalTest) {
+    assertEquals(expectedList.size(), actualList.getNumChild(), "same list size");
+    int index = 0;
+    for (T element : actualList) {
+      assertEquals(abs(expectedList.get(index)), element.getID(), "correct ID for A");
+      additionalTest.accept(expectedList.get(index), element);
+      index++;
+    }
+  }
+
+  protected static class ReceiverData {
+    int numberOfElements = 0;
+  }
+
+  protected static class IntList {
+    private final List<Integer> integers = newArrayList();
+    public IntList(Integer... values) {
+      addAll(integers, values);
+    }
+
+    public List<Integer> toList() {
+      return integers;
+    }
+
+    public static IntList list(Integer... values) {
+      return new IntList(values);
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a55da378b93cc3bcc741d1904dc58b3d9ae96bd
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java
@@ -0,0 +1,62 @@
+package org.jastadd.ragconnect.tests.singleListVariant;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import singleListVariantInc.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class SingleListVariantIncrementalVariantTest extends AbstractSingleListVariantTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListVariantIncrementalVariantTest() {
+    super("singleListVariantInc");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+    receiverRoot = localReceiverRoot;
+    assertEquals(0, receiverRoot.getT_EmptyList().getNumChild());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // no dependencies
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_T_all, bytes -> data.numberOfElements += 1);
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c65a09ec65add4afd7fa5ca8a2809b28e0be4df2
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java
@@ -0,0 +1,72 @@
+package org.jastadd.ragconnect.tests.singleListVariant;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import singleListVariant.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list manual".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class SingleListVariantManualVariantTest extends AbstractSingleListVariantTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListVariantManualVariantTest() {
+    super("singleListVariant");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+    receiverRoot = localReceiverRoot;
+    assertEquals(0, receiverRoot.getT_EmptyList().getNumChild());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // add dependencies: input
+    ((SenderRoot) senderRoot).addInputDependencyToT_Empty((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_Token((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneChild((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneOpt((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneList((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_TwoChildren((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneOfEach((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_Abstract((SenderRoot) senderRoot);
+    // add dependencies: shouldSetOptAndList
+    ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneOpt((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneList((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneOfEach((SenderRoot) senderRoot);
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_T_all, bytes -> data.numberOfElements += 1);
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java
similarity index 97%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java
index 069f622c84cf95508575143b3b9d23288f42bc4a..ebcccfe9898e374a5dc8d5886d83f999fc0def36 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java
@@ -1,4 +1,8 @@
-package org.jastadd.ragconnect.tests;
+package org.jastadd.ragconnect.tests.tree;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
 import java.util.List;
@@ -11,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
+@Tag("Tree")
 public abstract class AbstractTreeTest extends AbstractMqttTest {
   protected static final String TOPIC_ALFA = "alfa";
   protected ReceiverData data;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java
similarity index 95%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java
index 9d455b6abe09809c0277c71dd3f8028f05d63da3..6722e159ed064ee253dd79c01fbf3e009ebba874 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java
@@ -1,5 +1,6 @@
-package org.jastadd.ragconnect.tests;
+package org.jastadd.ragconnect.tests.tree;
 
+import org.jastadd.ragconnect.tests.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import treeInc.ast.*;
@@ -18,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
-@Tag("Tree")
 @Tag("Incremental")
 public class TreeIncrementalTest extends AbstractTreeTest {
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java
similarity index 95%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java
index 10c73aeff57d6752c8f6b903acbde2dd9d93aa8b..d60e8fa0590bc60306997a8896856da99b0a7956 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java
@@ -1,5 +1,6 @@
-package org.jastadd.ragconnect.tests;
+package org.jastadd.ragconnect.tests.tree;
 
+import org.jastadd.ragconnect.tests.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import tree.ast.MqttHandler;
@@ -20,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
-@Tag("Tree")
 public class TreeManualTest extends AbstractTreeTest {
 
   private Root model;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeAllowedTokensTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java
similarity index 97%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeAllowedTokensTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java
index e87a298fe92e0019fa7da2012d6630590d7a348e..12b2c99912ac7f2b4401aeefd76a3f91fce06f7b 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeAllowedTokensTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java
@@ -1,4 +1,8 @@
-package org.jastadd.ragconnect.tests;
+package org.jastadd.ragconnect.tests.treeAllowedTokens;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
 import java.time.Instant;
@@ -11,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
+@Tag("Tree")
 public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest {
 
   protected static final String TOPIC_INPUT1TRUE = "input1/true";
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java
similarity index 96%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java
index 0edfbd6bee0b412158a799ef388901e62d398495..e848ca4059b962aa3431209e23de5ed9ccad4a24 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java
@@ -1,5 +1,6 @@
-package org.jastadd.ragconnect.tests;
+package org.jastadd.ragconnect.tests.treeAllowedTokens;
 
+import org.jastadd.ragconnect.tests.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import treeAllowedTokensInc.ast.*;
@@ -19,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * @author rschoene - Initial contribution
  */
 @Tag("Incremental")
-@Tag("Tree")
 public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensTest {
 
   private Root model;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java
similarity index 96%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java
index 9780d56daaa807a13082e9776fa45b1ec4eee5a3..3903834434d1b5031e1db5a68e6a6d5dbdef9a43 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java
@@ -1,13 +1,12 @@
-package org.jastadd.ragconnect.tests;
+package org.jastadd.ragconnect.tests.treeAllowedTokens;
 
+import org.jastadd.ragconnect.tests.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import treeAllowedTokens.ast.*;
 
 import java.io.IOException;
 import java.nio.file.Paths;
-import java.time.Instant;
-import java.time.Period;
 import java.util.concurrent.TimeUnit;
 
 import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
@@ -19,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
-@Tag("Tree")
 public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest {
 
   private Root model;