diff --git a/part_3/exercise_4/.gitignore b/part_3/exercise_4/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d578053fb6c37346e61ef9da56c648dac284bc98
--- /dev/null
+++ b/part_3/exercise_4/.gitignore
@@ -0,0 +1,30 @@
+### IntelliJ IDEA ###
+/.idea/
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/part_3/exercise_4/exercise_4.iml b/part_3/exercise_4/exercise_4.iml
new file mode 100644
index 0000000000000000000000000000000000000000..c90834f2d607afe55e6104d8aa2cdfffb713f688
--- /dev/null
+++ b/part_3/exercise_4/exercise_4.iml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/part_3/exercise_4/src/Disease.java b/part_3/exercise_4/src/Disease.java
new file mode 100644
index 0000000000000000000000000000000000000000..895987871673ead437451649f93c59d80be5204f
--- /dev/null
+++ b/part_3/exercise_4/src/Disease.java
@@ -0,0 +1,34 @@
+import java.util.HashSet;
+import java.util.List;
+
+public class Disease {
+    public String name;
+    public List<Symptom> symptoms;
+
+    static class Symptom {
+        public String description;
+
+        public Symptom(String description) {
+            this.description = description;
+        }
+    }
+
+    public Disease(String name, List<Symptom> symptoms) {
+        this.name = name;
+        this.symptoms = symptoms;
+    }
+}
+
+class Human {
+    public String name;
+    public HashSet<Disease.Symptom> symptoms;
+
+    public Human(String name, List<Disease.Symptom> symptoms) {
+        this.name = name;
+        this.symptoms = new HashSet<>(symptoms);
+    }
+
+    public boolean mayHaveDisease(Disease disease) {
+        return symptoms.containsAll(disease.symptoms);
+    }
+}
\ No newline at end of file
diff --git a/part_3/exercise_4/src/Main.java b/part_3/exercise_4/src/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..f847775738d032380a2b478a8d0ab992839e7186
--- /dev/null
+++ b/part_3/exercise_4/src/Main.java
@@ -0,0 +1,164 @@
+import java.util.ArrayList;
+import java.util.List;
+
+public class Main {
+    public static void main(String[] args) {
+        testTemperature();
+        System.out.println();
+        testDisease();
+        System.out.println();
+        testFilter();
+        System.out.println();
+        testPoints();
+    }
+
+    private static void testTemperature() {
+        Temperature temp1 = new Temperature(37, Temperature.Scale.CELSIUS);
+        Temperature temp2 = new Temperature(13, Temperature.Scale.CELSIUS);
+        Temperature temp3 = new Temperature(2, Temperature.Scale.FAHRENHEIT);
+
+        System.out.println("Test temperature:");
+        System.out.println("-----------------");
+
+        System.out.println("Temp1: " + temp1);
+        System.out.println("Temp2: " + temp2);
+        System.out.println("Temp3: " + temp3);
+
+        System.out.println("Temp1 + temp2: " + (temp1.add(temp2)));
+        System.out.println("Temp1 + temp2 + temp3: " + (temp1.add(temp2).add(temp3)));
+        System.out.println("Temp3 + temp1: " + (temp3.add(temp1)));
+
+        System.out.println("Temp1: " + temp1);
+        System.out.println("Temp2: " + temp2);
+        System.out.println("Temp3: " + temp3);
+    }
+
+    private static void testDisease() {
+        Disease.Symptom cough = new Disease.Symptom("Cough");
+        Disease.Symptom headache = new Disease.Symptom("Headache");
+        Disease.Symptom fever = new Disease.Symptom("Fever");
+
+        ArrayList<Disease.Symptom> fluSymptoms = new ArrayList<>();
+        fluSymptoms.add(cough);
+        fluSymptoms.add(headache);
+        fluSymptoms.add(fever);
+
+        ArrayList<Disease.Symptom> coldSymptoms = new ArrayList<>();
+        coldSymptoms.add(cough);
+
+        Human human = new Human("Human A", coldSymptoms);
+
+        Disease flu = new Disease("Flu", fluSymptoms);
+        Disease covid19 = new Disease("Covid 19", fluSymptoms);
+        Disease cold = new Disease("Cold", coldSymptoms);
+
+        System.out.println("Test disease:");
+        System.out.println("-------------");
+        System.out.println("Human A has symptoms: cough");
+        System.out.println("Flu has symptoms: cough, headache, fever");
+        System.out.println("Covid 19 has symptoms: cough, headache, fever");
+        System.out.println("Cold has symptoms: cough");
+
+        System.out.println("Human A may have Flu: " + human.mayHaveDisease(flu));
+        System.out.println("Human A may have Covid19: " + human.mayHaveDisease(covid19));
+        System.out.println("Human A may have Cold: " + human.mayHaveDisease(cold));
+    }
+
+    private static void testFilter() {
+        ArrayList<StudentSearchResult> rows = new ArrayList<>();
+        rows.add(new StudentSearchResult(
+                "Student 1",
+                "Address 1",
+                "email1@email.com",
+                "Program 1",
+                10
+        ));
+        rows.add(new StudentSearchResult(
+                "Student 2",
+                "Address 2",
+                "email2@email.com",
+                "Program 1",
+                280
+        ));
+        rows.add(new StudentSearchResult(
+                "Student 3",
+                "Address 3",
+                "email3@email.com",
+                "Program 2",
+                281
+        ));
+        rows.add(new StudentSearchResult(
+                "Student 4",
+                "Address 4",
+                "email4@email.com",
+                "Program 2",
+                279
+        ));
+        rows.add(new StudentSearchResult(
+                "Student 5",
+                "Address 5",
+                "email5@email.com",
+                "Program 1",
+                300
+        ));
+
+        EncouragementStudentFilter filter = new EncouragementStudentFilter();
+        List<StudentSearchResult> encouragementStudents = rows.stream()
+                .filter(filter::filter)
+                .toList();
+
+        System.out.println("Test filter:");
+        System.out.println("------------");
+        System.out.println("All students:");
+        rows.forEach(System.out::println);
+        System.out.println();
+        System.out.println("Encouragement students:");
+        encouragementStudents.forEach(System.out::println);
+    }
+
+    private static void testPoints() {
+        System.out.println("Test point classes:");
+        System.out.println("-------------------");
+
+        System.out.println("Normal point class:");
+        NormalPoint normalPoint = new NormalPoint(0, 1);
+        NormalPoint normalPoint2 = normalPoint;
+        System.out.println("Reading X: " + normalPoint.values[0]);
+        System.out.println("Reading Y: " + normalPoint.values[1]);
+        System.out.println("Reading X (shared): " + normalPoint2.values[0]);
+        System.out.println("Reading Y (shared): " + normalPoint2.values[1]);
+        System.out.println("Writing X, Y to 2, 3");
+        normalPoint.values[0] = 2;
+        normalPoint.values[1] = 3;
+        System.out.println("Reading X: " + normalPoint.values[0]);
+        System.out.println("Reading Y: " + normalPoint.values[1]);
+        System.out.println("Reading X (shared): " + normalPoint2.values[0]);
+        System.out.println("Reading Y (shared): " + normalPoint2.values[1]);
+
+        System.out.println();
+        System.out.println("Record point class:");
+        RecordPoint recordPoint = new RecordPoint(0, 1);
+        RecordPoint recordPoint2 = recordPoint;
+        System.out.println("Reading X: " + recordPoint.x());
+        System.out.println("Reading Y: " + recordPoint.y());
+        System.out.println("Reading X (shared): " + recordPoint2.x());
+        System.out.println("Reading Y (shared): " + recordPoint2.y());
+        System.out.println("Writing: can not change X, Y");
+
+        System.out.println();
+        System.out.println("Record number point class:");
+        RecordNumberPoint recordNumberPoint = new RecordNumberPoint(0, 1);
+        RecordNumberPoint recordNumberPoint2 = recordNumberPoint;
+        System.out.println("Reading X: " + recordNumberPoint.x().value);
+        System.out.println("Reading Y: " + recordNumberPoint.y().value);
+        System.out.println("Reading X (shared): " + recordNumberPoint2.x().value);
+        System.out.println("Reading Y (shared): " + recordNumberPoint2.y().value);
+        System.out.println("Writing X, Y to 2, 3");
+        recordNumberPoint.x().value = 2;
+        recordNumberPoint.y().value = 3;
+        System.out.println("Reading X: " + recordNumberPoint.x().value);
+        System.out.println("Reading Y: " + recordNumberPoint.y().value);
+        System.out.println("Reading X (shared): " + recordNumberPoint2.x().value);
+        System.out.println("Reading Y (shared): " + recordNumberPoint2.y().value);
+    }
+}
\ No newline at end of file
diff --git a/part_3/exercise_4/src/NormalPoint.java b/part_3/exercise_4/src/NormalPoint.java
new file mode 100644
index 0000000000000000000000000000000000000000..506c39c39095eb7477c1eb86064d080b7a1ce7ca
--- /dev/null
+++ b/part_3/exercise_4/src/NormalPoint.java
@@ -0,0 +1,22 @@
+public class NormalPoint {
+    final int[] values = new int[2];
+
+    NormalPoint(int x, int y) {
+        values[0] = x;
+        values[1] = y;
+    }
+}
+
+record RecordPoint(int x, int y) {}
+
+class Number {
+    int value;
+
+    Number(int value) { this.value = value; }
+}
+
+record RecordNumberPoint(Number x, Number y) {
+    RecordNumberPoint(int x, int y) {
+        this(new Number(x), new Number(y));
+    }
+}
\ No newline at end of file
diff --git a/part_3/exercise_4/src/StudentSearchResult.java b/part_3/exercise_4/src/StudentSearchResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4b092efdfbab1b38786321b67a4f14c789a0c65
--- /dev/null
+++ b/part_3/exercise_4/src/StudentSearchResult.java
@@ -0,0 +1,2 @@
+public record StudentSearchResult(String name, String postalAddress, String emailAddress, String degreeProgram, int numberOfCredits) {
+}
diff --git a/part_3/exercise_4/src/StudentSearchResultFilterInterface.java b/part_3/exercise_4/src/StudentSearchResultFilterInterface.java
new file mode 100644
index 0000000000000000000000000000000000000000..24a7a56ec851b4ede587a765f41330bf0194d0f3
--- /dev/null
+++ b/part_3/exercise_4/src/StudentSearchResultFilterInterface.java
@@ -0,0 +1,10 @@
+public interface StudentSearchResultFilterInterface {
+    boolean filter(StudentSearchResult row);
+}
+
+record EncouragementStudentFilter() implements StudentSearchResultFilterInterface {
+    @Override
+    public boolean filter(StudentSearchResult row) {
+        return row.numberOfCredits() >= 280;
+    }
+}
\ No newline at end of file
diff --git a/part_3/exercise_4/src/Temperature.java b/part_3/exercise_4/src/Temperature.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e821f19f344294e943e2cf77ad11f4c73481e65
--- /dev/null
+++ b/part_3/exercise_4/src/Temperature.java
@@ -0,0 +1,54 @@
+import java.util.Objects;
+
+public record Temperature(double value, Scale scale) {
+    enum Scale {
+        CELSIUS("Celsius", 'C'), FAHRENHEIT("Fahrenheit", 'F'), Kelvin("KELVIN", 'K');
+
+        final String name;
+        final Character unit;
+
+        Scale(String name, Character unit) {
+            this.name = name;
+            this.unit = unit;
+        }
+    }
+
+    public Temperature {
+        Objects.requireNonNull(scale, "Scale should not be null");
+    }
+
+    @Override
+    public String toString() {
+        return value + "°" + scale.unit;
+    }
+
+    public Temperature add(Temperature temperature) {
+        if (temperature.scale() != scale) {
+            return convertToScale(temperature.scale()).add(temperature);
+        }
+
+        return new Temperature(value + temperature.value(), scale);
+    }
+
+    public Temperature convertToScale(Scale toScale) {
+        double convertedValue = switch (scale) {
+            case CELSIUS -> switch (toScale) {
+                case CELSIUS -> value;
+                case FAHRENHEIT -> value * (9 / 5.0) + 32; // F = C(9 ⁄ 5) + 32.
+                case Kelvin -> value + 273.15; // K = C + 273.15.
+            };
+            case FAHRENHEIT -> switch (toScale) {
+                case CELSIUS -> (value - 32) * 5 / 9.0; // C = (F − 32) × 5 ⁄ 9.
+                case FAHRENHEIT -> value;
+                case Kelvin -> (value - 32) * 5 / 9.0 + 273.15; // K = (F − 32) × 5 ⁄ 9 + 273.15.
+            };
+            case Kelvin -> switch (toScale) {
+                case CELSIUS -> value - 273.15; // C = K − 273.15.
+                case FAHRENHEIT -> (value - 273.15) * 9 / 5.0 + 32; // F = (K – 273.15) × 9 ⁄ 5 + 32.
+                case Kelvin -> value;
+            };
+        };
+
+        return new Temperature(convertedValue, toScale);
+    }
+}
diff --git a/part_3/exercise_4/src/readme.md b/part_3/exercise_4/src/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..2858b8c9199ea7ce79776314bdfac335b573373b
--- /dev/null
+++ b/part_3/exercise_4/src/readme.md
@@ -0,0 +1,202 @@
+## a) We want to represent temperatures. A temperature has value semantics and behaves differently than a double, but it also contains information that it is a temperature. There are three different scales for temperature (Celsius, Fahrenheit, and Kelvin) that should be supported. Additionally, the temperature should be printed according to its scale, e.g., 0°C, 32°F, or 273.15K. It should be easy to perform calculations with different temperature values (although Java does not support operator overloading, java.math.BigInteger demonstrates how operations could be implemented).
+
+### Answer:
+- As a temperature has value semantics, so it should be immutable and **Record** is suitable in this case.
+- There are 3 parts of a temperature: a value, a scale and a unit. For example: `32°F`: value is `32`, scale is `Fahrenheit` and unit is `F`.
+- Regarding the scale, because there are a limited number of supported scales, and the unit belongs to the scale. So, the scale can be an object of `Enum` with 2 properties: `name` and `unit`.  
+- When perform a calculation with different temperature value, the result should be another temperature object.
+
+##### Why not other options:
+- There are unlimited values can be created => we can not use `Enum` for temperature (although it is used for `Scale`).
+- There is no relation with other objects in this case => `Static Inner Class`, `Nested Class`, `Anonymous class` or `Sealed class` is not suitable.
+- There are multiple operations => `Function literals and interfaces` is also not suitable.
+- We can also implement it with a final basic class with final members and hashCode & equals that compare members in pairs.
+
+#### Example implementation:
+
+```java
+import java.util.Objects;
+
+public record Temperature(double value, Scale scale) {
+    enum Scale {
+        CELSIUS("Celsius", 'C'), FAHRENHEIT("Fahrenheit", 'F'), Kelvin("KELVIN", 'K');
+
+        final String name;
+        final Character unit;
+
+        Scale(String name, Character unit) {
+            this.name = name;
+            this.unit = unit;
+        }
+    }
+
+    public Temperature {
+        Objects.requireNonNull(scale, "Scale should not be null");
+    }
+
+    @Override
+    public String toString() {
+        return value + "°" + scale.unit;
+    }
+
+    public Temperature add(Temperature temperature) {
+        if (temperature.scale() != scale) {
+            return convertToScale(temperature.scale()).add(temperature);
+        }
+
+        return new Temperature(value + temperature.value(), scale);
+    }
+
+    public Temperature convertToScale(Scale toScale) {
+        double convertedValue = switch (scale) {
+            case CELSIUS -> switch (toScale) {
+                case CELSIUS -> value;
+                case FAHRENHEIT -> value * (9 / 5.0) + 32; // F = C(9 ⁄ 5) + 32.
+                case Kelvin -> value + 273.15; // K = C + 273.15.
+            };
+            case FAHRENHEIT -> switch (toScale) {
+                case CELSIUS -> (value - 32) * 5 / 9.0; // C = (F − 32) × 5 ⁄ 9.
+                case FAHRENHEIT -> value;
+                case Kelvin -> (value - 32) * 5 / 9.0 + 273.15; // K = (F − 32) × 5 ⁄ 9 + 273.15.
+            };
+            case Kelvin -> switch (toScale) {
+                case CELSIUS -> value - 273.15; // C = K − 273.15.
+                case FAHRENHEIT -> (value - 273.15) * 9 / 5.0 + 32; // F = (K – 273.15) × 9 ⁄ 5 + 32.
+                case Kelvin -> value;
+            };
+        };
+
+        return new Temperature(convertedValue, toScale);
+    }
+}
+```
+
+## b) We want to represent diseases known in medicine. A certain number of diseases, x, are known, but new ones can be discovered every year because pathogens evolve. A disease has the features name and symptoms. The target of the latter is a Human object.
+
+### Answer:
+- We choose the `Static Inner Class` in this case, as `Symptom` and `Disease` has a relationship, `Symptom` belongs to `Disease` but `Symptom` object may be created without `Disease` object (in case we have not known the disease yet).  
+
+##### Why not other options:
+- Because new disease can be discovered every year => `Enum` is not suitable.
+- The disease can be modified (for example: change the name or symptoms) => `Record` is not suitable.
+- `Sealed class`, `Anonymous class` is also not suitable as there is no relationship with other objects.
+- As a disease has `name`, `symptoms` properties, and may have no operation or multiple operations => `Function literals and interfaces` is not suitable.
+- `Nested class` is not suitable as multiple diseases can have a same symptom, and a human may have symptoms of unknown disease (we can create a `Symptom` object without knowing the `Disease` object).
+
+#### Example implementation:
+
+```java
+import java.util.HashSet;
+import java.util.List;
+
+public class Disease {
+    public String name;
+    public List<Symptom> symptoms;
+
+    static class Symptom {
+        public String description;
+
+        public Symptom(String description) {
+            this.description = description;
+        }
+    }
+
+    public Disease(String name, List<Symptom> symptoms) {
+        this.name = name;
+        this.symptoms = symptoms;
+    }
+}
+
+class Human {
+    public String name;
+    public HashSet<Disease.Symptom> symptoms;
+
+    public Human(String name, List<Disease.Symptom> symptoms) {
+        this.name = name;
+        this.symptoms = new HashSet<>(symptoms);
+    }
+
+    public boolean mayHaveDisease(Disease disease) {
+        return symptoms.containsAll(disease.symptoms);
+    }
+}
+```
+
+## c) We want to search the university student database for students registered as present in 2023. The database query returns a list of rows, where each individual row consists of a quintuple of four values (Name, Postal Address, Email Address, Degree Program, Number of Credits).
+
+### Answer:
+- We choose the `Record` for `Row` class as it is immutable and only used for searching. 
+
+### Why not other options:
+- There are unknown records of student => `Enum` is not suitable.
+- There is no relation with other objects in this case => `Static Inner Class`, `Nested Class`, `Anonymous class` or `Sealed class` is not suitable.
+- There is no operation or may have multiple operations => `Function literals and interfaces` is also not suitable.
+- We can also implement it with a final basic class with final members and hashCode & equals that compare members in pairs.
+
+#### Example implementation:
+
+```java
+public record StudentSearchResult(String name, String postalAddress, String emailAddress, String degreeProgram, int numberOfCredits) {
+}
+```
+
+## d) We want to operate on the list from the previous item C (type List<Row>) and filter out all students whose number of credits is less than 280. The remaining list contains those who should receive an encouragement message because graduation is almost within reach. Instead of writing the filter again as a for-loop each time, it should be reusable. The functional part of the filter's signature could be boolean filter(Row row).
+
+### Answer:
+- We choose the `Record` in this case for reusable as we don't want to repeat the filter, and there is no properties in this filter.
+- We can use `Basic class` instead of `Record` also.
+
+### Why not other options:
+- There is no relation with other objects in this case => `Static Inner Class`, `Nested Class` or `Sealed class` is not suitable.
+- `Enum` is not suitable in this case as there is no predefined values (objects).
+- `Anonymous class`, `Function literals and interfaces` is not suitable as we have to repeat the logic each time we want to filter.
+
+#### Example implementation:
+
+```java
+public interface StudentSearchResultFilterInterface {
+    boolean filter(StudentSearchResult row);
+}
+
+record EncouragementStudentFilter() implements StudentSearchResultFilterInterface {
+    @Override
+    public boolean filter(StudentSearchResult row) {
+        return row.numberOfCredits() >= 280;
+    }
+}
+```
+
+## e) Additionally, determine (e.g., by experimenting) how the following Point definitions differ in behavior, if we limit ourselves to different uses of the coordinates x and y of the point. Hint: reading, modifying & sharing information with multiple clients. The issues of thread programming and data serialization can be ignored in this course (they will be covered in another course).
+
+```java
+class Number {
+    int value;
+    
+    Number(int value) { this.value = value; }
+}
+
+// Seuraavat mallintavat kokonaislukumuotoisen
+// 2d-koordinaatiston pisteen (x, y).
+
+class Point {
+    final int[] values = new int[2];
+    
+    Point(int x, int y) {
+        values[0] = x;
+        values[1] = y;
+    }
+}
+
+record Point(int x, int y) {}
+
+record Point(Number x, Number y) {
+    Point(int x, int y) {
+        this(new Number(x), new Number(y));
+    }
+}
+```
+
+### Answer:
+- The Normal Point class: can read and change the coordinates (X, Y), if one of coordinates is changed, the values of the reference objects are also changed.
+- The Record Point class: can read the coordinates only.
+- The Record Point with Number class: can read the coordinates, can not change the value of X, Y (instances of Number), but is able to change the value (integer type) of the value of X, Y (e.g: change the value (integer type) of `value` property of `Number` class). If the integer value is changed, the value (integer) of the reference objects are also changed.