typesToMatch) {
+        return typesToMatch.stream()
+            .map(H09_TestUtils::match)
+            .reduce(Predicate::or)
+            .orElse(new H09_TestUtils.GenericPredicate(i -> false, "Expected type is not defined"));
+    }
+
+    /**
+     * This method returns the upper and lower bounds of the given type.
+     *
+     * The returned Pair contains a list of lower bounds in the left Parameter and a list of upper bounds in the right
+     * Parameter.
+     *
+     * 
If the given Type does not have any lower bounds the left element of the Pair will be null.
+     *
+     * 
If the given Type is not generic this method will return null.
+     *
+     * @param type the type to getrieve the Bounds from
+     * @return a Pair containing both the upper and the lower bounds of the given Type
+     */
+    public static Pair, List> getBounds(Type type) {
+        if (type instanceof WildcardType wildcardType) {
+            return ImmutablePair.of(Arrays.asList(wildcardType.getLowerBounds()), Arrays.asList(wildcardType.getUpperBounds()));
+        }
+        if (type instanceof ParameterizedType parameterizedType) {
+            return ImmutablePair.of(null, Arrays.asList(parameterizedType.getActualTypeArguments()));
+        }
+        if (type instanceof TypeVariable> typeVariable) {
+            return ImmutablePair.of(null, Arrays.asList(typeVariable.getBounds()));
+        }
+        if (type instanceof GenericArrayType) {
+            return ImmutablePair.of(null, List.of(Object[].class));
+        }
+        return null;
+    }
+
+    /**
+     * Retrieves the inner type of the given Type.
+     *
+     * @param type the type to get the inner type from.
+     * @return the inner type of the given type. Returns empty list if no inner type is present.
+     */
+    public static List getInnerTypes(Type type) {
+        if (!(type instanceof ParameterizedType parameterizedType)) {
+            return List.of();
+        }
+        return Arrays.asList(parameterizedType.getActualTypeArguments());
+    }
+
+    /**
+     * Retrieves the super Types of the given Type if it is a class. Returns upper bounds otherwise.
+     *
+     * @param type the type to get the super types from.
+     * @return the super types or upper bounds of the given type. Returns a list containing only Object if no supertype can be
+     * determined.
+     */
+    public static List getGenericSuperTypes(Type type) {
+        if (type instanceof Class> clazz) {
+            List superTypes = new ArrayList<>();
+            if (clazz.getGenericSuperclass() != null) {
+                superTypes.add(clazz.getGenericSuperclass());
+            }
+            if (clazz.getGenericInterfaces().length > 0) {
+                superTypes.addAll(List.of(clazz.getGenericInterfaces()));
+            }
+            return superTypes;
+        }
+
+        Pair, List> bounds = getBounds(type);
+        if (bounds != null) {
+            return bounds.getRight();
+        }
+        return List.of(Object.class);
+    }
+
+    /**
+     * Retrieves the return type of the given Method.
+     *
+     * @param method the method to get the return type from.
+     * @return the return type of the given method.
+     */
+    public static Type getReturnType(Method method) {
+        return method.getGenericReturnType();
+    }
+
+    /**
+     * Retrieves all parameters of the given method. Returned parameters may not generic.
+     *
+     * @param method the method that the generic types should be retrieved from.
+     * @param regex  a regex that is used to filter all generic type names.
+     * @return a List containing all types of the parameters from the method whose type names match the given regex.
+     */
+    public static List getTypeParameters(Method method, String regex) {
+        return Arrays.stream(method.getGenericParameterTypes()).filter(t -> t.getTypeName().matches(regex)).toList();
+    }
+
+    /**
+     * Retrieves all generic types that are defined by the given method.
+     *
+     * @param method the method that the generic types should be retrieved from.
+     * @param regex  a regex that is used to filter all generic type names.
+     * @return a List containing all defined types that match the given regex.
+     */
+    public static List getDefinedTypes(Method method, String regex) {
+        return Arrays.stream(method.getTypeParameters()).filter(t -> t.getTypeName().matches(regex)).map(t -> (Type) t).toList();
+    }
+
+    /**
+     * Retrieves all generic types that are defined by the given class.
+     *
+     * @param clazz the class that the generic types should be retrieved from.
+     * @param regex a regex that is used to filter all generic type names.
+     * @return a List containing all defined types that match the given regex.
+     */
+    public static List getDefinedTypes(Class clazz, String regex) {
+        return Arrays.stream(clazz.getTypeParameters()).filter(t -> t.getTypeName().matches(regex)).map(t -> (Type) t).toList();
+    }
+
+    /**
+     * Asserts that the given {@link Class} defines a certain set a generic Parameters.
+     *
+     * @param clazz    the Class that should be tested.
+     * @param expected a set of predicates that is used to check if all defined generic Types match an expected Type.
+     */
+    public static void assertDefinedParameters(Class> clazz, Set> expected) {
+
+        List> typeVariable = Arrays.asList(clazz.getTypeParameters());
+        CtType> ctClass = (CtType>) BasicTypeLink.of(clazz).getCtElement();
+        var actualNames =
+            ctClass.getFormalCtTypeParameters().stream().map(CtType::toStringDebug).map(s -> s.replace("\n", "")).toList();
+        Context context = contextBuilder()
+            .add("expected", expected)
+            .add("actual", actualNames)
+            .build();
+
+        assertTrue(
+            !typeVariable.isEmpty(),
+            emptyContext(),
+            r -> clazz.getSimpleName() + " does not have any generic parameters."
+        );
+
+        assertEquals(
+            expected.size(),
+            typeVariable.size(),
+            context,
+            r -> clazz.getSimpleName() + " does not have the expected number of generic parameters."
+        );
+        typeVariable.forEach(a ->
+            assertTrue(
+                expected.stream().anyMatch(e -> e.test(a)),
+                context,
+                r -> String.format("The type parameter %s of %s do not match any expected types.", a, clazz.getSimpleName())
+            )
+        );
+    }
+
+    /**
+     * Asserts that the given {@link Method} defines a specific set of generic types.
+     *
+     * @param method   the method that is checked for type definitions.
+     * @param expected a set of predicates that is used to check if all defined generic Types match an expected Type.
+     */
+    public static void assertDefinedParameters(Method method, Set> expected) {
+
+        List> typeVariable = Arrays.asList(method.getTypeParameters());
+        CtMethod> ctMethod = BasicMethodLink.of(method).getCtElement();
+        var actualNames = ctMethod.getFormalCtTypeParameters()
+            .stream()
+            .map(CtTypeParameter::toStringDebug)
+            .map(s -> s.replace("\n", ""))
+            .toList();
+        Context context = contextBuilder()
+            .add("expected", expected)
+            .add("actual", actualNames)
+            .build();
+
+        assertTrue(!typeVariable.isEmpty(), emptyContext(), r -> method.getName() + " does not have any generic parameters.");
+
+        assertEquals(
+            expected.size(),
+            typeVariable.size(),
+            context,
+            r -> method.getName() + " does not have the expected number of generic parameters."
+        );
+        typeVariable.forEach(a ->
+            assertTrue(
+                expected.stream().anyMatch(e -> e.test(a)),
+                context,
+                r -> String.format("The type parameter %s of %s do not match any expected types.", a, method.getName())
+            )
+        );
+    }
+
+    /**
+     * Asserts that the given {@link Method} has a return type that matches the given {@link Predicate}.
+     *
+     * @param method   the method that should be tested.
+     * @param expected the {@link Predicate} that shoul be used to check the return type.
+     */
+    public static void assertReturnParameter(Method method, Predicate expected) {
+        Type type = method.getGenericReturnType();
+
+        CtMethod> ctMethod = BasicMethodLink.of(method).getCtElement();
+        var actualNames = ctMethod.getType().toStringDebug().replace("\n", "");
+        Context context = contextBuilder()
+            .add("actual type", actualNames)
+            .add("expected", expected)
+            .build();
+
+        assertTrue(expected.test(type), context, r -> String.format("%s has a wrong return type.", method.getName()));
+    }
+
+    /**
+     * Asserts that the given {@link Method} has a correct list of parameters each parameter is checked with the given
+     * {@link Predicate} for the index.
+     *
+     * @param method   the method that should be checked
+     * @param expected a list containing a {@link Predicate} for each Parameter of the method.
+     */
+    public static void assertParameters(Method method, List> expected) {
+        Type[] type = method.getGenericParameterTypes();
+
+        assertEquals(
+            expected.size(),
+            type.length,
+            emptyContext(), r -> String.format("The method %s() does not have the correct amount of parameters", method.getName())
+        );
+
+        CtMethod> ctMethod = BasicMethodLink.of(method).getCtElement();
+        var actualNames =
+            ctMethod.getParameters()
+                .stream()
+                .map(CtParameter::getType)
+                .map(CtTypeReference::toStringDebug)
+                .map(s -> s.replace("\n", ""))
+                .toList();
+
+        for (int i = 0; i < type.length; i++) {
+            int finalI = i;
+
+            Context context = contextBuilder()
+                .add("actual type", actualNames.get(i))
+                .add("expected", expected.get(i))
+                .build();
+
+            assertTrue(
+                expected.get(i).test(type[i]),
+                context,
+                r -> String.format("%s has a wrong parameter at index %d.", method.getName(), finalI)
+            );
+        }
+
+    }
+
+    /**
+     * Asserts that the given field has a {@link Type} that matches the given {@link Predicate}.
+     *
+     * @param field    the field that should be checked.
+     * @param expected the {@link Predicate} that is used to check if the Field has a correct Type.
+     */
+    public static void assertType(Field field, Predicate expected) {
+        Type type = field.getGenericType();
+
+        CtField> ctField =
+            BasicTypeLink.of(field.getDeclaringClass()).getCtElement().filterChildren(new TypeFilter<>(CtField.class) {
+                @Override
+                public boolean matches(CtField element) {
+                    return super.matches(element) && element.getSimpleName().equals(field.getName());
+                }
+            }).first();
+        var actualNames = ctField.getType().toStringDebug();
+
+        Context context = contextBuilder()
+            .add("actual type", actualNames)
+            .add("expected", expected)
+            .build();
+
+        assertTrue(expected.test(type), context, r -> String.format("%s has a wrong type.", field.getName()));
+
+    }
+
+    /**
+     * Asserts that the given method is generic.
+     *
+     * @param toTest a method reference to the method that should be checked.
+     */
+    public static void assertGeneric(Method toTest) {
+
+        Predicate isGeneric = (method) -> !getTypeParameters(method, ".*").isEmpty();
+        isGeneric = isGeneric.or((method) -> getBounds(getReturnType(method)) != null);
+
+        assertTrue(
+            isGeneric.test(toTest),
+            emptyContext(),
+            r -> String.format("The method %s() is not Generic.", toTest.getName())
+        );
+    }
+
+    /**
+     * A simple Predicate that can store a custom toString() method for better readability.
+     */
+    public static class GenericPredicate implements Predicate {
+
+        /**
+         * The description that should be displayed if toString() is called.
+         */
+        private final String description;
+        /**
+         * The underlying predicate.
+         */
+        private final Predicate predicate;
+
+        /**
+         * Creates a new {@link GenericPredicate} from a {@link Predicate} and a short description that describes what the
+         * predicate matches.
+         *
+         * @param predicate   the predicate that should be used to match any object.
+         * @param description the description of what the predicate matches.
+         */
+        GenericPredicate(Predicate predicate, String description) {
+            this.predicate = predicate;
+            this.description = description;
+        }
+
+        @Override
+        public boolean test(Type type) {
+            return predicate.test(type);
+        }
+
+        @NotNull
+        @Override
+        public Predicate and(@NotNull Predicate super Type> other) {
+            return new GenericPredicate(predicate.and(other), "(" + this.description + " and " + other + ")");
+        }
+
+        @NotNull
+        @Override
+        public Predicate negate() {
+            return new GenericPredicate(predicate.negate(), "(not " + description + ")");
+        }
+
+        @NotNull
+        @Override
+        public Predicate or(@NotNull Predicate super Type> other) {
+            return new GenericPredicate(predicate.or(other), "(" + this.description + " or " + other + ")");
+        }
+
+        @Override
+        public String toString() {
+            return description;
+        }
+    }
+}
diff --git a/H09/src/graderPublic/java/h09/ReflectionUtils.java b/H09/src/graderPublic/java/h09/ReflectionUtils.java
new file mode 100644
index 0000000..9160c90
--- /dev/null
+++ b/H09/src/graderPublic/java/h09/ReflectionUtils.java
@@ -0,0 +1,191 @@
+package h09;
+
+import com.google.common.primitives.Primitives;
+import sun.misc.Unsafe;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.List;
+import java.util.Optional;
+
+public class ReflectionUtils {
+
+    public static void setFieldValue(Object instance, String fieldName, Object value) {
+        try {
+            Class> objectClass = instance.getClass();
+            Field declaredField;
+            try {
+                declaredField = objectClass.getDeclaredField(fieldName);
+            } catch (NoSuchFieldException e) {
+                declaredField = getSuperClassesIncludingSelf(objectClass).stream()
+                    .filter((c) -> List.of(c.getDeclaredFields()).stream()
+                        .map(Field::getName)
+                        .anyMatch(name -> name.equals(fieldName))
+                    )
+                    .map((c) -> {
+                        try {
+                            return c.getDeclaredField(fieldName);
+                        } catch (NoSuchFieldException ex) {
+                            throw new RuntimeException(ex);
+                        }
+                    })
+                    .findFirst()
+                    .orElseThrow(() -> new NoSuchFieldException(e.getMessage()));
+            }
+
+            //best case field in non Final
+            if (!Modifier.isFinal(declaredField.getModifiers())) {
+                try {
+                    declaredField.setAccessible(true);
+                    declaredField.set(instance, value);
+                    return;
+                } catch (Exception ignored) {
+                }
+            }
+
+            //field has setter
+            Optional setter = Arrays
+                .stream(objectClass.getDeclaredMethods())
+                .filter(
+                    m -> m.getName().equalsIgnoreCase("set" + fieldName)
+                ).findFirst();
+            if (setter.isPresent()) {
+                setter.get().invoke(instance, value);
+                return;
+            }
+
+            //rely on Unsafe to set value
+            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
+            unsafeField.setAccessible(true);
+            Unsafe unsafe = (Unsafe) unsafeField.get(null);
+
+            Field theInternalUnsafeField = Unsafe.class.getDeclaredField("theInternalUnsafe");
+            theInternalUnsafeField.setAccessible(true);
+            Object theInternalUnsafe = theInternalUnsafeField.get(null);
+
+            Method offset = Class.forName("jdk.internal.misc.Unsafe").getMethod("objectFieldOffset", Field.class);
+            unsafe.putBoolean(offset, 12, true);
+
+            switch (value) {
+                case Boolean val -> unsafe.putBoolean(instance, (long) offset.invoke(theInternalUnsafe, declaredField), val);
+                case Character val -> unsafe.putChar(instance, (long) offset.invoke(theInternalUnsafe, declaredField), val);
+                case Short val -> unsafe.putShort(instance, (long) offset.invoke(theInternalUnsafe, declaredField), val);
+                case Integer val -> unsafe.putInt(instance, (long) offset.invoke(theInternalUnsafe, declaredField), val);
+                case Long val -> unsafe.putLong(instance, (long) offset.invoke(theInternalUnsafe, declaredField), val);
+                case Double val -> unsafe.putDouble(instance, (long) offset.invoke(theInternalUnsafe, declaredField), val);
+                case Float val -> unsafe.putFloat(instance, (long) offset.invoke(theInternalUnsafe, declaredField), val);
+                default -> unsafe.putObject(instance, (long) offset.invoke(theInternalUnsafe, declaredField), value);
+            }
+        } catch (IllegalAccessException | NoSuchFieldException | ClassNotFoundException | NoSuchMethodException |
+                 InvocationTargetException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static  T getFieldValue(Object instance, String fieldName) {
+        Field f;
+        Class> fieldType = null;
+        try {
+            f = instance.getClass().getDeclaredField(fieldName);
+
+            try {
+                f.setAccessible(true);
+                return (T) f.get(instance);
+            } catch (Exception ignored) {
+            }
+
+            fieldType = f.getType();
+            if (Primitives.isWrapperType(fieldType)) {
+                fieldType = Primitives.unwrap(fieldType);
+            }
+
+            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
+            unsafeField.setAccessible(true);
+            Unsafe unsafe = (Unsafe) unsafeField.get(null);
+
+            Field theInternalUnsafeField = Unsafe.class.getDeclaredField("theInternalUnsafe");
+            theInternalUnsafeField.setAccessible(true);
+            Object theInternalUnsafe = theInternalUnsafeField.get(null);
+
+            Method offset = Class.forName("jdk.internal.misc.Unsafe").getMethod("objectFieldOffset", Field.class);
+            unsafe.putBoolean(offset, 12, true);
+
+            Object fieldValue;
+            if (boolean.class == fieldType) {
+                fieldValue = unsafe.getBoolean(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else if (byte.class == fieldType) {
+                fieldValue = unsafe.getByte(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else if (short.class == fieldType) {
+                fieldValue = unsafe.getShort(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else if (int.class == fieldType) {
+                fieldValue = unsafe.getInt(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else if (long.class == fieldType) {
+                fieldValue = unsafe.getLong(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else if (float.class == fieldType) {
+                fieldValue = unsafe.getFloat(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else if (double.class == fieldType) {
+                fieldValue = unsafe.getDouble(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else if (char.class == fieldType) {
+                fieldValue = unsafe.getChar(instance, (long) offset.invoke(theInternalUnsafe, f));
+            } else {
+                fieldValue = unsafe.getObject(instance, (long) offset.invoke(theInternalUnsafe, f));
+            }
+            return (T) fieldValue;
+        } catch (NoSuchFieldException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException |
+                 IllegalAccessException e) {
+            throw new RuntimeException(
+                "Could not set value for Field %s(%s) in %s. Please do not access this field.".formatted(
+                    fieldName,
+                    fieldType,
+                    instance.getClass()
+                ), e
+            );
+        }
+    }
+
+    public static void copyFields(Object source, Object dest) {
+        for (Field f : source.getClass().getDeclaredFields()) {
+            setFieldValue(dest, f.getName(), getFieldValue(source, f.getName()));
+        }
+    }
+
+    public static boolean actsLikePrimitive(Class> type) {
+        return type.isPrimitive() ||
+            Enum.class.isAssignableFrom(type) ||
+            Primitives.isWrapperType(type) ||
+            type == String.class;
+    }
+
+    public static List> getSuperClassesIncludingSelf(Class> clazz) {
+        List> classes = new ArrayList<>();
+        Deque> classDeque = new ArrayDeque<>();
+
+        classDeque.add(clazz);
+
+        while ((clazz = classDeque.peekFirst()) != null) {
+            classDeque.pop();
+
+            classes.add(clazz);
+            if (clazz.getSuperclass() != null) {
+                classDeque.add(clazz.getSuperclass());
+            }
+            if (clazz.getInterfaces().length > 0) {
+                classDeque.addAll(List.of(clazz.getInterfaces()));
+            }
+
+        }
+        return classes;
+    }
+
+    public static boolean isObjectMethod(Method methodToCheck) {
+        List objectMethods =
+            List.of("getClass", "hashCode", "equals", "clone", "toString", "notify", "notifyAll", "wait", "finalize");
+        return objectMethods.contains(methodToCheck.getName());
+    }
+}
diff --git a/H09/src/graderPublic/java/h09/StackOfObjectsTestPublic.java b/H09/src/graderPublic/java/h09/StackOfObjectsTestPublic.java
new file mode 100644
index 0000000..8dee2e6
--- /dev/null
+++ b/H09/src/graderPublic/java/h09/StackOfObjectsTestPublic.java
@@ -0,0 +1,101 @@
+package h09;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.sourcegrade.jagr.api.rubric.TestForSubmission;
+import org.tudalgo.algoutils.tutor.general.reflections.BasicTypeLink;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+import static h09.H09_TestUtils.assertDefinedParameters;
+import static h09.H09_TestUtils.assertParameters;
+import static h09.H09_TestUtils.assertReturnParameter;
+import static h09.H09_TestUtils.assertType;
+import static h09.H09_TestUtils.getTypeParameters;
+import static h09.H09_TestUtils.match;
+import static h09.H09_TestUtils.matchArray;
+import static h09.H09_TestUtils.matchNested;
+import static h09.H09_TestUtils.matchNoBounds;
+import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertNotNull;
+import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.emptyContext;
+import static org.tudalgo.algoutils.tutor.general.match.BasicStringMatchers.identical;
+
+@TestForSubmission
+public class StackOfObjectsTestPublic {
+
+    BasicTypeLink stackLink;
+    Class> ctClassStack;
+    Method get;
+    Method pop;
+    Method push;
+    Method remove;
+    Method of;
+    Field objs;
+
+
+    @BeforeEach
+    public void setUp() {
+        stackLink = BasicTypeLink.of(StackOfObjects.class);
+        ctClassStack = stackLink.reflection();
+        get = stackLink.getMethod(identical("get")).reflection();
+        pop = stackLink.getMethod(identical("pop")).reflection();
+        push = stackLink.getMethod(identical("push")).reflection();
+        remove = stackLink.getMethod(identical("remove")).reflection();
+        of = stackLink.getMethod(identical("of")).reflection();
+        objs = stackLink.getField(identical("objs")).reflection();
+    }
+
+    @Test
+    public void testClassParameter() {
+        assertDefinedParameters(ctClassStack, Set.of(matchNoBounds("O")));
+    }
+
+    @Test
+    public void testObjsType() {
+        assertType(objs, matchArray(matchNoBounds("O")));
+        assertNotNull(
+            ReflectionUtils.getFieldValue(new StackOfObjects<>(), "objs"),
+            emptyContext(),
+            r -> "Field objs is not correctly initialized"
+        );
+    }
+
+    @Test
+    public void testPushParameter() {
+        assertParameters(push, List.of(matchNoBounds("O")));
+    }
+
+    @Test
+    public void testRemoveParameter() {
+        assertParameters(remove, List.of(matchNoBounds("O")));
+    }
+
+    @Test
+    public void testPopParameter() {
+        assertReturnParameter(pop, matchNoBounds("O"));
+    }
+
+    @Test
+    public void testGetParameter() {
+        assertReturnParameter(get, matchNoBounds("O"));
+    }
+
+    @Test
+    public void testOfParameter() {
+        assertDefinedParameters(of, Set.of(matchNoBounds("O")));
+
+        List types = getTypeParameters(of, ".*");
+
+        assertReturnParameter(
+            of,
+            matchNested(StackOfObjects.class, match(((GenericArrayType) types.get(0)).getGenericComponentType()))
+        );
+
+        assertParameters(of, List.of(match(types.get(0))));
+    }
+}
diff --git a/H09/src/graderPublic/java/h09/WaterEnclosureTestPublic.java b/H09/src/graderPublic/java/h09/WaterEnclosureTestPublic.java
new file mode 100644
index 0000000..deccde2
--- /dev/null
+++ b/H09/src/graderPublic/java/h09/WaterEnclosureTestPublic.java
@@ -0,0 +1,140 @@
+package h09;
+
+import h09.abilities.Swims;
+import h09.animals.Penguin;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.sourcegrade.jagr.api.rubric.TestForSubmission;
+import org.tudalgo.algoutils.tutor.general.assertions.Context;
+import org.tudalgo.algoutils.tutor.general.reflections.BasicTypeLink;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertTrue;
+import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.contextBuilder;
+import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.fail;
+import static org.tudalgo.algoutils.tutor.general.match.BasicStringMatchers.identical;
+
+@TestForSubmission
+public class WaterEnclosureTestPublic {
+
+    BasicTypeLink waterEnclosureLink;
+    Class> waterEnclosureClass;
+    Method getStack;
+    Method feed;
+    Method getMeanElevation;
+    Field animals;
+
+
+    @BeforeEach
+    public void setUp() {
+        waterEnclosureLink = BasicTypeLink.of(WaterEnclosure.class);
+        waterEnclosureClass = waterEnclosureLink.reflection();
+        getStack = waterEnclosureLink.getMethod(identical("getStack")).reflection();
+        feed = waterEnclosureLink.getMethod(identical("feed")).reflection();
+        getMeanElevation = waterEnclosureLink.getMethod(identical("getMeanElevation")).reflection();
+        animals = waterEnclosureLink.getField(identical("animals")).reflection();
+    }
+
+    @Test
+    public void testFeed() {
+
+        WaterEnclosure enclosure = new WaterEnclosure();
+        List animals = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            Penguin mock = mock(Penguin.class, CALLS_REAL_METHODS);
+            ReflectionUtils.setFieldValue(mock, "isHungry", false);
+            ReflectionUtils.setFieldValue(mock, "elevation", Swims.MAX_ELEVATION);
+            if (i % 2 == 0) {
+                ReflectionUtils.setFieldValue(mock, "isHungry", true);
+            }
+            if (i % 3 == 0) {
+                ReflectionUtils.setFieldValue(mock, "elevation", Swims.MIN_ELEVATION);
+            }
+
+            enclosure.getStack().push(mock);
+            animals.add(mock);
+        }
+
+        Context context = contextBuilder()
+            .add("Animals", animals)
+            .build();
+
+        enclosure.feed();
+
+        for (int i = 0; i < 10; i++) {
+            if (enclosure.getStack().size() <= 0) {
+                fail(context, r -> "WaterEnclosure does not have correct number of Animals after feeding.");
+            }
+            Penguin mock = (Penguin) enclosure.getStack().pop();
+
+            int id = animals.indexOf(mock);
+
+            if (id % 2 == 0) {
+                verify(mock).eat();
+                verify(mock).swimDown();
+                if (i % 3 == 0) {
+                    verify(mock).swimUp();
+                } else {
+                    verify(mock, never()).swimUp();
+                }
+            } else {
+                verify(mock, never()).eat();
+            }
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("provide_testGetMeanElevation")
+    public void testGetMeanElevation(List elevations, double expected) {
+
+        WaterEnclosure enclosure = new WaterEnclosure<>();
+
+        for (double elevation : elevations) {
+            Penguin mock = mock(Penguin.class);
+            when(mock.getElevation()).thenReturn((float) elevation);
+
+            enclosure.getStack().push(mock);
+        }
+
+        float actual = enclosure.getMeanElevation();
+
+        Context context = contextBuilder()
+            .add("Elevations", elevations)
+            .build();
+
+        double error = 0.00001;
+
+        assertTrue(
+            Math.abs(Math.abs(expected) - Math.abs(actual)) < error,
+            context,
+            r -> "Average Elevation is not calculated correctly."
+        );
+    }
+
+    public static Stream provide_testGetMeanElevation() {
+        return Stream.of(
+            Arguments.of(List.of(0d, 0d, 0d, 0d, 0d, 0d), 0.0),
+            Arguments.of(List.of(0d, -1d, -2d, -3d, -4d, -5d), -2.5),
+            Arguments.of(List.of(-1d, -2d, -3d, -4d, -5d, -6d), -3.5),
+            Arguments.of(List.of(-6d, -5d, -4d, -3d, -2d, -1d), -3.5),
+            Arguments.of(List.of(-3d, -5d, 0d, -3d, -10d, -1d), -3.6666667),
+            Arguments.of(List.of(-3d, -2d, 0d, -3d, -2d, -3d), -2.1666667),
+            Arguments.of(List.of(-2d, -2d, -2d, -2d, -2d, -2d), -2.0),
+            Arguments.of(List.of(-1d, -2d, -2d), -1.6666666)
+        );
+    }
+}
diff --git a/H09/src/main/java/h09/Enclosure.java b/H09/src/main/java/h09/Enclosure.java
new file mode 100644
index 0000000..561255d
--- /dev/null
+++ b/H09/src/main/java/h09/Enclosure.java
@@ -0,0 +1,119 @@
+package h09;
+
+import h09.animals.Animal;
+import org.tudalgo.algoutils.student.annotation.DoNotTouch;
+import org.tudalgo.algoutils.student.annotation.StudentImplementationRequired;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * An object of a class implementing {@link Enclosure} has the ability to contain and manage a stack of {@link Animal}s.
+ */
+// TODO: H9.2.1
+public interface Enclosure {
+    /**
+     * @return the stack of animals which is used manage the contained {@link Animal}s
+     */
+    @StudentImplementationRequired("H9.2.1")
+    // TODO: H9.2.1
+    StackOfObjects getStack();
+
+    /**
+     * Feeds all contained animals.
+     */
+    @DoNotTouch
+    void feed();
+
+    /**
+     * Counts the number of hungry {@link Animal}s in the enclosure.
+     *
+     * @return number of hungry {@link Animal}s in the enclosure
+     */
+    @DoNotTouch
+    @SuppressWarnings("RedundantCast")
+    default int countHungry() {
+        int count = 0;
+        for (int i = 0; i < this.getStack().size(); i++)
+            if (((Animal) this.getStack().get(i)).isHungry()) count++;
+        return count;
+    }
+
+
+    /**
+     * Applies a {@link Consumer} operation on each {@link Animal} in the enclosure.
+     *
+     * @param func operation to be applied to each {@link Animal} in the enclosure
+     */
+    @StudentImplementationRequired("H9.3.1") // TODO: H9.3.1
+    default void forEach(Consumer func) {
+        for (int i = 0; i < this.getStack().size(); i++)
+            func.accept(this.getStack().get(i));
+    }
+
+    /**
+     * Tests a {@link Predicate} operation on each {@link Animal} in the enclosure and removes every {@link Animal}
+     * which does not satisfy the predicate. That means only {@link Animal}s for which the predicate returns 'true'
+     * remain in the enclosure.
+     *
+     * @param filter operation to test to each {@link Animal} in the enclosure
+     */
+    @StudentImplementationRequired("H9.3.2") // TODO: H9.3.2
+    default void filterObj(Predicate filter) {
+        for (int i = 0; i < this.getStack().size(); i++) {
+            Object a = this.getStack().get(i);
+            if (!filter.test(a)) {
+                this.getStack().remove(a);
+                i--;
+            }
+        }
+    }
+
+    /**
+     * Returns a new {@link Enclosure} that contains only the {@link Animal}s of the previous {@link Enclosure} which
+     * satisfied the predicate. That means only {@link Animal}s for which the predicate returns 'true' are included
+     * in the new enclosure.
+     *
+     * @param supp   {@link Supplier} which is used to create the new {@link Enclosure} to be returned
+     * @param filter operation to test to each {@link Animal} in the enclosure
+     * @param     Type of the new {@link Enclosure} which is returned
+     * @return a new {@link Enclosure} that contains only the {@link Animal}s of the previous {@link Enclosure} which
+     * satisfied the predicate
+     */
+    @StudentImplementationRequired("H9.3.3")
+    default Enclosure filterFunc(Supplier supp, Predicate