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/TastyPages.java b/src/main/java/mi/hdm/TastyPages.java
index 1fc5261f884d2413a6a4fc8ec37f8fee375db3dd..bad0adf3c20a9c01d40ba0133fa0fd5057fff29c 100644
--- a/src/main/java/mi/hdm/TastyPages.java
+++ b/src/main/java/mi/hdm/TastyPages.java
@@ -5,6 +5,7 @@ import javafx.scene.Parent;
 import javafx.scene.Scene;
 import javafx.stage.Stage;
 import mi.hdm.controllers.View;
+import mi.hdm.filesystem.FileManager;
 import mi.hdm.mealPlan.MealPlan;
 import mi.hdm.recipes.*;
 import mi.hdm.shoppingList.ShoppingList;
@@ -22,17 +23,50 @@ public class TastyPages extends Application {
     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<>());
+    public final static IngredientManager ingredientManager = new IngredientManager();
 
     private final static Logger log = LogManager.getLogger(TastyPages.class);
 
     private static Stage stage;
 
+    /**
+     * 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) {
+        //TODO
+    }
+
+    /**
+     * Creates a new instance of the application. All services will be empty when instantiated, except for the ingredientManager which
+     * is expected to hold the default ingredients that exist inside the resources folder
+     */
+    public TastyPages(IngredientManager ingredientManager) {
+        //TODO create new and empty instances of every dependency (except ingredient manager)
+    }
+
+    /**
+     * Creates a new, completely empty instance of the application. This constructor is not expected to be used.
+     */
+    public TastyPages() {
+    }
+
     public static void main(String[] args) {
         log.info("Starting TastyPages");
+        try {
+            TastyPages app = FileManager.deserializeFromFile();
+            assert app != null;
+            app.startApplication(args);
+        } catch (Exception e) {
+            log.fatal("Exception: check your filepaths!");
+            e.printStackTrace();
+            System.exit(1);
+        }
+        //create instances of every dependency -> load ingredients
+        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).getCode()), 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).getCode()), 40));
+    }
 
-        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));
-
+    public void startApplication(String[] args) {
         launch(args);
     }
 
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/HeaderController.java b/src/main/java/mi/hdm/controllers/HeaderController.java
index e302f683b6a6d07a4c5c1dfda4326e827e1b79f3..9953e2ab674fc7c7bb3e0af351ec9c533732fd27 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,7 +47,7 @@ public class HeaderController extends BaseController {
     public void searchByQuery() {
         String query = searchBox.getText();
         log.debug("User submitted search box");
-        RecipeSearch recipeSearch = new RecipeSearch(recipeManager.getRecipes());
+        RecipeSearch recipeSearch = new RecipeSearch(recipeManager, ingredientManager, recipeManager.getRecipes());
         lastSearchResults = recipeSearch.searchByQuery(query);
         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 14a25a6b15f73c11305b8866548f33974893c2d0..ab4731332a6585479b6b4c81fb6df090b4e83d30 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());
+        RecipeSearch recipeSearch = new RecipeSearch(recipeManager, ingredientManager, recipeManager.getRecipes());
         return recipeSearch.searchByCategory(currentlySelectedCategories);
     }
 }
diff --git a/src/main/java/mi/hdm/controllers/View.java b/src/main/java/mi/hdm/controllers/View.java
index fe0283bbd6d3c1477b98dd95390455b38d22fa9b..5139258eee3259605b27373cbbcb735746fdc9bd 100644
--- a/src/main/java/mi/hdm/controllers/View.java
+++ b/src/main/java/mi/hdm/controllers/View.java
@@ -5,6 +5,7 @@ import javafx.scene.Parent;
 import mi.hdm.TastyPages;
 import mi.hdm.mealPlan.MealPlan;
 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;
@@ -24,6 +25,7 @@ public enum View {
     private final ShoppingList shoppingList = TastyPages.shoppingList;
     private final MealPlan mealPlan = TastyPages.mealPlan;
     private final CategoryManager categoryManager = TastyPages.categoryManager;
+    private final IngredientManager ingredientManager = TastyPages.ingredientManager;
 
     private final String path;
     private final String windowTitle;
@@ -42,7 +44,7 @@ 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(recipeManager, ingredientManager, categoryManager)
             );
 
             case RECIPE_VIEW -> loader.setControllerFactory((callback) ->
@@ -65,4 +67,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..8788c33021ed7011b3af8c278abdc955f49c455d 100644
--- a/src/main/java/mi/hdm/controllers/ViewComponent.java
+++ b/src/main/java/mi/hdm/controllers/ViewComponent.java
@@ -3,6 +3,7 @@ package mi.hdm.controllers;
 import javafx.fxml.FXMLLoader;
 import javafx.scene.Parent;
 import mi.hdm.TastyPages;
+import mi.hdm.recipes.IngredientManager;
 import mi.hdm.recipes.RecipeManager;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -18,6 +19,7 @@ public enum ViewComponent {
 
     //dependencies
     private final RecipeManager recipeManager = TastyPages.recipeManager;
+    private final IngredientManager ingredientManager = TastyPages.ingredientManager;
 
     ViewComponent(String path) {
         this.path = path;
@@ -30,7 +32,7 @@ public enum ViewComponent {
 
         switch (this) {
             case HEADER -> loader.setControllerFactory((callback) ->
-                    new HeaderController(recipeManager)
+                    new HeaderController(recipeManager, ingredientManager)
             );
         }
 
diff --git a/src/main/java/mi/hdm/filesystem/CSVParser.java b/src/main/java/mi/hdm/filesystem/CSVParser.java
index c3c9c65a8bf469f3c064c777d531091839da159a..ec0f7487081ccc7854bc32e0710c0df5312c11cc 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
@@ -56,6 +61,7 @@ public class CSVParser {
     }
 
     private Ingredient getIngredientFromLine(String line, char split, int[] idx) throws NumberFormatException {
+        //TODO: take into account the measurement of the ingredient when reading it
         log.debug("Trying to parse line {}", line);
 
         final Measurement measurement = Measurement.GRAM;
diff --git a/src/main/java/mi/hdm/filesystem/FileManager.java b/src/main/java/mi/hdm/filesystem/FileManager.java
index 414b37fb5304873780cfb98988386c8c13e08be1..706869f64b2bf41b3af828720930929b3a11a2cb 100644
--- a/src/main/java/mi/hdm/filesystem/FileManager.java
+++ b/src/main/java/mi/hdm/filesystem/FileManager.java
@@ -1,24 +1,153 @@
 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.RecipeTypeAdapter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
+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.Map;
+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);
+
+    private final static Gson gson = new Gson();
 
-    public static FileManager getInstance() {
-        return fileManager;
+    public static void serializeToFile(TastyPages app) {
     }
 
-    public void serializeToFile(TastyPages app) {
+    public static TastyPages deserializeFromFile() throws Exception { //TODO: if this fails, log.fatal and System.exit
+        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(PATH_TO_DEFAULT_INGREDIENTS));
+            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;
+
+            if (Files.exists(Path.of(userPath + "ingredients.csv"))) {
+                ingredientManager = deserializeIngredientManager(PATH_TO_USER_DATA);
+            } else {
+                ingredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder(PATH_TO_DEFAULT_INGREDIENTS));
+            }
+
+            Path recipeFolder = Path.of(userPath + "/recipes");
+            if (Files.exists(recipeFolder) && Files.isDirectory(recipeFolder)) {
+                recipeManager = deserializeRecipeManager(PATH_TO_USER_DATA + "/recipes", ingredientManager);
+            } else {
+                recipeFolder.toFile().mkdir();
+                recipeManager = new RecipeManager();
+            }
+            return null;
+        }
     }
 
-    public TastyPages deserializeFromFile(String filepath) {
+    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 RecipeManager deserializeRecipeManager(String path, IngredientManager ingredientManager) throws Exception {
+        Path recipePath = Path.of(path);
+        List<Path> recipePaths;
+        try (Stream<Path> stream = Files.list(recipePath)) { //TODO: catch clause
+            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 "";
+                    }
+                })
+                .map(FileManager::JSONtoRecipe)
+                .toList();
+        return new RecipeManager(recipes);
+    }
+
+    private static CategoryManager deserializeCategoryManager() {
         return null;
     }
 
-    private void recipeToJSON() {
+    private static MealPlan deserializeMealPlan() {
+        return null;
+    }
+
+    private static ShoppingList deserializeShoppingList() {
+        return null;
+    }
+
+    private static String recipeToJSON(Recipe recipe, IngredientManager ingredientManager) {
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(Recipe.class, new RecipeTypeAdapter(ingredientManager))
+                .create();
+        return gson.toJson(recipe);
+    }
+
+    /**
+     * Used to convert an ingredient into a CSV String seperated by comma
+     *
+     * @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(
+                "%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 static Recipe JSONtoRecipe(String json) {
+        //when loading a recipe, is it important that its ingredients are the same objects as in the ingredient manager?
+        gson.fromJson(json, Recipe.class);
+        return null;
     }
 
-    private void ingredientToCSV() {
+    private static String getAbsolutePathFromResourceFolder(String resource) throws URISyntaxException {
+        URL resourceUrl = FileManager.class.getResource(resource);
+        assert resourceUrl != null;
+        Path path = Paths.get(resourceUrl.toURI());
+        return path.toFile().getAbsolutePath();
     }
 }
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..717a5f07ba02dc66a6d9bb4ec8c7a37bf32b3aa9 100644
--- a/src/main/java/mi/hdm/recipes/CategoryManager.java
+++ b/src/main/java/mi/hdm/recipes/CategoryManager.java
@@ -4,21 +4,27 @@ 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; //TODO: save in hash map with key being the code of each category
 
     public CategoryManager() {
-        allCategories = new ArrayList<>();
+        allCategories = new HashMap<>();
+    }
+
+    public CategoryManager(Map<Integer, Category> categories) {
+        allCategories = new HashMap<>(categories);
     }
 
     public CategoryManager(List<Category> categories) {
-        allCategories = categories;
+        allCategories = new HashMap<>();
+        categories.forEach(c -> allCategories.put(c.getCode(), c));
     }
 
     /**
@@ -30,18 +36,18 @@ public class CategoryManager {
      */
     public void addCategory(String name, int colourCode) {
         Category c = new Category(name, colourCode);
-        if (getCategoryByName(name).isPresent() || getCategoryByCode(colourCode).isPresent()) {
+        if (getCategoryByName(name).isPresent() || getCategoryByColourCode(colourCode).isPresent()) {
             log.error("Category {} not added because it already exists", c.getName());
             throw new InvalidCategoryException("Category already exists.");
         } else {
-            allCategories.add(c);
+            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.");
         }
     }
@@ -52,7 +58,7 @@ public class CategoryManager {
     }
 
     public List<Category> getAllCategories() {
-        return allCategories;
+        return allCategories.values().stream().toList();
     }
 
     public void clearCategories() {
@@ -61,20 +67,32 @@ public class CategoryManager {
     }
 
     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()
+    private Optional<Category> getCategoryByColourCode(int colourCode) {
+        return allCategories.values().stream()
                 .filter(c -> c.getColourCode().equals(numberToColorCodeString(colourCode)))
                 .findFirst();
     }
 
+    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();
     }
 }
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..9ea3d577cb652481823edd941d624cf624c62dd1 100644
--- a/src/main/java/mi/hdm/recipes/IngredientManager.java
+++ b/src/main/java/mi/hdm/recipes/IngredientManager.java
@@ -4,22 +4,30 @@ 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<>();
+    public IngredientManager() { //TODO: save in hash map with key being the uniquely generated code
+        allIngredients = new HashMap<>();
     }
 
     public IngredientManager(List<Ingredient> ingredients) {
+        allIngredients = new HashMap<>();
+        ingredients.forEach(i -> allIngredients.put(i.getName(), i));
+    }
+
+    public IngredientManager(Map<String, Ingredient> ingredients) {
         allIngredients = ingredients;
     }
 
+
     /**
      * Adds an ingredient if there is no equal ingredient (no ingredient with the same name).
      *
@@ -32,7 +40,7 @@ public class IngredientManager {
             throw new InvalidIngredientException("Ingredient already exists.");
         } else {
             log.info("Ingredient {} added successfully.", in.getName());
-            allIngredients.add(in);
+            allIngredients.put(in.getUniqueCode(), in);
         }
     }
 
@@ -41,15 +49,10 @@ 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);
+        allIngredients.values().remove(ingredient);
     }
 
     public void clearIngredients() {
@@ -57,12 +60,28 @@ public class IngredientManager {
         allIngredients.clear();
     }
 
-    public List<Ingredient> getAllIngredients() {
+    public Ingredient getIngredient(String code) {
+        Ingredient i = allIngredients.get(code);
+        if (i == null) {
+            throw new InvalidIngredientException("No ingredient with this code exists.");
+        }
+        return i;
+    }
+
+    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..3897d96d9bcdd42f02808a327c6c8d57e807a174 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);
 
     /**
@@ -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;
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 aba7f4a7431044b7187d19182464bd290905ce97..66898dba7b0aeae4fc674ffd2da3f0c6eaf71ca4 100644
--- a/src/main/java/mi/hdm/recipes/Recipe.java
+++ b/src/main/java/mi/hdm/recipes/Recipe.java
@@ -5,19 +5,17 @@ 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.*;
 
 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; //TODO: save in hash map with key being the uniquely generated code
     private String description;
     private List<String> preparation;
-    private List<Category> categories;
+    private List<Integer> categories; //TODO: save in hash map with key being the hash code
     private int preparationTimeMins;
     private NutritionTable nutritionTable;
     private final LocalDateTime creationTime;
@@ -40,7 +38,7 @@ public class Recipe implements RecipeComponent {
             Map<RecipeComponent, Integer> ingredients,
             String description,
             List<String> preparation,
-            List<Category> categories,
+            List<Integer> categories,
             int preparationTimeMins) {
 
         //Das ruft den anderen Konstruktor dieser Klasse auf (siehe unten)
@@ -63,11 +61,11 @@ public class Recipe implements RecipeComponent {
             Map<RecipeComponent, Integer> ingredients,
             String description,
             List<String> preparation,
-            List<Category> categories,
+            List<Integer> categories,
             int preparationTimeMins,
             NutritionTable nutritionTable) {
 
-        setIngredients(ingredients);
+        setIngredientFromRecipeComponents(ingredients);
         setDescription(description);
         setPreparation(preparation);
         setNutritionTable(nutritionTable);
@@ -76,6 +74,29 @@ public class Recipe implements RecipeComponent {
         setCategories(categories);
 
         this.creationTime = LocalDateTime.now();
+        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;
     }
 
     public void setName(String name) {
@@ -111,21 +132,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 +154,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,17 +182,32 @@ 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;
     }
 
+    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;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -185,4 +221,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..6149726b50d118ac910f1ce7538effcc0c546ff4 100644
--- a/src/main/java/mi/hdm/recipes/RecipeManager.java
+++ b/src/main/java/mi/hdm/recipes/RecipeManager.java
@@ -4,28 +4,24 @@ 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));
     }
 
     public RecipeManager() {
-        recipes = new ArrayList<>();
-        categories = new CategoryManager();
-        ingredients = new IngredientManager();
+        allRecipes = new HashMap<>();
     }
 
     public void addRecipe(Recipe recipe) {
@@ -33,32 +29,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);
+        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);
+        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);
             }
@@ -67,6 +62,6 @@ public class RecipeManager {
     }
 
     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 443898456ad5065c6ee14678fb7df2e777583079..59f6bc38274ec1c7351bc1368cfd083d8a748d45 100644
--- a/src/main/java/mi/hdm/recipes/RecipeSearch.java
+++ b/src/main/java/mi/hdm/recipes/RecipeSearch.java
@@ -8,10 +8,16 @@ import java.util.List;
 
 public class RecipeSearch {
     //TODO: lets make these methods static, no need to instantiate a new object every time
-    private final List<Recipe> recipesToSearch;
     private static final Logger log = LogManager.getLogger(RecipeSearch.class);
 
-    public RecipeSearch (List<Recipe> recipesToSearch) {
+    private final RecipeManager recipeManager;
+    private final IngredientManager ingredientManager;
+
+    private final List<Recipe> recipesToSearch;
+
+    public RecipeSearch(RecipeManager recipeManager, IngredientManager ingredientManager, List<Recipe> recipesToSearch) {
+        this.recipeManager = recipeManager;
+        this.ingredientManager = ingredientManager;
         this.recipesToSearch = recipesToSearch;
     }
 
@@ -36,9 +42,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);
+                        if (i.getName().toLowerCase().contains(l)) result.add(r);
+                    } else {
+                        Recipe recipe = recipeManager.getRecipeByCode(key);
+                        if (recipe.getName().toLowerCase().contains(l)) result.add(r);
                     }
                 }
             }
@@ -59,7 +69,7 @@ public class RecipeSearch {
 
         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)) { //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..34dc2294c82a240f948857c342d26f21576b6927 100644
--- a/src/main/java/mi/hdm/shoppingList/ShoppingList.java
+++ b/src/main/java/mi/hdm/shoppingList/ShoppingList.java
@@ -8,7 +8,6 @@ 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,13 +15,13 @@ import java.util.Optional;
 public class ShoppingList {
     private static final Logger log = LogManager.getLogger(ShoppingList.class);
 
-    private final Map<Ingredient, Boolean> list;
+    private final Map<String, Boolean> list;
 
     public ShoppingList() {
         list = new HashMap<>();
     }
 
-    public ShoppingList(Map<Ingredient, Boolean> shoppingListMap) {
+    public ShoppingList(Map<String, Boolean> shoppingListMap) {
         list = shoppingListMap;
     }
 
@@ -35,18 +34,26 @@ public class ShoppingList {
         if (ingredient == null) {
             throw new InvalidIngredientException("Can not add ingredient that is null");
         }
-        list.put(ingredient, false);
+        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((Recipe) element);*/ //TODO: needs recipe manager here
                         }
                 );
     }
@@ -54,17 +61,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 +85,15 @@ 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);
-            }
-        }
-        return Optional.empty();
-    }
 }
\ No newline at end of file
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..a2825a82177bf9c78ac253e141f2a13b3ab9486a
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/CategoryTypeAdapter.java
@@ -0,0 +1,53 @@
+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();
+            if (propertyName.equals("name")) {
+                name = jsonReader.nextString();
+            } else if (propertyName.equals("colourCode")) {
+                colourCode = jsonReader.nextString();
+            } else if (propertyName.equals("code")) {
+                code = jsonReader.nextInt();
+            } else {
+                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/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..5540133c33f54ad2aacdb6a966f8b7448952cc4a
--- /dev/null
+++ b/src/main/java/mi/hdm/typeAdapters/RecipeTypeAdapter.java
@@ -0,0 +1,107 @@
+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.IngredientManager;
+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;
+
+@SuppressWarnings("DuplicatedCode")
+public class RecipeTypeAdapter extends TypeAdapter<Recipe> {
+    private final static Logger log = LogManager.getLogger(RecipeTypeAdapter.class);
+
+    private final IngredientManager ingredientManager;
+
+    public RecipeTypeAdapter(IngredientManager ingredientManager) {
+        this.ingredientManager = ingredientManager;
+    }
+
+    @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/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/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 5e7e35c3c057ad9977ed5032478a96fd0f87f4ce..b75fabd227dca2e4f4f1b5919415ef17e2e1e80c 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -14,7 +14,7 @@
         <Logger name="mi.hdm.GuiDriver" level="info">
             <AppenderRef ref="A1"/>
         </Logger>
-        <Root level="info">
+        <Root level="debug">
             <AppenderRef ref="STDOUT"/>
         </Root>
     </Loggers>
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..e7f26b9c68c06e2c3ce8e4c376a1b59ce3add66c
--- /dev/null
+++ b/src/test/java/mi/hdm/filesystem/JsonTest.java
@@ -0,0 +1,61 @@
+package mi.hdm.filesystem;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import mi.hdm.TastyPages;
+import mi.hdm.recipes.*;
+import mi.hdm.typeAdapters.CategoryTypeAdapter;
+import mi.hdm.typeAdapters.NutritionTableTypeAdapter;
+import mi.hdm.typeAdapters.RecipeTypeAdapter;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class JsonTest {
+    private final static IngredientManager ingredientManager = TastyPages.ingredientManager;
+
+    @BeforeAll
+    public static void setupAll() {
+        ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientTwo());
+        ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientOne());
+    }
+
+    @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 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(ingredientManager))
+                .create();
+        String json = gson.toJson(expected);
+        System.out.println(json);
+        Recipe result = gson.fromJson(json, Recipe.class);
+        assertEquals(expected, result);
+    }
+}
diff --git a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java
index f29d421ab1091160c37bbf790b1d94e3c5b7dc81..034529eeb8a20b83ad827c77423764522decd3b2 100644
--- a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java
+++ b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java
@@ -1,5 +1,7 @@
 package mi.hdm.recipes;
 
+import mi.hdm.TastyPages;
+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,13 +19,24 @@ class RecipeSearchTest {
     private final static Category c1 = ValidObjectsPool.getValidCategoryOne();
     private final static Category c2 = ValidObjectsPool.getValidCategoryTwo();
 
+    private static final RecipeManager recipeManager = TastyPages.recipeManager;
+    private static final IngredientManager ingredientManager = TastyPages.ingredientManager;
+
     private List<Recipe> recipes;
     private RecipeSearch underTest;
 
+    @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);
+        underTest = new RecipeSearch(recipeManager, ingredientManager, recipes);
     }
 
     @ParameterizedTest
diff --git a/src/test/java/mi/hdm/recipes/RecipeTest.java b/src/test/java/mi/hdm/recipes/RecipeTest.java
index 8fdc169495b8c1c104d0812da9ff6084b6f7e773..913e7025c93a7ab8f5ef300b29429de5203fa128 100644
--- a/src/test/java/mi/hdm/recipes/RecipeTest.java
+++ b/src/test/java/mi/hdm/recipes/RecipeTest.java
@@ -88,7 +88,7 @@ class RecipeTest {
         //then
         assertEquals(
                 List.of(category),
-                underTest.getCategories()
+                underTest.getCategoryCodes()
         );
     }
 
@@ -106,7 +106,7 @@ class RecipeTest {
         //then
         assertEquals(
                 List.of(category),
-                underTest.getCategories()
+                underTest.getCategoryCodes()
         );
     }
 }
diff --git a/src/test/java/mi/hdm/recipes/ValidObjectsPool.java b/src/test/java/mi/hdm/recipes/ValidObjectsPool.java
index e9edac91ec77e3f9b25c5e0584d445007d9148f8..aaf8ac3a2457eccc7f4b5c50e353adbb0a86e3dd 100644
--- a/src/test/java/mi/hdm/recipes/ValidObjectsPool.java
+++ b/src/test/java/mi/hdm/recipes/ValidObjectsPool.java
@@ -3,7 +3,6 @@ package mi.hdm.recipes;
 import mi.hdm.mealPlan.MealPlan;
 
 import java.time.LocalDate;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -14,8 +13,8 @@ public class ValidObjectsPool {
     private final static NutritionTable nutritionTableTwo = new NutritionTable(263.1, 25.2, 0.2, 0.0, 0.0, 0.8);
     private final static Ingredient ingredientOne = new Ingredient(Measurement.GRAM, "Zucker", nutritionTableOne);
     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 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.getCode()), 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.getCode(), categoryOne.getCode()), 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);
 
diff --git a/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java b/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java
index ec06b37f4fa1afd4932b91b7f78c94fd218c02af..a44f3b30aeedf1e81429cd9fd1a599e34c0b284b 100644
--- a/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java
+++ b/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java
@@ -27,7 +27,7 @@ class ShoppingListTest {
     @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 +39,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);
@@ -51,7 +51,7 @@ class ShoppingListTest {
     @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 +64,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);