diff --git a/.gitignore b/.gitignore
index dfd7c3e83132deec18e16cf9be84cb2171cf75a7..5e142013c2bdbc64d4fe3558748328a282bbfc7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -177,3 +177,7 @@ local.properties
 output/
 doc/
 [lL]ocal[tT]est*
+
+# ANTLR
+
+gen/
diff --git a/build.sbt b/build.sbt
index 1d20ffe58cde25af7a5622357d72de6d3daf9b7b..408f0db69bdce446a1776a7ddb5393f33fc34239 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,3 +1,4 @@
+import com.simplytyped.Antlr4Plugin.autoImport.antlr4PackageName
 import sbt.Keys.{libraryDependencies, scalacOptions, version}
 
 val emfcommonVersion = "2.12.0"
@@ -40,5 +41,12 @@ lazy val generator = (project in file("."))
       case x =>
         val oldStrategy = (assemblyMergeStrategy in assembly).value
         oldStrategy(x)
-    }
-  ).dependsOn(syncProvider, rsumProvider)
+    },
+  ).dependsOn(
+    syncProvider,
+    rsumProvider
+  ).enablePlugins(Antlr4Plugin)
+
+antlr4PackageName in Antlr4 := Some("org.rosi_project.model_sync.model_join.representation.parser.antlr.generated")
+antlr4GenVisitor in Antlr4 := true
+antlr4GenListener in Antlr4 := false
diff --git a/lib/ModelSyncProvider b/lib/ModelSyncProvider
index b198d4633ab67b708a81e399be95596a5b4a0b6c..619bbf1efcfee406a134412275928322d9d28e05 160000
--- a/lib/ModelSyncProvider
+++ b/lib/ModelSyncProvider
@@ -1 +1 @@
-Subproject commit b198d4633ab67b708a81e399be95596a5b4a0b6c
+Subproject commit 619bbf1efcfee406a134412275928322d9d28e05
diff --git a/project/build.properties b/project/build.properties
index 8db5ca22266ea113c2b39c92dddb73c83e61ffee..cabf73b45107a5feb964e2c564c9687faafffdbe 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version = 1.2.1
\ No newline at end of file
+sbt.version = 1.2.7
diff --git a/project/sbt-antlr4.sbt b/project/sbt-antlr4.sbt
new file mode 100644
index 0000000000000000000000000000000000000000..7b26d1fc7b9ee12df151e57cb9e2528c1a4f8d9f
--- /dev/null
+++ b/project/sbt-antlr4.sbt
@@ -0,0 +1,2 @@
+
+addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.2")
diff --git a/src/main/antlr4/ModelJoin.g4 b/src/main/antlr4/ModelJoin.g4
new file mode 100644
index 0000000000000000000000000000000000000000..e5ceb0db71cdbe18fb99fc044b2b95dac13b20f7
--- /dev/null
+++ b/src/main/antlr4/ModelJoin.g4
@@ -0,0 +1,105 @@
+/*
+ * ModelJoin.g4
+ *
+ * Defines the structure of our ModelJoin grammar
+ *
+ * Author: Rico Bergmann
+ */
+grammar ModelJoin;
+
+/*
+ * Parser
+ */
+modeljoin : join+ EOF ;
+join : (naturaljoin | thetajoin | outerjoin) AS classres
+  ( OPENCURLYBRAKET
+    keepattributesexpr*
+    keepaggregatesexpr*
+    keepexpr*
+  CLOSEDCURLYBRAKET )? ;
+naturaljoin : NATURAL JOIN classres WITH classres ;
+thetajoin : THETA JOIN classres WITH classres WHERE oclcond;
+outerjoin : (leftouterjoin | rightouterjoin) OUTER JOIN classres WITH classres;
+leftouterjoin : LEFT ;
+rightouterjoin : RIGHT ;
+
+keepattributesexpr : KEEP ATTRIBUTES attrres (COMMA attrres)* ;
+keepaggregatesexpr : KEEP AGGREGATE aggrtype
+                      OPENBRAKET relattr CLOSEDBRAKET
+                      OVER classres AS attrres;
+keepexpr : (keeptypeexpr | keepoutgoingexpr | keepincomingexpr)
+  ( OPENCURLYBRAKET
+    keepattributesexpr*
+    keepaggregatesexpr*
+    keepexpr*
+  CLOSEDCURLYBRAKET)? ;
+keeptypeexpr : keepsupertypeexpr | keepsubtypeexpr ;
+keepsupertypeexpr : KEEP SUPERTYPE classres ( AS TYPE classres )? ;
+keepsubtypeexpr : KEEP SUBTYPE classres ( AS TYPE classres )? ;
+keepoutgoingexpr : KEEP OUTGOING attrres ( AS TYPE classres )? ;
+keepincomingexpr : KEEP INCOMING attrres (AS TYPE classres )? ;
+
+classres : WORD (DOT WORD)* ;
+attrres : WORD (DOT WORD)+ ;
+relattr : WORD ;
+oclcond : (WORD | NUMBER | specialchar | WHITESPACE | NEWLINE)+;
+aggrtype : SUM | AVG | MIN | MAX | SIZE ;
+
+specialchar : DOT
+              | OPENBRAKET
+              | CLOSEDBRAKET
+              | OPENCURLYBRAKET
+              | CLOSEDCURLYBRAKET
+              | COMMA
+              | UNDERSCORE
+              | SPECIALCHAR ;
+
+/*
+ * Lexer
+ */
+
+fragment LOWERCASE  : [a-z]                 ;
+fragment UPPERCASE  : [A-Z]                 ;
+fragment ANYCASE    : LOWERCASE | UPPERCASE ;
+fragment DIGIT      : [0-9]                 ;
+
+OPENBRAKET        : '('         ;
+CLOSEDBRAKET      : ')'         ;
+OPENCURLYBRAKET   : '{'         ;
+CLOSEDCURLYBRAKET : '}'         ;
+DOT               : '.'         ;
+COMMA             : ','         ;
+UNDERSCORE        : '_'         ;
+SPECIALCHAR       : [-><!="'] ;
+
+JOIN    : 'join'    ;
+NATURAL : 'natural' ;
+THETA   : 'theta'   ;
+WHERE   : 'where'   ;
+OUTER   : 'outer'   ;
+RIGHT   : 'right'   ;
+LEFT    : 'left'    ;
+WITH    : 'with'    ;
+AS      : 'as'      ;
+
+KEEP        : 'keep'        ;
+ATTRIBUTES  : 'attributes'  ;
+AGGREGATE   : 'aggregate'   ;
+SUPERTYPE   : 'supertype'   ;
+SUBTYPE     : 'subtype'     ;
+OUTGOING    : 'outgoing'    ;
+INCOMING    : 'incoming'    ;
+TYPE        : 'type'        ;
+OVER        : 'over'        ;
+
+SUM   : 'sum'   ;
+AVG   : 'avg'   ;
+MIN   : 'min'   ;
+MAX   : 'max'   ;
+SIZE  : 'size'  ;
+
+WORD    : ANYCASE (ANYCASE | DIGIT | UNDERSCORE)* ;
+NUMBER  : [+-]? DIGIT+ DOT? DIGIT* ;
+
+WHITESPACE  : ' ' -> skip                   ;
+NEWLINE     : ('\r'? '\n' | '\r')+ -> skip  ;
diff --git a/src/main/doc b/src/main/doc
index 89e2af4e060ae7c39c261fde55ba9140caf85853..83a9646a8d0f69d76443d38208c9df140790a0f7 160000
--- a/src/main/doc
+++ b/src/main/doc
@@ -1 +1 @@
-Subproject commit 89e2af4e060ae7c39c261fde55ba9140caf85853
+Subproject commit 83a9646a8d0f69d76443d38208c9df140790a0f7
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java
index 91253129c1f74bd7753e63008dd42d5ad38fd1ef..6f8afef2b4ba2623225ac2f1aed8ed72a6d838f5 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/core/OCLConstraint.java
@@ -1,6 +1,5 @@
 package org.rosi_project.model_sync.model_join.representation.core;
 
-import java.util.Objects;
 import javax.annotation.Nonnull;
 
 /**
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java
index 97f4169e44b24b72f20cd68ff3b9b08d1b4186a6..956a42704e52a9fdc0d34b091075d3f61a88db7a 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/JoinExpression.java
@@ -22,6 +22,8 @@ import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
  *
  * @author Rico Bergmann
  * @see NaturalJoinExpression
+ * @see OuterJoinExpression
+ * @see ThetaJoinExpression
  */
 public abstract class JoinExpression implements Iterable<KeepExpression> {
 
@@ -32,6 +34,8 @@ public abstract class JoinExpression implements Iterable<KeepExpression> {
 
     /**
      * The {@code natural join} combines two classes based on attributes with equal name and type.
+     *
+     * @see NaturalJoinExpression
      */
     NATURAL,
 
@@ -39,12 +43,16 @@ public abstract class JoinExpression implements Iterable<KeepExpression> {
      * The {@code outer join} works like the {@link #NATURAL} one, but leaves instances from one
      * class with no corresponding instance in the other class according to the {@code outer join
      * type}. See the subclass for details.
+     *
+     * @see OuterJoinExpression
      */
     OUTER,
 
     /**
      * The {@code theta join} is more general than the {@link #NATURAL} and {@link #OUTER} one as
      * it enables an arbitrary criteria to define whether two instances are "joinable" or not.
+     *
+     * @see ThetaJoinExpression
      */
     THETA
   }
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java
index ccef41ebee66c2853f84328c88d3b35e56ab62ea..d0da5aa0b03a4d87b7cacf86d1d7c8442e577eb1 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAggregateExpression.java
@@ -225,6 +225,11 @@ public class KeepAggregateExpression extends KeepExpression {
     return target;
   }
 
+  @Override
+  public void accept(@Nonnull KeepExpressionVisitor visitor) {
+    visitor.visit(this);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java
index 507822727f4401a0416f15b1b6b80df327ad8630..99cdb8109e16e584442425b0eac0aceefbe49337 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepAttributesExpression.java
@@ -49,6 +49,11 @@ public class KeepAttributesExpression extends KeepExpression {
     return new ArrayList<>(attributes);
   }
 
+  @Override
+  public void accept(@Nonnull KeepExpressionVisitor visitor) {
+    visitor.visit(this);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java
index e028f412e492434e7e7d32667c20af4e480be394..8dbafbae1a62504772bb0968772a2f05457222cb 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepCalculatedExpression.java
@@ -94,6 +94,11 @@ public class KeepCalculatedExpression extends KeepExpression {
     return target;
   }
 
+  @Override
+  public void accept(@Nonnull KeepExpressionVisitor visitor) {
+    visitor.visit(this);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java
index 8fc8846d5b7fef54ca5f67b2b091d7bf3a00d251..bd879001f642b3dc1a5beef04e196f86534e97ab 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepExpression.java
@@ -1,5 +1,7 @@
 package org.rosi_project.model_sync.model_join.representation.grammar;
 
+import javax.annotation.Nonnull;
+
 /**
  * A {@code KeepExpression} configures the resulting model of a join.
  * <p>
@@ -10,6 +12,49 @@ package org.rosi_project.model_sync.model_join.representation.grammar;
  */
 public abstract class KeepExpression {
 
+  /**
+   * The {@code visitor} enables the execution of arbitrary algorithms on {@code KeepExpression}s
+   * without taking care of the actual traversal of the expression tree.
+   */
+  public interface KeepExpressionVisitor {
+
+    /**
+     * Runs the algorithm as appropriate for {@link KeepAggregateExpression}s.
+     */
+    void visit(@Nonnull KeepAggregateExpression expression);
+
+    /**
+     * Runs the algorithm as appropriate for {@link KeepAttributesExpression}s.
+     */
+    void visit(@Nonnull KeepAttributesExpression expression);
+
+    /**
+     * Runs the algorithm as appropriate for {@link KeepCalculatedExpression}s.
+     */
+    void visit(@Nonnull KeepCalculatedExpression expression);
+
+    /**
+     * Runs the algorithm as appropriate for {@link KeepReferenceExpression}s.
+     */
+    void visit(@Nonnull KeepReferenceExpression expression);
+
+    /**
+     * Runs the algorithm as appropriate for {@link KeepSubTypeExpression}s.
+     */
+    void visit(@Nonnull KeepSubTypeExpression expression);
+
+    /**
+     * Runs the algorithm as appropriate for {@link KeepSuperTypeExpression}s.
+     */
+    void visit(@Nonnull KeepSuperTypeExpression expression);
+
+  }
+
   // TODO `as` statements may be omitted !?!? ... (╯°□°)╯︵ ┻━┻
 
+  /**
+   * Applies a {@code visitor} to {@code this} expression structure.
+   */
+  public abstract void accept(@Nonnull KeepExpressionVisitor visitor);
+
 }
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java
index 2a33e1a4f3dc061d9b1e4143e1fa1328466c49a4..b111ca3e3bead3cc6299424bd930c31a74dc841b 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepReferenceExpression.java
@@ -5,25 +5,126 @@ import java.util.List;
 import java.util.Objects;
 import javax.annotation.Nonnull;
 import org.rosi_project.model_sync.model_join.representation.core.AttributePath;
+import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
+import org.rosi_project.model_sync.model_join.representation.util.Assert;
 
 // TODO the Xtext grammar also specifies an optional `as reference` part - what does that part do?
+// most likely `as reference` will refer to another type that was already defined in its dedicated
+// join statement.
 
 /**
- * A {@code KeepReferenceExpression} retains links to other instances in the
- * original models. All objects that are referenced in the base models will also
- * be available in the resulting ModelJoin views.
+ * A {@code KeepReferenceExpression} retains links to other instances in the original models. All
+ * objects that are referenced in the base models will also be available in the resulting ModelJoin
+ * views.
  * <p>
- * TODO complete doc
+ * As references are directional, each expression also distinguishes between {@code outgoing} and
+ * {@code incoming} references - depending on whether the model class of {@code this} expression
+ * owns the reference ({@code outgoing}) or instances of this model class are being referenced
+ * ({@code incoming}).
+ * <p>
+ * The referenced instances will be made available according to a number of keep statements which
+ * specify the attributes to include. This boils down to the creation of a nested ModelJoin view for
+ * these instances.
  *
  * @author Rico Bergmann
  */
 public class KeepReferenceExpression extends KeepExpression {
 
+  /**
+   * The {@code ReferenceDirection} defines whether a reference is owned by the containing model
+   * class or whether it is the class being referenced.
+   */
   public enum ReferenceDirection {
+
+    /**
+     * Indicates that the containing model class owns the reference.
+     */
     OUTGOING,
+
+    /**
+     * Indicates that the containing model class is referenced by some other class.
+     */
     INCOMING
   }
 
+  /**
+   * The {@code KeepReferenceBuilder} enables the construction of new {@code
+   * KeepReferenceExpression} instances through a nice and fluent interface.
+   *
+   * @author Rico Bergmann
+   */
+  public static class KeepReferenceBuilder {
+
+    private ReferenceDirection referenceDirection;
+    private AttributePath attribute;
+    private ClassResource target;
+
+    @Nonnull
+    private List<KeepExpression> keeps;
+
+    /**
+     * Default constructor.
+     */
+    private KeepReferenceBuilder() {
+      this.keeps = new ArrayList<>();
+    }
+
+    /**
+     * Creates an outgoing reference for the given attribute.
+     */
+    @Nonnull
+    public KeepReferenceBuilder outgoing(@Nonnull AttributePath attribute) {
+      this.referenceDirection = ReferenceDirection.OUTGOING;
+      this.attribute = attribute;
+      return this;
+    }
+
+    /**
+     * Creates an incoming reference for the given attribute.
+     */
+    @Nonnull
+    public KeepReferenceBuilder incoming(@Nonnull AttributePath attribute) {
+      this.referenceDirection = ReferenceDirection.INCOMING;
+      this.attribute = attribute;
+      return this;
+    }
+
+    /**
+     * Specifies the name of the attribute in the target join.
+     */
+    @Nonnull
+    public KeepReferenceBuilder as(@Nonnull ClassResource target) {
+      this.target = target;
+      return this;
+    }
+
+    /**
+     * Adds an attribute to the view for the referenced instances.
+     */
+    @Nonnull
+    public KeepReferenceBuilder keep(@Nonnull KeepExpression keepExpression) {
+      this.keeps.add(keepExpression);
+      return this;
+    }
+
+    /**
+     * Finishes the construction process.
+     */
+    @Nonnull
+    public KeepReferenceExpression buildExpression() {
+      Assert.noNullArguments("All components must be specified", attribute, referenceDirection, target, keeps);
+      return new KeepReferenceExpression(attribute, referenceDirection, target, keeps);
+    }
+  }
+
+  /**
+   * Starts the creation process for a new {@code KeepReferenceExpression}.
+   */
+  @Nonnull
+  public static KeepReferenceBuilder keep() {
+    return new KeepReferenceBuilder();
+  }
+
   @Nonnull
   private final AttributePath attribute;
 
@@ -31,15 +132,24 @@ public class KeepReferenceExpression extends KeepExpression {
   private final ReferenceDirection referenceDirection;
 
   @Nonnull
-  private final AttributePath target;
+  private final ClassResource target;
 
   @Nonnull
   private final List<KeepExpression> keeps;
 
+  /**
+   * Full constructor.
+   *
+   * @param attribute the attribute of the source model which contains the references
+   * @param referenceDirection whether the source model owns the reference or is being
+   *     referenced
+   * @param target the name of the class which enca
+   * @param keeps the statements which should be build the "nested" view for the references
+   */
   public KeepReferenceExpression(
       @Nonnull AttributePath attribute,
       @Nonnull ReferenceDirection referenceDirection,
-      @Nonnull AttributePath target,
+      @Nonnull ClassResource target,
       @Nonnull List<KeepExpression> keeps) {
     this.attribute = attribute;
     this.referenceDirection = referenceDirection;
@@ -47,26 +157,46 @@ public class KeepReferenceExpression extends KeepExpression {
     this.keeps = keeps;
   }
 
+  /**
+   * Provides the attribute of the source model which contains the references.
+   */
   @Nonnull
   public AttributePath getAttribute() {
     return attribute;
   }
 
+  /**
+   * Provides the kind of reference, that is whether the containing Join owns the reference or is
+   * being referenced by another model class or Join.
+   */
   @Nonnull
   public ReferenceDirection getReferenceDirection() {
     return referenceDirection;
   }
 
+  /**
+   * Provides the name of the attribute under which the referenced instances should be made
+   * available.
+   */
   @Nonnull
-  public AttributePath getTarget() {
+  public ClassResource getTarget() {
     return target;
   }
 
+  /**
+   * Provides all {@code KeepExpression keep expressions} that should be used to build the Join for
+   * the referenced instances.
+   */
   @Nonnull
   public List<KeepExpression> getKeeps() {
     return new ArrayList<>(keeps);
   }
 
+  @Override
+  public void accept(@Nonnull KeepExpressionVisitor visitor) {
+    visitor.visit(this);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -76,8 +206,7 @@ public class KeepReferenceExpression extends KeepExpression {
       return false;
     }
     KeepReferenceExpression that = (KeepReferenceExpression) o;
-    return attribute.equals(that.attribute) &&
-        target.equals(that.target);
+    return attribute.equals(that.attribute) && target.equals(that.target);
   }
 
   @Override
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java
index 299ffa50377f322e0cfa8eb6e270f9ea961a052d..fd732649f7fdb5337a49007a18edea44d04f3947 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSubTypeExpression.java
@@ -1,47 +1,144 @@
 package org.rosi_project.model_sync.model_join.representation.grammar;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import javax.annotation.Nonnull;
-import org.rosi_project.model_sync.model_join.representation.core.AttributePath;
 import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
+import org.rosi_project.model_sync.model_join.representation.util.Assert;
 
 /**
+ * A {@code KeepSubTypeExpression} instructs the {@code ModelJoin} runtime to instantiate the most
+ * specific type that inherits from this type as well. The additional instance will be built
+ * according to the {@code KeepExpression keep statements} that it is constructed of.
+ * <p>
+ * In order to prevent ambiguities a {@code KeepSubTypeExpression} may only be specified on joins
+ * that do not participate in another {@link JoinExpression join statement}.
+ *
  * @author Rico Bergmann
  */
 public class KeepSubTypeExpression extends KeepExpression {
 
+  /**
+   * The {@code KeepSubTypeBuilder} enables the construction of new {@code KeepSubTypeExpression}
+   * instances through a nice and fluent interface.
+   *
+   * @author Rico Bergmann
+   */
+  public static class KeepSubTypeBuilder {
+
+    private ClassResource typeToKeep;
+    private ClassResource target;
+    private List<KeepExpression> keeps;
+
+    /**
+     * Default constructor.
+     */
+    private KeepSubTypeBuilder() {
+      this.keeps = new ArrayList<>();
+    }
+
+    /**
+     * Specifies the subtype that should be instantiated.
+     */
+    @Nonnull
+    public KeepSubTypeBuilder subtype(@Nonnull ClassResource type) {
+      this.typeToKeep = type;
+      return this;
+    }
+
+    /**
+     * Specifies the name of the class that should be generated for the subtype instances.
+     */
+    @Nonnull
+    public KeepSubTypeBuilder as(@Nonnull ClassResource target) {
+      this.target = target;
+      return this;
+    }
+
+    /**
+     * Adds a {@link KeepExpression keep statement} for the subtype-class. The described attribute
+     * will be initialized for each instantiated subtype element.
+     */
+    @Nonnull
+    public KeepSubTypeBuilder keep(@Nonnull KeepExpression keep) {
+      this.keeps.add(keep);
+      return this;
+    }
+
+    /**
+     * Finishes the construction process.
+     */
+    @Nonnull
+    public KeepSubTypeExpression buildExpression() {
+      Assert.noNullArguments("All components must be specified", typeToKeep, target, keeps);
+      return new KeepSubTypeExpression(typeToKeep, target, keeps);
+    }
+
+  }
+
+  /**
+   * Starts the creation process for a new {@code KeepSubTypeExpression}.
+   */
+  @Nonnull
+  public static KeepSubTypeBuilder keep() {
+    return new KeepSubTypeBuilder();
+  }
+
   @Nonnull
   private final ClassResource typeToKeep;
 
   @Nonnull
-  private final AttributePath target;
+  private final ClassResource target;
 
   @Nonnull
   private final List<KeepExpression> keeps;
 
+  /**
+   * Full constructor.
+   *
+   * @param typeToKeep the subtype that should be instantiated.
+   * @param target the name of the view that should be generated for all matching instances
+   * @param keeps the keep statements that should form the attributes of the generated view
+   *     instances
+   */
   public KeepSubTypeExpression(
       @Nonnull ClassResource typeToKeep,
-      @Nonnull AttributePath target,
+      @Nonnull ClassResource target,
       @Nonnull List<KeepExpression> keeps) {
     this.typeToKeep = typeToKeep;
     this.target = target;
     this.keeps = keeps;
   }
 
+  /**
+   * Provides the subtype that should be instantiated.
+   */
   @Nonnull
   public ClassResource getType() {
     return typeToKeep;
   }
 
+  /**
+   * Provides the name of the view that should be generated for all matching instances.
+   */
   @Nonnull
-  public AttributePath getTarget() {
+  public ClassResource getTarget() {
     return target;
   }
 
+  /**
+   * Provides all {@code KeepExpression keep expressions} that should be used to build the Join for
+   * the instances of the subclass instances.
+   */
   @Nonnull
   public List<KeepExpression> getKeeps() {
-    return keeps;
+    return new ArrayList<>(keeps);
+  }
+
+  @Override
+  public void accept(@Nonnull KeepExpressionVisitor visitor) {
+    visitor.visit(this);
   }
 
   @Override
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java
index c5801712ed50d2f42dae7f923904ec88e2859e54..703adefd8e24e401f39d7d68c20fa8d1449150ab 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/grammar/KeepSuperTypeExpression.java
@@ -1,48 +1,143 @@
 package org.rosi_project.model_sync.model_join.representation.grammar;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import javax.annotation.Nonnull;
-import org.rosi_project.model_sync.model_join.representation.core.AttributePath;
 import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
+import org.rosi_project.model_sync.model_join.representation.util.Assert;
+
+// TODO add doc
 
 /**
+ * A {@code KeepSuperTypeExpression} instructs the {@code ModelJoin} runtime to instantiate the a
+ * supertype of some class in a join along an instance of that class. The additional instance will
+ * be built according to the {@code KeepExpression keep statements} that it is constructed of.
  *
  * @author Rico Bergmann
  */
 public class KeepSuperTypeExpression extends KeepExpression {
 
+  /**
+   * The {@code KeepSuperTypeBuilder} enables the construction of new {@code KeepSuperTypeExpression}
+   * instances through a nice and fluent interface.
+   *
+   * @author Rico Bergmann
+   */
+  public static class KeepSuperTypeBuilder {
+
+    private ClassResource typeToKeep;
+    private ClassResource target;
+    private List<KeepExpression> keeps;
+
+    /**
+     * Default constructor.
+     */
+    private KeepSuperTypeBuilder() {
+      this.keeps = new ArrayList<>();
+    }
+
+    /**
+     * Specifies the supertype that should be instantiated.
+     */
+    @Nonnull
+    public KeepSuperTypeBuilder supertype(@Nonnull ClassResource type) {
+      this.typeToKeep = type;
+      return this;
+    }
+
+    /**
+     * Specifies the name of the class that should be generated for the subtype instances.
+     */
+    @Nonnull
+    public KeepSuperTypeBuilder as(@Nonnull ClassResource target) {
+      this.target = target;
+      return this;
+    }
+
+    /**
+     * Adds a {@link KeepExpression keep statement} for the subtype-class. The described attribute
+     * will be initialized for each instantiated subtype element.
+     */
+    @Nonnull
+    public KeepSuperTypeBuilder keep(@Nonnull KeepExpression keep) {
+      this.keeps.add(keep);
+      return this;
+    }
+
+    /**
+     * Finishes the construction process.
+     */
+    @Nonnull
+    public KeepSuperTypeExpression buildExpression() {
+      Assert.noNullArguments("All components must be specified", typeToKeep, target, keeps);
+      return new KeepSuperTypeExpression(typeToKeep, target, keeps);
+    }
+
+  }
+
+  /**
+   * Starts the creation process for a new {@code KeepSuperTypeExpression}.
+   */
+  @Nonnull
+  public static KeepSuperTypeBuilder keep() {
+    return new KeepSuperTypeBuilder();
+  }
+
   @Nonnull
   private final ClassResource typeToKeep;
 
   @Nonnull
-  private final AttributePath target;
+  private final ClassResource target;
 
   @Nonnull
   private final List<KeepExpression> keeps;
 
+  /**
+   * Full constructor.
+   *
+   * @param typeToKeep the supertype that should be instantiated.
+   * @param target the name of the view that should be generated for all matching instances
+   * @param keeps the keep statements that should form the attributes of the generated view
+   *     instances
+   */
   public KeepSuperTypeExpression(
       @Nonnull ClassResource typeToKeep,
-      @Nonnull AttributePath target,
+      @Nonnull ClassResource target,
       @Nonnull List<KeepExpression> keeps) {
     this.typeToKeep = typeToKeep;
     this.target = target;
     this.keeps = keeps;
   }
 
+  /**
+   * Provides the supertype that should be instantiated.
+   */
   @Nonnull
   public ClassResource getType() {
     return typeToKeep;
   }
 
+  /**
+   * Provides the name of the view that should be generated for all matching instances.
+   */
   @Nonnull
-  public AttributePath getTarget() {
+  public ClassResource getTarget() {
     return target;
   }
 
+  /**
+   * Provides all {@code KeepExpression keep expressions} that should be used to build the Join for
+   * the instances of the superclass instances.
+   */
   @Nonnull
   public List<KeepExpression> getKeeps() {
-    return keeps;
+    return new ArrayList<>(keeps);
+  }
+
+  @Override
+  public void accept(@Nonnull KeepExpressionVisitor visitor) {
+    visitor.visit(this);
   }
 
   @Override
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java
index 93d6bf8036102f80382c7a2dbcebe2f9d6b3e701..87c6f0cc6383feb4e067c767e08bd12c303d308e 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParser.java
@@ -1,33 +1,24 @@
 package org.rosi_project.model_sync.model_join.representation.parser;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.util.Optional;
-import java.util.regex.Pattern;
 import javax.annotation.Nonnull;
 import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
-import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder;
 
 /**
- * The {@code ModelJoinParser} reads a {@code ModelJoin} file from disk and creates matching
- * representation for it.
+ * The {@code ModelJoinParser} reads a {@code ModelJoin} file from disk and creates a matching
+ * logical representation for it.
  *
  * @author Rico Bergmann
  */
-public class ModelJoinParser {
+public interface ModelJoinParser {
 
   /**
    * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}, wrapped into an {@code
    * Optional}. If parsing fails, an empty {@code Optional} will be returned.
    */
   @Nonnull
-  public static Optional<ModelJoinExpression> read(@Nonnull File modelFile) {
-    ModelJoinParser parser = new ModelJoinParser(modelFile, ErrorReportingStrategy.OPTIONAL);
-    return parser.run();
-  }
+  Optional<ModelJoinExpression> read(@Nonnull File modelFile);
 
   /**
    * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}. If parsing fails, a
@@ -36,149 +27,6 @@ public class ModelJoinParser {
    * @throws ModelJoinParsingException if the model may not be parsed for some reason.
    */
   @Nonnull
-  public static ModelJoinExpression readOrThrow(@Nonnull File modelFile) {
-    ModelJoinParser parser = new ModelJoinParser(modelFile, ErrorReportingStrategy.EXCEPTION);
-    Optional<ModelJoinExpression> result = parser.run();
+  ModelJoinExpression readOrThrow(@Nonnull File modelFile);
 
-    if (result.isPresent()) {
-      return result.get();
-    } else {
-      /*
-       * Theoretically, the parser should throw the exception by itself, so this code should never
-       * actually run. It is merely here for safety purposes (unchecked access of Optional.get())
-       */
-      throw new ModelJoinParsingException("Result not present but no exception was thrown either");
-    }
-  }
-
-  /**
-   * Indicates what should happen if parsing fails.
-   */
-  enum ErrorReportingStrategy {
-
-    /**
-     * On failure an empty {@code Optional} should be returned.
-     */
-    OPTIONAL,
-
-    /**
-     * On failure an {@link ModelJoinParsingException} should be thrown.
-     */
-    EXCEPTION
-  }
-
-  private static final Pattern EMPTY_LINE = Pattern.compile("^\\s*$");
-  private static final Pattern IMPORT_STATEMENT = Pattern.compile("^import .*");
-  private static final Pattern TARGET_STATEMENT = Pattern.compile("^target .*");
-  private static final Pattern JOIN_STATEMENT = Pattern
-      .compile("^(((left|right) outer)|theta|natural) join .*");
-
-  @Nonnull
-  private final File modelFile;
-
-  @Nonnull
-  private final ErrorReportingStrategy errorReportingStrategy;
-
-  /**
-   * Full constructor.
-   *
-   * @param modelFile the file to parse
-   * @param errorReportingStrategy what to do in case an error occurs.
-   */
-  private ModelJoinParser(@Nonnull File modelFile,
-      @Nonnull ErrorReportingStrategy errorReportingStrategy) {
-    this.modelFile = modelFile;
-    this.errorReportingStrategy = errorReportingStrategy;
-  }
-
-  /**
-   * Creates the {@code ModelJoin} representation from the {@code modelFile}.
-   * <p>
-   * Depending on the selected {@code errorReportingStrategy} either an Exception will be thrown, or
-   * an empty {@code Optional} will be returned if something goes wrong.
-   */
-  private Optional<ModelJoinExpression> run() {
-    ModelJoinExpression resultingModel;
-    try (FileInputStream modelInputStream = new FileInputStream(modelFile);
-        InputStreamReader modelInputReader = new InputStreamReader(modelInputStream);
-        BufferedReader bufferedModelReader = new BufferedReader(modelInputReader)) {
-
-      ModelJoinBuilder modelJoinBuilder = ModelJoinBuilder.createNewModelJoin();
-      readModelFileAndPopulateBuilder(bufferedModelReader, modelJoinBuilder);
-      resultingModel = modelJoinBuilder.build();
-
-    } catch (ModelJoinParsingException e) {
-      switch (errorReportingStrategy) {
-        case OPTIONAL:
-          return Optional.empty();
-        case EXCEPTION:
-          throw e;
-        default:
-          throw new AssertionError(errorReportingStrategy);
-      }
-    } catch (IOException e) {
-      switch (errorReportingStrategy) {
-        case OPTIONAL:
-          return Optional.empty();
-        case EXCEPTION:
-          throw new ModelJoinParsingException(e);
-        default:
-          throw new AssertionError(errorReportingStrategy);
-      }
-    }
-    return Optional.of(resultingModel);
-  }
-
-  /**
-   * Reads the {@code modelFile} and constructs the corresponding {@code ModelJoin} on the fly.
-   * <p>
-   * On error, an {@code IOException} or an {@link ModelJoinParsingException} will be thrown.
-   *
-   * @throws ModelJoinParsingException if the model file's content are malformed or another
-   *     component failed
-   * @throws IOException if the model file might not be read any further
-   */
-  private void readModelFileAndPopulateBuilder(BufferedReader modelReader,
-      ModelJoinBuilder modelBuilder) throws IOException {
-    String currentLine;
-    while ((currentLine = modelReader.readLine()) != null) {
-      if (lineShouldBeSkipped(currentLine)) {
-        // the line should be skipped. So we do nothing.
-        continue;
-      } else if (lineStartsJoinDeclaration(currentLine)) {
-        // a new join is being declared. Delegate to the join parser to handle the rest.
-        JoinParser joinParser = new JoinParser(currentLine, modelReader);
-        modelBuilder.add(joinParser.run());
-      } else {
-        // we do not know what to do with the current line. Should abort.
-        throw new ModelJoinParsingException("Unexpected line: '" + currentLine + "'");
-      }
-    }
-  }
-
-  /**
-   * Checks, whether the parser should consider the given {@code line} any further, or just ignore
-   * it.
-   * <p>
-   * A line should be skipped, iff.
-   * <ul>
-   * <li>It only consists of whitespace characters</li>
-   * <li>It is an {@code import statement} (as those are not yet considered in the current {@code
-   * ModelJoin} abstraction)</li>
-   * <li>It is a {@code target statement} (as those are not yet considered in the current {@code
-   * ModelJoin} abstraction)</li>
-   * </ul>
-   */
-  private boolean lineShouldBeSkipped(String line) {
-    return EMPTY_LINE.matcher(line).matches() //
-        || IMPORT_STATEMENT.matcher(line).matches() //
-        || TARGET_STATEMENT.matcher(line).matches();
-  }
-
-  /**
-   * Checks, whether the given {@code line} is the beginning of a {@code join statement}.
-   */
-  private boolean lineStartsJoinDeclaration(String line) {
-    return JOIN_STATEMENT.matcher(line).matches();
-  }
 }
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java
index 4a1067c893af62cb25d152315abc32e71a685794..6b72c01ee5d4926b87db1cfa80bc9839e385d104 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/ModelJoinParsingException.java
@@ -1,8 +1,8 @@
 package org.rosi_project.model_sync.model_join.representation.parser;
 
 /**
- * The {@code ModelJoinParsingException} indicates that a file could not be read into a
- * {@code ModelJoin} instance correctly.
+ * The {@code ModelJoinParsingException} indicates that a {@code ModelJoin} file could not be read
+ * correctly, e.g. because of missing values or a malformed structure.
  *
  * @author Rico Bergmann
  */
@@ -11,21 +11,21 @@ public class ModelJoinParsingException extends RuntimeException {
   /**
    * @see RuntimeException#RuntimeException(String)
    */
-  ModelJoinParsingException(String message) {
+  public ModelJoinParsingException(String message) {
     super(message);
   }
 
   /**
    * @see RuntimeException#RuntimeException(String, Throwable)
    */
-  ModelJoinParsingException(String message, Throwable cause) {
+  public ModelJoinParsingException(String message, Throwable cause) {
     super(message, cause);
   }
 
   /**
    * @see RuntimeException#RuntimeException(Throwable)
    */
-  ModelJoinParsingException(Throwable cause) {
+  public ModelJoinParsingException(Throwable cause) {
     super(cause);
   }
 }
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/AntlrBackedModelJoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/AntlrBackedModelJoinParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..86773781a112c27a910fca63acc710f9d50f331f
--- /dev/null
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/AntlrBackedModelJoinParser.java
@@ -0,0 +1,104 @@
+package org.rosi_project.model_sync.model_join.representation.parser.antlr;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Optional;
+import javax.annotation.Nonnull;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.TokenStream;
+import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParser;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinLexer;
+
+/**
+ * The {@code AntlrBackedModelJoinParser} reads a {@code ModelJoin} file according to a compiled
+ * {@code ANTLR} grammar and generates the matching {@link ModelJoinExpression} for the file.
+ *
+ * @author Rico Bergmann
+ * @see <a href="https://www.antlr.org/">ANTLR project</a>
+ */
+public class AntlrBackedModelJoinParser implements ModelJoinParser {
+
+  @Nonnull
+  @Override
+  public Optional<ModelJoinExpression> read(@Nonnull File modelFile) {
+    try {
+      return Optional.of(invokeAntlr(modelFile));
+    } catch (ModelJoinParsingException e) {
+      return Optional.empty();
+    }
+  }
+
+  @Nonnull
+  @Override
+  public ModelJoinExpression readOrThrow(@Nonnull File modelFile) {
+    return invokeAntlr(modelFile);
+  }
+
+  /**
+   * Executes the whole ANTLR workflow.
+   *
+   * @throws ModelJoinParsingException if the workflow fails. Details on the error will be added
+   *     to the exception.
+   */
+  protected ModelJoinExpression invokeAntlr(@Nonnull File modelFile) {
+    CharStream modelStream = consumeCompleteModelFileContent(modelFile);
+    ModelJoinLexer lexer = initializeLexerBasedOn(modelStream);
+    TokenStream tokenStream = tokenize(lexer);
+
+    org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. //
+        ModelJoinParser modelJoinParser = initializeParserBasedOn(tokenStream);
+
+    org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. //
+        ModelJoinParser.ModeljoinContext modelJoinContext = modelJoinParser.modeljoin();
+
+    ModelJoinVisitor modelJoinVisitor = new ModelJoinVisitor();
+
+    return modelJoinVisitor.visitModeljoin(modelJoinContext);
+  }
+
+  /**
+   * Converts the content of a {@code ModelJoin} file into an ANTLR input stream.
+   *
+   * @throws ModelJoinParsingException if the file may not be read
+   */
+  @Nonnull
+  protected CharStream consumeCompleteModelFileContent(@Nonnull File modelFile) {
+    try {
+      return CharStreams.fromPath(modelFile.toPath());
+    } catch (IOException e) {
+      throw new ModelJoinParsingException(e);
+    }
+  }
+
+  /**
+   * Sets up a lexer for the given {@code ModelJoin} file.
+   */
+  @Nonnull
+  protected ModelJoinLexer initializeLexerBasedOn(@Nonnull CharStream modelFile) {
+    return new ModelJoinLexer(modelFile);
+  }
+
+  /**
+   * Executes the lexing step.
+   */
+  @Nonnull
+  protected TokenStream tokenize(@Nonnull ModelJoinLexer lexer) {
+    return new CommonTokenStream(lexer);
+  }
+
+  /**
+   * Sets up the parser.
+   */
+  @Nonnull
+  protected org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. //
+      ModelJoinParser initializeParserBasedOn(@Nonnull TokenStream tokens) {
+    return new org.rosi_project.model_sync.model_join.representation.parser.antlr.generated. //
+        ModelJoinParser(tokens);
+  }
+
+}
+
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/JoinStatementVisitor.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/JoinStatementVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..a00d25b41693d9019379c34adb0349fb4b6870d8
--- /dev/null
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/JoinStatementVisitor.java
@@ -0,0 +1,174 @@
+package org.rosi_project.model_sync.model_join.representation.parser.antlr;
+
+import javax.annotation.Nonnull;
+import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
+import org.rosi_project.model_sync.model_join.representation.core.OCLConstraint;
+import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression.JoinType;
+import org.rosi_project.model_sync.model_join.representation.grammar.NaturalJoinExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.OuterJoinExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.ThetaJoinExpression;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinBaseVisitor;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.JoinContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.NaturaljoinContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.OuterjoinContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.ThetajoinContext;
+import org.rosi_project.model_sync.model_join.representation.util.JoinFactory;
+import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.NaturalJoinBuilder;
+import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.OuterJoinBuilder;
+import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.ThetaJoinBuilder;
+
+/**
+ * The {@code JoinStatementVisitor} creates {@link JoinExpression} instances for a parsed {@code
+ * ModelJoin} file.
+ *
+ * @author Rico Bergmann
+ */
+public class JoinStatementVisitor extends ModelJoinBaseVisitor<JoinExpression> {
+
+  private final KeepStatementVisitor keepStatementVisitor;
+
+  /**
+   * Default constructor. Nothing special about it.
+   */
+  JoinStatementVisitor() {
+    this.keepStatementVisitor = new KeepStatementVisitor();
+  }
+
+  @Override
+  public JoinExpression visitJoin(JoinContext ctx) {
+    JoinType joinType = determineJoinType(ctx);
+    return invokeJoinBuilderAccordingTo(joinType, ctx);
+  }
+
+  /**
+   * Checks, which kind of join is specified in a specific {@code JoinContext}.
+   */
+  private JoinType determineJoinType(@Nonnull JoinContext ctx) {
+    if (ctx.thetajoin() != null) {
+      return JoinType.THETA;
+    } else if (ctx.naturaljoin() != null) {
+      return JoinType.NATURAL;
+    } else if (ctx.outerjoin() != null) {
+      return JoinType.OUTER;
+    } else {
+      throw new ModelJoinParsingException("Unkown join type in join " + ctx);
+    }
+  }
+
+  /**
+   * Generates a {@link JoinExpression} according to the {@link JoinType} of the given {@code
+   * JoinContext}.
+   */
+  private JoinExpression invokeJoinBuilderAccordingTo(@Nonnull JoinType type,
+      @Nonnull JoinContext ctx) {
+    switch (type) {
+      case THETA:
+        return generateThetaJoin(ctx);
+      case NATURAL:
+        return generateNaturalJoin(ctx);
+      case OUTER:
+        return generateOuterJoin(ctx);
+      default:
+        throw new AssertionError(type);
+    }
+  }
+
+  /**
+   * Creates the {@link ThetaJoinExpression} instance contained in a {@code JoinContext}. This
+   * method assumes that the provided {@code ctx} indeed contains a theta join.
+   */
+  private ThetaJoinExpression generateThetaJoin(@Nonnull JoinContext ctx) {
+    ThetajoinContext thetajoinContext = ctx.thetajoin();
+
+    ClassResource left = ClassResource.fromQualifiedName(thetajoinContext.classres(0).getText());
+    ClassResource right = ClassResource.fromQualifiedName(thetajoinContext.classres(1).getText());
+
+    ThetaJoinBuilder joinBuilder = JoinFactory.createNew()
+        .theta()
+        .join(left)
+        .with(right)
+        .as(ClassResource.fromQualifiedName(ctx.classres().getText()))
+        .where(OCLConstraint.of(thetajoinContext.oclcond().getText()));
+
+    ctx.keepaggregatesexpr().stream()
+        .map(keepStatementVisitor::visitKeepaggregatesexpr)
+        .forEach(joinBuilder::keep);
+    ctx.keepattributesexpr().stream()
+        .map(keepStatementVisitor::visitKeepattributesexpr)
+        .forEach(joinBuilder::keep);
+    ctx.keepexpr().stream()
+        .map(keepStatementVisitor::visitKeepexpr)
+        .forEach(joinBuilder::keep);
+
+    return joinBuilder.done();
+  }
+
+  /**
+   * Creates the {@link NaturalJoinExpression} instance contained in a {@code JoinContext}. This
+   * method assumes that the provided {@code ctx} indeed contains a natural join.
+   */
+  private NaturalJoinExpression generateNaturalJoin(@Nonnull JoinContext ctx) {
+    NaturaljoinContext naturalJoinContext = ctx.naturaljoin();
+
+    ClassResource left = ClassResource.fromQualifiedName(naturalJoinContext.classres(0).getText());
+    ClassResource right = ClassResource.fromQualifiedName(naturalJoinContext.classres(1).getText());
+
+    NaturalJoinBuilder joinBuilder = JoinFactory.createNew()
+        .natural()
+        .join(left)
+        .with(right)
+        .as(ClassResource.fromQualifiedName(ctx.classres().getText()));
+
+    ctx.keepaggregatesexpr().stream()
+        .map(keepStatementVisitor::visitKeepaggregatesexpr)
+        .forEach(joinBuilder::keep);
+    ctx.keepattributesexpr().stream()
+        .map(keepStatementVisitor::visitKeepattributesexpr)
+        .forEach(joinBuilder::keep);
+    ctx.keepexpr().stream()
+        .map(keepStatementVisitor::visitKeepexpr)
+        .forEach(joinBuilder::keep);
+
+    return joinBuilder.done();
+  }
+
+  /**
+   * Creates the {@link OuterJoinExpression} instance contained in a {@code JoinContext}. This
+   * method assumes that the provided {@code ctx} indeed contains an outer join.
+   */
+  private OuterJoinExpression generateOuterJoin(@Nonnull JoinContext ctx) {
+    OuterjoinContext outerJoinContext = ctx.outerjoin();
+
+    ClassResource left = ClassResource.fromQualifiedName(outerJoinContext.classres(0).getText());
+    ClassResource right = ClassResource.fromQualifiedName(outerJoinContext.classres(1).getText());
+
+    OuterJoinBuilder joinBuilder = JoinFactory.createNew()
+        .outer()
+        .join(left)
+        .with(right)
+        .as(ClassResource.fromQualifiedName(ctx.classres().getText()));
+
+    if (outerJoinContext.leftouterjoin() != null) {
+      joinBuilder.leftOuter();
+    } else if (outerJoinContext.rightouterjoin() != null) {
+      joinBuilder.rightOuter();
+    } else {
+      throw new ModelJoinParsingException("Expected either 'left' or 'right' 'outer join'");
+    }
+
+    ctx.keepaggregatesexpr().stream()
+        .map(keepStatementVisitor::visitKeepaggregatesexpr)
+        .forEach(joinBuilder::keep);
+    ctx.keepattributesexpr().stream()
+        .map(keepStatementVisitor::visitKeepattributesexpr)
+        .forEach(joinBuilder::keep);
+    ctx.keepexpr().stream()
+        .map(keepStatementVisitor::visitKeepexpr)
+        .forEach(joinBuilder::keep);
+
+    return joinBuilder.done();
+  }
+
+}
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/KeepStatementVisitor.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/KeepStatementVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7e6397b28bfeed1bebdf88779a8697b126af397
--- /dev/null
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/KeepStatementVisitor.java
@@ -0,0 +1,293 @@
+package org.rosi_project.model_sync.model_join.representation.parser.antlr;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.rosi_project.model_sync.model_join.representation.core.AttributePath;
+import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
+import org.rosi_project.model_sync.model_join.representation.core.RelativeAttribute;
+import org.rosi_project.model_sync.model_join.representation.grammar.KeepAggregateExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.KeepAggregateExpression.AggregationType;
+import org.rosi_project.model_sync.model_join.representation.grammar.KeepAttributesExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.KeepExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.KeepReferenceExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.KeepSubTypeExpression;
+import org.rosi_project.model_sync.model_join.representation.grammar.KeepSuperTypeExpression;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinBaseVisitor;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.AttrresContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepaggregatesexprContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepattributesexprContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepexprContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepincomingexprContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepoutgoingexprContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepsubtypeexprContext;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.KeepsupertypeexprContext;
+
+/**
+ * The {@code KeepStatementVisitor} creates {@link KeepExpression} instances from a parsed {@code
+ * ModelJoin} file.
+ *
+ * @author Rico Bergmann
+ */
+public class KeepStatementVisitor extends ModelJoinBaseVisitor<KeepExpression> {
+
+  /**
+   * A simple enumeration of the possible keep statements for compound statements as defined in the
+   * {@code ModelJoin} ANTLR grammar.
+   */
+  private enum KeepExprType {
+
+    /**
+     * The {@code KEEP_TYPE_EXPR} subsumes both {@code keep subtype} as well as {@code keep
+     * supertype} expressions.
+     */
+    KEEP_TYPE_EXPR,
+
+    /**
+     * The {@code KEEP_INCOMING_EXPR} corresponds to a {@code keep incoming reference} statement.
+     */
+    KEEP_INCOMING_EXPR,
+
+    /**
+     * The {@code KEEP_OUTGOING_EXPR} corresponds to a {@code keep outgoing reference} statement.
+     */
+    KEEP_OUTGOING_EXPR
+
+  }
+
+  @Override
+  public KeepExpression visitKeepattributesexpr(KeepattributesexprContext ctx) {
+    return new KeepAttributesExpression(
+        ctx.attrres().stream()
+            .map(AttrresContext::getText)
+            .map(AttributePath::fromQualifiedPath)
+            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll)
+    );
+
+  }
+
+  @Override
+  public KeepExpression visitKeepaggregatesexpr(KeepaggregatesexprContext ctx) {
+    String rawAggregationType = ctx.aggrtype().getText();
+    AggregationType aggregationType = AggregationType.valueOf(rawAggregationType.toUpperCase());
+
+    return new KeepAggregateExpression(
+        aggregationType /* SUM / MAX / MIN etc */,
+        RelativeAttribute.of(ctx.relattr().getText()) /* the attribute in the target relation  */,
+        AttributePath.fromQualifiedPath(ctx.classres().getText()) /* the source attribute */,
+        AttributePath.fromQualifiedPath(ctx.attrres().getText()) /* name in the new ModelJoin */
+    );
+  }
+
+  @Override
+  public KeepExpression visitKeepexpr(KeepexprContext ctx) {
+    KeepExprType keepExprType = determineExpressionTypeFor(ctx);
+
+    switch (keepExprType) {
+      case KEEP_TYPE_EXPR:
+        return buildKeepTypeExpressionFor(ctx);
+      case KEEP_INCOMING_EXPR:
+        return buildKeepIncomingReferenceExpressionFor(ctx);
+      case KEEP_OUTGOING_EXPR:
+        return buildKeepOutgoingReferenceExpressionFor(ctx);
+      default:
+        throw new AssertionError(keepExprType);
+    }
+  }
+
+  /**
+   * Checks, which kind of expression is specified in a specific {@code KeepexprContext}.
+   */
+  private KeepExprType determineExpressionTypeFor(KeepexprContext ctx) {
+
+    /*
+     * From the ANTLR grammar we know that exactly one of the IF cases has to hold
+     */
+    if (ctx.keeptypeexpr() != null) {
+      return KeepExprType.KEEP_TYPE_EXPR;
+    } else if (ctx.keepincomingexpr() != null) {
+      return KeepExprType.KEEP_INCOMING_EXPR;
+    } else if (ctx.keepoutgoingexpr() != null) {
+      return KeepExprType.KEEP_OUTGOING_EXPR;
+    } else {
+      throw new ModelJoinParsingException(
+          "Expected either 'keep reference' or 'keep type'  expression: " + ctx);
+    }
+  }
+
+  /**
+   * Creates the appropriate {@link KeepExpression} for a {@code KeepexprContext}.
+   */
+  private KeepExpression buildKeepTypeExpressionFor(KeepexprContext ctx) {
+    if (ctx.keeptypeexpr().keepsubtypeexpr() != null) {
+      return buildKeepSubtypeExpressionFor(ctx);
+    } else if (ctx.keeptypeexpr().keepsupertypeexpr() != null) {
+      return buildKeepSupertypeExpressionFor(ctx);
+    } else {
+      throw new ModelJoinParsingException(
+          "Expected either 'keep supertype' or 'keep subtype'  expression: " + ctx);
+    }
+  }
+
+  /**
+   * Creates the {@link KeepReferenceExpression} instance contained in a {@code KeepexprContext}.
+   * This method assumes that the provided {@code ctx} indeed contains a keep incoming reference
+   * statement.
+   */
+  private KeepReferenceExpression buildKeepIncomingReferenceExpressionFor(KeepexprContext ctx) {
+    KeepincomingexprContext incomingCtx = ctx.keepincomingexpr();
+
+    AttributePath attributeToKeep = AttributePath
+        .fromQualifiedPath(incomingCtx.attrres().getText());
+
+    List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream()
+        .map(this::visitKeepattributesexpr)
+        .collect(Collectors.toList());
+
+    nestedExpressions.addAll(ctx.keepaggregatesexpr().stream()
+        .map(this::visitKeepaggregatesexpr)
+        .collect(Collectors.toList())
+    );
+
+    nestedExpressions.addAll(ctx.keepexpr().stream()
+        .map(this::visitKeepexpr)
+        .collect(Collectors.toList())
+    );
+
+    KeepReferenceExpression.KeepReferenceBuilder expressionBuilder = KeepReferenceExpression.keep()
+        .incoming(attributeToKeep);
+
+    ClassResource targetClass;
+    if (incomingCtx.classres() != null) {
+      targetClass = ClassResource.fromQualifiedName(incomingCtx.classres().getText());
+    } else {
+      targetClass = attributeToKeep.getContainingClass();
+    }
+
+    expressionBuilder.as(targetClass);
+    nestedExpressions.forEach(expressionBuilder::keep);
+
+    return expressionBuilder.buildExpression();
+  }
+
+  /**
+   * Creates the {@link KeepReferenceExpression} instance contained in a {@code KeepexprContext}.
+   * This method assumes that the provided {@code ctx} indeed contains a keep outgoing reference
+   * statement.
+   */
+  private KeepReferenceExpression buildKeepOutgoingReferenceExpressionFor(KeepexprContext ctx) {
+    KeepoutgoingexprContext outgoingCtx = ctx.keepoutgoingexpr();
+
+    AttributePath attributeToKeep = AttributePath
+        .fromQualifiedPath(outgoingCtx.attrres().getText());
+
+    List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream()
+        .map(this::visitKeepattributesexpr)
+        .collect(Collectors.toList());
+
+    nestedExpressions.addAll(ctx.keepaggregatesexpr().stream()
+        .map(this::visitKeepaggregatesexpr)
+        .collect(Collectors.toList())
+    );
+
+    nestedExpressions.addAll(ctx.keepexpr().stream()
+        .map(this::visitKeepexpr)
+        .collect(Collectors.toList())
+    );
+
+    KeepReferenceExpression.KeepReferenceBuilder expressionBuilder = KeepReferenceExpression.keep()
+        .outgoing(attributeToKeep);
+
+    ClassResource targetClass;
+    if (outgoingCtx.classres() != null) {
+      targetClass = ClassResource.fromQualifiedName(outgoingCtx.classres().getText());
+    } else {
+      targetClass = attributeToKeep.getContainingClass();
+    }
+
+    expressionBuilder.as(targetClass);
+    nestedExpressions.forEach(expressionBuilder::keep);
+
+    return expressionBuilder.buildExpression();
+  }
+
+  /**
+   * Creates the {@link KeepSubTypeExpression} instance contained in a {@code KeepexprContext}. This
+   * method assumes that the provided {@code ctx} indeed contains a keep subtype statement.
+   */
+  private KeepSubTypeExpression buildKeepSubtypeExpressionFor(KeepexprContext ctx) {
+    KeepsubtypeexprContext subtypeCtx = ctx.keeptypeexpr().keepsubtypeexpr();
+
+    ClassResource typeToKeep = ClassResource.fromQualifiedName(subtypeCtx.classres(0).getText());
+
+    List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream()
+        .map(this::visitKeepattributesexpr)
+        .collect(Collectors.toList());
+
+    nestedExpressions.addAll(ctx.keepaggregatesexpr().stream()
+        .map(this::visitKeepaggregatesexpr)
+        .collect(Collectors.toList())
+    );
+
+    nestedExpressions.addAll(ctx.keepexpr().stream()
+        .map(this::visitKeepexpr)
+        .collect(Collectors.toList())
+    );
+
+    KeepSubTypeExpression.KeepSubTypeBuilder expressionBuilder = KeepSubTypeExpression.keep()
+        .subtype(typeToKeep);
+
+    ClassResource targetClass;
+    if (subtypeCtx.classres() != null) {
+      targetClass = ClassResource.fromQualifiedName(subtypeCtx.classres(1).getText());
+    } else {
+      targetClass = typeToKeep;
+    }
+
+    expressionBuilder.as(targetClass);
+    nestedExpressions.forEach(expressionBuilder::keep);
+
+    return expressionBuilder.buildExpression();
+  }
+
+  /**
+   * Creates the {@link KeepSuperTypeExpression} instance contained in a {@code KeepexprContext}. This
+   * method assumes that the provided {@code ctx} indeed contains a keep supertype statement.
+   */
+  private KeepSuperTypeExpression buildKeepSupertypeExpressionFor(KeepexprContext ctx) {
+    KeepsupertypeexprContext supertypeCtx = ctx.keeptypeexpr().keepsupertypeexpr();
+
+    ClassResource typeToKeep = ClassResource.fromQualifiedName(supertypeCtx.classres(0).getText());
+
+    List<KeepExpression> nestedExpressions = ctx.keepattributesexpr().stream()
+        .map(this::visitKeepattributesexpr)
+        .collect(Collectors.toList());
+
+    nestedExpressions.addAll(ctx.keepaggregatesexpr().stream()
+        .map(this::visitKeepaggregatesexpr)
+        .collect(Collectors.toList())
+    );
+
+    nestedExpressions.addAll(ctx.keepexpr().stream()
+        .map(this::visitKeepexpr)
+        .collect(Collectors.toList())
+    );
+
+    KeepSuperTypeExpression.KeepSuperTypeBuilder expressionBuilder = KeepSuperTypeExpression.keep()
+        .supertype(typeToKeep);
+
+    ClassResource targetClass;
+    if (supertypeCtx.classres() != null) {
+      targetClass = ClassResource.fromQualifiedName(supertypeCtx.classres(1).getText());
+    } else {
+      targetClass = typeToKeep;
+    }
+
+    expressionBuilder.as(targetClass);
+    nestedExpressions.forEach(expressionBuilder::keep);
+
+    return expressionBuilder.buildExpression();
+  }
+
+}
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/ModelJoinVisitor.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/ModelJoinVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1883fa77954a74ed677d8a5b6cc358ca2721946
--- /dev/null
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/antlr/ModelJoinVisitor.java
@@ -0,0 +1,28 @@
+package org.rosi_project.model_sync.model_join.representation.parser.antlr;
+
+import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinBaseVisitor;
+import org.rosi_project.model_sync.model_join.representation.parser.antlr.generated.ModelJoinParser.ModeljoinContext;
+import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder;
+
+/**
+ * The {@code ModelJoinVisitor} creates a {@link ModelJoinExpression} instances from a parsed {@code
+ * ModelJoin} file.
+ *
+ * @author Rico Bergmann
+ */
+public class ModelJoinVisitor extends ModelJoinBaseVisitor<ModelJoinExpression> {
+
+  @Override
+  public ModelJoinExpression visitModeljoin(ModeljoinContext ctx) {
+    JoinStatementVisitor joinVisitor = new JoinStatementVisitor();
+    ModelJoinBuilder modelJoinBuilder = ModelJoinBuilder.createNewModelJoin();
+
+    ctx.join().stream() //
+        .map(joinVisitor::visitJoin) //
+        .forEach(modelJoinBuilder::add);
+
+    return modelJoinBuilder.build();
+  }
+
+}
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/DefaultModelJoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/DefaultModelJoinParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..25f4b9ce3871862e570befcb4f28ad761f467345
--- /dev/null
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/DefaultModelJoinParser.java
@@ -0,0 +1,187 @@
+package org.rosi_project.model_sync.model_join.representation.parser.legacy;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
+import org.rosi_project.model_sync.model_join.representation.grammar.ModelJoinExpression;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParser;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException;
+import org.rosi_project.model_sync.model_join.representation.util.ModelJoinBuilder;
+
+/**
+ * The {@code DefaultModelJoinParser} reads a {@code ModelJoin} file from disk and creates matching
+ * representation for it.
+ *
+ * @author Rico Bergmann
+ */
+@Deprecated
+public class DefaultModelJoinParser {
+
+  /**
+   * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}, wrapped into an {@code
+   * Optional}. If parsing fails, an empty {@code Optional} will be returned.
+   */
+  @Nonnull
+  public static Optional<ModelJoinExpression> read(@Nonnull File modelFile) {
+    DefaultModelJoinParser parser = new DefaultModelJoinParser(modelFile, ErrorReportingStrategy.OPTIONAL);
+    return parser.run();
+  }
+
+  /**
+   * Converts the {@code ModelJoin} file into a {@link ModelJoinExpression}. If parsing fails, a
+   * {@link ModelJoinParsingException} will be thrown.
+   *
+   * @throws ModelJoinParsingException if the model may not be parsed for some reason.
+   */
+  @Nonnull
+  public static ModelJoinExpression readOrThrow(@Nonnull File modelFile) {
+    DefaultModelJoinParser parser = new DefaultModelJoinParser(modelFile, ErrorReportingStrategy.EXCEPTION);
+    Optional<ModelJoinExpression> result = parser.run();
+
+    if (result.isPresent()) {
+      return result.get();
+    } else {
+      /*
+       * Theoretically, the parser should throw the exception by itself, so this code should never
+       * actually run. It is merely here for safety purposes (unchecked access of Optional.get())
+       */
+      throw new ModelJoinParsingException("Result not present but no exception was thrown either");
+    }
+  }
+
+  /**
+   * Indicates what should happen if parsing fails.
+   */
+  enum ErrorReportingStrategy {
+
+    /**
+     * On failure an empty {@code Optional} should be returned.
+     */
+    OPTIONAL,
+
+    /**
+     * On failure an {@link ModelJoinParsingException} should be thrown.
+     */
+    EXCEPTION
+  }
+
+  private static final Pattern EMPTY_LINE = Pattern.compile("^\\s*$");
+  private static final Pattern IMPORT_STATEMENT = Pattern.compile("^import .*");
+  private static final Pattern TARGET_STATEMENT = Pattern.compile("^target .*");
+  private static final Pattern JOIN_STATEMENT = Pattern
+      .compile("^(((left|right) outer)|theta|natural) join .*");
+
+  @Nonnull
+  private final File modelFile;
+
+  @Nonnull
+  private final ErrorReportingStrategy errorReportingStrategy;
+
+  /**
+   * Full constructor.
+   *
+   * @param modelFile the file to parse
+   * @param errorReportingStrategy what to do in case an error occurs.
+   */
+  private DefaultModelJoinParser(@Nonnull File modelFile,
+      @Nonnull ErrorReportingStrategy errorReportingStrategy) {
+    this.modelFile = modelFile;
+    this.errorReportingStrategy = errorReportingStrategy;
+  }
+
+  /**
+   * Creates the {@code ModelJoin} representation from the {@code modelFile}.
+   * <p>
+   * Depending on the selected {@code errorReportingStrategy} either an Exception will be thrown, or
+   * an empty {@code Optional} will be returned if something goes wrong.
+   */
+  private Optional<ModelJoinExpression> run() {
+    ModelJoinExpression resultingModel;
+    try (FileInputStream modelInputStream = new FileInputStream(modelFile);
+        InputStreamReader modelInputReader = new InputStreamReader(modelInputStream);
+        BufferedReader bufferedModelReader = new BufferedReader(modelInputReader)) {
+
+      ModelJoinBuilder modelJoinBuilder = ModelJoinBuilder.createNewModelJoin();
+      readModelFileAndPopulateBuilder(bufferedModelReader, modelJoinBuilder);
+      resultingModel = modelJoinBuilder.build();
+
+    } catch (ModelJoinParsingException e) {
+      switch (errorReportingStrategy) {
+        case OPTIONAL:
+          return Optional.empty();
+        case EXCEPTION:
+          throw e;
+        default:
+          throw new AssertionError(errorReportingStrategy);
+      }
+    } catch (IOException e) {
+      switch (errorReportingStrategy) {
+        case OPTIONAL:
+          return Optional.empty();
+        case EXCEPTION:
+          throw new ModelJoinParsingException(e);
+        default:
+          throw new AssertionError(errorReportingStrategy);
+      }
+    }
+    return Optional.of(resultingModel);
+  }
+
+  /**
+   * Reads the {@code modelFile} and constructs the corresponding {@code ModelJoin} on the fly.
+   * <p>
+   * On error, an {@code IOException} or an {@link ModelJoinParsingException} will be thrown.
+   *
+   * @throws ModelJoinParsingException if the model file's content are malformed or another
+   *     component failed
+   * @throws IOException if the model file might not be read any further
+   */
+  private void readModelFileAndPopulateBuilder(BufferedReader modelReader,
+      ModelJoinBuilder modelBuilder) throws IOException {
+    String currentLine;
+    while ((currentLine = modelReader.readLine()) != null) {
+      if (lineShouldBeSkipped(currentLine)) {
+        // the line should be skipped. So we do nothing.
+        continue;
+      } else if (lineStartsJoinDeclaration(currentLine)) {
+        // a new join is being declared. Delegate to the join parser to handle the rest.
+        JoinParser joinParser = new JoinParser(currentLine, modelReader);
+        modelBuilder.add(joinParser.run());
+      } else {
+        // we do not know what to do with the current line. Should abort.
+        throw new ModelJoinParsingException("Unexpected line: '" + currentLine + "'");
+      }
+    }
+  }
+
+  /**
+   * Checks, whether the parser should consider the given {@code line} any further, or just ignore
+   * it.
+   * <p>
+   * A line should be skipped, iff.
+   * <ul>
+   * <li>It only consists of whitespace characters</li>
+   * <li>It is an {@code import statement} (as those are not yet considered in the current {@code
+   * ModelJoin} abstraction)</li>
+   * <li>It is a {@code target statement} (as those are not yet considered in the current {@code
+   * ModelJoin} abstraction)</li>
+   * </ul>
+   */
+  private boolean lineShouldBeSkipped(String line) {
+    return EMPTY_LINE.matcher(line).matches() //
+        || IMPORT_STATEMENT.matcher(line).matches() //
+        || TARGET_STATEMENT.matcher(line).matches();
+  }
+
+  /**
+   * Checks, whether the given {@code line} is the beginning of a {@code join statement}.
+   */
+  private boolean lineStartsJoinDeclaration(String line) {
+    return JOIN_STATEMENT.matcher(line).matches();
+  }
+}
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/JoinParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/JoinParser.java
similarity index 98%
rename from src/main/java/org/rosi_project/model_sync/model_join/representation/parser/JoinParser.java
rename to src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/JoinParser.java
index 36b2de29fe0b005b6fa9923553013237cae45066..41f40e980bdfe7e2a8d9ec95a01a720e141c8ff7 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/JoinParser.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/JoinParser.java
@@ -1,4 +1,4 @@
-package org.rosi_project.model_sync.model_join.representation.parser;
+package org.rosi_project.model_sync.model_join.representation.parser.legacy;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -10,6 +10,7 @@ import org.rosi_project.model_sync.model_join.representation.core.ClassResource;
 import org.rosi_project.model_sync.model_join.representation.core.OCLConstraint;
 import org.rosi_project.model_sync.model_join.representation.grammar.JoinExpression;
 import org.rosi_project.model_sync.model_join.representation.grammar.KeepExpression;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException;
 import org.rosi_project.model_sync.model_join.representation.util.JoinFactory;
 import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.NaturalJoinBuilder;
 import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.OuterJoinBuilder;
@@ -19,7 +20,7 @@ import org.rosi_project.model_sync.model_join.representation.util.JoinFactory.Th
  * The {@code JoinParser} takes care of reading an individual {@code join statement} and creates the
  * corresponding {@link JoinExpression}.
  * <p>
- * This class is primarily intended to be created and invoked by the {@link ModelJoinParser} only as
+ * This class is primarily intended to be created and invoked by the {@link DefaultModelJoinParser} only as
  * will modify the state of that as well.
  *
  * @author Rico Bergmann
@@ -50,7 +51,7 @@ class JoinParser {
   /**
    * Full constructor.
    *
-   * @param startingLine the line that was last read by the {@link ModelJoinParser} and should
+   * @param startingLine the line that was last read by the {@link DefaultModelJoinParser} and should
    *     therefore become the starting point for {@code this} parser.
    * @param modelReader the reader that provides access to the following lines in the {@code
    *     ModelJoin} file currently being read.
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/KeepParser.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/KeepParser.java
similarity index 97%
rename from src/main/java/org/rosi_project/model_sync/model_join/representation/parser/KeepParser.java
rename to src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/KeepParser.java
index 1ea6db9a87f2f63ee7aec6959d1ead6e13690e43..ee20f8827e2e537ee135c17b7f5728f993eef2f9 100644
--- a/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/KeepParser.java
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/parser/legacy/KeepParser.java
@@ -1,4 +1,4 @@
-package org.rosi_project.model_sync.model_join.representation.parser;
+package org.rosi_project.model_sync.model_join.representation.parser.legacy;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -12,6 +12,7 @@ import javax.annotation.Nonnull;
 import org.rosi_project.model_sync.model_join.representation.core.AttributePath;
 import org.rosi_project.model_sync.model_join.representation.grammar.KeepAttributesExpression;
 import org.rosi_project.model_sync.model_join.representation.grammar.KeepExpression;
+import org.rosi_project.model_sync.model_join.representation.parser.ModelJoinParsingException;
 
 /**
  * The {@code KeepParser} takes care of reading the {@code keep statements} and creating the
diff --git a/src/main/java/org/rosi_project/model_sync/model_join/representation/util/Assert.java b/src/main/java/org/rosi_project/model_sync/model_join/representation/util/Assert.java
new file mode 100644
index 0000000000000000000000000000000000000000..d72ecca9b380b86828c5134808cef2e48bc46cd8
--- /dev/null
+++ b/src/main/java/org/rosi_project/model_sync/model_join/representation/util/Assert.java
@@ -0,0 +1,36 @@
+package org.rosi_project.model_sync.model_join.representation.util;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Utility class to provide a number of useful assertion mechanisms.
+ * <p>
+ * On failure, an {@code AssertionError} with a dedicated message will be thrown.
+ *
+ * @author Rico Bergmann
+ */
+public class Assert {
+
+  /**
+   * Ensures that an object is not {@code null}.
+   */
+  public static void notNull(Object obj, @Nonnull String failureMessage) {
+    if (obj == null) {
+      throw new AssertionError(failureMessage);
+    }
+  }
+
+  /**
+   * Ensures that none of the arguments is {@code null}.
+   */
+  public static void noNullArguments(@Nonnull String failureMessage, Object... arguments) {
+    if (arguments == null) {
+      throw new AssertionError("Vararg arguments may not be null");
+    }
+    for (Object arg : arguments) {
+      if (arg == null) {
+        throw new AssertionError(failureMessage);
+      }
+    }
+  }
+}