diff --git a/pom.xml b/pom.xml
index 0e8382b993894da6738068e5f5705536e79edac9..19e61cf85b4f2e726ff0cbd2f10bd19de0af5257 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,12 @@
             <version>2.17.1</version>
         </dependency>
 
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.10.1</version>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/src/main/java/mi/hdm/GuiController.java b/src/main/java/mi/hdm/GuiController.java
new file mode 100644
index 0000000000000000000000000000000000000000..28651a997ff43d1132c343afc757d54517c9ca02
--- /dev/null
+++ b/src/main/java/mi/hdm/GuiController.java
@@ -0,0 +1,54 @@
+package mi.hdm;
+
+import javafx.application.Application;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import mi.hdm.controllers.View;
+import mi.hdm.filesystem.FileManager;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class GuiController extends Application {
+    private final static Logger log = LogManager.getLogger(GuiController.class);
+
+    private static Stage stage;
+    private static TastyPages model;
+
+    public static void main(String[] args) {
+        log.info("Starting TastyPages");
+        try {
+            model = FileManager.deserializeFromFilesystem();
+        } catch (Exception e) {
+            log.fatal("Internal exception, check filepaths and URIs");
+            e.printStackTrace();
+            System.exit(1);
+        }
+        launch(args);
+        log.info("Shutting down application");
+        log.info("Saving all data");
+        FileManager.serializeToFile(model);
+    }
+
+    @Override
+    public void start(Stage stage) throws Exception {
+        GuiController.stage = stage;
+        final View startupView = View.MAIN;
+
+        Parent parent = startupView.getScene();
+        final Scene scene = new Scene(parent, 1400, 800);
+
+        stage.setTitle(startupView.getWindowTitle());
+        stage.setMaximized(true); //open scene in full screen
+        stage.setScene(scene);
+        stage.show();
+    }
+
+    public static TastyPages getModel() {
+        return model;
+    }
+
+    public static Stage getApplicationStage() {
+        return stage;
+    }
+}
diff --git a/src/main/java/mi/hdm/TastyPages.java b/src/main/java/mi/hdm/TastyPages.java
index 8e29204005ebcbf71a04cb53ee83d79cacc2bd2a..ea65bde8ab36b87b3674fb189c86ce13ea28ef7e 100644
--- a/src/main/java/mi/hdm/TastyPages.java
+++ b/src/main/java/mi/hdm/TastyPages.java
@@ -1,57 +1,76 @@
 package mi.hdm;
 
-import javafx.application.Application;
-import javafx.scene.Parent;
-import javafx.scene.Scene;
-import javafx.stage.Stage;
-import mi.hdm.controllers.View;
 import mi.hdm.mealPlan.MealPlan;
-import mi.hdm.recipes.*;
+import mi.hdm.recipes.CategoryManager;
+import mi.hdm.recipes.IngredientManager;
+import mi.hdm.recipes.RecipeManager;
 import mi.hdm.shoppingList.ShoppingList;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class TastyPages extends Application {
-    //TODO: instantiate all dependencies HERE! The getScene() method from View.java should go here as well for dependency injection
-    public final static RecipeManager recipeManager = new RecipeManager();
-    public final static ShoppingList shoppingList = new ShoppingList();
-    public final static CategoryManager categoryManager = new CategoryManager(List.of(new Category("Mittagessen", 0xFF4040), new Category("mjam", 0x00FF00)));
-    public final static MealPlan mealPlan = new MealPlan(new HashMap<>());
+/**
+ * This class contains all the services in the application: recipe manager, ingredient manager, shopping list,
+ * category manager and meal plan.
+ */
+public class TastyPages {
+    private final RecipeManager recipeManager;
+    private final IngredientManager ingredientManager;
+    private final ShoppingList shoppingList;
+    private final CategoryManager categoryManager;
+    private final MealPlan mealPlan;
 
     private final static Logger log = LogManager.getLogger(TastyPages.class);
 
-    private static Stage stage;
-    public static final View startUpView = View.MAIN;
+    /**
+     * Creates a new instance of the application holding all user data. The parameters are expected to be parsed with the FileManager
+     */
+    public TastyPages(RecipeManager recipeManager, IngredientManager ingredientManager, CategoryManager categoryManager, MealPlan mealPlan, ShoppingList shoppingList) {
+        this.recipeManager = recipeManager;
+        this.ingredientManager = ingredientManager;
+        this.categoryManager = categoryManager;
+        this.mealPlan = mealPlan;
+        this.shoppingList = shoppingList;
+        log.info("Loaded TastyPages with all dependencies.");
+    }
 
-    public static void main(String[] args) {
-        log.info("Starting TastyPages");
+    public TastyPages(IngredientManager ingredientManager) {
+        this.ingredientManager = ingredientManager;
+        this.recipeManager = new RecipeManager();
+        this.categoryManager = new CategoryManager();
+        this.mealPlan = new MealPlan(recipeManager);
+        this.shoppingList = new ShoppingList(recipeManager);
+        log.info("Loaded TastyPages with ingredient manager");
+    }
 
-        recipeManager.addRecipe(new Recipe("Mein erstes Rezept", Map.of(new Ingredient(Measurement.GRAM, "Mehl", new NutritionTable(100, 20, 8, 14, 2.5, 3)), 100), "Description", List.of("joa"), List.of(categoryManager.getAllCategories().get(1)), 40));
-        recipeManager.addRecipe(new Recipe("Mein letztes Rezept", Map.of(new Ingredient(Measurement.GRAM, "Zucker", new NutritionTable(100, 500, 8, 14, 2.5, 3)), 100), "Description", List.of("joa"), List.of(categoryManager.getAllCategories().get(0)), 40));
+    /**
+     * Creates a new, completely empty instance of the application. This constructor is not expected to be used.
+     */
+    public TastyPages() {
+        this.recipeManager = new RecipeManager();
+        this.ingredientManager = new IngredientManager();
+        this.categoryManager = new CategoryManager();
+        this.mealPlan = new MealPlan(recipeManager);
+        this.shoppingList = new ShoppingList(recipeManager);
+        log.info("Created empty instance of TastyPages");
+    }
 
-        launch(args);
+    public RecipeManager getRecipeManager() {
+        return recipeManager;
     }
 
-    public static Stage getApplicationStage() {
-        return stage;
+    public IngredientManager getIngredientManager() {
+        return ingredientManager;
     }
 
-    @Override
-    public void start(Stage stage) throws IOException {
-        TastyPages.stage = stage;
-        final View startupView = View.MAIN;
+    public ShoppingList getShoppingList() {
+        return shoppingList;
+    }
 
-        Parent parent = startupView.getScene();
-        final Scene scene = new Scene(parent, 1400, 800);
+    public CategoryManager getCategoryManager() {
+        return categoryManager;
+    }
 
-        stage.setTitle(startupView.getWindowTitle());
-        stage.setMaximized(true); //open scene in full screen
-        stage.setScene(scene);
-        stage.show();
+    public MealPlan getMealPlan() {
+        return mealPlan;
     }
 }
diff --git a/src/main/java/mi/hdm/components/RecipeVbox.java b/src/main/java/mi/hdm/components/RecipeVbox.java
index c393f6b9dc5b804b92b7370cf6d64e613f21be68..7577123fa6652913561aeb510befab6402383b20 100644
--- a/src/main/java/mi/hdm/components/RecipeVbox.java
+++ b/src/main/java/mi/hdm/components/RecipeVbox.java
@@ -5,6 +5,7 @@ import javafx.scene.control.Label;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.VBox;
 import mi.hdm.recipes.Category;
+import mi.hdm.recipes.CategoryManager;
 import mi.hdm.recipes.Recipe;
 
 import java.util.ArrayList;
@@ -16,7 +17,7 @@ import java.util.List;
 public class RecipeVbox extends VBox {
     private final Recipe recipe;
 
-    public RecipeVbox(Recipe recipe) {
+    public RecipeVbox(Recipe recipe, CategoryManager categoryManager) {
         super();
         this.recipe = recipe;
 
@@ -30,20 +31,20 @@ public class RecipeVbox extends VBox {
         List<Label> categoryLabels = new ArrayList<>();
 
         //add a maximum of 3 categories to the recipe tile
-        for (int i = 0; i < Math.min(recipe.getCategories().size(), 3); i++) {
-            Category c = recipe.getCategories().get(i);
-            Label l = new Label(c.getName());
+        List<Category> categories = categoryManager.getCategoriesFromKeys(recipe.getCategoryCodes());
+        for (int i = 0; i < Math.min(recipe.getCategoryCodes().size(), 3); i++) {
+            Label l = new Label(categories.get(i).getName());
             //TODO: make border radius work on the styling
-            l.setStyle("-fx-background-color: " + c.getColourCode() + ";" +
+            l.setStyle("-fx-background-color: " + categories.get(i).getColourCode() + ";" +
                     "-fx-padding: 3px;" +
                     "-fx-border-radius: 5px;"
             );
             categoryLabels.add(l);
         }
-        HBox categories = new HBox();
-        categories.getChildren().addAll(categoryLabels);
+        HBox categoryHbox = new HBox();
+        categoryHbox.getChildren().addAll(categoryLabels);
 
-        this.getChildren().addAll(recipeName, recipeDescription, categories);
+        this.getChildren().addAll(recipeName, recipeDescription, categoryHbox);
 
         this.setPrefWidth(180);
         this.setPrefHeight(250);
diff --git a/src/main/java/mi/hdm/controllers/BaseController.java b/src/main/java/mi/hdm/controllers/BaseController.java
index e0a2bc92126eaaf3c0bb87dc432303b54b5be9b2..2d6abfa2cb9a55870b8252e97d96fc8b2543917c 100644
--- a/src/main/java/mi/hdm/controllers/BaseController.java
+++ b/src/main/java/mi/hdm/controllers/BaseController.java
@@ -4,7 +4,7 @@ import javafx.scene.Parent;
 import javafx.scene.Scene;
 import javafx.scene.layout.AnchorPane;
 import javafx.stage.Stage;
-import mi.hdm.TastyPages;
+import mi.hdm.GuiController;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -12,13 +12,13 @@ import java.io.IOException;
 
 public abstract class BaseController {
     private final static Logger log = LogManager.getLogger(BaseController.class);
-    private final static Stage stage = TastyPages.getApplicationStage();
 
     protected Scene currentScene;
     private static View currentView = TastyPages.startUpView;
 
     protected void changeScene(final View newScene) {
         if (newScene == currentView) { return; }
+        final Stage stage = GuiController.getApplicationStage();
         try {
             boolean wasMaximized = stage.isMaximized();
             double sizeX = stage.getWidth();
diff --git a/src/main/java/mi/hdm/controllers/HeaderController.java b/src/main/java/mi/hdm/controllers/HeaderController.java
index cb6c76bb896df29af8a22213cf702138f08e268e..fa60084bea511a0d349c6b4bec33dbde1f6e3bc0 100644
--- a/src/main/java/mi/hdm/controllers/HeaderController.java
+++ b/src/main/java/mi/hdm/controllers/HeaderController.java
@@ -2,14 +2,14 @@ package mi.hdm.controllers;
 
 import javafx.fxml.FXML;
 import javafx.scene.control.TextField;
+import mi.hdm.recipes.IngredientManager;
 import mi.hdm.recipes.Recipe;
 import mi.hdm.recipes.RecipeManager;
 import mi.hdm.recipes.RecipeSearch;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.util.List;
-
+import java.util.Collection;
 
 public class HeaderController extends BaseController {
     private final static Logger log = LogManager.getLogger(HeaderController.class);
@@ -18,10 +18,13 @@ public class HeaderController extends BaseController {
     private TextField searchBox;
 
     private final RecipeManager recipeManager;
-    private List<Recipe> lastSearchResults;
+    private final IngredientManager ingredientManager;
+
+    private Collection<Recipe> lastSearchResults;
 
-    public HeaderController(RecipeManager recipeManager) {
+    public HeaderController(RecipeManager recipeManager, IngredientManager ingredientManager) {
         this.recipeManager = recipeManager;
+        this.ingredientManager = ingredientManager;
         lastSearchResults = recipeManager.getRecipes();
     }
 
@@ -44,8 +47,8 @@ public class HeaderController extends BaseController {
     public void searchByQuery() {
         String query = searchBox.getText();
         log.debug("User submitted search box");
-        //RecipeSearch recipeSearch = new RecipeSearch(recipeManager.getRecipes()); //not needed when methods are static
-        lastSearchResults = RecipeSearch.searchByQuery(recipeManager.getRecipes(), query);
+        RecipeSearch recipeSearch = new RecipeSearch(recipeManager, ingredientManager);
+        lastSearchResults = recipeSearch.searchByQuery(recipeManager.getRecipes(), query); //TODO: pass last search results
         System.out.println("Results: ");
         lastSearchResults.forEach(result -> System.out.println("   " + result.getName()));
     }
diff --git a/src/main/java/mi/hdm/controllers/MainPageController.java b/src/main/java/mi/hdm/controllers/MainPageController.java
index 4cdba9b21b14f44ff0ccd0b6be8e9d0dc17da0ee..55581fb42c7b9003db87eaea8c8ffbd32abe7db4 100644
--- a/src/main/java/mi/hdm/controllers/MainPageController.java
+++ b/src/main/java/mi/hdm/controllers/MainPageController.java
@@ -18,6 +18,7 @@ public class MainPageController extends BaseController {
     private final static Logger log = LogManager.getLogger(MainPageController.class);
 
     private final RecipeManager recipeManager;
+    private final IngredientManager ingredientManager;
     private final CategoryManager categoryManager;
 
     private final List<CategoryCheckBox> categoryCheckboxesInGui = new ArrayList<>();
@@ -30,8 +31,9 @@ public class MainPageController extends BaseController {
     @FXML
     private HBox categories;
 
-    public MainPageController(RecipeManager recipeManager, CategoryManager categoryManager) {
+    public MainPageController(RecipeManager recipeManager, IngredientManager ingredientManager, CategoryManager categoryManager) {
         this.recipeManager = recipeManager;
+        this.ingredientManager = ingredientManager;
         this.categoryManager = categoryManager;
     }
 
@@ -53,7 +55,7 @@ public class MainPageController extends BaseController {
         currentRecipesInGUI.clear(); //remove all recipes...
 
         for (Recipe recipe : recipes) {
-            RecipeVbox recipeVbox = new RecipeVbox(recipe);
+            RecipeVbox recipeVbox = new RecipeVbox(recipe, categoryManager);
             recipeVbox.setOnMouseClicked(mouseEvent -> System.out.format("User selected '%s'.%n", recipe.getName())); //TODO: this needs to display the recipe view
             currentRecipesInGUI.add(recipeVbox);
         }
@@ -80,7 +82,7 @@ public class MainPageController extends BaseController {
                         .filter(CheckBox::isSelected)
                         .map(CategoryCheckBox::getAssociatedCategory)
                         .toList();
-        //RecipeSearch recipeSearch = new RecipeSearch(recipeManager.getRecipes()); //not needed when methods are static
-        return RecipeSearch.searchByCategory(recipeManager.getRecipes(), currentlySelectedCategories);
+        RecipeSearch recipeSearch = new RecipeSearch(recipeManager, ingredientManager);
+        return recipeSearch.searchByCategory(recipeManager.getRecipes(), currentlySelectedCategories);
     }
 }
diff --git a/src/main/java/mi/hdm/controllers/View.java b/src/main/java/mi/hdm/controllers/View.java
index 471e0364954c27de795a66c372dd3aff7594d2da..22fecfa10f14383daf16fcaf895bb16ee3df46cd 100644
--- a/src/main/java/mi/hdm/controllers/View.java
+++ b/src/main/java/mi/hdm/controllers/View.java
@@ -2,11 +2,8 @@ package mi.hdm.controllers;
 
 import javafx.fxml.FXMLLoader;
 import javafx.scene.Parent;
+import mi.hdm.GuiController;
 import mi.hdm.TastyPages;
-import mi.hdm.mealPlan.MealPlan;
-import mi.hdm.recipes.CategoryManager;
-import mi.hdm.recipes.RecipeManager;
-import mi.hdm.shoppingList.ShoppingList;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -20,10 +17,7 @@ public enum View {
 
     private static final Logger log = LogManager.getLogger(View.class);
 
-    private final RecipeManager recipeManager = TastyPages.recipeManager;
-    private final ShoppingList shoppingList = TastyPages.shoppingList;
-    private final MealPlan mealPlan = TastyPages.mealPlan;
-    private final CategoryManager categoryManager = TastyPages.categoryManager;
+    private final static TastyPages model = GuiController.getModel();
 
     private final String path;
     private final String windowTitle;
@@ -41,19 +35,19 @@ public enum View {
         //set the correct controller factory for views (necessary to enable dependency injection)
         switch (this) {
             case MAIN -> loader.setControllerFactory((callback) ->
-                    new MainPageController(recipeManager, categoryManager)
+                    new MainPageController(model.getRecipeManager(), model.getIngredientManager(), model.getCategoryManager())
             );
 
             case RECIPE_VIEW -> loader.setControllerFactory((callback) ->
-                    new RecipeViewController(recipeManager)
+                    new RecipeViewController(model.getRecipeManager())
             );
 
             case SHOPPINGLIST_VIEW -> loader.setControllerFactory((callback) ->
-                    new ShoppingListViewController(recipeManager, shoppingList)
+                    new ShoppingListViewController(model.getRecipeManager(), model.getShoppingList())
             );
 
             case MEALPLAN_VIEW -> loader.setControllerFactory((callback) ->
-                    new MealPlanController(mealPlan)
+                    new MealPlanController(model.getMealPlan())
             );
         }
         log.debug("Set controller factory for {}", windowTitle);
@@ -64,4 +58,8 @@ public enum View {
     public String getWindowTitle() {
         return windowTitle;
     }
+
+    public String getPath() {
+        return path;
+    }
 }
diff --git a/src/main/java/mi/hdm/controllers/ViewComponent.java b/src/main/java/mi/hdm/controllers/ViewComponent.java
index 940c70e7606d4391585fb6e17df2e5fad41c518b..c5391cf45cdd1a2f01a43f4af973152d9b19abeb 100644
--- a/src/main/java/mi/hdm/controllers/ViewComponent.java
+++ b/src/main/java/mi/hdm/controllers/ViewComponent.java
@@ -2,8 +2,8 @@ package mi.hdm.controllers;
 
 import javafx.fxml.FXMLLoader;
 import javafx.scene.Parent;
+import mi.hdm.GuiController;
 import mi.hdm.TastyPages;
-import mi.hdm.recipes.RecipeManager;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -16,8 +16,7 @@ public enum ViewComponent {
 
     private final String path;
 
-    //dependencies
-    private final RecipeManager recipeManager = TastyPages.recipeManager;
+    private static final TastyPages app = GuiController.getModel();
 
     ViewComponent(String path) {
         this.path = path;
@@ -30,7 +29,7 @@ public enum ViewComponent {
 
         switch (this) {
             case HEADER -> loader.setControllerFactory((callback) ->
-                    new HeaderController(recipeManager)
+                    new HeaderController(app.getRecipeManager(), app.getIngredientManager())
             );
         }
 
diff --git a/src/main/java/mi/hdm/filesystem/CSVParser.java b/src/main/java/mi/hdm/filesystem/CSVParser.java
index c3c9c65a8bf469f3c064c777d531091839da159a..8888ec8c6c5067a1e648e4c097e5ccc9fd8e3eea 100644
--- a/src/main/java/mi/hdm/filesystem/CSVParser.java
+++ b/src/main/java/mi/hdm/filesystem/CSVParser.java
@@ -28,6 +28,11 @@ public class CSVParser {
     public List<Ingredient> getIngredientsFromCSV(String filepath, char split, String... extract) throws IOException {
         log.info("Trying to read ingredients from CSV: {}", filepath);
 
+        if (extract.length == 0) {
+            log.fatal("Arguments for parser are incorrect.");
+            throw new InvalidPropertiesFormatException("Arguments to extract are length 0");
+        }
+
         //try-block with automatic resource management
         try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
             final String header = reader.readLine(); //read first line of CSV
@@ -46,11 +51,11 @@ public class CSVParser {
 
         } catch (FileNotFoundException fileNotFoundException) {
             final Path filename = Path.of(filepath).getFileName();
-            log.error("File '{}' not found at specified filepath: {}.", filename, filepath);
+            log.error("File '{}' not found at specified filepath: '{}'.", filename, filepath);
             fileNotFoundException.printStackTrace();
             throw fileNotFoundException;
         } catch (IOException io) {
-            log.error("An error occurred while reading CSV: {}", filepath);
+            log.error("An error occurred while reading CSV: '{}'", filepath);
             throw io;
         }
     }
@@ -65,7 +70,6 @@ public class CSVParser {
         final List<BigDecimal> nutrition = new ArrayList<>();
 
         for (int i = 1; i < idx.length; i++) {
-            //String element = splitLine.get(idx[i]).split(" ")[0];
             String element = splitLine.get(idx[i]);
             double quantity = parseNumberFromString(element);
             if (getMeasurementFromString(element).equals("mg")) {
@@ -126,8 +130,6 @@ public class CSVParser {
             } else if (!hasDot && numValue.length() > 0 && c == '.') {
                 hasDot = true;
                 numValue.append(c);
-            } else {
-                break;
             }
         }
         return Double.parseDouble(numValue.toString());
diff --git a/src/main/java/mi/hdm/filesystem/FileManager.java b/src/main/java/mi/hdm/filesystem/FileManager.java
index 414b37fb5304873780cfb98988386c8c13e08be1..1ffffe9ad3508907f3b37aa49d3d27df2831694e 100644
--- a/src/main/java/mi/hdm/filesystem/FileManager.java
+++ b/src/main/java/mi/hdm/filesystem/FileManager.java
@@ -1,24 +1,312 @@
 package mi.hdm.filesystem;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import mi.hdm.TastyPages;
+import mi.hdm.mealPlan.MealPlan;
+import mi.hdm.recipes.*;
+import mi.hdm.shoppingList.ShoppingList;
+import mi.hdm.typeAdapters.CategoryManagerTypeAdapter;
+import mi.hdm.typeAdapters.MealPlanTypeAdapter;
+import mi.hdm.typeAdapters.RecipeTypeAdapter;
+import mi.hdm.typeAdapters.ShoppingListTypeAdapter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * Containing methods to serialize and deserialize parts of the app content for saving to / loading from the filesystem
+ */
 public class FileManager {
-    private static final FileManager fileManager = new FileManager();
+    private final static String PATH_TO_DEFAULT_INGREDIENTS = "/data/nutrition.csv";
+    private final static String PATH_TO_USER_DATA = System.getProperty("user.home") + "\\AppData\\Roaming\\TastyPages";
+
+    private final static Logger log = LogManager.getLogger(FileManager.class);
+
+    public static void serializeToFile(TastyPages app) {
+        log.info("Writing user data to '{}'", PATH_TO_USER_DATA);
+        //Serialize ingredient manager
+        serializeIngredientManager(app.getIngredientManager(), Path.of(PATH_TO_USER_DATA, "ingredients.csv"));
+        serializeRecipeManager(app.getRecipeManager(), Path.of(PATH_TO_USER_DATA, "recipes"));
+        serializeCategoryManager(app.getCategoryManager(), Path.of(PATH_TO_USER_DATA, "categories.json"));
+        serializeMealPlan(app.getMealPlan(), Path.of(PATH_TO_USER_DATA, "mealplan.json"));
+        serializeShoppingList(app.getShoppingList(), app.getRecipeManager(), Path.of(PATH_TO_USER_DATA, "shoppingList.json"));
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public static TastyPages deserializeFromFilesystem() throws IOException, URISyntaxException {
+        log.info("Trying to read {}", PATH_TO_USER_DATA);
+        Path userPath = Path.of(PATH_TO_USER_DATA);
+        if (userPath.toFile().mkdirs()) { //If .mkdirs() returns true, that means the folder has not existed before -> in this case, only load default ingredients!
+            log.info("TastyPages folder has been created at {}", userPath);
+            IngredientManager deserializedIngredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder());
+            return new TastyPages(deserializedIngredientManager);
+        } else { //otherwise, read user data
+            log.info("Found TastyPages folder at {}, loading user data from there.", userPath);
+            IngredientManager ingredientManager;
+            RecipeManager recipeManager;
+            CategoryManager categoryManager;
+            MealPlan mealPlan;
+            ShoppingList shoppingList;
+
+            //Deserialize ingredient manager
+            {
+                Path toIngredients = Path.of(userPath + "/ingredients.csv");
+                if (Files.exists(toIngredients)) {
+                    log.info("Loading ingredients from '{}'", toIngredients);
+                    ingredientManager = deserializeIngredientManager(toIngredients.toString());
+                } else {
+                    log.info("Could not find /ingredients.csv, falling back to default ingredients.");
+                    ingredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder());
+                }
+            }
+            //Deserialize recipe manager
+            {
+                Path recipeFolder = Path.of(userPath + "/recipes");
+                if (Files.exists(recipeFolder) && Files.isDirectory(recipeFolder)) {
+                    log.info("Loading recipes from '{}'", recipeFolder);
+                    recipeManager = deserializeRecipeManager(recipeFolder);
+                } else {
+                    log.info("Could not find recipe folder, creating empty instance of RecipeManger");
+                    recipeFolder.toFile().mkdir();
+                    recipeManager = new RecipeManager();
+                }
+            }
+            //Deserialize category manager
+            {
+                Path toCategoryJson = Path.of(userPath + "/categories.json");
+                if (Files.exists(toCategoryJson) && Files.isRegularFile(toCategoryJson)) {
+                    log.info("Loading categories from '{}'", toCategoryJson);
+                    String fileContent = Files.readString(toCategoryJson);
+                    categoryManager = deserializeCategoryManager(fileContent);
+                } else {
+                    log.info("Could not find /categories.json, creating empty instance of CategoryManager");
+                    toCategoryJson.toFile().createNewFile();
+                    categoryManager = new CategoryManager();
+                }
+            }
+            //Deserialize meal plan
+            {
+                Path toMealPlanJson = Path.of(userPath + "/mealplan.json");
+                if (Files.exists(toMealPlanJson) && Files.isRegularFile(toMealPlanJson)) {
+                    log.info("Loading meal plan from '{}'", toMealPlanJson);
+                    String fileContent = Files.readString(toMealPlanJson);
+                    mealPlan = deserializeMealPlan(fileContent, recipeManager);
+                } else {
+                    log.info("Could not find /mealplan.json, creating empty instance of MealPlan");
+                    toMealPlanJson.toFile().createNewFile();
+                    mealPlan = new MealPlan(recipeManager);
+                }
+            }
+            //Deserialize shopping list
+            {
+                Path toShoppingListJson = Path.of(userPath + "/shoppingList.json");
+                if (Files.exists(toShoppingListJson) && Files.isRegularFile(toShoppingListJson)) {
+                    log.info("Loading shopping list from '{}'", toShoppingListJson);
+                    String fileContent = Files.readString(toShoppingListJson);
+                    shoppingList = deserializeShoppingList(fileContent, recipeManager);
+                } else {
+                    log.info("Could not find /shoppingList.json, creating empty shopping list");
+                    toShoppingListJson.toFile().createNewFile();
+                    shoppingList = new ShoppingList(recipeManager);
+                }
+            }
+            return new TastyPages(recipeManager, ingredientManager, categoryManager, mealPlan, shoppingList);
+        }
+    }
+
+    private static IngredientManager deserializeIngredientManager(String path) throws IOException {
+        CSVParser parser = new CSVParser();
+        List<Ingredient> ingredients = parser.getIngredientsFromCSV(
+                path,
+                ',',
+                "name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium"
+        );
+        return new IngredientManager(ingredients);
+    }
+
+    private static void serializeIngredientManager(IngredientManager ingredientManager, Path path) {
+        log.info("Writing ingredients to '{}'", path.getFileName());
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile()))) {
+            writer.write("name,calories,carbohydrate,fat,protein,fiber,sodium\n");
+            final List<Ingredient> ingredients = ingredientManager.getAllIngredients().values().stream().toList();
+            for (int i = 0; i < ingredients.size(); i++) {
+                if (i % 200 == 0)
+                    writer.flush(); //flush the writer regularly
+                writer.write(ingredientToCSV(ingredients.get(i)));
+            }
+        } catch (IOException e) {
+            log.error("Exception when writing ingredients to file!");
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static RecipeManager deserializeRecipeManager(Path path) throws IOException {
+        List<Path> recipePaths;
+        try (Stream<Path> stream = Files.list(path)) {
+            recipePaths = stream.toList();
+        }
+
+        List<Recipe> recipes = recipePaths
+                .stream()
+                .map(p -> {
+                    try {
+                        return Files.readString(p);
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                        log.error("Could not read file contents for {}. Check file permissions and content", p);
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .map(FileManager::JSONtoRecipe)
+                .toList();
+        return new RecipeManager(recipes);
+    }
+
+    private static void serializeRecipeManager(RecipeManager recipeManager, Path path) {
+        log.info("Writing all recipes to /recipes");
+        recipeManager.getRecipes()
+                .forEach(recipe -> {
+                            try {
+                                String recipePath = path + "\\" + recipe.getName() + ".json";
+                                Files.writeString(Path.of(recipePath), recipeToJSON(recipe));
+                                log.debug("Wrote recipe '{}' to '{}'", recipe.getName(), recipePath);
+                            } catch (IOException e) { //TODO: is it smart to just swallow this exception?
+                                log.error("Error writing recipe '{}' to file. Recipe will be lost.", recipe.getName());
+                                e.printStackTrace();
+                            }
+                        }
+                );
+    }
+
+    private static CategoryManager deserializeCategoryManager(String json) {
+        if (json.isBlank()) {
+            log.info("JSON file is blank, creating empty instance of Category Manager");
+            return new CategoryManager();
+        }
+        Gson gson = new GsonBuilder().registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter()).create();
+        return gson.fromJson(json, CategoryManager.class);
+    }
+
+    private static void serializeCategoryManager(CategoryManager categoryManager, Path path) {
+        log.info("Writing categories to categories.json");
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter())
+                .create();
+        String json = gson.toJson(categoryManager);
+        try {
+            Files.writeString(path, json);
+        } catch (IOException io) {
+            log.error("Error writing categories to file.");
+            io.printStackTrace();
+            throw new RuntimeException();
+        }
+    }
+
+    private static MealPlan deserializeMealPlan(String json, RecipeManager recipeManager) {
+        if (json.isBlank()) {
+            log.info("JSON file is blank, creating empty instance of Meal Plan");
+            return new MealPlan(recipeManager);
+        }
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(recipeManager))
+                .create();
+        return gson.fromJson(json, MealPlan.class);
+    }
+
+    private static void serializeMealPlan(MealPlan mealPlan, Path path) {
+        Gson gson = new GsonBuilder().
+                registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(null))
+                .create();
+        String json = gson.toJson(mealPlan);
+        try {
+            Files.writeString(path, json);
+        } catch (IOException io) {
+            log.error("Error writing meal plan to file.");
+            io.printStackTrace();
+            throw new RuntimeException();
+        }
+    }
+
+    private static ShoppingList deserializeShoppingList(String json, RecipeManager recipeManager) {
+        if (json.isBlank()) {
+            log.info("JSON file is blank, creating empty instance of Shopping List");
+            return new ShoppingList(recipeManager);
+        }
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager))
+                .create();
+        return gson.fromJson(json, ShoppingList.class);
+    }
 
-    public static FileManager getInstance() {
-        return fileManager;
+    private static void serializeShoppingList(ShoppingList shoppingList, RecipeManager recipeManager, Path path) {
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager))
+                .create();
+        String json = gson.toJson(shoppingList);
+        try {
+            Files.writeString(path, json);
+        } catch (IOException io) {
+            log.error("Error writing shopping list to file.");
+            io.printStackTrace();
+            throw new RuntimeException();
+        }
     }
 
-    public void serializeToFile(TastyPages app) {
+    private static String recipeToJSON(Recipe recipe) {
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(Recipe.class, new RecipeTypeAdapter())
+                .create();
+        return gson.toJson(recipe);
     }
 
-    public TastyPages deserializeFromFile(String filepath) {
-        return null;
+    /**
+     * Used to convert an ingredient into a CSV String seperated by comma. Floating point numbers
+     * will use dots as decimal points
+     *
+     * @return a comma seperated String containing the name of the ingredient as well as the contents of its nutrition table
+     */
+    private static String ingredientToCSV(Ingredient ingredient) {
+        //"name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium"
+        Map<Nutrition, BigDecimal> map = ingredient.getNutritionTable().getTable();
+        return String.format(
+                Locale.ENGLISH,
+                "\"%s\", %f, %f, %f, %f, %f, %f%n",
+                ingredient.getName(),
+                map.get(Nutrition.CALORIES).doubleValue(),
+                map.get(Nutrition.CARBS).doubleValue(),
+                map.get(Nutrition.FAT).doubleValue(),
+                map.get(Nutrition.PROTEINS).doubleValue(),
+                map.get(Nutrition.FIBERS).doubleValue(),
+                map.get(Nutrition.SALT).doubleValue()
+        );
     }
 
-    private void recipeToJSON() {
+    private static Recipe JSONtoRecipe(String json) {
+        Gson gson = new GsonBuilder().registerTypeAdapter(Recipe.class, new RecipeTypeAdapter()).create();
+        return gson.fromJson(json, Recipe.class);
     }
 
-    private void ingredientToCSV() {
+    private static String getAbsolutePathFromResourceFolder() throws URISyntaxException {
+        URL resourceUrl = FileManager.class.getResource(FileManager.PATH_TO_DEFAULT_INGREDIENTS);
+        assert resourceUrl != null;
+        Path path = Paths.get(resourceUrl.toURI());
+        return path.toFile().getAbsolutePath();
     }
 }
diff --git a/src/main/java/mi/hdm/mealPlan/MealPlan.java b/src/main/java/mi/hdm/mealPlan/MealPlan.java
index ef6f68712568be1b04d1ad27315960966d55707e..a3270489dfec5387cb24df178f1e1915ac1ce4d4 100644
--- a/src/main/java/mi/hdm/mealPlan/MealPlan.java
+++ b/src/main/java/mi/hdm/mealPlan/MealPlan.java
@@ -4,6 +4,7 @@ import mi.hdm.exceptions.InvalidMealPlanException;
 import mi.hdm.recipes.NutritionCalculator;
 import mi.hdm.recipes.NutritionTable;
 import mi.hdm.recipes.Recipe;
+import mi.hdm.recipes.RecipeManager;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -14,16 +15,29 @@ import java.util.*;
  * Allow the user to create a meal plan for the week. The user can check out his nutrition scores for the week.
  */
 public class MealPlan {
+    private final Map<LocalDate, String> plan;
+
+    private final RecipeManager recipeManager;
+
     private static final Logger log = LogManager.getLogger(MealPlan.class);
-    private final Map<LocalDate, Recipe> plan;
 
-    public MealPlan(Map<LocalDate, Recipe> plan) {
+    public MealPlan(Map<LocalDate, String> plan, RecipeManager recipeManager) {
+        //TODO: check dates of the map, dates that have already passed or that are too far in the future should be removed before adding them
+        this.recipeManager = recipeManager;
+        assert plan != null;
         if (!plan.isEmpty()) {
-            if (plan.keySet().stream().anyMatch(Objects::isNull))
+            if (plan.keySet().stream().anyMatch(Objects::isNull)) {
                 throw new InvalidMealPlanException("The specified map is invalid (null key)");
+            }
         }
+        this.plan = new HashMap<>(plan);
+        log.info("Created MealPlan with {}/7 days being filled.", this.plan.size());
+    }
 
-        this.plan = plan;
+    public MealPlan(RecipeManager recipeManager) {
+        this.recipeManager = recipeManager;
+        plan = new HashMap<>();
+        log.info("Created empty instance of MealPlan");
     }
 
     public void addRecipeToMealPlan(Recipe recipe, LocalDate date) {
@@ -33,7 +47,19 @@ public class MealPlan {
         if (!isDateValid(date)) {
             throw new InvalidMealPlanException("You can only add recipes between today and in 7 days from now!");
         }
-        plan.put(date, recipe);
+        log.info("Added recipe {} to meal plan.", recipe.getName());
+        plan.put(date, recipe.getUniqueCode());
+    }
+
+    public void addRecipeToMealPlan(String recipeCode, LocalDate date) {
+        if (recipeCode == null || recipeManager.getRecipeByCode(recipeCode) == null) {
+            throw new NullPointerException("Your specified recipe code was null or invalid");
+        }
+        if (!isDateValid(date)) {
+            throw new InvalidMealPlanException("You can only add recipes between today and in 7 days from now!");
+        }
+        log.info("Added recipe {} to meal plan.", recipeManager.getRecipeByCode(recipeCode).getName());
+        plan.put(date, recipeCode);
     }
 
     public void clear(LocalDate date) {
@@ -54,11 +80,19 @@ public class MealPlan {
         plan.clear();
     }
 
-    public Optional<Recipe> getRecipeForDay(LocalDate date) {
+    public Optional<String> getRecipeCodeForDay(LocalDate date) {
         return Optional.ofNullable(plan.get(date));
     }
 
     public Map<LocalDate, Recipe> getAllRecipesFromPlan() {
+        Map<LocalDate, Recipe> result = new HashMap<>();
+        for (LocalDate d : plan.keySet()) {
+            result.put(d, recipeManager.getRecipeByCode(plan.get(d)));
+        }
+        return result;
+    }
+
+    public Map<LocalDate, String> getAllRecipeCodesFromPlan() {
         return plan;
     }
 
@@ -80,4 +114,13 @@ public class MealPlan {
         LocalDate invalid = LocalDate.now().plusDays(7);
         return !(date.isBefore(LocalDate.now()) || invalid.isBefore(date));
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) return true;
+        if (o instanceof MealPlan m) {
+            return m.getAllRecipeCodesFromPlan().equals(this.plan);
+        }
+        return false;
+    }
 }
diff --git a/src/main/java/mi/hdm/recipes/Category.java b/src/main/java/mi/hdm/recipes/Category.java
index e654671eec55e2c1ca6c1aa5046ff0d0e12ebfb9..f5a7fd3bb5d4dfbdb3ae36086850dceca52aed7a 100644
--- a/src/main/java/mi/hdm/recipes/Category.java
+++ b/src/main/java/mi/hdm/recipes/Category.java
@@ -2,23 +2,30 @@ package mi.hdm.recipes;
 
 import mi.hdm.exceptions.InvalidCategoryException;
 
-public class Category {
+import java.util.Objects;
 
+public class Category {
+    private final int code;
     private final String name;
     private final String colourCode; //0x für Hexzahlen
 
-
-    public Category (String name, int colourCode) {
-        if(name == null || name.equals("")) {
+    public Category(String name, int colourCode) {
+        if (name == null || name.equals("")) {
             throw new InvalidCategoryException("Can not add category with name " + name);
         }
-        if(colourCode < 0 || colourCode > 0xFFFFFF) {
+        if (colourCode < 0 || colourCode > 0xFFFFFF) {
             throw new InvalidCategoryException("Can not add category with color code " + colourCode);
         }
         this.name = name;
         this.colourCode = CategoryManager.numberToColorCodeString(colourCode);
+        this.code = Objects.hash(name, colourCode);
     }
 
+    public Category(String name, String colourCode, int code) {
+        this.name = name;
+        this.colourCode = colourCode;
+        this.code = code;
+    }
 
     public String getColourCode() {
         return colourCode;
@@ -41,4 +48,7 @@ public class Category {
         return String.format("Category %s with color code %s", name, colourCode);
     }
 
+    public int getCode() {
+        return code;
+    }
 }
diff --git a/src/main/java/mi/hdm/recipes/CategoryManager.java b/src/main/java/mi/hdm/recipes/CategoryManager.java
index 63fa28b604591ecd9241d0577ce6376ede848a46..addeb2266f4623e5d777ef967d07432b8dd09c2e 100644
--- a/src/main/java/mi/hdm/recipes/CategoryManager.java
+++ b/src/main/java/mi/hdm/recipes/CategoryManager.java
@@ -4,21 +4,30 @@ import mi.hdm.exceptions.InvalidCategoryException;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 public class CategoryManager {
     private static final Logger log = LogManager.getLogger(CategoryManager.class);
 
-    private final List<Category> allCategories;
+    private final Map<Integer, Category> allCategories;
 
     public CategoryManager() {
-        allCategories = new ArrayList<>();
+        allCategories = new HashMap<>();
+        log.info("Created empty instance of CategoryManager");
+    }
+
+    @Deprecated
+    public CategoryManager(Map<Integer, Category> categories) {
+        allCategories = new HashMap<>(categories);
+        log.info("Created instance of CategoryManager with {} categories.", categories.size());
     }
 
     public CategoryManager(List<Category> categories) {
-        allCategories = categories;
+        allCategories = new HashMap<>();
+        categories.forEach(c -> allCategories.put(c.getCode(), c));
     }
 
     /**
@@ -30,20 +39,24 @@ public class CategoryManager {
      */
     public void addCategory(String name, int colourCode) {
         Category c = new Category(name, colourCode);
-        if (getCategoryByName(name).isPresent() || getCategoryByCode(colourCode).isPresent()) {
-            log.error("Category {} not added because it already exists", c.getName());
+        addCategory(c);
+    }
+
+    public void addCategory(Category c) {
+        if (allCategories.get(c.getCode()) != null || getCategoryByName(c.getName()).isPresent() || getCategoryByColourCode(c.getColourCode()).isPresent()) {
+            log.error("Category '{}' not added because it already exists", c.getName());
             throw new InvalidCategoryException("Category already exists.");
         } else {
-            allCategories.add(c);
-            log.info("Category {} added successfully.", c.getName());
+            allCategories.put(c.getCode(), c);
+            log.info("Category '{}' added successfully.", c.getName());
         }
     }
 
     public void deleteCategory(Category c) {
-        log.info("Category {} deleted successfully.", c.getName());
-        if (!allCategories.remove(c)) {
+        if (!allCategories.values().remove(c)) {
             throw new InvalidCategoryException("Category is not listed.");
         }
+        log.info("Category '{}' deleted successfully.", c.getName());
     }
 
     public void deleteCategory(String name) {
@@ -52,29 +65,54 @@ public class CategoryManager {
     }
 
     public List<Category> getAllCategories() {
-        return allCategories;
+        return allCategories.values().stream().toList();
     }
 
     public void clearCategories() {
         allCategories.clear();
-        log.info("List allCategories cleared successfully.");
+        log.info("List of categories cleared successfully.");
     }
 
     private Optional<Category> getCategoryByName(String name) {
-        return allCategories.stream()
+        return allCategories.values().stream()
                 .filter(c -> c.getName().equals(name))
                 .findFirst();
     }
 
-    private Optional<Category> getCategoryByCode(int colourCode) {
-        return allCategories.stream()
-                .filter(c -> c.getColourCode().equals(numberToColorCodeString(colourCode)))
+    private Optional<Category> getCategoryByColourCode(String colourCode) {
+        return allCategories.values().stream()
+                .filter(c -> c.getColourCode().equals(colourCode))
                 .findFirst();
     }
 
+    public Optional<Category> get(Integer key) {
+        return Optional.ofNullable(allCategories.get(key));
+    }
+
+    public List<Category> getCategoriesFromKeys(List<Integer> keys) {
+        return keys
+                .stream()
+                .map(key -> {
+                    Category c = allCategories.get(key);
+                    if (c == null)
+                        throw new InvalidCategoryException("Invalid category: no category exists with key " + key);
+                    return c;
+                })
+                .toList();
+    }
+
     public static String numberToColorCodeString(int colourCode) {
         String intToHex = Integer.toHexString(colourCode);
         intToHex = intToHex.indent(6 - intToHex.length()).replace(" ", "0");
-        return String.format("#%s", intToHex);
+        return ("#" + intToHex).strip();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o instanceof CategoryManager c) {
+            return c.getAllCategories().equals(this.getAllCategories());
+        }
+        return false;
     }
 }
diff --git a/src/main/java/mi/hdm/recipes/Ingredient.java b/src/main/java/mi/hdm/recipes/Ingredient.java
index d07cc2eacca41f3487aac7a98534acf6a6e36553..3b1bc194fc62712602aae1b22498091590948d65 100644
--- a/src/main/java/mi/hdm/recipes/Ingredient.java
+++ b/src/main/java/mi/hdm/recipes/Ingredient.java
@@ -2,7 +2,10 @@ package mi.hdm.recipes;
 
 import mi.hdm.exceptions.InvalidIngredientException;
 
+import java.util.Objects;
+
 public class Ingredient implements RecipeComponent {
+    private final String code;
     private final Measurement unit;
     private final String name;
     private final NutritionTable nutritionTable;
@@ -18,12 +21,15 @@ public class Ingredient implements RecipeComponent {
         this.unit = unit;
         this.name = name;
         this.nutritionTable = nutritionTable;
+        this.code = calculateUniqueCode();
     }
 
+    @Override
     public String getName() {
         return name;
     }
 
+    @Override
     public NutritionTable getNutritionTable() {
         return nutritionTable;
     }
@@ -33,6 +39,16 @@ public class Ingredient implements RecipeComponent {
         return unit;
     }
 
+    @Override
+    public String getUniqueCode() {
+        return code;
+    }
+
+    private String calculateUniqueCode() {
+        int hash = Objects.hash(name, nutritionTable.getTable());
+        return "i" + hash;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o instanceof Ingredient in) {
diff --git a/src/main/java/mi/hdm/recipes/IngredientManager.java b/src/main/java/mi/hdm/recipes/IngredientManager.java
index 909e84a234fec836f9a36c35ad7043502b88f6ea..7d5fe5d8696999026ed853004d049e14de3c47a1 100644
--- a/src/main/java/mi/hdm/recipes/IngredientManager.java
+++ b/src/main/java/mi/hdm/recipes/IngredientManager.java
@@ -4,22 +4,32 @@ import mi.hdm.exceptions.InvalidIngredientException;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+
 public class IngredientManager {
     private static final Logger log = LogManager.getLogger(IngredientManager.class);
 
-    private final List<Ingredient> allIngredients;
+    private final Map<String, Ingredient> allIngredients; //Map key of ingredient to ingredient object
 
     public IngredientManager() {
-        allIngredients = new ArrayList<>();
+        allIngredients = new HashMap<>();
     }
 
     public IngredientManager(List<Ingredient> ingredients) {
-        allIngredients = ingredients;
+        allIngredients = new HashMap<>();
+        ingredients.forEach(i -> allIngredients.put(i.getUniqueCode(), i));
+        log.info("Created ingredient manager with {} ingredients.", allIngredients.keySet().size());
+    }
+
+    public IngredientManager(Map<String, Ingredient> ingredients) {
+        allIngredients = new HashMap<>(ingredients);
+        log.info("Created ingredient manager with {} ingredients.", allIngredients.keySet().size());
     }
 
+
     /**
      * Adds an ingredient if there is no equal ingredient (no ingredient with the same name).
      *
@@ -28,11 +38,11 @@ public class IngredientManager {
      */
     public void addIngredient(Ingredient in) {
         if (getIngredientByName(in.getName()).isPresent()) {
-            log.error("Ingredient {} not added because it already exists.", in.getName());
+            log.error("Ingredient '{}' not added because it already exists.", in.getName());
             throw new InvalidIngredientException("Ingredient already exists.");
         } else {
-            log.info("Ingredient {} added successfully.", in.getName());
-            allIngredients.add(in);
+            log.info("Ingredient '{}' added successfully.", in.getName());
+            allIngredients.put(in.getUniqueCode(), in);
         }
     }
 
@@ -41,28 +51,35 @@ public class IngredientManager {
         addIngredient(in);
     }
 
-    public Ingredient deleteIngredient(int i) {
-        log.info("Ingredient {} deleted successfully.", allIngredients.get(i).getName());
-        return allIngredients.remove(i);
-    }
-
     public void deleteIngredient(String name) {
         Ingredient ingredient = getIngredientByName(name).orElseThrow(() -> new InvalidIngredientException("No ingredient with name " + name));
-        log.info("Ingredient {} deleted successfully.", name);
-        allIngredients.remove(ingredient);
+        log.info("Ingredient '{}' deleted successfully.", name);
+        allIngredients.values().remove(ingredient);
     }
 
     public void clearIngredients() {
-        log.info("List allIngredients cleared successfully.");
+        log.info("List of ingredients cleared successfully.");
         allIngredients.clear();
     }
 
-    public List<Ingredient> getAllIngredients() {
+    public Optional<Ingredient> getIngredient(String code) {
+        return Optional.ofNullable(allIngredients.get(code));
+    }
+
+    public Map<String, Ingredient> getAllIngredients() {
         return allIngredients;
     }
 
+    public Map<Ingredient, Integer> getIngredientsFromKeys(Map<String, Integer> m) {
+        Map<Ingredient, Integer> result = new HashMap<>();
+        for (String key : m.keySet()) {
+            result.put(allIngredients.get(key), m.get(key));
+        }
+        return result;
+    }
+
     private Optional<Ingredient> getIngredientByName(String name) {
-        for (final Ingredient i : allIngredients) {
+        for (final Ingredient i : allIngredients.values()) {
             if (name.equals(i.getName())) {
                 return Optional.of(i);
             }
diff --git a/src/main/java/mi/hdm/recipes/NutritionCalculator.java b/src/main/java/mi/hdm/recipes/NutritionCalculator.java
index 7e7a396dbb1ff319d36b00d7b90aba8ff306bb6f..8c2de0a5e6a51ef362e54fe537c76580fe5607c3 100644
--- a/src/main/java/mi/hdm/recipes/NutritionCalculator.java
+++ b/src/main/java/mi/hdm/recipes/NutritionCalculator.java
@@ -9,7 +9,6 @@ import java.time.LocalDate;
 import java.util.Map;
 
 public class NutritionCalculator {
-
     private static final Logger log = LogManager.getLogger(NutritionCalculator.class);
 
     /**
@@ -17,7 +16,7 @@ public class NutritionCalculator {
      *
      * @return the nutrition table for this recipe based on its ingredients. All nutrition values are added up.
      */
-    public static NutritionTable calculateNutritionTable(Map<RecipeComponent, Integer> ingredients) {
+    public static NutritionTable calculateNutritionTable(Map<RecipeComponent, Integer> ingredients) { //TODO: this needs to accept a map of keys & ing. manager + recipe manager
         if (ingredients == null || ingredients.isEmpty()) {
             log.info("Map ingredients is null or empty. Therefore new nutritionTable() was generated.");
             return NutritionTable.empty();
@@ -31,7 +30,6 @@ public class NutritionCalculator {
                 totalSalt = BigDecimal.ZERO;
 
         for (RecipeComponent entry : ingredients.keySet()) {
-
             final Map<Nutrition, BigDecimal> nutritionTable = entry.getNutritionTable().getTable();
 
             int divisor;
@@ -53,7 +51,6 @@ public class NutritionCalculator {
 
 
     public static NutritionTable calculateNutritionTable(MealPlan mealPlan) {
-
         Map<LocalDate, Recipe> plan = mealPlan.getAllRecipesFromPlan();
 
         double totalCals = 0,
@@ -63,7 +60,6 @@ public class NutritionCalculator {
                 totalFibers = 0,
                 totalSalt = 0;
 
-        //we calculate with double -> still rounding errors! that's why testCalculateNutritionTableFromIngredients failes
         for (Recipe r : plan.values()) {
             final Map<Nutrition, BigDecimal> nutritionTable = r.getNutritionTable().getTable();
             totalCals += nutritionTable.get(Nutrition.CALORIES).doubleValue();
diff --git a/src/main/java/mi/hdm/recipes/NutritionTable.java b/src/main/java/mi/hdm/recipes/NutritionTable.java
index 226f32ee7622d0abe2d7ae7cfce5dc0b19cbed4b..0ef2320c694e17d164aad31fbf4eb7a055cf39fa 100644
--- a/src/main/java/mi/hdm/recipes/NutritionTable.java
+++ b/src/main/java/mi/hdm/recipes/NutritionTable.java
@@ -82,9 +82,15 @@ public class NutritionTable {
 
     @Override
     public String toString() {
-        return "NutritionTable{" +
-                "table=" + table +
-                '}';
+        String nutritionTable = "Nutrition Table";
+
+        final StringBuilder stringBuilder = new StringBuilder(nutritionTable + "\n");
+        stringBuilder.append("-".repeat(23)).append("\n");
+        table.keySet()
+                .forEach(key -> stringBuilder.append(String.format("| %10s : %06.2f |%n", key, table.get(key).doubleValue())));
+        stringBuilder.append("-".repeat(23));
+
+        return stringBuilder.toString();
     }
 
     public static NutritionTable empty() {
diff --git a/src/main/java/mi/hdm/recipes/Recipe.java b/src/main/java/mi/hdm/recipes/Recipe.java
index bfd7207a6fa1e20cd16c5e07cb35a0e71084c4b7..d5c9e824a60a601191ef6bb8b1d5c3827ec71827 100644
--- a/src/main/java/mi/hdm/recipes/Recipe.java
+++ b/src/main/java/mi/hdm/recipes/Recipe.java
@@ -5,19 +5,18 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
+import java.util.stream.Collectors;
 
 public class Recipe implements RecipeComponent {
     private static final Logger log = LogManager.getLogger(Recipe.class);
 
-    private Map<RecipeComponent, Integer> ingredients;
+    private final String code;
     private String name;
+    private Map<String, Integer> ingredients;
     private String description;
     private List<String> preparation;
-    private List<Category> categories;
+    private List<Integer> categories;
     private int preparationTimeMins;
     private NutritionTable nutritionTable;
     private final LocalDateTime creationTime;
@@ -66,15 +65,39 @@ public class Recipe implements RecipeComponent {
             int preparationTimeMins,
             NutritionTable nutritionTable) {
 
-        setIngredients(ingredients);
+        setIngredientFromRecipeComponents(ingredients);
         setDescription(description);
         setPreparation(preparation);
         setNutritionTable(nutritionTable);
         setName(name);
         setPreparationTimeMins(preparationTimeMins);
-        setCategories(categories);
+        setCategoriesFromObjects(categories);
 
         this.creationTime = LocalDateTime.now();
+<<<<<<< src/main/java/mi/hdm/recipes/Recipe.java
+        this.code = calculateUniqueCode();
+    }
+
+    public Recipe(
+            String name,
+            Map<String, Integer> ingredients,
+            String description,
+            List<String> preparation,
+            List<Integer> categories,
+            Integer preparationTimeMins,
+            NutritionTable nutritionTable,
+            LocalDateTime creationTime,
+            String code) {
+
+        this.ingredients = ingredients;
+        setDescription(description);
+        setPreparation(preparation);
+        setNutritionTable(nutritionTable);
+        setName(name);
+        setPreparationTimeMins(preparationTimeMins);
+        setCategories(categories);
+        this.creationTime = creationTime;
+        this.code = code;
         filepathForImage = "C:\\Users\\Lara Blersch\\Documents\\Studium\\SE2\\tasty-pages\\src\\main\\resources\\images\\dish-fork-and-knife.png"; //path to default image which can be changed by the user later
     }
 
@@ -111,21 +134,21 @@ public class Recipe implements RecipeComponent {
         log.info("Preparation set successfully.");
     }
 
-    public List<Category> getCategories() {
+    public List<Integer> getCategoryCodes() {
         return categories;
     }
 
-    public void setCategories(List<Category> categories) {
+    public void setCategories(List<Integer> categories) {
         if (categories == null) {
             categories = new ArrayList<>();
         }
 
-        this.categories = categories;
+        this.categories = new ArrayList<>(categories);
         log.info("Categories set successfully.");
     }
 
     public void addCategory(Category category) {
-        categories.add(category);
+        categories.add(category.getCode());
         log.info("Category {} added successfully.", category.getName());
     }
 
@@ -133,9 +156,9 @@ public class Recipe implements RecipeComponent {
         return preparationTimeMins;
     }
 
-    public void setPreparationTimeMins(int preparationTimeMins) {
-        if (preparationTimeMins < 0) {
-            throw new InvalidRecipeException("PreparationTime must be a positive value");
+    public void setPreparationTimeMins(Integer preparationTimeMins) {
+        if (preparationTimeMins == null || preparationTimeMins < 0) {
+            throw new InvalidRecipeException("Preparation time must be a positive integer");
         }
         this.preparationTimeMins = preparationTimeMins;
     }
@@ -161,20 +184,49 @@ public class Recipe implements RecipeComponent {
         return Measurement.PIECE;
     }
 
-    public Map<RecipeComponent, Integer> getIngredients() {
+    public Map<String, Integer> getIngredients() {
         return ingredients;
     }
 
-    public void setIngredients(Map<RecipeComponent, Integer> ingredients) {
+    public void setIngredientFromRecipeComponents(Map<RecipeComponent, Integer> ingredients) {
+        if (ingredients == null || ingredients.size() == 0) {
+            throw new InvalidRecipeException("Ingredient list can not be null or empty");
+        }
+        this.ingredients = recipeObjectMapToKeyMap(ingredients);
+    }
+
+    public void setIngredientFromKeys(Map<String, Integer> ingredients) {
         if (ingredients == null || ingredients.size() == 0) {
             throw new InvalidRecipeException("Ingredient list can not be null or empty");
         }
         this.ingredients = ingredients;
     }
 
+<<<<<<< src/main/java/mi/hdm/recipes/Recipe.java
+
+    private void setCategoriesFromObjects(List<Category> categories) {
+        if (categories != null) {
+            this.categories = categories.stream()
+                    .filter(Objects::nonNull)
+                    .map(Category::getCode)
+                    .collect(Collectors.toCollection(ArrayList::new));
+        } else {
+            this.categories = new ArrayList<>();
+        }
+    }
+
+
+    private Map<String, Integer> recipeObjectMapToKeyMap(Map<RecipeComponent, Integer> map) {
+        Map<String, Integer> result = new HashMap<>();
+        for (RecipeComponent c : map.keySet()) {
+            result.put(c.getUniqueCode(), map.get(c));
+        }
+        return result;
+    
     public void setImage(String path) {
         filepathForImage = path;
     }
+=======
 
     @Override
     public boolean equals(Object o) {
@@ -189,4 +241,14 @@ public class Recipe implements RecipeComponent {
         String desc = description == null ? "No description" : description;
         return String.format("Recipe: %s%n--------%n%s%n%nCreated: %s%n", name, desc, creationTime);
     }
+
+    @Override
+    public String getUniqueCode() {
+        return code;
+    }
+
+    private String calculateUniqueCode() {
+        int hash = Objects.hash(name, creationTime);
+        return "r" + hash;
+    }
 }
diff --git a/src/main/java/mi/hdm/recipes/RecipeComponent.java b/src/main/java/mi/hdm/recipes/RecipeComponent.java
index 0e147423db109a518bf5630729190a3bb086a2d5..bd85f8849061fc1063a039632066cc60267607c1 100644
--- a/src/main/java/mi/hdm/recipes/RecipeComponent.java
+++ b/src/main/java/mi/hdm/recipes/RecipeComponent.java
@@ -7,4 +7,11 @@ public interface RecipeComponent {
     NutritionTable getNutritionTable();
 
     Measurement getMeasurement();
+
+    /**
+     * The code is generated using by hashing fields of the object. It will be generated on object creation and never change.
+     *
+     * @return Recipes will return "r"+hash, ingredients will return "i"+hash
+     */
+    String getUniqueCode();
 }
diff --git a/src/main/java/mi/hdm/recipes/RecipeManager.java b/src/main/java/mi/hdm/recipes/RecipeManager.java
index 1ec73070920b48a5928c26eb215f679d636609e5..2dd8cb8978b611e2a49e57c399f68055c4d23862 100644
--- a/src/main/java/mi/hdm/recipes/RecipeManager.java
+++ b/src/main/java/mi/hdm/recipes/RecipeManager.java
@@ -4,28 +4,26 @@ import mi.hdm.exceptions.InvalidRecipeException;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 public class RecipeManager {
     //TODO observable array list?
     private static final Logger log = LogManager.getLogger(RecipeManager.class);
 
-    private final List<Recipe> recipes;
-    private final CategoryManager categories;
-    private final IngredientManager ingredients;
+    private final Map<String, Recipe> allRecipes;
 
-    public RecipeManager(List<Recipe> recipes, CategoryManager categories, IngredientManager ingredients) {
-        this.recipes = recipes;
-        this.categories = categories;
-        this.ingredients = ingredients;
+    public RecipeManager(List<Recipe> recipes) {
+        this.allRecipes = new HashMap<>();
+        recipes.forEach(i -> allRecipes.put(i.getName(), i));
+        log.info("Loaded recipe manager with {} recipes", allRecipes.keySet().size());
     }
 
     public RecipeManager() {
-        recipes = new ArrayList<>();
-        categories = new CategoryManager();
-        ingredients = new IngredientManager();
+        allRecipes = new HashMap<>();
+        log.info("Created empty instance of RecipeManager");
     }
 
     public void addRecipe(Recipe recipe) {
@@ -33,32 +31,31 @@ public class RecipeManager {
             log.error("Recipe '{}' not added because another recipe with the same name already exists", recipe.getName());
             throw new InvalidRecipeException("Recipe with this name already exists.");
         }
-        recipes.add(recipe);
-        log.info("Recipe {} added successfully.", recipe.getName());
+        allRecipes.put(recipe.getUniqueCode(), recipe);
+        log.info("Recipe '{}' added successfully.", recipe.getName());
     }
 
-    public Recipe deleteRecipe(int i) {
-        log.info("Recipe {} deleted successfully.", recipes.get(i).getName());
-        return recipes.remove(i);
+    public Recipe getRecipeByCode(String code) {
+        return allRecipes.get(code);
     }
 
     public void deleteRecipe(String name) {
         Recipe r = getRecipeByName(name).orElseThrow(() -> new InvalidRecipeException("A recipe with this name does not exist"));
-        log.info("Recipe {} deleted successfully.", name);
-        recipes.remove(r);
+        log.info("Recipe '{}' deleted successfully.", name);
+        allRecipes.values().remove(r);
     }
 
     // Exception vs return false: What's the best approach?
     // -> false for normal situations, exceptions for real faults which should not occur
     public void deleteRecipe(Recipe r) {
-        if (!recipes.remove(r)) {
+        if (!allRecipes.values().remove(r)) {
             throw new InvalidRecipeException("Recipe is not listed.");
         }
         log.info("Recipe deleted successfully.");
     }
 
     private Optional<Recipe> getRecipeByName(String name) {
-        for (final Recipe r : recipes) {
+        for (final Recipe r : allRecipes.values()) {
             if (name.equals(r.getName())) {
                 return Optional.of(r);
             }
@@ -66,7 +63,11 @@ public class RecipeManager {
         return Optional.empty();
     }
 
+    public void clear() {
+        allRecipes.clear();
+    }
+
     public List<Recipe> getRecipes() {
-        return recipes;
+        return allRecipes.values().stream().toList();
     }
 }
diff --git a/src/main/java/mi/hdm/recipes/RecipeSearch.java b/src/main/java/mi/hdm/recipes/RecipeSearch.java
index 7a770088e676c7828cd42f3066f6338797dd7197..2e9cb343a7afd4d2119de6b3f35ad065b22dc9ff 100644
--- a/src/main/java/mi/hdm/recipes/RecipeSearch.java
+++ b/src/main/java/mi/hdm/recipes/RecipeSearch.java
@@ -7,10 +7,17 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class RecipeSearch {
-    //private final List<Recipe> recipesToSearch; //not needed when methods are static
+<<<<<<< src/main/java/mi/hdm/recipes/RecipeSearch.java
+    //TODO: lets make these methods static, no need to instantiate a new object every time
     private static final Logger log = LogManager.getLogger(RecipeSearch.class);
 
-    //public RecipeSearch (List<Recipe> recipesToSearch) { this.recipesToSearch = recipesToSearch; }  //not needed when methods are static
+    private final RecipeManager recipeManager;
+    private final IngredientManager ingredientManager;
+
+    public RecipeSearch(RecipeManager recipeManager, IngredientManager ingredientManager) {
+        this.recipeManager = recipeManager;
+        this.ingredientManager = ingredientManager;
+    }
 
     /**
      * A method to search a list of recipes for keywords.
@@ -19,7 +26,7 @@ public class RecipeSearch {
      * @param query a String with keywords to search for
      * @return a list of Recipes which contain one or several of the keywords in its name or its ingredients
      */
-    public static List<Recipe> searchByQuery(List<Recipe> recipesToSearch, String query) {
+    public List<Recipe> searchByQuery(List<Recipe> recipesToSearch, String query) {
         if (query == null) return recipesToSearch;
         query = query.replaceAll("[.,!+?;-]", " ").trim();
         if (query.isBlank()) return recipesToSearch;
@@ -33,9 +40,13 @@ public class RecipeSearch {
                 if (r.getName().toLowerCase().contains(l)) {  //check if recipe name contains words from query (convert name of recipe to lowercase too)
                     result.add(r);
                 }
-                for (final RecipeComponent c : r.getIngredients().keySet()) {
-                    if (c.getName().toLowerCase().contains(l)) {  //check if ingredients contain words from query
-                        result.add(r);
+                for (final String key : r.getIngredients().keySet()) {
+                    if (key.charAt(0) == 'i') {
+                        Ingredient i = ingredientManager.getIngredient(key).orElseThrow(() -> new RuntimeException("This ingredient does not exist in ingredient manager"));
+                        if (i.getName().toLowerCase().contains(l)) result.add(r);
+                    } else {
+                        Recipe recipe = recipeManager.getRecipeByCode(key);
+                        if (recipe.getName().toLowerCase().contains(l)) result.add(r);
                     }
                 }
             }
@@ -49,14 +60,14 @@ public class RecipeSearch {
      * @param categoriesToSearch a list of categories to which the recipes shall belong
      * @return a list of recipes which belong to all categories of categoriesToSearch
      */
-    public static List<Recipe> searchByCategory(List<Recipe> recipesToSearch, List<Category> categoriesToSearch) {
+    public List<Recipe> searchByCategory(List<Recipe> recipesToSearch, List<Category> categoriesToSearch) {
         if (categoriesToSearch == null || categoriesToSearch.isEmpty()) return recipesToSearch;
 
         List<Recipe> result = new ArrayList<>(recipesToSearch); //create List containing all the recipes to search
 
         for (final Recipe r : recipesToSearch) {
             for (final Category c : categoriesToSearch) {
-                if (!r.getCategories().contains(c)) { //if a recipe does not contain one of the categories it will be removed from the result set
+                if (!r.getCategoryCodes().contains(c.getCode())) { //if a recipe does not contain one of the categories it will be removed from the result set
                     result.remove(r);
                 }
             }
diff --git a/src/main/java/mi/hdm/shoppingList/ShoppingList.java b/src/main/java/mi/hdm/shoppingList/ShoppingList.java
index 340f9e3898edd728b6f26801d6b95e3ca4ff3acd..d9cf385deb1cc621766ad916fcdc0a5caaed2898 100644
--- a/src/main/java/mi/hdm/shoppingList/ShoppingList.java
+++ b/src/main/java/mi/hdm/shoppingList/ShoppingList.java
@@ -3,12 +3,12 @@ package mi.hdm.shoppingList;
 import mi.hdm.exceptions.InvalidIngredientException;
 import mi.hdm.recipes.Ingredient;
 import mi.hdm.recipes.Recipe;
+import mi.hdm.recipes.RecipeManager;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Optional;
 
 /**
  * Shopping list that the user can add ingredients to. The elements on the list can be marked as done or undone.
@@ -16,14 +16,20 @@ import java.util.Optional;
 public class ShoppingList {
     private static final Logger log = LogManager.getLogger(ShoppingList.class);
 
-    private final Map<Ingredient, Boolean> list;
+    private final RecipeManager recipeManager;
 
-    public ShoppingList() {
+    private final Map<String, Boolean> list;
+
+    public ShoppingList(RecipeManager recipeManager) {
+        this.recipeManager = recipeManager;
         list = new HashMap<>();
+        log.info("Created empty instance of ShoppingList");
     }
 
-    public ShoppingList(Map<Ingredient, Boolean> shoppingListMap) {
-        list = shoppingListMap;
+    public ShoppingList(Map<String, Boolean> shoppingListMap, RecipeManager recipeManager) {
+        list = new HashMap<>(shoppingListMap);
+        this.recipeManager = recipeManager;
+        log.info("Created ShoppingList with {} entries.", list.size());
     }
 
     public void clear() {
@@ -35,18 +41,26 @@ public class ShoppingList {
         if (ingredient == null) {
             throw new InvalidIngredientException("Can not add ingredient that is null");
         }
-        list.put(ingredient, false);
-        log.info("Added {} to shopping list.", ingredient.getName());
+        list.put(ingredient.getUniqueCode(), false);
+        log.info("Added '{}' to shopping list.", ingredient.getName());
+    }
+
+    public void addToShoppingList(String key) {
+        if (key == null || key.charAt(0) != 'i') {
+            throw new InvalidIngredientException("Can not add ingredient for this key: " + key);
+        }
+        list.put(key, false);
+        log.info("Added ingredient with code {} to shopping list.", key);
     }
 
     public void addAllToShoppingList(Recipe recipe) {
         recipe.getIngredients().keySet()
                 .forEach(element -> {
-                            if (element instanceof Ingredient i)
-                                addToShoppingList(i);
-                                //adds recipes recursively (see below); comment next 2 lines out to avoid adding recipes recursively
-                            else
-                                addAllToShoppingList((Recipe) element);
+                    if (element.startsWith("i"))
+                        addToShoppingList(element);
+                        //adds recipes recursively (see below); comment next 2 lines out to avoid adding recipes recursively
+                    else
+                        addAllToShoppingList(recipeManager.getRecipeByCode(element));
                         }
                 );
     }
@@ -54,17 +68,23 @@ public class ShoppingList {
     public void flipStatus(Ingredient in) {
         if (in == null)
             throw new NullPointerException("Ingredient can not be null");
-        if (!list.containsKey(in))
+        if (!list.containsKey(in.getUniqueCode()))
             throw new InvalidIngredientException("No ingredient with name " + in.getName());
 
-        boolean newStatus = !list.get(in);
-        list.put(in, newStatus);
+        boolean newStatus = !list.get(in.getUniqueCode());
+        list.put(in.getUniqueCode(), newStatus);
         log.info("Ingredient {} marked as {}.", in.getName(), newStatus ? "done" : "not done");
     }
 
-    public void flipStatus(String name) {
-        Ingredient in = getIngredientByName(name).orElseThrow(() -> new InvalidIngredientException("No ingredient with name " + name));
-        flipStatus(in);
+    public void flipStatus(String key) {
+        if (key == null)
+            throw new NullPointerException("Ingredient can not be null");
+        if (!list.containsKey(key))
+            throw new InvalidIngredientException("No ingredient with key " + key);
+
+        boolean newStatus = !list.get(key);
+        list.put(key, newStatus);
+        log.info("Ingredient with key {} marked as {}.", key, newStatus ? "done" : "not done");
     }
 
     /**
@@ -72,26 +92,24 @@ public class ShoppingList {
      */
     public void removeAllBought() {
         int removed = 0;
-        for (Ingredient i : list.keySet()) {
-            if (list.get(i)) list.remove(i);
+        for (String s : list.keySet()) {
+            if (list.get(s)) list.remove(s);
             removed++;
-            log.debug("Removed ingredient '{}' from shopping list.", i.getName());
+            log.debug("Removed ingredient '{}' from shopping list.", s);
         }
         log.info("Removed {} ingredients from shopping list.", removed);
     }
 
-    public Map<Ingredient, Boolean> getList() {
+    public Map<String, Boolean> getList() {
         return list;
     }
 
-    private Optional<Ingredient> getIngredientByName(String name) {
-        if (name == null) throw new NullPointerException("Name can not be null");
-
-        for (final Ingredient i : list.keySet()) {
-            if (name.equals(i.getName())) {
-                return Optional.of(i);
-            }
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) return true;
+        if (obj instanceof ShoppingList s) {
+            return s.getList().equals(this.list);
         }
-        return Optional.empty();
+        return false;
     }
 }
\ No newline at end of file
diff --git a/src/main/java/mi/hdm/typeAdapters/CategoryManagerTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/CategoryManagerTypeAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0a2bb8ba748e48e3bb51d0d8e44ef2359b8cf5b
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/CategoryManagerTypeAdapter.java
@@ -0,0 +1,48 @@
+package mi.hdm.typeAdapters;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import mi.hdm.recipes.Category;
+import mi.hdm.recipes.CategoryManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CategoryManagerTypeAdapter extends TypeAdapter<CategoryManager> {
+    @Override
+    public void write(JsonWriter writer, CategoryManager categoryManager) throws IOException {
+        writer.beginObject();
+        writer.name("categories").jsonValue(new GsonBuilder()
+                .registerTypeAdapter(Category.class, new CategoryTypeAdapter())
+                .create()
+                .toJson(categoryManager.getAllCategories()));
+        writer.endObject();
+    }
+
+    @Override
+    public CategoryManager read(JsonReader reader) throws IOException {
+        List<Category> categories = new ArrayList<>();
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String propertyName = reader.nextName();
+            if (propertyName.equals("categories")) {
+                CategoryTypeAdapter typeAdapter = new CategoryTypeAdapter();
+                reader.beginArray();
+                Gson gson = new GsonBuilder()
+                        .registerTypeAdapter(Category.class, typeAdapter)
+                        .create();
+                while (reader.hasNext()) {
+                    Category next = gson.fromJson(reader, Category.class);
+                    categories.add(next);
+                }
+                reader.endArray();
+            }
+        }
+        reader.endObject();
+        return new CategoryManager(categories);
+    }
+}
diff --git a/src/main/java/mi/hdm/typeAdapters/CategoryTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/CategoryTypeAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..d53d539cfcfebb646c000aef1c1856d5d71ecd92
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/CategoryTypeAdapter.java
@@ -0,0 +1,50 @@
+package mi.hdm.typeAdapters;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import mi.hdm.recipes.Category;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+
+public class CategoryTypeAdapter extends TypeAdapter<Category> {
+    private final static Logger log = LogManager.getLogger(CategoryTypeAdapter.class);
+
+    @Override
+    public void write(JsonWriter jsonWriter, Category category) throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("name").value(category.getName());
+        jsonWriter.name("colourCode").value(category.getColourCode());
+        jsonWriter.name("code").value(category.getCode());
+        jsonWriter.endObject();
+    }
+
+    @Override
+    public Category read(JsonReader jsonReader) throws IOException {
+        String name = null;
+        String colourCode = null;
+        Integer code = null;
+
+        jsonReader.beginObject();
+        while (jsonReader.hasNext()) {
+            String propertyName = jsonReader.nextName();
+            switch (propertyName) {
+                case "name" -> name = jsonReader.nextString();
+                case "colourCode" -> colourCode = jsonReader.nextString();
+                case "code" -> code = jsonReader.nextInt();
+                default -> jsonReader.skipValue();
+            }
+        }
+        jsonReader.endObject();
+
+        if (name == null || colourCode == null || code == null) {
+            String msg = "Null value: invalid or missing value in JSON object for a category";
+            log.error(msg);
+            throw new IOException(msg);
+        }
+
+        return new Category(name, colourCode, code);
+    }
+}
diff --git a/src/main/java/mi/hdm/typeAdapters/MealPlanTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/MealPlanTypeAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8771f2e90d6915dafb2f253121a0243083ac382
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/MealPlanTypeAdapter.java
@@ -0,0 +1,50 @@
+package mi.hdm.typeAdapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import mi.hdm.mealPlan.MealPlan;
+import mi.hdm.recipes.RecipeManager;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MealPlanTypeAdapter extends TypeAdapter<MealPlan> {
+    private final RecipeManager recipeManager;
+
+    public MealPlanTypeAdapter(RecipeManager recipeManager) {
+        this.recipeManager = recipeManager;
+    }
+
+    @Override
+    public void write(JsonWriter writer, MealPlan mealPlan) throws IOException {
+        writer.beginObject();
+        Map<LocalDate, String> map = mealPlan.getAllRecipeCodesFromPlan();
+        writer.name("mealPlan").jsonValue(new Gson().toJson(map));
+        writer.endObject();
+    }
+
+    @Override
+    public MealPlan read(JsonReader reader) throws IOException {
+        final Map<LocalDate, String> map = new HashMap<>();
+        reader.beginObject();
+        String name = reader.nextName();
+        if (name.equals("mealPlan")) {
+            reader.beginObject();
+            while (reader.hasNext()) {
+                String date = reader.nextName();
+                String code = reader.nextString();
+                map.put(LocalDate.parse(date), code);
+            }
+            reader.endObject();
+        } else {
+            return null;
+        }
+        reader.endObject();
+
+        return new MealPlan(map, recipeManager);
+    }
+}
diff --git a/src/main/java/mi/hdm/typeAdapters/NutritionTableTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/NutritionTableTypeAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..beb5743378556849e38463b5f56d71bcf8e2d18e
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/NutritionTableTypeAdapter.java
@@ -0,0 +1,52 @@
+package mi.hdm.typeAdapters;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import mi.hdm.recipes.NutritionTable;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+
+public class NutritionTableTypeAdapter extends TypeAdapter<NutritionTable> {
+    private final static Logger log = LogManager.getLogger(NutritionTableTypeAdapter.class);
+
+    @Override
+    public void write(JsonWriter jsonWriter, NutritionTable nutritionTable) throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("values").value(new Gson().toJson(nutritionTable.getTable()));
+        jsonWriter.endObject();
+    }
+
+    @Override
+    public NutritionTable read(JsonReader jsonReader) throws IOException {
+        Double calories = null, carbs = null, fat = null, proteins = null, fibers = null, salt = null;
+        jsonReader.beginObject();
+        if (jsonReader.hasNext()) {
+            String propertyName = jsonReader.nextName();
+            if (propertyName.equals("values")) {
+                String valuesJson = jsonReader.nextString();
+                JsonObject valuesObject = JsonParser.parseString(valuesJson).getAsJsonObject();
+                calories = valuesObject.get("CALORIES").getAsDouble();
+                carbs = valuesObject.get("CARBS").getAsDouble();
+                fat = valuesObject.get("FAT").getAsDouble();
+                proteins = valuesObject.get("PROTEINS").getAsDouble();
+                fibers = valuesObject.get("FIBERS").getAsDouble();
+                salt = valuesObject.get("SALT").getAsDouble();
+            } else {
+                jsonReader.skipValue();
+            }
+        }
+        jsonReader.endObject();
+        try {
+            return new NutritionTable(calories, carbs, fat, proteins, fibers, salt);
+        } catch (NullPointerException e) {
+            log.error("IO Exception: invalid or missing values in JSON object for ingredient.");
+            throw new IOException("null pointer exception - invalid or missing values in JSON");
+        }
+    }
+}
diff --git a/src/main/java/mi/hdm/typeAdapters/RecipeTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/RecipeTypeAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e4432d2a2974b8723467ba7af0fa9babe00906d
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/RecipeTypeAdapter.java
@@ -0,0 +1,99 @@
+package mi.hdm.typeAdapters;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import mi.hdm.recipes.NutritionTable;
+import mi.hdm.recipes.Recipe;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+public class RecipeTypeAdapter extends TypeAdapter<Recipe> {
+    private final static Logger log = LogManager.getLogger(RecipeTypeAdapter.class);
+
+    @Override
+    public void write(JsonWriter writer, Recipe recipe) throws IOException {
+        writer.beginObject();
+        writer.name("name").value(recipe.getName());
+        writer.name("nutritionTable").jsonValue(new GsonBuilder().registerTypeAdapter(NutritionTable.class, new NutritionTableTypeAdapter()).create().toJson(recipe.getNutritionTable()));
+        writer.name("code").value(recipe.getUniqueCode());
+
+        writer.name("ingredients").jsonValue(new Gson().toJson(recipe.getIngredients()));
+
+        writer.name("description").value(recipe.getDescription());
+        writer.name("preparation").jsonValue(new Gson().toJson(recipe.getPreparation()));
+
+        writer.name("categories").jsonValue(new Gson().toJson(recipe.getCategoryCodes()));
+
+        writer.name("preparationTimeMins").value(recipe.getPreparationTimeMins());
+        writer.name("creationTime").value(recipe.getCreationTime().toString());
+        writer.endObject();
+        log.debug("Wrote recipe '{}' to JSON.", recipe.getName());
+    }
+
+    @Override
+    public Recipe read(JsonReader reader) throws IOException {
+        String name = null;
+        Map<String, Integer> ingredients = null;
+        String description = null;
+        List<String> preparation = null;
+        List<Integer> categories = null;
+        Integer preparationTimeMins = null;
+        NutritionTable nutritionTable = null;
+        LocalDateTime creationTime = null;
+        String code = null;
+
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String propertyName = reader.nextName();
+            switch (propertyName) {
+                case "name":
+                    name = reader.nextString();
+                    break;
+                case "ingredients":
+                    ingredients = new Gson().fromJson(reader, new TypeToken<Map<String, Integer>>() {
+                    }.getType());
+                    break;
+                case "description":
+                    description = reader.nextString();
+                    break;
+                case "preparation":
+                    preparation = new Gson().fromJson(reader, new TypeToken<List<String>>() {
+                    }.getType());
+                    break;
+                case "categories":
+                    categories = new Gson().fromJson(reader, new TypeToken<List<Integer>>() {
+                    }.getType());
+                    break;
+                case "preparationTimeMins":
+                    preparationTimeMins = reader.nextInt();
+                    break;
+                case "nutritionTable":
+                    nutritionTable = new GsonBuilder().registerTypeAdapter(NutritionTable.class, new NutritionTableTypeAdapter()).create().fromJson(reader, NutritionTable.class);
+                    break;
+                case "creationTime":
+                    creationTime = LocalDateTime.parse(reader.nextString());
+                    break;
+                case "code":
+                    code = reader.nextString();
+                    break;
+                default:
+                    reader.skipValue();
+                    break;
+            }
+        }
+        reader.endObject();
+
+        log.debug("Read recipe '{}' from JSON.", name);
+
+        return new Recipe(name, ingredients, description, preparation, categories, preparationTimeMins, nutritionTable, creationTime, code);
+    }
+}
diff --git a/src/main/java/mi/hdm/typeAdapters/ShoppingListTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/ShoppingListTypeAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..b640d5d65851f15f3fe3ad19b8e118192b7626ab
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/ShoppingListTypeAdapter.java
@@ -0,0 +1,42 @@
+package mi.hdm.typeAdapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import mi.hdm.recipes.RecipeManager;
+import mi.hdm.shoppingList.ShoppingList;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class ShoppingListTypeAdapter extends TypeAdapter<ShoppingList> {
+    private final RecipeManager recipeManager;
+
+    public ShoppingListTypeAdapter(RecipeManager recipeManager) {
+        this.recipeManager = recipeManager;
+    }
+
+    @Override
+    public void write(JsonWriter writer, ShoppingList shoppingList) throws IOException {
+        final Map<String, Boolean> map = shoppingList.getList();
+        writer.beginObject();
+        writer.name("shoppingList").jsonValue(new Gson().toJson(map));
+        writer.endObject();
+    }
+
+    @Override
+    public ShoppingList read(JsonReader reader) throws IOException {
+        Map<String, Boolean> map = null;
+        reader.beginObject();
+        String name = reader.nextName();
+        if (name.equals("shoppingList")) {
+            map = new Gson().fromJson(reader, new TypeToken<Map<String, Boolean>>() {
+            }.getType());
+        }
+        reader.endObject();
+        if (map == null) return null;
+        return new ShoppingList(map, recipeManager);
+    }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 4dd3f6d7da4b6cc0be7d1b77968ebf3e2ba84db2..ca7b90450d52322be1667a7987fad50ad0796758 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -2,8 +2,9 @@ module gui {
     requires javafx.controls;
     requires javafx.fxml;
     requires org.apache.logging.log4j;
+    requires com.google.gson;
 
-    opens mi.hdm to javafx.fxml;
+    opens mi.hdm to javafx.fxml, com.google.gson;
     opens mi.hdm.controllers to javafx.fxml;
 
     exports mi.hdm;
diff --git a/src/test/java/mi/hdm/filesystem/JsonTest.java b/src/test/java/mi/hdm/filesystem/JsonTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3ffbdc41274626bc2f66c7fce412eacead6c73c6
--- /dev/null
+++ b/src/test/java/mi/hdm/filesystem/JsonTest.java
@@ -0,0 +1,118 @@
+package mi.hdm.filesystem;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import mi.hdm.mealPlan.MealPlan;
+import mi.hdm.recipes.*;
+import mi.hdm.shoppingList.ShoppingList;
+import mi.hdm.typeAdapters.*;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class JsonTest {
+    private final static IngredientManager ingredientManager = new IngredientManager();
+    private final static CategoryManager categoryManager = new CategoryManager();
+    private final static RecipeManager recipeManager = new RecipeManager();
+
+    @BeforeAll
+    public static void setupAll() {
+        ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientTwo());
+        ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientOne());
+
+        categoryManager.addCategory("Kategorie 1", 0xFF0000);
+        categoryManager.addCategory("Kategorie 2", 0xFF00F8);
+    }
+
+    @Test
+    public void testCategoryToJSON() {
+        Category expected = ValidObjectsPool.getValidCategoryOne();
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(Category.class, new CategoryTypeAdapter())
+                .create();
+
+        String jsonResult = gson.toJson(expected);
+
+        Category result = gson.fromJson(jsonResult, Category.class);
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testCategoryManagerToJson() {
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter())
+                .create();
+        String jsonResult = gson.toJson(categoryManager);
+        System.out.println(jsonResult);
+        CategoryManager result = gson.fromJson(jsonResult, CategoryManager.class);
+        assertEquals(categoryManager, result);
+    }
+
+    @Test
+    public void testNutritionTableToJSON() {
+        NutritionTable expected = ValidObjectsPool.getValidNutritionTableOne();
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(NutritionTable.class, new NutritionTableTypeAdapter())
+                .create();
+
+        String json = gson.toJson(expected, NutritionTable.class);
+
+        NutritionTable result = gson.fromJson(json, NutritionTable.class);
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testRecipeToJSON() {
+        Recipe expected = ValidObjectsPool.getValidRecipeOne();
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(Recipe.class, new RecipeTypeAdapter())
+                .create();
+        String json = gson.toJson(expected);
+        System.out.println(json);
+        Recipe result = gson.fromJson(json, Recipe.class);
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testMealPlanToJson() {
+        MealPlan expected = ValidObjectsPool.getValidMealPlanOne();
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(new RecipeManager()))
+                .create();
+        String json = gson.toJson(expected);
+        System.out.println(json);
+        MealPlan result = gson.fromJson(json, MealPlan.class);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testShoppingListToJson() {
+        ShoppingList expected = ValidObjectsPool.getShoppingList();
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager))
+                .create();
+        String json = gson.toJson(expected);
+        System.out.println(json);
+        ShoppingList result = gson.fromJson(json, ShoppingList.class);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void getCorrectObjectsFromRecipe() {
+        Ingredient ingredient = ValidObjectsPool.getValidIngredientOne();
+        Category category = ValidObjectsPool.getValidCategoryOne();
+        Recipe recipe = new Recipe("Expect", Map.of(ingredient, 100), "Description", List.of("Prepare"), List.of(category), 15);
+
+        IngredientManager ingredientManager = new IngredientManager(List.of(ingredient));
+        CategoryManager categoryManager = new CategoryManager(List.of(category));
+
+        assertEquals(category, categoryManager.get(category.getCode()).orElseThrow());
+        assertEquals(ingredient, ingredientManager.getIngredient(ingredient.getUniqueCode()).orElseThrow());
+    }
+}
diff --git a/src/test/java/mi/hdm/mealPlan/MealPlanTest.java b/src/test/java/mi/hdm/mealPlan/MealPlanTest.java
index 8ed43c3f1101d1c5901a4d72cb3e474bee7c68fa..52cf84a0913a09023f6a89f17bc69bb2877ae418 100644
--- a/src/test/java/mi/hdm/mealPlan/MealPlanTest.java
+++ b/src/test/java/mi/hdm/mealPlan/MealPlanTest.java
@@ -2,7 +2,9 @@ package mi.hdm.mealPlan;
 
 import mi.hdm.exceptions.InvalidMealPlanException;
 import mi.hdm.recipes.Recipe;
+import mi.hdm.recipes.RecipeManager;
 import mi.hdm.recipes.ValidObjectsPool;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -15,12 +17,20 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class MealPlanTest {
     private MealPlan underTest;
+    private final static RecipeManager recipeManager = ValidObjectsPool.getRecipeManager();
     private final static Recipe recipeOne = ValidObjectsPool.getValidRecipeOne();
     private final static Recipe recipeTwo = ValidObjectsPool.getValidRecipeTwo();
 
+    @BeforeAll
+    public static void setupAll() {
+        recipeManager.clear();
+        recipeManager.addRecipe(recipeOne);
+        recipeManager.addRecipe(recipeTwo);
+    }
+
     @BeforeEach
     public void setup() {
-        underTest = new MealPlan(new HashMap<>());
+        underTest = new MealPlan(new HashMap<>(), recipeManager);
     }
 
     @Test
@@ -28,7 +38,7 @@ class MealPlanTest {
         //given
         LocalDate date = LocalDate.now();
 
-        assertThrows(NullPointerException.class, () -> underTest = new MealPlan(Map.of(null, recipeOne, date, recipeTwo)));
+        assertThrows(NullPointerException.class, () -> underTest = new MealPlan(Map.of(null, recipeOne.getUniqueCode(), date, recipeTwo.getUniqueCode()), recipeManager));
     }
 
     @Test
@@ -64,16 +74,16 @@ class MealPlanTest {
     public void shouldRemoveOldRecipes() {
         //given
         LocalDate date = LocalDate.of(2022, 1, 1);
-        Map<LocalDate, Recipe> expected = Map.of();
-        HashMap<LocalDate, Recipe> entries = new HashMap<>();
-        entries.put(date, recipeOne);
-        underTest = new MealPlan(entries);
+        Map<LocalDate, String> expected = Map.of();
+        HashMap<LocalDate, String> entries = new HashMap<>();
+        entries.put(date, recipeOne.getUniqueCode());
+        underTest = new MealPlan(entries, recipeManager);
 
         //when
         underTest.removePastRecipes();
 
         //expect
-        assertEquals(expected, underTest.getAllRecipesFromPlan());
+        assertEquals(expected, underTest.getAllRecipeCodesFromPlan());
     }
 
     @Test
@@ -81,10 +91,10 @@ class MealPlanTest {
         //given
         LocalDate old = LocalDate.of(2022, 1, 1);
         LocalDate now = LocalDate.now();
-        HashMap<LocalDate, Recipe> entries = new HashMap<>();
-        entries.put(old, recipeOne);
-        entries.put(now, recipeTwo);
-        underTest = new MealPlan(entries);
+        HashMap<LocalDate, String> entries = new HashMap<>();
+        entries.put(old, recipeOne.getUniqueCode());
+        entries.put(now, recipeTwo.getUniqueCode());
+        underTest = new MealPlan(entries, recipeManager);
 
         Map<LocalDate, Recipe> expected = Map.of(now, recipeTwo);
 
diff --git a/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java b/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java
index 2be5d61a80479759f2d398864c05d759940e6835..80f02a3594ecd65287d1759f61b72f35060f4be8 100644
--- a/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java
+++ b/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java
@@ -1,23 +1,25 @@
 package mi.hdm.recipes;
 
-import mi.hdm.mealPlan.MealPlan;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import java.time.LocalDate;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 public class NutritionCalculatorTest {
-    List<RecipeComponent> ingredients = ValidObjectsPool.getValidIngredientList();
-    Map<RecipeComponent, Integer> recipeMap;
+    private List<RecipeComponent> ingredients = ValidObjectsPool.getValidIngredientList();
+    private Map<RecipeComponent, Integer> recipeMap;
+    private final RecipeManager recipeManager = new RecipeManager();
+
+    private Recipe recipe = ValidObjectsPool.getValidRecipeOne();
 
     @BeforeEach
     public void setUp() {
         recipeMap = new HashMap<>();
         ingredients.forEach(ingredient -> recipeMap.put(ingredient, 100));
+        recipeManager.addRecipe(recipe);
     }
 
     @Test
diff --git a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java
index 1c5e296ad4eeec9d2243cb411fac62ce248f0a00..9fa5d9a82cf81ad0208c1ee8860f383ac6d52db1 100644
--- a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java
+++ b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java
@@ -1,5 +1,6 @@
 package mi.hdm.recipes;
 
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -17,20 +18,31 @@ class RecipeSearchTest {
     private final static Category c1 = ValidObjectsPool.getValidCategoryOne();
     private final static Category c2 = ValidObjectsPool.getValidCategoryTwo();
 
+    private static final RecipeManager recipeManager = new RecipeManager();
+    private static final IngredientManager ingredientManager = new IngredientManager();
+
     private List<Recipe> recipes;
     //private RecipeSearch underTest; //not needed when methods are static
 
+    @BeforeAll
+    public static void setupAll() {
+        ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientOne());
+        ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientTwo());
+        recipeManager.addRecipe(r1);
+        recipeManager.addRecipe(r2);
+    }
+
     @BeforeEach
     public void setup() {
         recipes = List.of(r1, r2);
-        //underTest = new RecipeSearch(recipes); //not needed when methods are static
+        underTest = new RecipeSearch(recipeManager, ingredientManager);
     }
 
     @ParameterizedTest
     @NullAndEmptySource
     @ValueSource(strings = {"   \n", ".", "!", "..."})
     public void testSearchByEmptyQuery(String input) {
-        assertEquals(recipes, RecipeSearch.searchByQuery(recipes, input));
+        assertEquals(recipes, underTest.searchByQuery(recipes, input));
     }
 
     @ParameterizedTest
@@ -40,7 +52,7 @@ class RecipeSearchTest {
         List<Recipe> expected = List.of(r1);
 
         //when
-        List<Recipe> result = RecipeSearch.searchByQuery(recipes, query);
+        List<Recipe> result = underTest.searchByQuery(recipes, query);
 
         //expected
         assertEquals(expected, result);
@@ -53,7 +65,7 @@ class RecipeSearchTest {
         String query = "Apple ";
 
         //when
-        List<Recipe> result = RecipeSearch.searchByQuery(recipes, query);
+        List<Recipe> result = underTest.searchByQuery(recipes, query);
 
         //expected
         assertEquals(expected, result);
@@ -62,13 +74,13 @@ class RecipeSearchTest {
     @ParameterizedTest
     @NullAndEmptySource
     public void testSearchByEmptyCategories(List<Category> input) {
-        assertEquals(recipes, RecipeSearch.searchByCategory(recipes, input));
+        assertEquals(recipes, underTest.searchByCategory(recipes, input));
     }
 
     @Test
     public void testSearchByCategories() {
-        assertEquals(List.of(r1, r2), RecipeSearch.searchByCategory(recipes, List.of(c1)));
-        assertEquals(List.of(r2), RecipeSearch.searchByCategory(recipes, List.of(c1, c2)));
-        assertEquals(List.of(r2), RecipeSearch.searchByCategory(recipes, List.of(c2)));
+        assertEquals(List.of(r1, r2), underTest.searchByCategory(recipes, List.of(c1)));
+        assertEquals(List.of(r2), underTest.searchByCategory(recipes, List.of(c1, c2)));
+        assertEquals(List.of(r2), underTest.searchByCategory(recipes, List.of(c2)));
     }
 }
diff --git a/src/test/java/mi/hdm/recipes/RecipeTest.java b/src/test/java/mi/hdm/recipes/RecipeTest.java
index 8fdc169495b8c1c104d0812da9ff6084b6f7e773..f623ab74679da08dd81af86e64e35d240e816c5c 100644
--- a/src/test/java/mi/hdm/recipes/RecipeTest.java
+++ b/src/test/java/mi/hdm/recipes/RecipeTest.java
@@ -87,8 +87,8 @@ class RecipeTest {
 
         //then
         assertEquals(
-                List.of(category),
-                underTest.getCategories()
+                List.of(category.getCode()),
+                underTest.getCategoryCodes()
         );
     }
 
@@ -105,8 +105,8 @@ class RecipeTest {
 
         //then
         assertEquals(
-                List.of(category),
-                underTest.getCategories()
+                List.of(category.getCode()),
+                underTest.getCategoryCodes()
         );
     }
 }
diff --git a/src/test/java/mi/hdm/recipes/ValidObjectsPool.java b/src/test/java/mi/hdm/recipes/ValidObjectsPool.java
index e9edac91ec77e3f9b25c5e0584d445007d9148f8..252c59799e61e7402907015d316310a66d6b93f0 100644
--- a/src/test/java/mi/hdm/recipes/ValidObjectsPool.java
+++ b/src/test/java/mi/hdm/recipes/ValidObjectsPool.java
@@ -1,13 +1,14 @@
 package mi.hdm.recipes;
 
 import mi.hdm.mealPlan.MealPlan;
+import mi.hdm.shoppingList.ShoppingList;
 
 import java.time.LocalDate;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 public class ValidObjectsPool {
+    private final static RecipeManager recipeManager = new RecipeManager();
     private final static Category categoryOne = new Category("Frühstück", 0xFF0000);
     private final static Category categoryTwo = new Category("Abendessen", 0x00FF00);
     private final static NutritionTable nutritionTableOne = new NutritionTable(100.0, 17.2, 9.4, 4.3, 2.4, 0.4);
@@ -16,8 +17,9 @@ public class ValidObjectsPool {
     private final static Ingredient ingredientTwo = new Ingredient(Measurement.GRAM, "Apple", nutritionTableTwo);
     private final static Recipe recipeOne = new Recipe("Valid recipe 1", Map.of(ingredientOne, 100, ingredientTwo, 200), "Description for this recipe", List.of("step one", "step two"), List.of(categoryOne), 15);
     private final static Recipe recipeTwo = new Recipe("Apfelkuchen", Map.of(ingredientOne, 250), "Mein liebster APfelkuchen", List.of("mjam mjam", "ich liebe kochen"), List.of(categoryTwo, categoryOne), 25);
-    private final static Map<LocalDate, Recipe> recipeMap1 = Map.of(LocalDate.now(), recipeOne, LocalDate.now().plusDays(1), recipeTwo);
-    private final static MealPlan planOne = new MealPlan(recipeMap1);
+    private final static Map<LocalDate, String> recipeMap1 = Map.of(LocalDate.now(), recipeOne.getUniqueCode(), LocalDate.now().plusDays(1), recipeTwo.getUniqueCode());
+    private final static MealPlan planOne = new MealPlan(recipeMap1, recipeManager);
+    private final static ShoppingList shoppingList = new ShoppingList(Map.of("r-192991", true, "r-89134", false), recipeManager);
 
     public static NutritionTable getValidNutritionTableOne() {
         return nutritionTableOne;
@@ -59,5 +61,15 @@ public class ValidObjectsPool {
         return recipeTwo;
     }
 
-    public static MealPlan getValidMealPlanOne() { return planOne; }
+    public static MealPlan getValidMealPlanOne() {
+        return planOne;
+    }
+
+    public static ShoppingList getShoppingList() {
+        return shoppingList;
+    }
+
+    public static RecipeManager getRecipeManager() {
+        return recipeManager;
+    }
 }
diff --git a/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java b/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java
index ec06b37f4fa1afd4932b91b7f78c94fd218c02af..808a42ba2c8a6cfb9a51fd1c6c545c31e476b0db 100644
--- a/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java
+++ b/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java
@@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class ShoppingListTest {
-    private final ShoppingList underTest = new ShoppingList();
+    private final ShoppingList underTest = new ShoppingList(ValidObjectsPool.getRecipeManager());
     private final static Ingredient ing1 = ValidObjectsPool.getValidIngredientOne();
     private final static Ingredient ing2 = ValidObjectsPool.getValidIngredientTwo();
 
@@ -21,13 +21,14 @@ class ShoppingListTest {
 
     @BeforeEach
     public void setup() {
+        ValidObjectsPool.getRecipeManager().clear();
         underTest.clear();
     }
 
     @Test
     public void shouldAddIngredient() {
         //given
-        Map<Ingredient, Boolean> expected = Map.of(ing1, false);
+        Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), false);
 
         //when
         underTest.addToShoppingList(ing1);
@@ -39,7 +40,7 @@ class ShoppingListTest {
     @Test
     public void shouldAddAllIngredients() {
         //given
-        Map<Ingredient, Boolean> expected = Map.of(ing1, false, ing2, false);
+        Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), false, ing2.getUniqueCode(), false);
 
         //when
         underTest.addAllToShoppingList(r1);
@@ -48,10 +49,16 @@ class ShoppingListTest {
         assertEquals(expected, underTest.getList());
     }
 
+    @Test
+    public void shouldAllAllIngredientsRecursively() {
+        //TODO: write test for adding ingredients to shopping list recursively
+        throw new RuntimeException("not implemented");
+    }
+
     @Test
     public void testSetStatusByObject() {
         //given
-        Map<Ingredient, Boolean> expected = Map.of(ing1, true);
+        Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), true);
         underTest.addToShoppingList(ing1);
 
         //when
@@ -64,24 +71,24 @@ class ShoppingListTest {
     }
 
     @Test
-    public void testSetStatusByName() {
+    public void testSetStatusByKey() {
         //given
-        Map<Ingredient, Boolean> expected = Map.of(ing1, true);
+        Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), true);
         underTest.addToShoppingList(ing1);
 
         //when
-        underTest.flipStatus(ing1.getName());
+        underTest.flipStatus(ing1.getUniqueCode());
 
         //expect
         assertEquals(expected, underTest.getList());
-        assertThrows(InvalidIngredientException.class, () -> underTest.flipStatus(ing2.getName()));
-        assertThrows(NullPointerException.class, () -> underTest.flipStatus((String) null));
+        assertThrows(InvalidIngredientException.class, () -> underTest.flipStatus(ing2));
+        assertThrows(NullPointerException.class, () -> underTest.flipStatus((Ingredient) null));
     }
 
     @Test
     public void shouldRemoveAllBoughtItems() {
         //given
-        Map<Ingredient, Boolean> expected = Map.of();
+        Map<String, Boolean> expected = Map.of();
         underTest.addToShoppingList(ing1);
         underTest.flipStatus(ing1);