diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 87d2c9ca04f5e7383dc6efd5e7a33d87f2d9b8c6..401f721305affd0b14d18923027c42dd32c6edfb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -42,6 +42,8 @@ test:
     - ./gradlew --console=plain --no-daemon allTests
   artifacts:
     when: always
+    paths:
+      - "ragconnect.tests/test.log"
     reports:
       junit: "ragconnect.tests/build/test-results/**/TEST-*.xml"
     expire_in: 1 week
@@ -94,9 +96,6 @@ ragdoc_view:
     - OUTPUT_DIR=$(pwd -P)/pages/docs/ragdoc
     - cd /ragdoc-view/src/ && rm -rf data && ln -s $DATA_DIR
     - /ragdoc-view/build-view.sh --output-path=$OUTPUT_DIR
-  only:
-    - dev
-    - master
   artifacts:
     paths:
       - "pages/docs/ragdoc"
diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md
index fe3b87a7249a5b554902ea7239b58f91ac55a500..1f41f95d1e11cb361d9d4e4b3675d20e09e575fe 100644
--- a/pages/docs/changelog.md
+++ b/pages/docs/changelog.md
@@ -1,5 +1,20 @@
 # Changelog
 
+## 1.0.0 (dev)
+
+### Changes
+
+- Allow connection endpoints for relations ([#37](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/37)) and attributes ([#38](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/38))
+- Allow send connection endpoints non-NTA nonterminals ([#36](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/36))
+- Allow context-free context endpoints ([#34](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/34))
+- Experimental support for Java handler ([#52](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/52))
+- Make specification language more concise ([#33](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/33))
+
+### Development Changes
+
+- Make grammar(s) more concise  ([#40](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/40))
+- Enhance documentation, adding a DSL description
+
 ## 0.3.2
 
 - Allow connection endpoints for list nonterminals ([#21](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/21))
diff --git a/pages/docs/compiler.md b/pages/docs/compiler.md
index d60822b10956accb9313f4a1b677574996a2e198..80a9acbb5e82cf693560e8287c5db320515eb4ad 100644
--- a/pages/docs/compiler.md
+++ b/pages/docs/compiler.md
@@ -6,16 +6,16 @@ Additional options are as follows.
 | Name | Required (Default) | Description |
 |---|---|---|
 | `--rootNode` | Yes | Root node in the base grammar. |
-| `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `mqtt, rest`. |
+| `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `java` (experimental), `mqtt`, `rest`. |
 | `--printYaml` | No (false) | Print out YAML instead of generating files. |
 | `--verbose` | No (false) | Print more messages while compiling. |
 | `--logReads` | No (false) | Enable logging for every received message. |
 | `--logWrites` | No (false) | Enable logging for every sent message. |
 | `--logIncremental` | No (false) | Enable logging for observer in incremental dependency tracking. |
 | `--logTarget` | No (`console`) | Logging target to use, currently available: `console, slf4j`. |
-| `--experimental-jastadd-329` | No (false) | Use trace events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking](/using#dependency-tracking-automatically-derived). |
-| `--incremental` | No (false) | Enables incremental dependency tracking (if `trace` is also set appropriately). |
-| `--trace[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). |
+| `--experimental-jastadd-329` | No (false) | Use tracing events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking](/using#dependency-tracking-automatically-derived). |
+| `--incremental` | No (false) | Enables incremental dependency tracking (if `tracing` is also set appropriately). |
+| `--tracing[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). |
 | `--version` | No (false) | Print version info and exit (reused JastAdd option) |
 | `--o` | No (`.`) | Output directory (reused JastAdd option) |
 
@@ -29,6 +29,16 @@ However, depending on the selected protocols and/or used features, additional de
 
 ## Communication protocol characteristics
 
+### Java
+
+- Protocol identifier: `java`
+- URI scheme: `java://<ignored-host>[:ignored-port]/<topic>`
+    - the value for host and port are always ignored, but are necessary to form a legal URI
+- No required runtime dependencies
+- Additional remarks:
+    - First leading slash not included in topic.
+    - Currently, the default mappings are applied, which requires a consumer to expect `byte[]` (instead of a more intuitive token or node value). This might change in future versions.
+
 ### MQTT
 
 - Protocol identifier: `mqtt`
@@ -59,15 +69,15 @@ However, depending on the selected protocols and/or used features, additional de
 
 ### Automatic dependency tracking
 
-- Condition: When passing `--incremental` and `--trace=flush` to RagConnect
+- Condition: When passing `--incremental` and `--tracing=flush` to RagConnect
 - Required runtime dependencies: _none_
 - Required options for RelAST compiler: _none_
 - Required options for JastAdd:
     - `--incremental`
-    - `--trace=flush`
+    - `--tracing=flush`
 - Remarks:
     - Other (additional) values passed to those two options must be equal (e.g., `--incremental=param` passed to RagConnect must be also passed to JastAdd)
-    - Other values besides `flush` can be added to `--trace`
+    - Other values besides `flush` can be added to `--tracing`
     - [Feature description](/using#dependency-tracking-automatically-derived)
 
 ### (Safer) Automatic dependency tracking
diff --git a/pages/docs/dsl.md b/pages/docs/dsl.md
index e8b6b2bb11c67168bafd33efa48b505248ea9b37..98ee13c4254027096b645268c693c401dadc6d10 100644
--- a/pages/docs/dsl.md
+++ b/pages/docs/dsl.md
@@ -21,19 +21,19 @@ A breakdown of the parts of that syntax:
 - The first word (`send` or `receive`) defines the kind of endpoint - sending or receiving, respectively.
 - The optional `indexed` applies only for list children and lets the endpoint act on elements of that list.
   This only works for receiving endpoints, and is further changed by `with add`.
-  - A lonely `indexed` assigns each incoming "topic" to an index in a list.
-    This can be useful if multiple instances of this endpoint are connected, or the communication protocol supports wildcard topics.
-    For the former case, the connect method with an explicit index can be used, whereas the "normal" connect method without the index acts as a method for "wildcard-connect".
-  - Combining `indexed with add`, incoming data is required to be an element of the list, and will be appended to the list.
+    - A lonely `indexed` assigns each incoming "topic" to an index in a list.
+      This can be useful if multiple instances of this endpoint are connected, or the communication protocol supports wildcard topics.
+      For the former case, the connect method with an explicit index can be used, whereas the "normal" connect method without the index acts as a method for "wildcard-connect".
+    - Combining `indexed with add`, incoming data is required to be an element of the list, and will be appended to the list.
 - The second optional keyword `with add` can also be used only for receiving endpoints targeting a list children.
   As described above, it can be combined with `indexed`.
   If used on its own, the incoming data is interpreted as a complete list and its elements will be appended to the current list.
 - The `<Non-Terminal>[.<Target>["(<AttributeType>)"]]` notation describes the actual affected node.
-  - If the target is omitted, all nodes of that non-terminal type can be connected, irrespective of their context. This is a context-free endpoint definition.
-  - The target can be any child on the right-hand side of a production rule, a role of a relation, or an attribute.
-    The brackets `(<AttributeType>)` after the target must be used in case of an attribute, and only then.
-    Here, the return type of the attribute has to be specified, as aspect files are not parsed by RagConnect.
-    Hence, RagConnect can not and will not verify the existence of the attribute, and the possible non-existence of an attribute will be found by the Java compiler.
+    - If the target is omitted, all nodes of that non-terminal type can be connected, irrespective of their context. This is a context-free endpoint definition.
+    - The target can be any child on the right-hand side of a production rule, a role of a relation, or an attribute.
+      The brackets `(<AttributeType>)` after the target must be used in case of an attribute, and only then.
+      Here, the return type of the attribute has to be specified, as aspect files are not parsed by RagConnect.
+      Hence, RagConnect can not and will not verify the existence of the attribute, and the possible non-existence of an attribute will be found by the Java compiler.
 - Optionally, an endpoint can use one or more [mappings](#mappings).
   They will be applied before sending, or after receiving a message.
   Mappings will always be applied in the order they are listed after `using`.
@@ -49,7 +49,7 @@ Specifying such an endpoint has several consequences:
 
 **Example**:
 
-```java
+```
 // grammar
 Root ::= A SingleA:A [OptA:A] ListA:A* ;
 A ::= <Value> ;
@@ -60,13 +60,15 @@ receive Root.SingleA using MyMapping; // specialized endpoint
 ```
 
 Implied, additional connect specifications:
-```java
+
+```
 receive Root.A;
 receive Root.OptA;
 receive indexed Root.ListA;
 ```
 
 Application code:
+
 ```java
 A a = root.getOptA();
 // new method on A:
diff --git a/pages/docs/using.md b/pages/docs/using.md
index d4d2a30bea5b060e1ccd071b5b2b9e0730c41357..87d91925f1fdd176faa57f724193dd24a1f5accc 100644
--- a/pages/docs/using.md
+++ b/pages/docs/using.md
@@ -47,7 +47,7 @@ Otherwise, the deprecated manual dependencies must be used.
 
 ### Dependency tracking: Automatically derived
 
-To automatically track dependencies, the two additional parameters `--incremental` and `--trace=flush` have to be provided to both RagConnect and (in the later stage) JastAdd.
+To automatically track dependencies, the two additional parameters `--incremental` and `--tracing=flush` have to be provided to both RagConnect and (in the later stage) JastAdd.
 This will generate a different implementation of RagConnect relying on enabled incremental evaluation of JastAdd.
 The value for `incremental` has only been tested for `incremental=param`.
 The value for `trace` can include other values besides `flush`.
diff --git a/ragconnect.base/src/main/jastadd/Handlers.jrag b/ragconnect.base/src/main/jastadd/Handlers.jrag
index 57c7ccda67ae38524dc46de4f1db192cc345cb91..e64a8c5d0db5467f9a4b0d7451e8c38ab22a8bbf 100644
--- a/ragconnect.base/src/main/jastadd/Handlers.jrag
+++ b/ragconnect.base/src/main/jastadd/Handlers.jrag
@@ -1,4 +1,5 @@
 aspect RagConnectHandlers {
+  syn Handler RagConnect.javaHandler() = resolveHandlerByName("java");
   syn Handler RagConnect.mqttHandler() = resolveHandlerByName("mqtt");
   syn Handler RagConnect.restHandler() = resolveHandlerByName("rest");
 
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
index 69ff5d8932cd33a97e44b2fa3562b13cd52617f2..95f9b865b7217091537e954fa8ac98c292e45822 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.jadd
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -15,6 +15,10 @@ aspect SharedMustache {
 
   syn boolean RagConnect.configExperimentalJastAdd329() = getConfiguration().getExperimentalJastAdd329();
 
+  syn boolean RagConnect.configEvaluationCounter() = getConfiguration().getEvaluationCounter();
+
+  syn String RagConnect.evaluationCounterVariable() = internalRagConnectPrefix() + "evaluationCounter";
+
   syn String RagConnect.internalRagConnectPrefix() = "_ragconnect_";
 
   syn String RagConnect.logDebug() = logStatement("debug");
@@ -99,6 +103,10 @@ aspect MustacheHandler {
   // === RagConnect ===
   syn String RagConnect.closeMethodName() = "ragconnectCloseConnections";
 
+  syn String RagConnect.evaluationCounterInnerClass() = internalRagConnectPrefix() + "Counter";
+
+  syn String RagConnect.evaluationCounterSummaryMethodName() = "ragconnectEvaluationCounterSummary";
+
   syn boolean RagConnect.hasRootTypeComponents() = !rootTypeComponents().isEmpty();
 
   syn List<TypeComponent> RagConnect.rootTypeComponents() {
@@ -118,6 +126,10 @@ aspect MustacheHandler {
 
   syn String Handler.fieldName() = ragconnect().internalRagConnectPrefix() + getUniqueName() + "Handler";
 
+  syn String Handler.pushMethodName() = "ragconnect" + capitalize(getUniqueName()) + "Push";
+
+  syn String Handler.registerConsumerMethodName() = "ragconnect" + capitalize(getUniqueName()) + "RegisterConsumer";
+
   syn String Handler.setupWaitUntilReadyMethodName() = "ragconnectSetup" + capitalize(getUniqueName()) + "WaitUntilReady";
 }
 
diff --git a/ragconnect.base/src/main/jastadd/Mappings.jrag b/ragconnect.base/src/main/jastadd/Mappings.jrag
index 9fd55aa4cad7eb22f75913f7942b547ca91ddec2..e92fc4fa27569ee353b0c16798e2ce3b7eb07a29 100644
--- a/ragconnect.base/src/main/jastadd/Mappings.jrag
+++ b/ragconnect.base/src/main/jastadd/Mappings.jrag
@@ -243,7 +243,7 @@ aspect Mappings {
           return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
         } catch (Exception ignore) {
         }
-        System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
+        System.err.println("Could not find suitable default receive mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
@@ -287,7 +287,7 @@ aspect Mappings {
         } catch (Exception ignore) {
           // exception should be logged to debug/fine
         }
-        System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
+        System.err.println("Could not find suitable default send mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index cf12d73202b144240ed929ba499d99a8e797aa89..df50e3dab1dde22e38bca0588396de647b49c8cb 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -40,5 +40,6 @@ Configuration ::=
 <JastAddOpt:String>
 <IncrementalOptionActive:boolean>
 <CacheAllOptionActive:boolean>
+<EvaluationCounter:boolean>
 <ExperimentalJastAdd329:boolean>;
 rel Configuration.RootNode -> TypeDecl ;
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 8d4ab2ce26452684112ecebc9885591f82124cb4..fdb52151104f2ba8d3eb18a7487f9102ffcd19df 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
@@ -31,10 +31,12 @@ public class Compiler extends AbstractCompiler {
   private BooleanOption optionLogIncremental;
   private ValueOption optionLogTarget;
   private BooleanOption optionExperimentalJastAdd329;
+  private BooleanOption optionEvaluationCounter;
 
   private static final String OPTION_LOGGING_TARGET_CONSOLE = "console";
   private static final String OPTION_LOGGING_TARGET_SLF4J = "slf4j";
 
+  private static final String OPTION_PROTOCOL_JAVA = "java";
   private static final String OPTION_PROTOCOL_MQTT = "mqtt";
   private static final String OPTION_PROTOCOL_REST = "rest";
 
@@ -179,6 +181,7 @@ public class Compiler extends AbstractCompiler {
         new ValueOption("protocols", "Protocols to enable")
             .acceptMultipleValues(true)
             .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT")
+            .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java (experimental)")
             .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST")
     );
     optionPrintYaml = addOption(
@@ -204,6 +207,9 @@ public class Compiler extends AbstractCompiler {
     optionExperimentalJastAdd329 = addOption(
         new BooleanOption("experimental-jastadd-329", "Use trace events INC_FLUSH_START and INC_FLUSH_END (JastAdd issue #329).")
             .defaultValue(false));
+    optionEvaluationCounter = addOption(
+            new BooleanOption("evaluationCounter", "Enable counters for evaluation.")
+                    .defaultValue(false));
   }
 
   private RagConnect parseProgram(Collection<String> files) throws CompilerException {
@@ -299,15 +305,16 @@ public class Compiler extends AbstractCompiler {
    * Set all configuration values.
    * @param ragConnect the RagConnect instance to set configuration values
    */
-  private void setConfiguration(RagConnect ragConnect) {
+  private void setConfiguration(RagConnect ragConnect) throws CompilerException {
     ragConnect.setConfiguration(new Configuration());
     ragConnect.getConfiguration().setLoggingEnabledForReads(optionLogReads.value());
     ragConnect.getConfiguration().setLoggingEnabledForWrites(optionLogWrites.value());
     ragConnect.getConfiguration().setLoggingEnabledForIncremental(optionLogIncremental.value());
     ragConnect.getConfiguration().setLoggingTarget(optionLogTarget.value());
     ragConnect.getConfiguration().setExperimentalJastAdd329(optionExperimentalJastAdd329.value());
+    ragConnect.getConfiguration().setEvaluationCounter(optionEvaluationCounter.value());
 
-    // reuse "--incremental" and "--trace=flush" options of JastAdd
+    // reuse "--incremental" and "--tracing=flush" options of JastAdd
     boolean incrementalOptionActive = this.getConfiguration().incremental() && this.getConfiguration().traceFlush();
     ragConnect.getConfiguration().setIncrementalOptionActive(incrementalOptionActive);
     if (isVerbose()) {
@@ -326,11 +333,12 @@ public class Compiler extends AbstractCompiler {
       rootNode = ragConnect.getProgram().resolveTypeDecl(optionRootNode.value());
     } catch (RuntimeException re) {
       // root node was not found
-      throw new RuntimeException("Could not resolve root node '" + optionRootNode.value() + "'!", re);
+      throw new CompilerException("Could not resolve root node '" + optionRootNode.value() + "'!", re);
     }
     ragConnect.getConfiguration().setRootNode(rootNode);
 
     // Handler ::= <ClassName> <UniqueName> <InUse:boolean>;
+    ragConnect.addHandler(new Handler("JavaHandler", "java", optionProtocols.hasValue(OPTION_PROTOCOL_JAVA)));
     ragConnect.addHandler(new Handler("MqttServerHandler", "mqtt", optionProtocols.hasValue(OPTION_PROTOCOL_MQTT)));
     ragConnect.addHandler(new Handler("RestServerHandler", "rest", optionProtocols.hasValue(OPTION_PROTOCOL_REST)));
   }
diff --git a/ragconnect.base/src/main/resources/JavaHandler.mustache b/ragconnect.base/src/main/resources/JavaHandler.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..d8d907b2969b114517eaf9944428a6cefd9842fc
--- /dev/null
+++ b/ragconnect.base/src/main/resources/JavaHandler.mustache
@@ -0,0 +1,82 @@
+/**
+ * Singleton class providing routing functionality for byte[] based message calls.
+ */
+public class JavaHandler {
+  public static JavaHandler JAVA_HANDLER_INSTANCE = null;
+  private java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> callbackList = new java.util.concurrent.ConcurrentHashMap<>();
+  private final java.util.Map<RagConnectToken, java.util.function.BiConsumer<String, byte[]>> tokensForRemoval = new java.util.HashMap<>();
+  private String name;
+
+  private JavaHandler() {
+    this("RagConnect");
+  }
+
+  public JavaHandler(String name) {
+    this.name = name;
+  }
+
+  private static String extractPath(java.net.URI uri) {
+    String path = uri.getPath();
+    if (path.charAt(0) == '/') {
+      path = path.substring(1);
+    }
+    return path;
+  }
+
+  RagConnectToken registerConsumer(String path, java.util.function.Consumer<byte[]> consumer) {
+    RagConnectToken token = new RagConnectToken(java.net.URI.create("internal://host/" + path), null);
+    registerCallback(token, (s, bytes) -> consumer.accept(bytes));
+    return token;
+  }
+
+  boolean registerCallback(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
+    String path = extractPath(connectToken.uri);
+    {{logInfo}}("[JAVA_HANDLER] Registering new callback for {{log_}}.", path);
+
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> registeredCallbacks = callbackList.get(path);
+
+    if (registeredCallbacks == null) {
+      registeredCallbacks = java.util.Collections.synchronizedList(new java.util.ArrayList<>());
+      callbackList.put(path, registeredCallbacks);
+    }
+    registeredCallbacks.add(callback);
+    tokensForRemoval.put(connectToken, callback);
+
+    return true;
+  }
+
+  boolean unregisterCallback(RagConnectToken connectToken) {
+    String path = extractPath(connectToken.uri);
+    java.util.function.BiConsumer<String, byte[]> callback = tokensForRemoval.get(connectToken);
+    {{logInfo}}("[JAVA_HANDLER] Unregistering callback with uuid: on path: {{log_}}", path);
+
+    return callbackList.get(path).remove(callback);
+  }
+
+  void close() {
+  }
+
+  boolean push(String uriString, byte[] data) {
+    String path = extractPath(java.net.URI.create(uriString));
+    {{logDebug}}("[JAVA_HANDLER] Pushing a message for {{log_}}.", path);
+    if (data == null) {
+      {{logDebug}}("[JAVA_HANDLER] data was null, aborting");
+      return false;
+    }
+    String dataString = new String(data);
+    {{logDebug}}("[JAVA_HANDLER] Data: {{log_}}", dataString);
+
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacks = callbackList.get(path);
+
+    if (callbacks == null) {
+      {{logError}}("[JAVA_HANDLER] Could not publish message. No callback registered for path {{log_}}", path);
+      return false;
+    }
+
+    for (java.util.function.BiConsumer<String, byte[]> callback : callbacks) {
+      callback.accept(path, data);
+    }
+
+    return true;
+  }
+}
diff --git a/ragconnect.base/src/main/resources/handleUri.mustache b/ragconnect.base/src/main/resources/handleUri.mustache
index 2312cd6983822c0e0b2987459047b7ca2a277fc2..efb4aacfbf1e003dcb540ba77bdaca68a8aed6cd 100644
--- a/ragconnect.base/src/main/resources/handleUri.mustache
+++ b/ragconnect.base/src/main/resources/handleUri.mustache
@@ -10,14 +10,14 @@ try {
   return false;
 }
 if (scheme == null || scheme.isBlank()) {
-  {{logError}}("Missing or empty scheme in " + uri);
+  {{logError}}("Missing or empty scheme in {{log_}}", uri);
   return false;
 }
 if (host == null || host.isBlank()) {
-  {{logError}}("Missing or empty host in " + uri);
+  {{logError}}("Missing or empty host in {{log_}}", uri);
   return false;
 }
 if (path == null || path.isBlank()) {
-  {{logError}}("Missing or empty path in " + uri);
+  {{logError}}("Missing or empty path in {{log_}}", uri);
   return false;
 }
diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache
index 49194266f96e84a4c24bc0987b83647082132dc2..75bd14ce26638ceeee267251988a01b516de0970 100644
--- a/ragconnect.base/src/main/resources/handler.mustache
+++ b/ragconnect.base/src/main/resources/handler.mustache
@@ -19,18 +19,30 @@ aspect RagConnectHandler {
     {{/configIncrementalOptionActive}}
   }
 
+{{#javaHandler}}
+  {{#InUse}}
+  public RagConnectToken {{rootNodeName}}.{{registerConsumerMethodName}}(String path, java.util.function.Consumer<byte[]> consumer) {
+    return {{fieldName}}.registerConsumer(path, consumer);
+  }
+  public boolean {{rootNodeName}}.{{pushMethodName}}(String uriString, byte[] data) {
+    return {{fieldName}}.push(uriString, data);
+  }
+  {{> JavaHandler}}
+  {{/InUse}}
+{{/javaHandler}}
+
 {{#mqttHandler}}
   {{#InUse}}
   public void {{rootNodeName}}.{{setupWaitUntilReadyMethodName}}(long time, java.util.concurrent.TimeUnit unit) {
     {{fieldName}}.setupWaitUntilReady(time, unit);
   }
+  {{> MqttHandler}}
   {{/InUse}}
-{{> MqttHandler}}
 {{/mqttHandler}}
 
 {{#restHandler}}
   {{#InUse}}
-{{> RestHandler}}
+  {{> RestHandler}}
   {{/InUse}}
 {{/restHandler}}
 
diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache
index 482d54783782ada82cacd9ce75b81d30c1e21e68..efd17af4e8947232f44b93c3749d86b1e637dd46 100644
--- a/ragconnect.base/src/main/resources/mappingApplication.mustache
+++ b/ragconnect.base/src/main/resources/mappingApplication.mustache
@@ -1,6 +1,12 @@
+{{#configEvaluationCounter}}
+  {{evaluationCounterVariable}}.incrementCall("{{parentTypeName}}", "{{entityName}}");
+{{/configEvaluationCounter}}
 {{#Send}}
 {{^PrimitiveType}}
 if ({{firstInputVarName}} == null) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementFirstNull("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
 {{/PrimitiveType}}
@@ -12,13 +18,22 @@ try {
   {{/innerMappingDefinitions}}
 } catch (RagConnectRejectMappingException e) {
   // do not print message in case of rejection
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementReject("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 } catch (Exception e) {
   e.printStackTrace();
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementException("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
 {{^AlwaysApply}}
 if ({{{condition}}}) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSkip("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
 {{/AlwaysApply}}
diff --git a/ragconnect.base/src/main/resources/mappingDefinition.mustache b/ragconnect.base/src/main/resources/mappingDefinition.mustache
index 5be93e4bdc91ca2eaa54f364eb8e5f7c069da2a5..b6a035eb41b9d3f877b731293203de399399e6c0 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 {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{FromVariableName}}) throws Exception {
   {{{Content}}}
 }
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index ac7b906067f5f9ecd4a2bf0c51a368797f55dfbc..7e054a4927b527e6d32a30e32a6bffa71d86b97f 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -285,3 +285,98 @@ aspect RagConnectObserver {
   }
 }
 {{/configIncrementalOptionActive}}
+
+aspect EvaluationCounter {
+  public String ASTNode.{{evaluationCounterSummaryMethodName}}() {
+  {{#configEvaluationCounter}}
+    return {{evaluationCounterVariable}}.summary();
+  {{/configEvaluationCounter}}
+  {{^configEvaluationCounter}}
+    String message = "Option --evaluationCounter was not set. No Summary available";
+    {{logWarn}}(message);
+    return message;
+  {{/configEvaluationCounter}}
+  }
+{{#configEvaluationCounter}}
+  static EvaluationCounter ASTNode.{{evaluationCounterVariable}} = new EvaluationCounter();
+
+  public class EvaluationCounter {
+    private java.util.Map<String, java.util.Map<String, {{evaluationCounterInnerClass}}>> counters = new java.util.HashMap<>();
+    private final java.util.function.Function<? super String, ? extends java.util.Map<String, {{evaluationCounterInnerClass}}>> parentAbsent = key -> {
+      return new java.util.HashMap<>();
+    };
+    private final java.util.function.Function<? super String, ? extends {{evaluationCounterInnerClass}}> entityAbsent = key -> {
+      return new {{evaluationCounterInnerClass}}();
+    };
+
+    public void incrementReceive(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).receive += 1;
+    }
+
+    public void incrementSend(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).send += 1;
+    }
+
+    public void incrementCall(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).call += 1;
+    }
+
+    public void incrementFirstNull(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).firstNull += 1;
+    }
+
+    public void incrementSkip(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).skip += 1;
+    }
+
+    public void incrementException(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).exception += 1;
+    }
+
+    public void incrementReject(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).reject += 1;
+    }
+
+    public String summary() {
+      StringBuilder sb = new StringBuilder();
+      // header
+      sb.append("parentTypeName,entityName,receive,send,call,firstNull,skip,exception,reject").append("\n");
+      // values
+      java.util.Set<String> sortedParentTypes = new java.util.TreeSet<>(counters.keySet());
+      for (String parentType : sortedParentTypes) {
+        java.util.Set<String> sortedEntityNames = new java.util.TreeSet<>(counters.get(parentType).keySet());
+        for (String entityName : sortedEntityNames) {
+          {{evaluationCounterInnerClass}} count = getCounter(parentType, entityName);
+          java.util.StringJoiner sj = new java.util.StringJoiner(",", "", "\n");
+          sj.add(parentType)
+             .add(entityName)
+             .add(Integer.toString(count.receive))
+             .add(Integer.toString(count.send))
+             .add(Integer.toString(count.call))
+             .add(Integer.toString(count.firstNull))
+             .add(Integer.toString(count.skip))
+             .add(Integer.toString(count.exception))
+             .add(Integer.toString(count.reject))
+          ;
+          sb.append(sj);
+        }
+      }
+      return sb.toString();
+    }
+
+    private {{evaluationCounterInnerClass}} getCounter(String parentTypeName, String entityName) {
+      return counters.computeIfAbsent(parentTypeName, parentAbsent).computeIfAbsent(entityName, entityAbsent);
+    }
+  }
+
+  class {{evaluationCounterInnerClass}} {
+    int receive = 0;
+    int send = 0;
+    int call = 0;
+    int firstNull = 0;
+    int skip = 0;
+    int exception = 0;
+    int reject = 0;
+  }
+{{/configEvaluationCounter}}
+}
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index cb4dc8dc718eba53b0cd6a3bce9b4f996cdd4675..81b0ecbbd96aa40155ec8f396549a4af2160b64e 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -25,6 +25,9 @@ private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) {
  */
 public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParameterName}}{{#typeIsList}}{{#IndexBasedListAccess}}{{^WithAdd}}, int index{{/WithAdd}}{{/IndexBasedListAccess}}{{/typeIsList}}) throws java.io.IOException {
   java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
+    {{#configEvaluationCounter}}
+      {{evaluationCounterVariable}}.incrementReceive("{{parentTypeName}}", "{{entityName}}");
+    {{/configEvaluationCounter}}
     {{> mappingApplication}}
 {{#configLoggingEnabledForReads}}
     {{logDebug}}("[Receive] {{log_}} -> {{entityName}} = {{log_}}", {{connectParameterName}}, {{lastResult}});
@@ -70,6 +73,9 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
 */
 public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParameterName}}) throws java.io.IOException {
   java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
+    {{#configEvaluationCounter}}
+      {{evaluationCounterVariable}}.incrementReceive("{{parentTypeName}}", "{{entityName}}");
+    {{/configEvaluationCounter}}
     int index = {{resolveInListMethodName}}(topic);
     {{> mappingApplication}}
 {{#configLoggingEnabledForReads}}
@@ -93,6 +99,13 @@ private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connec
   RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}");
   boolean success;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+      success = {{attributeName}}().registerCallback(connectToken, consumer);
+      break;
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt":
@@ -130,6 +143,12 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
   }
   RagConnectDisconnectHandlerMethod disconnectingMethod;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java": disconnectingMethod = {{attributeName}}()::unregisterCallback;
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt": disconnectingMethod = {{attributeName}}()::disconnect;
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 1e66fdedcc7620aefe31a89fcece47dbcf9708ac..abcbdffe2d4f7aaff5dfce8700cb11e72ec8d83d 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -5,6 +5,22 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
   RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}");
   boolean success;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+    final JavaHandler handler = {{attributeName}}();
+
+    {{senderName}}.add(() -> {
+      handler.push(path, {{lastValueGetterCall}});
+    }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
+    {{updateMethodName}}();
+    if (writeCurrentValue) {
+      {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken);
+    }
+    success = true;
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt":
@@ -78,6 +94,13 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
   {{/configIncrementalOptionActive}}
   RagConnectDisconnectHandlerMethod disconnectingMethod;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+      disconnectingMethod = {{senderName}}::remove;
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt":
@@ -111,14 +134,25 @@ protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAcces
   {{> mappingApplication}}
   {{lastValueSetter}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}{{lastResult}});
   // normally we would return true here. unless no connect method was called so far to initialize {{senderName}} yet
+  {{#configEvaluationCounter}}
+    if ({{senderName}} == null) {
+      {{evaluationCounterVariable}}.incrementSkip("{{parentTypeName}}", "{{entityName}}");
+    }
+  {{/configEvaluationCounter}}
   return {{senderName}} != null;
 }
 
 protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSend("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{senderName}}.run({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
 }
 
 protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index, {{/IndexBasedListAccess}}RagConnectToken token) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSend("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{senderName}}.run({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}token);
 }
 
diff --git a/ragconnect.tests/.gitignore b/ragconnect.tests/.gitignore
index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..31993cebfe5fdbd5c2727c631ebfec8394963232 100644
--- a/ragconnect.tests/.gitignore
+++ b/ragconnect.tests/.gitignore
@@ -3,3 +3,4 @@ src/gen-res/
 src/gen/
 out/
 *.class
+test.log
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 18dd839d9f8ec04684cdc45d2e40d1dacbf83362..4da4ee9ea07110aa3e91cc18a71074f19155c079 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -70,6 +70,8 @@ dependencies {
 
     testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
     api group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
+
+    implementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: "1.0.2-68"
 }
 
 // --- Preprocessors ---
@@ -649,9 +651,7 @@ task compileRelationIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/relation/Test.relast'),
                       file('src/test/01-input/relation/Test.connect')]
         rootNode = 'Root'
-        logWrites = true
-        logIncremental = true
-        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329', '--evaluationCounter'])
     }
     relast {
         useJastAddNames = true
@@ -666,9 +666,31 @@ task compileRelationIncremental(type: RagConnectTest) {
     }
 }
 
+// --- Test: java-incremental ---
+task compileJavaIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/javaInc')
+        inputFiles = [file('src/test/01-input/java/Test.relast'),
+                      file('src/test/01-input/java/Test.connect')]
+        rootNode = 'Root'
+        protocols = ['java']
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329', '--evaluationCounter'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/javaInc/javaInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'javaInc.ast'
+        inputFiles = [file('src/test/01-input/java/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+
 // --- Task order ---
 classes.dependsOn(':ragconnect.base:jar')
-//compileAttributeIncremental.outputs.upToDateWhen { false }
 
 // --- Misc ---
 static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) {
diff --git a/ragconnect.tests/src/test/01-input/java/README.md b/ragconnect.tests/src/test/01-input/java/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..272a8518ce52567f6528810d1c3225a163675be5
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/README.md
@@ -0,0 +1,3 @@
+# Java
+
+Idea: Use receive and send definitions using the Java handler.
diff --git a/ragconnect.tests/src/test/01-input/java/Test.connect b/ragconnect.tests/src/test/01-input/java/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..d516cfe5cda16dbd0c50a3f485d5477d704ecdba
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.connect
@@ -0,0 +1,26 @@
+send SenderRoot.SendToken ;
+send SenderRoot.SendNode ;
+send SenderRoot.SendManyNode ;
+send SenderRoot.SendNTA ;
+
+AddSuffix maps A a to A {:
+  A result = new A();
+  String changedValue = a.getValue() + "-post";
+  result.setValue(changedValue);
+  result.setInner(new Inner(a.getInner().getInnerValue() + "-post"));
+  return result;
+:}
+
+AddStringSuffix maps String s to String {:
+  return s + "post";
+:}
+
+AddPlusOne maps int i to int {:
+  return i + 1;
+:}
+
+receive ReceiverRoot.SomeToken;
+receive ReceiverRoot.SomeNode;
+receive ReceiverRoot.SomeNodeWithMapping using AddSuffix;
+receive ReceiverRoot.ManyNode;
+receive ReceiverRoot.NTA;
diff --git a/ragconnect.tests/src/test/01-input/java/Test.jadd b/ragconnect.tests/src/test/01-input/java/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..0bb643ed9a95cf0ca83d312d7cb9717eaacc2436
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.jadd
@@ -0,0 +1,30 @@
+aspect Computation {
+  syn String SenderRoot.basic() = getInput();
+  syn String SenderRoot.simple() = getInput() + "Post";
+  syn A SenderRoot.getSendNTA() {
+    A result = new A();
+    result.setValue(getInput());
+    Inner inner = new Inner();
+    inner.setInnerValue("1");
+    result.setInner(inner);
+    return result;
+  }
+}
+aspect MakeCodeCompile {
+
+}
+aspect MakeCodeWork {
+
+}
+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 A.customID() {
+    return getClass().getSimpleName() + getValue();
+  }
+  @Override
+  protected String Inner.customID() {
+    return getClass().getSimpleName() + getInnerValue();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/java/Test.relast b/ragconnect.tests/src/test/01-input/java/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..62a23c231f4f54a99fad7d104173c3128b64393d
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.relast
@@ -0,0 +1,10 @@
+Root ::= SenderRoot* ReceiverRoot;
+SenderRoot ::= <Input> <SendToken> SendNode:A SendManyNode:A* /SendNTA:A/ ;
+ReceiverRoot ::=
+    <SomeToken>
+    SomeNode:A
+    SomeNodeWithMapping:A
+    ManyNode:A*
+    NTA:A;
+A ::= <Value> Inner ;
+Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
index c537a1db389f34545e59fc9c45f3525ce421dd75..61fc420c7e984070983dbfc181feb3519f7f1aa1 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,14 +14,12 @@ import java.util.concurrent.TimeUnit;
  * @author rschoene - Initial contribution
  */
 @Tag("mqtt")
-public abstract class AbstractMqttTest {
+public abstract class AbstractMqttTest extends RagConnectTest {
 
   private static boolean checkDone = false;
 
   protected static MqttHandler publisher;
 
-  protected Logger logger = LoggerFactory.getLogger(getClass());
-
   /**
    * if the initial/current value shall be sent upon connecting
    */
@@ -61,7 +59,6 @@ public abstract class AbstractMqttTest {
   public final void testCommunicateSendInitialValue() throws IOException, InterruptedException {
     this.writeCurrentValue = true;
 
-    logger.debug("Start testCommunicateSendInitialValue");
     createModel();
     setupReceiverAndConnect();
 
@@ -80,7 +77,6 @@ public abstract class AbstractMqttTest {
   public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
     this.writeCurrentValue = false;
 
-    logger.debug("Start testCommunicateOnlyUpdatedValue");
     createModel();
     setupReceiverAndConnect();
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
index d774d91d91fefab6b86d0b0bdf2f2c8e4e72d729..9d4c730ad3fa8a27e8de22a10f80d4594ff3dbf9 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
@@ -22,9 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
-public class Errors {
+public class Errors extends RagConnectTest {
 
-  private static final Logger logger = LoggerFactory.getLogger(Errors.class);
   private static final String ERROR_DIRECTORY = "errors/";
   private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY;
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c33e9c65e346a45fd4458428a81a273a9cf8148
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -0,0 +1,246 @@
+package org.jastadd.ragconnect.tests;
+
+import javaInc.ast.*;
+import org.assertj.core.groups.Tuple;
+import org.jastadd.ragconnect.tests.TestUtils.TestChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.tuple;
+import static org.jastadd.ragconnect.tests.TestUtils.javaUri;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Testing the Java handler.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class JavaTest extends RagConnectTest {
+
+  private static final String TOPIC_RECEIVE_TOKEN = "receiveToken";
+  private static final String TOPIC_RECEIVE_NODE_PLAIN = "receiveNode/plain";
+  private static final String TOPIC_RECEIVE_NODE_MAPPED = "receiveNode/mapped";
+  private static final String TOPIC_RECEIVE_MANY = "receiveMany";
+  private static final String TOPIC_RECEIVE_NTA = "receiveNTA";
+
+  private static final String TOPIC_SEND_TOKEN = "sendToken";
+  private static final String TOPIC_SEND_NODE = "sendNode";
+  private static final String TOPIC_SEND_MANY = "sendMany";
+  private static final String TOPIC_SEND_NTA = "sendNTA";
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private ReceiverRoot receiverRoot;
+
+  private String lastValueToken;
+  private A lastValueNode;
+  private JastAddList<A> lastValueMany;
+  private A lastValueNTA;
+  private TestChecker checker;
+
+  void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot().setInput("1").setSendNode(createA("1"));
+    receiverRoot = new ReceiverRoot();
+    model.addSenderRoot(senderRoot);
+    model.setReceiverRoot(receiverRoot);
+  }
+
+  private static A createA(String value) {
+    return new A().setValue(value).setInner(new Inner("inner"));
+  }
+
+  private void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    // checker
+    checker = new TestChecker();
+    checker.setActualString(TOPIC_SEND_TOKEN, () -> lastValueToken)
+            .setCheckForString(TOPIC_SEND_NODE, (name, expected) -> checkA(expected, lastValueNode, name))
+            .setCheckForTuple(TOPIC_SEND_MANY, (name, expected) -> checkAList(expected, lastValueMany, name))
+            .setCheckForString(TOPIC_SEND_NTA, (name, expected) -> checkA(expected, lastValueNTA, name))
+            .setActualString(TOPIC_RECEIVE_TOKEN, () -> receiverRoot.getSomeToken())
+            .setCheckForString(TOPIC_RECEIVE_NODE_PLAIN, (name, expected) ->
+                    checkA(expected, receiverRoot.getSomeNode(), name))
+            .setCheckForString(TOPIC_RECEIVE_NODE_MAPPED, (name, expected) ->
+                    checkA(expected, receiverRoot.getSomeNodeWithMapping(), name))
+            .setCheckForTuple(TOPIC_RECEIVE_MANY, (name, expected) ->
+                    checkAList(expected, receiverRoot.getManyNodeList(), name))
+            .setCheckForString(TOPIC_RECEIVE_NTA, (name, expected) ->
+                    checkA(expected, receiverRoot.getNTA(), name))
+            .put(TOPIC_RECEIVE_TOKEN, "")
+            .put(TOPIC_RECEIVE_MANY, tuple())
+            .setActualNumberOfValues(() -> 0)
+            .disableManualWait();
+
+    // callbacks
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_TOKEN, bytes -> lastValueToken = new String(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_NODE, bytes -> lastValueNode = ExposingASTNode.INSTANCE.bytesToA(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_MANY, bytes -> lastValueMany = ExposingASTNode.INSTANCE.bytesToList(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_NTA, bytes -> lastValueNTA = ExposingASTNode.INSTANCE.bytesToA(bytes));
+
+    // receive
+    receiverRoot.connectSomeToken(javaUri(TOPIC_RECEIVE_TOKEN));
+    receiverRoot.connectSomeNode(javaUri(TOPIC_RECEIVE_NODE_PLAIN));
+    receiverRoot.connectSomeNodeWithMapping(javaUri(TOPIC_RECEIVE_NODE_MAPPED));
+    receiverRoot.connectManyNodeList(javaUri(TOPIC_RECEIVE_MANY));
+    receiverRoot.connectNTA(javaUri(TOPIC_RECEIVE_NTA));
+
+    // send
+    senderRoot.connectSendToken(javaUri(TOPIC_SEND_TOKEN), writeCurrentValue);
+    senderRoot.connectSendNode(javaUri(TOPIC_SEND_NODE), writeCurrentValue);
+    senderRoot.connectSendManyNodeList(javaUri(TOPIC_SEND_MANY), writeCurrentValue);
+    senderRoot.connectSendNTA(javaUri(TOPIC_SEND_NTA), writeCurrentValue);
+  }
+
+  @Test
+  public void testCommunicateSendInitialValue() throws IOException {
+    createModel();
+    setupReceiverAndConnect(true);
+    checker.put(TOPIC_SEND_TOKEN, "")
+            .put(TOPIC_SEND_NODE, "1")
+            .put(TOPIC_SEND_MANY, tuple())
+            .put(TOPIC_SEND_NTA, "1|1")
+    ;
+
+    communicateBoth();
+  }
+
+  @Test
+  public void testCommunicateOnlyUpdatedValue() throws IOException {
+    createModel();
+    setupReceiverAndConnect(false);
+
+    checker.put(TOPIC_SEND_TOKEN, (String) null)
+            .put(TOPIC_SEND_NODE, (String) null)
+            .put(TOPIC_SEND_MANY, (Tuple) null)
+            .put(TOPIC_SEND_NTA, (String) null)
+    ;
+
+    communicateBoth();
+  }
+
+  private void communicateBoth() {
+    checker.check();
+
+    senderRoot.setInput("2");
+    checker.put(TOPIC_SEND_NTA, "2|1").check();
+
+    senderRoot.getSendNode().setValue("3");
+    checker.put(TOPIC_SEND_NODE, "3").check();
+
+    senderRoot.setSendToken("test-4");
+    checker.put(TOPIC_SEND_TOKEN, "test-4").check();
+
+    senderRoot.addSendManyNode(createA("5"));
+    checker.put(TOPIC_SEND_MANY, tuple("5")).check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
+    checker.put(TOPIC_RECEIVE_TOKEN, "7").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NODE_PLAIN, ExposingASTNode.INSTANCE.aToBytes(createA("8")));
+    checker.put(TOPIC_RECEIVE_NODE_PLAIN, "8").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NODE_MAPPED, ExposingASTNode.INSTANCE.aToBytes(createA("9")));
+    checker.put(TOPIC_RECEIVE_NODE_MAPPED, "9-post|inner-post").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_MANY, ExposingASTNode.INSTANCE.listToBytes(new JastAddList<>(createA("10"), createA("11"))));
+    checker.put(TOPIC_RECEIVE_MANY, tuple("10", "11")).check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NTA, ExposingASTNode.INSTANCE.aToBytes(createA("12")));
+    checker.put(TOPIC_RECEIVE_NTA, "12").check();
+
+    System.out.println(model.ragconnectEvaluationCounterSummary());
+  }
+
+  private void checkA(String expectedValue, A actual, String alias) {
+    if (expectedValue == null) {
+      assertNull(actual, alias);
+    } else {
+      final String expectedValueInA, expectedInnerValue;
+      if (expectedValue.contains("|")) {
+        String[] tokens = expectedValue.split("\\|");
+        expectedValueInA = tokens[0];
+        expectedInnerValue = tokens[1];
+      } else {
+        expectedValueInA = expectedValue;
+        expectedInnerValue = "inner";
+      }
+      assertNotNull(actual, alias);
+      assertEquals(expectedValueInA, actual.getValue(), alias);
+      assertNotNull(actual.getInner(), alias + ".inner");
+      assertEquals(expectedInnerValue, actual.getInner().getInnerValue(), alias + ".inner");
+    }
+  }
+
+  private void checkAList(Tuple expectedTuple, JastAddList<A> actual, String alias) {
+    if (expectedTuple == null) {
+      assertNull(actual, alias);
+      return;
+    }
+    List<Object> expected = expectedTuple.toList();
+    assertEquals(expected.size(), actual.getNumChild(), alias + ".size");
+    for (int i = 0, expectedSize = expected.size(); i < expectedSize; i++) {
+      String s = (String) expected.get(i);
+      checkA(s, actual.getChild(i), alias + "[" + i + "]");
+    }
+  }
+
+  @AfterEach
+  public void alwaysCloseConnections() {
+    logger.debug("Closing connections");
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @SuppressWarnings({"rawtypes" , "unchecked"})
+  static class ExposingASTNode extends ASTNode {
+    static ExposingASTNode INSTANCE = new ExposingASTNode();
+
+    public A bytesToA(byte[] bytes) {
+      try {
+        return _ragconnect__apply__TreeDefaultBytesToAMapping(bytes);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public JastAddList<A> bytesToList(byte[] input) {
+      try {
+        return _ragconnect__apply__TreeDefaultBytesToJastAddListAListMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] aToBytes(A input) {
+      try {
+        return _ragconnect__apply__TreeDefaultAToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] listToBytes(JastAddList<A> input) {
+      try {
+        return _ragconnect__apply__TreeDefaultJastAddListToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] stringToBytes(String input) {
+      try {
+        return _ragconnect__apply__DefaultStringToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
index 091990a64a31c01a6f2c59b934cfe15b6fb76ece..be61201ef687a72bc252380166cf1abbf5b82eb4 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
@@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * @author rschoene - Initial contribution
  */
 @Tag("mqtt")
-public class MqttHandlerTest {
+public class MqttHandlerTest extends RagConnectTest {
 
   @Test
   public void defaultBehaviour() {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2da1577980ad00b644f3f95c25d7e66ca11be79
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java
@@ -0,0 +1,26 @@
+package org.jastadd.ragconnect.tests;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base for all RagConnect tests.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class RagConnectTest {
+  protected Logger logger = LoggerFactory.getLogger(getClass());
+
+  @BeforeEach
+  public void logStart(TestInfo testInfo) {
+    logger.info("Starting {}.{}", getClass().getSimpleName(), testInfo.getDisplayName());
+  }
+
+  @AfterEach
+  public void logEnd(TestInfo testInfo) {
+    logger.info("Finished {}.{}", getClass().getSimpleName(), testInfo.getDisplayName());
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
index 67c82177d96fe34c6379b9d62537f785292827df..5f74942484f67dae810feb70e92d573ecebf9aba 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
@@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
-public class RegressionTests {
+public class RegressionTests extends RagConnectTest {
 
   private static final String REGRESSION_TEST_OUTPUT_DIRECTORY = "regression-test/";
 
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 3457ce0c69123ebbc31853e69af5e84b5ddf827b..3fb3e225d1003fb92b576a3fa1bb345a6b0302d9 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
@@ -62,6 +62,10 @@ public class TestUtils {
     return "rest://localhost:" + port + "/" + path;
   }
 
+  public static String javaUri(String path) {
+    return "java://localhost/" + path;
+  }
+
   public static int getMqttDefaultPort() {
     return 1883;
   }
@@ -213,71 +217,117 @@ public class TestUtils {
         awaitMqtt().alias(name).until(actual, Predicate.isEqual(expected));
       }
     }
+    static class ValuesToCompare<T> {
+      protected final Map<String, ActualAndExpected<T>> values = new HashMap<>();
+      protected final TestChecker parent;
+
+      ValuesToCompare(TestChecker parent) {
+        this.parent = parent;
+      }
+
+      public TestChecker setActual(String name, Callable<T> actual) {
+        values.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
+        return parent;
+      }
+
+      public TestChecker setCheck(String name, BiConsumer<String, T> check) {
+        values.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
+        return parent;
+      }
+
+      public TestChecker put(String name, T expected) {
+        values.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
+        return parent;
+      }
+
+      ActualAndExpected<T> get(String name) {
+        return values.get(name);
+      }
+
+      void forEach(BiConsumer<? super String, ? super ActualAndExpected<T>> action) {
+        values.forEach(action);
+      }
+    }
+    static class IntegerValuesToCompare extends ValuesToCompare<Integer> {
+      IntegerValuesToCompare(TestChecker parent) {
+        super(parent);
+      }
+
+      public TestChecker incNumberOfValues() {
+        return addToNumberOfValues(1);
+      }
+
+      public TestChecker addToNumberOfValues(int increment) {
+        // if there is at least one call to this, we do not need to manually wait in the next check()
+        parent.needManualWait = false;
+        Integer currentExpected = values.computeIfAbsent(NUMBER_OF_VALUES, ActualAndExpected::new).expected;
+        return put(NUMBER_OF_VALUES, currentExpected + increment);
+      }
+
+      public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
+        setActual(NUMBER_OF_VALUES, actual);
+        values.get(NUMBER_OF_VALUES).expected = 0;
+        return parent;
+      }
+    }
     private final static String NUMBER_OF_VALUES = "numberOfValues";
-    private final Map<String, ActualAndExpected<String>> stringValues = new HashMap<>();
-    private final Map<String, ActualAndExpected<Tuple>> tupleValues = new HashMap<>();
-    private final Map<String, ActualAndExpected<Integer>> intValues = new HashMap<>();
+    public final ValuesToCompare<String> stringValues = new ValuesToCompare<>(this);
+    public final ValuesToCompare<Tuple> tupleValues = new ValuesToCompare<>(this);
+    public final IntegerValuesToCompare intValues = new IntegerValuesToCompare(this);
     private boolean needManualWait = true;
+    private boolean useManualWait = true;
 
     public TestChecker incNumberOfValues() {
-      return addToNumberOfValues(1);
+      return intValues.incNumberOfValues();
     }
 
     public TestChecker addToNumberOfValues(int increment) {
-      // if there is at least one call to this, we do not need to manually wait in the next check()
-      needManualWait = false;
-      Integer currentExpected = intValues.computeIfAbsent(NUMBER_OF_VALUES, ActualAndExpected::new).expected;
-      return put(NUMBER_OF_VALUES, currentExpected + increment);
+      return intValues.addToNumberOfValues(increment);
     }
 
     public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
-      setActualInteger(NUMBER_OF_VALUES, actual);
-      intValues.get(NUMBER_OF_VALUES).expected = 0;
-      return this;
+      return intValues.setActualNumberOfValues(actual);
     }
 
     public TestChecker setActualString(String name, Callable<String> actual) {
-      stringValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
-      return this;
+      return stringValues.setActual(name, actual);
     }
 
     public TestChecker setCheckForString(String name, BiConsumer<String, String> check) {
-      stringValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
-      return this;
+      return stringValues.setCheck(name, check);
     }
 
     public TestChecker put(String name, String expected) {
-      stringValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
-      return this;
+      return stringValues.put(name, expected);
     }
 
     public TestChecker setActualTuple(String name, Callable<Tuple> actual) {
-      tupleValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
-      return this;
+      return tupleValues.setActual(name, actual);
     }
 
     public TestChecker setCheckForTuple(String name, BiConsumer<String, Tuple> check) {
-      tupleValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
-      return this;
+      return tupleValues.setCheck(name, check);
     }
 
     public TestChecker put(String name, Tuple expected) {
-      tupleValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
-      return this;
+      return tupleValues.put(name, expected);
     }
 
     public TestChecker setActualInteger(String name, Callable<Integer> actual) {
-      intValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
-      return this;
+      return intValues.setActual(name, actual);
     }
 
     public TestChecker setCheckForInteger(String name, BiConsumer<String, Integer> check) {
-      intValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
-      return this;
+      return intValues.setCheck(name, check);
     }
 
     public TestChecker put(String name, Integer expected) {
-      intValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
+      return intValues.put(name, expected);
+    }
+
+    public TestChecker disableManualWait() {
+      useManualWait = false;
+      needManualWait = false;
       return this;
     }
 
@@ -298,7 +348,7 @@ public class TestUtils {
           aae.check(name);
         }
       });
-      needManualWait = true;
+      needManualWait = useManualWait;
     }
   }
 
@@ -324,56 +374,56 @@ public class TestUtils {
   @SuppressWarnings({"unused", "rawtypes"})
   public static class DefaultMappings {
     static class ReadNode extends defaultOnlyRead.ast.ASTNode {
-      public static boolean DefaultBytesToBooleanMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToBooleanMapping(input);
+      public boolean DefaultBytesToBooleanMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToBooleanMapping(input);
       }
-      public static int DefaultBytesToIntMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToIntMapping(input);
+      public int DefaultBytesToIntMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToIntMapping(input);
       }
-      public static short DefaultBytesToShortMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToShortMapping(input);
+      public short DefaultBytesToShortMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToShortMapping(input);
       }
-      public static long DefaultBytesToLongMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToLongMapping(input);
+      public long DefaultBytesToLongMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToLongMapping(input);
       }
-      public static float DefaultBytesToFloatMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToFloatMapping(input);
+      public float DefaultBytesToFloatMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToFloatMapping(input);
       }
-      public static double DefaultBytesToDoubleMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToDoubleMapping(input);
+      public double DefaultBytesToDoubleMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToDoubleMapping(input);
       }
-      public static char DefaultBytesToCharMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToCharMapping(input);
+      public char DefaultBytesToCharMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToCharMapping(input);
       }
-      public static String DefaultBytesToStringMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToStringMapping(input);
+      public String DefaultBytesToStringMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToStringMapping(input);
       }
     }
 
     static class WriteNode extends defaultOnlyWrite.ast.ASTNode {
-      public static byte[] DefaultBooleanToBytesMapping(boolean input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultBooleanToBytesMapping(input);
+      public byte[] DefaultBooleanToBytesMapping(boolean input) throws Exception {
+        return _ragconnect__apply__DefaultBooleanToBytesMapping(input);
       }
-      public static byte[] DefaultIntToBytesMapping(int input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultIntToBytesMapping(input);
+      public byte[] DefaultIntToBytesMapping(int input) throws Exception {
+        return _ragconnect__apply__DefaultIntToBytesMapping(input);
       }
-      public static byte[] DefaultShortToBytesMapping(short input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultShortToBytesMapping(input);
+      public byte[] DefaultShortToBytesMapping(short input) throws Exception {
+        return _ragconnect__apply__DefaultShortToBytesMapping(input);
       }
-      public static byte[] DefaultLongToBytesMapping(long input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultLongToBytesMapping(input);
+      public byte[] DefaultLongToBytesMapping(long input) throws Exception {
+        return _ragconnect__apply__DefaultLongToBytesMapping(input);
       }
-      public static byte[] DefaultFloatToBytesMapping(float input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultFloatToBytesMapping(input);
+      public byte[] DefaultFloatToBytesMapping(float input) throws Exception {
+        return _ragconnect__apply__DefaultFloatToBytesMapping(input);
       }
-      public static byte[] DefaultDoubleToBytesMapping(double input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultDoubleToBytesMapping(input);
+      public byte[] DefaultDoubleToBytesMapping(double input) throws Exception {
+        return _ragconnect__apply__DefaultDoubleToBytesMapping(input);
       }
-      public static byte[] DefaultCharToBytesMapping(char input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultCharToBytesMapping(input);
+      public byte[] DefaultCharToBytesMapping(char input) throws Exception {
+        return _ragconnect__apply__DefaultCharToBytesMapping(input);
       }
-      public static byte[] DefaultStringToBytesMapping(String input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultStringToBytesMapping(input);
+      public byte[] DefaultStringToBytesMapping(String input) throws Exception {
+        return _ragconnect__apply__DefaultStringToBytesMapping(input);
       }
     }
     @FunctionalInterface
@@ -381,9 +431,12 @@ public class TestUtils {
       void accept(JsonGenerator g, String fieldName) throws E;
     }
 
+    static ReadNode readNode = new ReadNode();
+    static WriteNode writeNode = new WriteNode();
+
     public static boolean BytesToBool(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToBooleanMapping(input);
+        return readNode.DefaultBytesToBooleanMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return false;
@@ -391,7 +444,7 @@ public class TestUtils {
     }
     public static int BytesToInt(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToIntMapping(input);
+        return readNode.DefaultBytesToIntMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -399,7 +452,7 @@ public class TestUtils {
     }
     public static short BytesToShort(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToShortMapping(input);
+        return readNode.DefaultBytesToShortMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -407,7 +460,7 @@ public class TestUtils {
     }
     public static long BytesToLong(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToLongMapping(input);
+        return readNode.DefaultBytesToLongMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -415,7 +468,7 @@ public class TestUtils {
     }
     public static float BytesToFloat(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToFloatMapping(input);
+        return readNode.DefaultBytesToFloatMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -423,7 +476,7 @@ public class TestUtils {
     }
     public static double BytesToDouble(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToDoubleMapping(input);
+        return readNode.DefaultBytesToDoubleMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -431,7 +484,7 @@ public class TestUtils {
     }
     public static char BytesToChar(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToCharMapping(input);
+        return readNode.DefaultBytesToCharMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -439,7 +492,7 @@ public class TestUtils {
     }
     public static String BytesToString(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToStringMapping(input);
+        return readNode.DefaultBytesToStringMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -447,7 +500,7 @@ public class TestUtils {
     }
     public static byte[] BoolToBytes(boolean input) {
       try {
-        return WriteNode.DefaultBooleanToBytesMapping(input);
+        return writeNode.DefaultBooleanToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -455,7 +508,7 @@ public class TestUtils {
     }
     public static byte[] IntToBytes(int input) {
       try {
-        return WriteNode.DefaultIntToBytesMapping(input);
+        return writeNode.DefaultIntToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -463,7 +516,7 @@ public class TestUtils {
     }
     public static byte[] ShortToBytes(short input) {
       try {
-        return WriteNode.DefaultShortToBytesMapping(input);
+        return writeNode.DefaultShortToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -471,7 +524,7 @@ public class TestUtils {
     }
     public static byte[] LongToBytes(long input) {
       try {
-        return WriteNode.DefaultLongToBytesMapping(input);
+        return writeNode.DefaultLongToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -479,7 +532,7 @@ public class TestUtils {
     }
     public static byte[] FloatToBytes(float input) {
       try {
-        return WriteNode.DefaultFloatToBytesMapping(input);
+        return writeNode.DefaultFloatToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -487,7 +540,7 @@ public class TestUtils {
     }
     public static byte[] DoubleToBytes(double input) {
       try {
-        return WriteNode.DefaultDoubleToBytesMapping(input);
+        return writeNode.DefaultDoubleToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -495,7 +548,7 @@ public class TestUtils {
     }
     public static byte[] CharToBytes(char input) {
       try {
-        return WriteNode.DefaultCharToBytesMapping(input);
+        return writeNode.DefaultCharToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -503,7 +556,7 @@ public class TestUtils {
     }
     public static byte[] StringToBytes(String input) {
       try {
-        return WriteNode.DefaultStringToBytesMapping(input);
+        return writeNode.DefaultStringToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java
index b0840d6d2d6d35ed50f759d61198b2d25586d800..121843c724d075318e483aa0ac072a53c64998e0 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java
@@ -21,9 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
-public class Warnings {
+public class Warnings extends RagConnectTest {
 
-  private static final Logger logger = LoggerFactory.getLogger(Warnings.class);
   private static final String WARNING_DIRECTORY = "warnings/";
   private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + WARNING_DIRECTORY;
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
index a88f97fefabd47159f91d268a96ffe67a3dcee7e..4ac0f55dc829a628c63c79409e69cf97f60985cf 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
@@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.*;
  * @author rschoene - Initial contribution
  */
 @Tag("Incremental")
-@Tag("New")
 public class RelationTest extends AbstractMqttTest {
 
   private static final String TOPIC_WILDCARD = "rel/#";
@@ -617,6 +616,7 @@ public class RelationTest extends AbstractMqttTest {
     biB(3).getInner().setInnerValue("inner-bi-b3");
     checker.check();
 
+    logger.debug(model.ragconnectEvaluationCounterSummary());
   }
 
   private void assertNullOrA(String expectedValue, A actual, String alias) {
diff --git a/ragconnect.tests/src/test/resources/log4j2.xml b/ragconnect.tests/src/test/resources/log4j2.xml
index 653d6c357cef6a51c43a2fe9e54d4a3e86148abf..9a1b22ee0120043991dfcfc7ad9c41f6dd5b4f06 100644
--- a/ragconnect.tests/src/test/resources/log4j2.xml
+++ b/ragconnect.tests/src/test/resources/log4j2.xml
@@ -4,6 +4,9 @@
         <Console name="Console" target="SYSTEM_OUT">
             <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level [%t] %logger{20} - %msg%n}" disableAnsi="false"/>
         </Console>
+        <File name="TestLogs" fileName="test.log" append="false">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level [%t] %logger{20} - %msg%n"/>
+        </File>
     </Appenders>
     <Loggers>
         <Root level="debug">