diff --git a/process/UnitTest_lib/process.js b/process/UnitTest_lib/process.js
index 382d37e021567c924734a1350a7a368acd1796d3..210ab48b8f435e633a665feed0a9dd766630d990 100644
--- a/process/UnitTest_lib/process.js
+++ b/process/UnitTest_lib/process.js
@@ -261,7 +261,7 @@ Tester.prototype.equals = function(pExpect, pCustomDescription)
 
     if(Utils.isObject(this.actualValue) || Utils.isObject(this.expectedValue))
     {
-        this.expectedDisplayValue = JSON.stringify(this.actualValue);
+        this.expectedDisplayValue = JSON.stringify(this.actualValue, _getCircularReplacer());
         this._testResult = Utils.isEqual(this.actualValue, this.expectedValue);
         this._generateAssertDescription({custom: pCustomDescription, operator: "===", name: "Object value"});
     }
@@ -272,6 +272,24 @@ Tester.prototype.equals = function(pExpect, pCustomDescription)
     }
 
     return this;
+    
+    //custom replacer that supports cyclic references
+    function _getCircularReplacer () 
+    {
+        var seen = new WeakSet();
+        return function (key, value) 
+        {
+            if (typeof value === "object" && value !== null) 
+            {
+                if (seen.has(value)) 
+                {
+                    return "{...}";
+                }
+                seen.add(value);
+            }
+            return value;
+        }
+    }
 }
 
 /**
@@ -1202,7 +1220,7 @@ Tester.prototype._generateTestTitle = function(pTest, pDataProviderIndex)
         var titleValues = [];
 
         this.dataProvider[pDataProviderIndex].forEach(function(pElement) {
-            titleValues.push(pElement === undefined ? "undefined" : JSON.stringify(pElement));
+            titleValues.push(pElement === undefined ? "undefined" : JSON.stringify(pElement, _getCircularReplacer()));
         });
         this._log("", this.t.info("\t\u2699 Test: " + pTest.name) + this.t.debug(" (#" + (pDataProviderIndex + 1) + ": " + titleValues.join(" | ") + ")"));
     }
@@ -1210,6 +1228,24 @@ Tester.prototype._generateTestTitle = function(pTest, pDataProviderIndex)
     {
         this._log("info", "\t\u2699 Test: " + pTest.name);
     }
+    
+    //custom replacer that supports cyclic references
+    function _getCircularReplacer () 
+    {
+        var seen = new WeakSet();
+        return function (key, value) 
+        {
+            if (typeof value === "object" && value !== null) 
+            {
+                if (seen.has(value)) 
+                {
+                    return "{...}";
+                }
+                seen.add(value);
+            }
+            return value;
+        }
+    }
 }
 
 /**
diff --git a/process/Util_lib/process.js b/process/Util_lib/process.js
index 8ea572643943a2724f7a323e207122ff530957de..4ef286fe09d3ed55ed6394c8b950ed650c71c67c 100644
--- a/process/Util_lib/process.js
+++ b/process/Util_lib/process.js
@@ -100,7 +100,7 @@ Utils.isNotNullOrEmptyString = function (pValue)
  */
 Utils.clone = function (pObject)
 {
-    var referenceMap = new Map();
+    var referenceMap = new WeakMap();
     return _clone(pObject);
     
     function _clone (pObject)
@@ -173,35 +173,62 @@ Utils.clone = function (pObject)
  */
 Utils.isEqual = function (pFirstObject, pSecondObject)
 {
-    var firstType = typeof pFirstObject;
-    var secondType = typeof pSecondObject;
-    if (firstType !== secondType)
-        return false;
-    if (firstType === "object" && pFirstObject !== null && pSecondObject !== null) //check for null because typeof null is also "object"
+    var comparedObjects = new WeakMap();
+    return _isEqual(pFirstObject, pSecondObject);
+    
+    function _isEqual (pFirstObject, pSecondObject)
     {
-        var isFirstArray = Array.isArray(pFirstObject);
-        var isSecondArray = Array.isArray(pSecondObject);
-        if (isFirstArray !== isSecondArray) //return false if only one object is an array
-            return false;
-        if (isFirstArray && pFirstObject.length !== pSecondObject.length)
+        var firstType = typeof pFirstObject;
+        var secondType = typeof pSecondObject;
+        if (firstType !== secondType)
+        {
             return false;
-        
-        for (let key in pSecondObject)
+        }
+        if (firstType === "object" && pFirstObject !== null && pSecondObject !== null) //check for null because typeof null is also "object"
         {
-            if (!(key in pFirstObject))
+            //All object comparisons are saved in comparedObjects, this makes it possible to detect recursions.
+            //If two objects have been checked for equality already, it can be safely assumed they are equal,
+            //because if they weren't, this function would have returned false already
+            if (comparedObjects.get(pFirstObject) === pSecondObject)
+            {
+                return true;
+            }
+            comparedObjects.set(pFirstObject, pSecondObject);
+            
+            var isFirstArray = Array.isArray(pFirstObject);
+            var isSecondArray = Array.isArray(pSecondObject);
+            if (isFirstArray !== isSecondArray) //return false if only one object is an array
+            {
+                return false;
+            }
+            if (isFirstArray && pFirstObject.length !== pSecondObject.length)
+            {
                 return false;
+            }
+            
+            for (let key in pSecondObject)
+            {
+                if (!(key in pFirstObject))
+                {
+                    return false;
+                }
+            }
+            for (let key in pFirstObject)
+            {
+                if (!(key in pSecondObject) || !_isEqual(pFirstObject[key], pSecondObject[key]))
+                {
+                    return false;
+                }
+            }
+            return true;
         }
-        for (let key in pFirstObject)
+        if (firstType === "number" && Number.isNaN(pFirstObject) && Number.isNaN(pSecondObject)) //NaN should be equal to NaN
         {
-            if (!(key in pSecondObject) || !Utils.isEqual(pFirstObject[key], pSecondObject[key]))
-                return false;
+            return true;
         }
-        return true;
+
+        return pFirstObject === pSecondObject;
     }
-    if (firstType === "number" && Number.isNaN(pFirstObject) && Number.isNaN(pSecondObject)) //NaN should be equal to NaN
-        return true;
-    
-    return pFirstObject === pSecondObject;
 }
 
 /**
diff --git a/process/Utils_test/Utils_test.aod b/process/Utils_test/Utils_test.aod
new file mode 100644
index 0000000000000000000000000000000000000000..f877069734b9b8465243129b62e612e9f887887a
--- /dev/null
+++ b/process/Utils_test/Utils_test.aod
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2">
+  <name>Utils_test</name>
+  <title>[TEST] Util_lib</title>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <icon>VAADIN:CHECK_CIRCLE</icon>
+  <process>%aditoprj%/process/Utils_test/process.js</process>
+  <variants>
+    <element>EXECUTABLE</element>
+  </variants>
+</process>
diff --git a/process/Utils_test/process.js b/process/Utils_test/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..3a094ad4dcb4e17784be072b5c6089ac636b2d8a
--- /dev/null
+++ b/process/Utils_test/process.js
@@ -0,0 +1,542 @@
+import("Util_lib");
+import("system.result");
+import("system.vars");
+import("UnitTest_lib");
+
+var isEmpty = new TestSuite("Utils.isEmpty", [
+    new Test("should test if an object, string or array is empty",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isEmpty(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [true, ""],
+                [true, []],
+                [true, {}],
+                [true, new Set()],
+                [true, new Map()],
+                [true, function () {}],
+                [false, "never gonna"],
+                [false, ["give"]],
+                [false, {you: "up"}],
+                [false, new Set(["never gonna"])],
+                [false, new Map([["let", "you down"]])]
+            ];
+        }
+    )
+]);
+
+var isNullOrEmpty = new TestSuite("Utils.isNullOrEmpty", [
+    new Test("should test if value is null, undefined or empty",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isNullOrEmpty(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [true, null],
+                [true, undefined],
+                [true, ""],
+                [true, []],
+                [true, {}],
+                [true, new Set()],
+                [true, new Map()],
+                [true, function () {}],
+                [false, "never gonna"],
+                [false, ["give"]],
+                [false, {you: "up"}],
+                [false, new Set(["never gonna"])],
+                [false, new Map([["let", "you down"]])]
+            ];
+        }
+    )
+]);
+
+var isNullOrEmptyString = new TestSuite("Utils.isNullOrEmptyString", [
+    new Test("should test if value is null, undefined or empty string",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isNullOrEmptyString(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [true, null],
+                [true, undefined],
+                [true, ""],
+                [false, 0],
+                [false, 42],
+                [false, false],
+                [false, true],
+                [false, []],
+                [false, {}],
+                [false, new Set()],
+                [false, new Map()],
+                [false, function () {}],
+                [false, "never gonna"],
+                [false, ["give"]],
+                [false, {you: "up"}],
+                [false, new Set(["never gonna"])],
+                [false, new Map([["let", "you down"]])]
+            ];
+        }
+    )
+]);
+
+var isNotNullOrEmptyString = new TestSuite("Utils.isNotNullOrEmptyString", [
+    new Test("should test if value isn't null, undefined or empty string",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isNotNullOrEmptyString(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, ""],
+                [true, 0],
+                [true, 42],
+                [true, false],
+                [true, true],
+                [true, []],
+                [true, {}],
+                [true, new Set()],
+                [true, new Map()],
+                [true, function () {}],
+                [true, "never gonna"],
+                [true, ["give"]],
+                [true, {you: "up"}],
+                [true, new Set(["never gonna"])],
+                [true, new Map([["let", "you down"]])]
+            ];
+        }
+    )
+]);
+
+var clone = new TestSuite("Utils.clone", [
+    new Test("should test if value is copied correctly",
+        function (pTester, pDataProvider) 
+        {
+            var original = pDataProvider[0];
+            var copy = Utils.clone(original);
+
+            pTester.expectThat(original).equals(copy).assert();
+        },
+        function dataProvider() 
+        {
+            var recursiveObj = {test: "Test"};
+            recursiveObj.recursion = recursiveObj;
+            return [
+                [null],
+                [undefined],
+                [0],
+                [42],
+                [""],
+                ["yeee"],
+                [[]],
+                [{}],
+                [["one", "two", 3, 4]],
+                [{one: "two", three: 4}],
+                [["one", "two", [3, 4, {five: "6"}]]],
+                [{one: "two", three: [4, {five: "6"}]}],
+                [new Set(["one", "two", 3, 4])],
+                [new Map([["one", "two"], [3, 4]])],
+                [recursiveObj]
+            ];
+        }
+    )
+]);
+
+var isEqual = new TestSuite("Utils.isEqual", [
+    new Test("should test if object equality is checked correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isEqual(pDataProvider[1], pDataProvider[2]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            var num = 0;
+            var str = "0";
+            var objStr = new String("0");
+            var obj_1 = {a: 1, b: 2, c: 3};
+            var obj_2 = {x: null, y: "test", z: true};
+            var recursiveObj1 = {test: "Test"};
+            recursiveObj1.recursion = recursiveObj1;
+            var recursiveObj2 = {test: "Test"};
+            recursiveObj2.recursion = recursiveObj2;
+            
+            return [
+                [true,  num,     num],
+                [true,  str,     str],
+                [true,  true,    true],
+                [true,  objStr,  objStr],
+                [true,  obj_1,   obj_1],
+                [true,  obj_2,   obj_2],
+                [false, true,    false],
+                [false, obj_1,   obj_2],
+                [false, num,     objStr],
+                [false, num,     str],
+                [false, objStr,  str],
+                [false, null,    undefined],
+                [false, objStr,  null],
+                [false, objStr,  undefined],
+                [false, null,    "null"],
+                [true, recursiveObj1, recursiveObj2]
+            ];
+        }
+    )
+]);
+
+var isFunction = new TestSuite("Utils.isFunction", [
+    new Test("should test if functions are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isFunction(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [true, function () {}],
+                [true, (function () {}).bind({})],
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [false, 0],
+                [false, "str"],
+                [false, {}],
+                [false, []],
+                [false, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var isString = new TestSuite("Utils.isString", [
+    new Test("should test if strings are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isString(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [false, 0],
+                [true, ""],
+                [true, "str"],
+                [false, new String("")],
+                [false, {}],
+                [false, []],
+                [false, function () {}],
+                [false, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var isNumber = new TestSuite("Utils.isNumber", [
+    new Test("should test if numbers are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isNumber(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [true, 0],
+                [true, NaN],
+                [true, Infinity],
+                [false, "0"],
+                [false, {}],
+                [false, []],
+                [false, function () {}],
+                [false, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var isNumeric = new TestSuite("Utils.isNumeric", [
+    new Test("should test if numbers are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isNumeric(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [true, 0],
+                [true, "0"],
+                [false, "notnumber"],
+                [false, {}],
+                [false, []],
+                [false, function () {}],
+                [false, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var isInteger = new TestSuite("Utils.isInteger", [
+    new Test("should test if integers are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isInteger(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [true, 0],
+                [true, "0"],
+                [true, 1.0],
+                [true, "1.0"],
+                [false, 1.2],
+                [false, "1.2"],
+                [false, NaN],
+                [false, Infinity],
+                [false, "notnumber"],
+                [false, {}],
+                [false, []],
+                [false, function () {}],
+                [false, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var isFloat = new TestSuite("Utils.isFloat", [
+    new Test("should test if floats are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isFloat(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [false, 0],
+                [false, "0"],
+                [false, 1.0],
+                [false, "1.0"],
+                [true, 1.2],
+                [true, "1.2"],
+                [false, NaN],
+                [false, Infinity],
+                [false, "notnumber"],
+                [false, {}],
+                [false, []],
+                [false, function () {}],
+                [false, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var isObject = new TestSuite("Utils.isObject", [
+    new Test("should test if objects are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isObject(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [false, 0],
+                [false, "0"],
+                [false, NaN],
+                [false, Infinity],
+                [true, {}],
+                [true, []],
+                [false, function () {}],
+                [true, new Map()],
+                [true, new Set()]
+            ];
+        }
+    )
+]);
+
+var isMap = new TestSuite("Utils.isMap", [
+    new Test("should test if maps are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isMap(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [false, true],
+                [false, false],
+                [false, 0],
+                [false, "0"],
+                [false, NaN],
+                [false, Infinity],
+                [false, {}],
+                [false, []],
+                [false, function () {}],
+                [true, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var isBoolean = new TestSuite("Utils.isBoolean", [
+    new Test("should test if booleans are identified correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.isBoolean(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [false, null],
+                [false, undefined],
+                [true, true],
+                [true, false],
+                [false, 0],
+                [false, "0"],
+                [false, NaN],
+                [false, Infinity],
+                [false, {}],
+                [false, []],
+                [false, function () {}],
+                [false, new Map()],
+                [false, new Set()]
+            ];
+        }
+    )
+]);
+
+var toBoolean = new TestSuite("Utils.toBoolean", [
+    new Test("should test if boolean-ish values are parsed correctly",
+        function (pTester, pDataProvider) 
+        {
+            var expectValue = pDataProvider[0];
+            var actualValue = Utils.toBoolean(pDataProvider[1]);
+
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [true, true],
+                [true, 1],
+                [true, "1"],
+                [true, "true"],
+                [true, {}],
+                [true, []],
+                [true, function () {}],
+                [true, new Map()],
+                [true, new Set()],
+                [false, false],
+                [false, 0],
+                [false, "0"],
+                [false, ""],
+                [false, "false"],
+                [false, null],
+                [false, "null"],
+                [false, undefined],
+                [false, "undefined"]
+            ];
+        }
+    )
+]);
+
+var tester = new Tester("Test Util_lib");
+tester.initCoverage(Utils);
+tester.test(isEmpty);
+tester.test(isNullOrEmpty);
+tester.test(isNullOrEmptyString);
+tester.test(clone);
+tester.test(isEqual);
+tester.test(isFunction);
+tester.test(isString);
+tester.test(isNumber);
+tester.test(isNumeric);
+tester.test(isInteger);
+tester.test(isFloat);
+tester.test(isObject);
+tester.test(isMap);
+tester.test(isBoolean);
+tester.test(toBoolean);
+
+tester.summary();
+    
+result.object(tester.getResults());