diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..6c234cdb79640f2147fad6b43c5394692e20ba67
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,40 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Gradle template
+.gradle
+/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# User-specific stuff:
+**/.idea/
+
+# CMake
+cmake-build-debug/
+
+## File-based project format:
+*.iws
+*.iml
+
+## Plugin-specific files:
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..265d280a1b52b87936bb8d1a5faf661298553fc5
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,40 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
+    }
+}
+
+// Apply the java plugin to add support for Java
+apply plugin: 'java'
+
+// Apply the application plugin to add support for building an application
+apply plugin: 'application'
+
+apply plugin: 'com.github.ben-manes.versions'
+
+repositories {
+    jcenter()
+}
+
+// Define the main class for the application
+mainClassName = 'App'
+
+dependencies {
+    /* Commons Lang3 */
+    implementation "org.apache.commons:commons-lang3:$commons_lang_version"
+
+    /* Retrofit */
+    implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
+    implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
+
+    /* JUnit 5 */
+    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
+    testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
+    testRuntime("org.junit.platform:junit-platform-launcher:${junitPlatformVersion}")
+    testRuntime("org.apache.logging.log4j:log4j-core:${log4jVersion}")
+    testRuntime("org.apache.logging.log4j:log4j-jul:${log4jVersion}")
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..0f59cd88c3f14b55737295c90017777b7ce12c1e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,5 @@
+commons_lang_version=3.7
+retrofit2_version=2.3.0
+junitPlatformVersion = 1.1.0-M1
+junitJupiterVersion = 5.1.0-M1
+log4jVersion = 2.10.0
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..933b6473ce1ea83ca681969501e2c05453c313a1
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000000000000000000000000000000000..cccdd3d517fc5249beaefa600691cf150f2fa3e6
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000000000000000000000000000000000..f9553162f122c71b34635112e717c3e733b5b212
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..17d6647c52913a72e949dd319f17346a3848fc0a
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,18 @@
+/*
+ * This settings file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ * In a single project build this file can be empty or even removed.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user guide at https://docs.gradle.org/4.4/userguide/multi_project_builds.html
+ */
+
+/*
+// To declare projects as part of a multi-project build use the 'include' method
+include 'shared'
+include 'api'
+include 'services:webservice'
+*/
+
+rootProject.name = '11-futures-cli'
diff --git a/src/main/java/de/fhro/inf/prg3/a11/App.java b/src/main/java/de/fhro/inf/prg3/a11/App.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa61eb10ca110ff8f3b080cbcad8bfa91da54462
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/App.java
@@ -0,0 +1,130 @@
+package de.fhro.inf.prg3.a11;
+
+import de.fhro.inf.prg3.a11.openmensa.OpenMensaAPI;
+import de.fhro.inf.prg3.a11.openmensa.OpenMensaAPIService;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Scanner;
+import java.util.stream.IntStream;
+
+/**
+ * @author Peter Kurfer
+ * Created on 12/16/17.
+ */
+public class App {
+    private static final String OPEN_MENSA_DATE_FORMAT = "yyyy-MM-dd";
+
+    private static final SimpleDateFormat dateFormat = new SimpleDateFormat(OPEN_MENSA_DATE_FORMAT, Locale.getDefault());
+    private static final Scanner inputScanner = new Scanner(System.in);
+    private static final OpenMensaAPI openMensaAPI = OpenMensaAPIService.getInstance().getOpenMensaAPI();
+    private static final Calendar currentDate = Calendar.getInstance();
+    private static int currentCanteenId = -1;
+
+    public static void main(String[] args) throws ParseException {
+        MenuSelection selection;
+        /* loop while true to get back to the menu every time an action was performed */
+        do {
+            selection = menu();
+            switch (selection) {
+                case SHOW_CANTEENS:
+                    printCanteens();
+                    break;
+                case SET_CANTEEN:
+                    readCanteen();
+                    break;
+                case SHOW_MEALS:
+                    printMeals();
+                    break;
+                case SET_DATE:
+                    readDate();
+                    break;
+                case QUIT:
+                    System.exit(0);
+
+            }
+        } while (true);
+    }
+
+    private static void printCanteens() {
+        System.out.print("Fetching canteens [");
+        /* TODO fetch all canteens and print them to STDOUT
+         * at first get a page without an index to be able to extract the required pagination information
+         * afterwards you can iterate the remaining pages
+         * keep in mind that you should await the process as the user has to select canteen with a specific id */
+    }
+
+    private static void printMeals() {
+        /* TODO fetch all meals for the currently selected canteen
+         * to avoid errors retrieve at first the state of the canteen and check if the canteen is opened at the selected day
+         * don't forget to check if a canteen was selected previously! */
+    }
+
+    /**
+     * Utility method to select a canteen
+     */
+    private static void readCanteen() {
+        /* typical input reading pattern */
+        boolean readCanteenId = false;
+        do {
+            try {
+                System.out.println("Enter canteen id:");
+                currentCanteenId = inputScanner.nextInt();
+                readCanteenId = true;
+            }catch (Exception e) {
+                System.out.println("Sorry could not read the canteen id");
+            }
+        }while (!readCanteenId);
+    }
+
+    /**
+     * Utility method to read a date and update the calendar
+     */
+    private static void readDate() {
+        /* typical input reading pattern */
+        boolean readDate = false;
+        do {
+            try {
+                System.out.println("Pleae enter date in the format yyyy-mm-dd:");
+                Date d = dateFormat.parse(inputScanner.next());
+                currentDate.setTime(d);
+                readDate = true;
+            }catch (ParseException p) {
+                System.out.println("Sorry, the entered date could not be parsed.");
+            }
+        }while (!readDate);
+
+    }
+
+    /**
+     * Utility method to print menu and read the user selection
+     * @return user selection as MenuSelection
+     */
+    private static MenuSelection menu() {
+        IntStream.range(0, 20).forEach(i -> System.out.print("#"));
+        System.out.println();
+        System.out.println("1) Show canteens");
+        System.out.println("2) Set canteen");
+        System.out.println("3) Show meals");
+        System.out.println("4) Set date");
+        System.out.println("5) Quit");
+        IntStream.range(0, 20).forEach(i -> System.out.print("#"));
+        System.out.println();
+
+        switch (inputScanner.nextInt()) {
+            case 1:
+                return MenuSelection.SHOW_CANTEENS;
+            case 2:
+                return MenuSelection.SET_CANTEEN;
+            case 3:
+                return MenuSelection.SHOW_MEALS;
+            case 4:
+                return MenuSelection.SET_DATE;
+            default:
+                return MenuSelection.QUIT;
+        }
+    }
+}
diff --git a/src/main/java/de/fhro/inf/prg3/a11/MenuSelection.java b/src/main/java/de/fhro/inf/prg3/a11/MenuSelection.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d73375277f86a30c1174d05be318e4a2a5b1430
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/MenuSelection.java
@@ -0,0 +1,13 @@
+package de.fhro.inf.prg3.a11;
+
+/**
+ * @author Peter Kurfer
+ * Created on 12/18/17.
+ */
+public enum MenuSelection {
+    SHOW_CANTEENS,
+    SET_CANTEEN,
+    SHOW_MEALS,
+    SET_DATE,
+    QUIT
+}
diff --git a/src/main/java/de/fhro/inf/prg3/a11/openmensa/OpenMensaAPI.java b/src/main/java/de/fhro/inf/prg3/a11/openmensa/OpenMensaAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..bcd20405b2de32f7778233a03545233b92ec6561
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/openmensa/OpenMensaAPI.java
@@ -0,0 +1,54 @@
+package de.fhro.inf.prg3.a11.openmensa;
+
+import de.fhro.inf.prg3.a11.openmensa.model.Canteen;
+import de.fhro.inf.prg3.a11.openmensa.model.Meal;
+import de.fhro.inf.prg3.a11.openmensa.model.State;
+import retrofit2.Response;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @author Peter Kurfer
+ */
+public interface OpenMensaAPI {
+
+    /**
+     * Retrieve the first page of all canteens
+     * includes the response wrapper to be able to extract required headers for further pagination handling
+     * @return first page of canteens wrapped in a response object
+     */
+    @GET("canteens")
+    CompletableFuture<Response<List<Canteen>>> getCanteens();
+
+    /**
+     * Retrieve any page of all canteens
+     * does not include the response wrapper because the required information should be retrieved before this method is used
+     * @param pageNumber index of the page to retrieve
+     * @return List of canteens as async future
+     */
+    @GET("canteens")
+    CompletableFuture<List<Canteen>> getCanteens(@Query("page")int pageNumber);
+
+    /**
+     * Get the state of a canteen specified by its id at the specified date
+     * @param canteenId id of the canteen
+     * @param date date for which the state should be looked up
+     * @return state of the mensa - may be closed or !closed
+     */
+    @GET("canteens/{canteenId}/days/{date}")
+    CompletableFuture<State> getMensaState(@Path("canteenId") int canteenId, @Path("date") String date);
+
+    /**
+     * Retrieve the meals for specified date served at canteen specified by its id
+     * @param canteenId id of the canteen
+     * @param date date for which the meals should be retrieved
+     * @return List of meals wrapped as async future
+     */
+    @GET("canteens/{canteenId}/days/{date}/meals")
+    CompletableFuture<List<Meal>> getMeals(@Path("canteenId") int canteenId, @Path("date") String date);
+
+}
diff --git a/src/main/java/de/fhro/inf/prg3/a11/openmensa/OpenMensaAPIService.java b/src/main/java/de/fhro/inf/prg3/a11/openmensa/OpenMensaAPIService.java
new file mode 100644
index 0000000000000000000000000000000000000000..53e00b06ede5f6a3ed2db9692681671423b39105
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/openmensa/OpenMensaAPIService.java
@@ -0,0 +1,37 @@
+package de.fhro.inf.prg3.a11.openmensa;
+
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * OpenMensaAPI service
+ * holds an instance of OpenMensaAPI to avoid multiple instantiations of the API
+ * @author Peter Kurfer
+ */
+
+public final class OpenMensaAPIService {
+
+    /* singleton instance */
+    private static final OpenMensaAPIService ourInstance = new OpenMensaAPIService();
+    private final OpenMensaAPI openMensaAPI;
+
+    /* singleton accessor */
+    public static OpenMensaAPIService getInstance() {
+        return ourInstance;
+    }
+
+    private OpenMensaAPIService() {
+
+        /* Initialize Retrofit */
+        Retrofit retrofit = new Retrofit.Builder()
+                .baseUrl("http://openmensa.org/api/v2/")
+                .addConverterFactory(GsonConverterFactory.create())
+                .build();
+
+        openMensaAPI = retrofit.create(OpenMensaAPI.class);
+    }
+
+    public OpenMensaAPI getOpenMensaAPI() {
+        return openMensaAPI;
+    }
+}
diff --git a/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/Canteen.java b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/Canteen.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa8a9bebf9be9c8353c98cc41cb982e0ecb01844
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/Canteen.java
@@ -0,0 +1,90 @@
+package de.fhro.inf.prg3.a11.openmensa.model;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+/**
+ * Data transfer object for a canteen retrieved from the OpenMensaAPI
+ * @author Peter Kurfer
+ */
+
+public final class Canteen {
+    private int id;
+    private String name;
+    private String city;
+    private String address;
+    private double[] coordinates;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public void setCity(String city) {
+        this.city = city;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public double[] getCoordinates() {
+        return coordinates;
+    }
+
+    public void setCoordinates(double[] coordinates) {
+        this.coordinates = coordinates;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s\t%s", getId(), getName());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (!(o instanceof Canteen)) return false;
+
+        Canteen canteen = (Canteen) o;
+
+        return new EqualsBuilder()
+                .append(getId(), canteen.getId())
+                .append(getName(), canteen.getName())
+                .append(getCity(), canteen.getCity())
+                .append(getAddress(), canteen.getAddress())
+                .append(getCoordinates(), canteen.getCoordinates())
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37)
+                .append(getId())
+                .append(getName())
+                .append(getCity())
+                .append(getAddress())
+                .append(getCoordinates())
+                .toHashCode();
+    }
+}
diff --git a/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/Meal.java b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/Meal.java
new file mode 100644
index 0000000000000000000000000000000000000000..b55511c349a2e5f4f6a7072dc6a29ce1c1649251
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/Meal.java
@@ -0,0 +1,86 @@
+package de.fhro.inf.prg3.a11.openmensa.model;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Data transfer object for a meal retrieved from the OpenMensaAPI
+ * @author Peter Kurfer
+ */
+
+public final class Meal {
+    private int id;
+    private String name;
+    private String category;
+    private List<String> notes;
+
+    public Meal() {
+        notes = new LinkedList<>();
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public void setCategory(String category) {
+        this.category = category;
+    }
+
+    public List<String> getNotes() {
+        return notes;
+    }
+
+    public void setNotes(List<String> notes) {
+        this.notes = notes;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (!(o instanceof Meal)) return false;
+
+        Meal meal = (Meal) o;
+
+        return new EqualsBuilder()
+                .append(getId(), meal.getId())
+                .append(getName(), meal.getName())
+                .append(getCategory(), meal.getCategory())
+                .append(getNotes(), meal.getNotes())
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37)
+                .append(getId())
+                .append(getName())
+                .append(getCategory())
+                .append(getNotes())
+                .toHashCode();
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+}
diff --git a/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/PageInfo.java b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/PageInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..e85a6f187e8bb99fef83db36a77856a40352e7bb
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/PageInfo.java
@@ -0,0 +1,87 @@
+package de.fhro.inf.prg3.a11.openmensa.model;
+
+import okhttp3.Headers;
+import retrofit2.Response;
+
+import java.util.function.Function;
+
+/**
+ * Utility class to handle HTTP response header containing the pagination information
+ * required to fetch all pages of an entity
+ *
+ * @author Peter Kurfer
+ */
+
+public final class PageInfo {
+
+    private static final String TOTAL_PAGES_HEADER = "X-Total-Pages";
+    private static final String TOTAL_ITEM_COUNT_HEADER = "X-Total-Count";
+    private static final String ITEM_COUNT_PER_PAGE_HEADER = "X-Per-Page";
+    private static final String CURRENT_PAGE_INDEX_HEADER = "X-Current-Page";
+
+    private final int totalCountOfPages;
+    private final int totalCountOfItems;
+    private final int itemCountPerPage;
+    private final int currentPageIndex;
+
+    /**
+     * Default constructor
+     * only used by 'factory method' `extractFromReponse(...)`
+     */
+    private PageInfo(int totalCountOfPages, int totalCountOfItems, int itemCountPerPage, int currentPageIndex) {
+        this.totalCountOfPages = totalCountOfPages;
+        this.totalCountOfItems = totalCountOfItems;
+        this.itemCountPerPage = itemCountPerPage;
+        this.currentPageIndex = currentPageIndex;
+    }
+
+    /**
+     * @return total count of pages or -1 if required header was not present
+     */
+    public int getTotalCountOfPages() {
+        return totalCountOfPages;
+    }
+
+    /**
+     * @return total item count or -1 if required header was not present
+     */
+    public int getTotalCountOfItems() {
+        return totalCountOfItems;
+    }
+
+    /**
+     * @return item count on every page or -1 if required header was not present
+     */
+    public int getItemCountPerPage() {
+        return itemCountPerPage;
+    }
+
+    /**
+     * @return current page index or -1 if required header was not present
+     */
+    public int getCurrentPageIndex() {
+        return currentPageIndex;
+    }
+
+    /**
+     * Factory method to create a PageInfo by parsing the headers of a response which includes the required information
+     *
+     * @param apiResponse response object to parse
+     * @param <T> concrete response type
+     * @return PageInfo instance - may contain default fallback values see getters
+     */
+    public static <T extends Response<?>> PageInfo extractFromResponse(T apiResponse) {
+        Headers headers = apiResponse.headers();
+        int totalPages = extractFromHeaders(headers, TOTAL_PAGES_HEADER, -1);
+        int totalItemCount = extractFromHeaders(headers, TOTAL_ITEM_COUNT_HEADER, -1);
+        int itemCountPerPage = extractFromHeaders(headers, ITEM_COUNT_PER_PAGE_HEADER, -1);
+        int currentPageIndex = extractFromHeaders(headers, CURRENT_PAGE_INDEX_HEADER, -1);
+
+        return new PageInfo(totalPages, totalItemCount, itemCountPerPage, currentPageIndex);
+    }
+
+    private static int extractFromHeaders(Headers headers, String headerName, int fallback) {
+        String headerValue = headers.get(headerName);
+        return headerValue == null ? fallback : Integer.parseInt(headerValue);
+    }
+}
diff --git a/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/State.java b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/State.java
new file mode 100644
index 0000000000000000000000000000000000000000..352a84ba6e4616db1affe0ad194b23497d623498
--- /dev/null
+++ b/src/main/java/de/fhro/inf/prg3/a11/openmensa/model/State.java
@@ -0,0 +1,66 @@
+package de.fhro.inf.prg3.a11.openmensa.model;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+/**
+ * Data transfer object for a state of a canteen retrieved from the OpenMensaAPI
+ * @author Peter Kurfer
+ */
+
+public final class State {
+
+    private String date;
+    private boolean closed = true;
+
+    public State() {
+        date = "";
+    }
+
+    public String getDate() {
+        return date;
+    }
+
+    public void setDate(String date) {
+        this.date = date;
+    }
+
+    public boolean isClosed() {
+        return closed;
+    }
+
+    public void setClosed(boolean closed) {
+        this.closed = closed;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (!(o instanceof State)) return false;
+
+        State state = (State) o;
+
+        return new EqualsBuilder()
+                .append(isClosed(), state.isClosed())
+                .append(getDate(), state.getDate())
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37)
+                .append(getDate())
+                .append(isClosed())
+                .toHashCode();
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("date", date)
+                .append("closed", closed)
+                .toString();
+    }
+}
diff --git a/src/test/java/de/fhro/inf/prg3/a11/tests/OpenMensaApiTests.java b/src/test/java/de/fhro/inf/prg3/a11/tests/OpenMensaApiTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ad44f2437ddd0b1c5d0c965fdd68b0d2a4cfc64
--- /dev/null
+++ b/src/test/java/de/fhro/inf/prg3/a11/tests/OpenMensaApiTests.java
@@ -0,0 +1,84 @@
+package de.fhro.inf.prg3.a11.tests;
+
+import de.fhro.inf.prg3.a11.openmensa.OpenMensaAPI;
+import de.fhro.inf.prg3.a11.openmensa.OpenMensaAPIService;
+import de.fhro.inf.prg3.a11.openmensa.model.Canteen;
+import de.fhro.inf.prg3.a11.openmensa.model.PageInfo;
+import de.fhro.inf.prg3.a11.openmensa.model.State;
+import org.junit.jupiter.api.Test;
+import retrofit2.Response;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Peter Kurfer
+ */
+
+class OpenMensaApiTests {
+
+    private static final int FHRO_MENSA_ID = 229;
+    private static final String OPEN_MENSA_DATE_FORMAT = "yyyy-MM-dd";
+    private final SimpleDateFormat dateFormat;
+    private final OpenMensaAPI openMensaAPI;
+    private final Calendar calendar;
+
+    OpenMensaApiTests() {
+        dateFormat = new SimpleDateFormat(OPEN_MENSA_DATE_FORMAT, Locale.getDefault());
+        openMensaAPI = OpenMensaAPIService.getInstance().getOpenMensaAPI();
+        calendar = Calendar.getInstance();
+    }
+
+    @Test
+    void testGetFirstMensaPage() throws ExecutionException, InterruptedException {
+        Response<List<Canteen>> canteensResponse = openMensaAPI.getCanteens().get();
+
+        assertNotNull(canteensResponse);
+        assertNotNull(canteensResponse.body());
+        assertNotEquals(0, canteensResponse.body().size());
+
+        for (Canteen c : canteensResponse.body()) {
+            System.out.println(c.getName());
+        }
+    }
+
+    @Test
+    void testExtractPageInfo() throws ExecutionException, InterruptedException {
+        Response<List<Canteen>> canteensResponse = openMensaAPI.getCanteens().get();
+
+        PageInfo pageInfo = PageInfo.extractFromResponse(canteensResponse);
+
+        assertNotNull(pageInfo);
+        assertEquals(canteensResponse.body().size(), pageInfo.getItemCountPerPage());
+        assertTrue(pageInfo.getTotalCountOfItems() > 0);
+        assertTrue(pageInfo.getTotalCountOfPages() > 0);
+        assertTrue(pageInfo.getCurrentPageIndex() > 0);
+    }
+
+    @Test
+    void testGetMensaState() throws ExecutionException, InterruptedException {
+        State mensaState = openMensaAPI.getMensaState(FHRO_MENSA_ID, dateFormat.format(calendar.getTime())).get();
+
+        assertNotNull(mensaState);
+    }
+
+    @Test
+    void testGetMultiplePages() throws ExecutionException, InterruptedException {
+        Response<List<Canteen>> firstPage = openMensaAPI.getCanteens().get();
+
+        assertNotNull(firstPage);
+        assertNotNull(firstPage.body());
+
+        PageInfo pageInfo = PageInfo.extractFromResponse(firstPage);
+        for(int i = 2; i <= pageInfo.getTotalCountOfPages(); i++) {
+            List<Canteen> canteensPage = openMensaAPI.getCanteens(i).get();
+            assertNotNull(canteensPage);
+            assertNotEquals(0, canteensPage.size());
+        }
+    }
+}