diff --git a/DIRECTORY.md b/DIRECTORY.md
index fb49d163..6f246ff1 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -30,6 +30,7 @@
             * [Blowfish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Blowfish.java)
             * [Caesar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Caesar.java)
             * [ColumnarTranspositionCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java)
+            * [DES](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/DES.java)
             * [HillCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/HillCipher.java)
             * [Polybius](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Polybius.java)
             * [ProductCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ProductCipher.java)
@@ -151,14 +152,15 @@
               * [AVLSimple](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java)
               * [AVLTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java)
               * [BinaryTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java)
+              * [BSTFromSortedArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTFromSortedArray.java)
               * [BSTIterative](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTIterative.java)
               * [BSTRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursive.java)
               * [BSTRecursiveGeneric](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java)
               * [CeilInBinarySearchTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTree.java)
+              * [CheckBinaryTreeIsValidBST](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBST.java)
               * [CheckIfBinaryTreeBalanced](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CheckIfBinaryTreeBalanced.java)
               * [CheckTreeIsSymmetric](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetric.java)
               * [CreateBinaryTreeFromInorderPreorder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CreateBinaryTreeFromInorderPreorder.java)
-              * [CreateBSTFromSortedArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CreateBSTFromSortedArray.java)
               * [FenwickTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java)
               * [GenericTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java)
               * [InorderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/InorderTraversal.java)
@@ -166,7 +168,6 @@
               * [LazySegmentTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/LazySegmentTree.java)
               * [LCA](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/LCA.java)
               * [LevelOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/LevelOrderTraversal.java)
-              * [LevelOrderTraversalHelper](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/LevelOrderTraversalHelper.java)
               * [nearestRightKey](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/nearestRightKey.java)
               * [PostOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PostOrderTraversal.java)
               * [PreOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PreOrderTraversal.java)
@@ -175,9 +176,7 @@
               * [SameTreesCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SameTreesCheck.java)
               * [SegmentTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java)
               * [TreeRandomNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TreeRandomNode.java)
-              * [TreeTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TreeTraversal.java)
               * [TrieImp](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java)
-              * [ValidBSTOrNot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/ValidBSTOrNot.java)
               * [VerticalOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversal.java)
               * [ZigzagTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/ZigzagTraversal.java)
           * devutils
@@ -202,6 +201,7 @@
             * [BoundaryFill](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java)
             * [BruteForceKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java)
             * [CatalanNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/CatalanNumber.java)
+            * [ClimbingStairs](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/ClimbingStairs.java)
             * [CoinChange](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java)
             * [CountFriendsPairing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/CountFriendsPairing.java)
             * [DiceThrow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java)
@@ -382,6 +382,7 @@
             * [KochSnowflake](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/KochSnowflake.java)
             * [Krishnamurthy](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Krishnamurthy.java)
             * [LinearCongruentialGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/LinearCongruentialGenerator.java)
+            * [LineSweep](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/LineSweep.java)
             * [LowestBasePalindrome](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java)
             * [Luhn](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Luhn.java)
             * [Mandelbrot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Mandelbrot.java)
@@ -403,7 +404,6 @@
             * [StackPostfixNotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/StackPostfixNotation.java)
             * [StringMatchFiniteAutomata](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/StringMatchFiniteAutomata.java)
             * [Sudoku](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Sudoku.java)
-            * [ThreeSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ThreeSum.java)
             * [TopKWords](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/TopKWords.java)
             * [TowerOfHanoi](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/TowerOfHanoi.java)
             * [TwoPointers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/TwoPointers.java)
@@ -526,6 +526,7 @@
               * [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java)
             * [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java)
             * [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java)
+            * [DESTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DESTest.java)
             * [PolybiusTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PolybiusTest.java)
             * [RSATest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RSATest.java)
             * [SimpleSubCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/SimpleSubCipherTest.java)
@@ -575,7 +576,10 @@
               * [PriorityQueuesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java)
             * trees
               * [BinaryTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java)
+              * [BSTFromSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTFromSortedArrayTest.java)
+              * [BSTIterativeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTIterativeTest.java)
               * [CeilInBinarySearchTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTreeTest.java)
+              * [CheckBinaryTreeIsValidBSTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBSTTest.java)
               * [CheckTreeIsSymmetricTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetricTest.java)
               * [InorderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/InorderTraversalTest.java)
               * [KDTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/KDTreeTest.java)
@@ -585,7 +589,6 @@
               * [PreOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/PreOrderTraversalTest.java)
               * [SameTreesCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SameTreesCheckTest.java)
               * [TreeTestUtils](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreeTestUtils.java)
-              * [ValidBSTOrNotTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/ValidBSTOrNotTest.java)
               * [VerticalOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversalTest.java)
               * [ZigzagTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/ZigzagTraversalTest.java)
           * divideandconquer
@@ -593,6 +596,7 @@
             * [StrassenMatrixMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplicationTest.java)
           * dynamicprogramming
             * [CatalanNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/CatalanNumberTest.java)
+            * [climbStairsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/climbStairsTest.java)
             * [EggDroppingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/EggDroppingTest.java)
             * [KnapsackMemoizationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java)
             * [LevenshteinDistanceTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LevenshteinDistanceTests.java)
@@ -674,6 +678,7 @@
             * [CRCAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java)
             * [FirstFitCPUTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/FirstFitCPUTest.java)
             * [KadaneAlogrithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/KadaneAlogrithmTest.java)
+            * [LineSweepTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LineSweepTest.java)
             * [LinkListSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LinkListSortTest.java)
             * [NewManShanksPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java)
             * [NextFitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/NextFitTest.java)
@@ -731,6 +736,7 @@
             * [CheckAnagramsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java)
             * [CheckVowelsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CheckVowelsTest.java)
             * [HammingDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/HammingDistanceTest.java)
+            * [HorspoolSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java)
             * [IsomorphicTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/IsomorphicTest.java)
             * [LetterCombinationsOfPhoneNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumberTest.java)
             * [longestNonRepeativeSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/longestNonRepeativeSubstringTest.java)
diff --git a/src/main/java/com/thealgorithms/ciphers/DES.java b/src/main/java/com/thealgorithms/ciphers/DES.java
new file mode 100644
index 00000000..8b226e28
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/DES.java
@@ -0,0 +1,344 @@
+package com.thealgorithms.ciphers;
+
+/**
+ * This class is build to demonstrate the application of the DES-algorithm on a
+ * plain English message. The supplied key must be in form of a 64 bit binary String.
+ */
+public class DES {
+
+    private String key;
+    private String subKeys[];
+
+    private void sanitize(String key) {
+        int length = key.length();
+        if (length != 64) {
+            throw new IllegalArgumentException("DES key must be supplied as a 64 character binary string");
+        }
+    }
+
+    DES(String key) {
+        sanitize(key);
+        this.key = key;
+        subKeys = getSubkeys(key);
+    }
+
+    public String getKey() {
+        return this.key;
+    }
+
+    public void setKey(String key) {
+        sanitize(key);
+        this.key = key;
+    }
+
+    //Permutation table to convert initial 64 bit key to 56 bit key
+	private static int[] PC1 =
+	{
+		57, 49, 41, 33, 25, 17,  9,
+        1, 58, 50, 42, 34, 26, 18,
+        10,  2, 59, 51, 43, 35, 27,
+        19, 11,  3, 60, 52, 44, 36,
+        63, 55, 47, 39, 31, 23, 15,
+        7, 62, 54, 46, 38, 30, 22,
+        14,  6, 61, 53, 45, 37, 29,
+        21, 13,  5, 28, 20, 12,  4
+	};
+
+    //Lookup table used to shift the initial key, in order to generate the subkeys
+    private static int[] KEY_SHIFTS =
+	{
+		1,  1,  2,  2,  2,  2,  2,  2,  1,  2,  2,  2,  2,  2,  2,  1
+	};
+
+    //Table to convert the 56 bit subkeys to 48 bit subkeys
+    private static int[] PC2 =
+	{
+		14, 17, 11, 24,  1,  5,
+        3, 28, 15,  6, 21, 10,
+        23, 19, 12,  4, 26,  8,
+        16,  7, 27, 20, 13,  2,
+        41, 52, 31, 37, 47, 55,
+        30, 40, 51, 45, 33, 48,
+        44, 49, 39, 56, 34, 53,
+        46, 42, 50, 36, 29, 32
+	};
+
+    //Initial permutatation of each 64 but message block
+    private static int[] IP =
+	{
+		 58, 50, 42, 34, 26, 18, 10 , 2,
+         60, 52, 44, 36, 28, 20, 12, 4,
+         62, 54, 46, 38, 30, 22, 14, 6,
+         64, 56, 48, 40, 32, 24, 16, 8,
+         57, 49, 41, 33, 25, 17, 9, 1,
+         59, 51, 43, 35, 27, 19, 11, 3,
+         61, 53, 45, 37, 29, 21, 13, 5,
+         63, 55, 47, 39, 31, 23, 15, 7
+	};
+
+    //Expansion table to convert right half of message blocks from 32 bits to 48 bits
+    private static int[] expansion =
+	{
+		32,  1,  2,  3,  4,  5,
+		4,  5,  6,  7,  8,  9,
+		8,  9, 10, 11, 12, 13,
+		12, 13, 14, 15, 16, 17,
+		16, 17, 18, 19, 20, 21,
+		20, 21, 22, 23, 24, 25,
+		24, 25, 26, 27, 28, 29,
+		28, 29, 30, 31, 32,  1
+	};
+
+    //The eight substitution boxes are defined below
+    private static int[][] s1 = {
+		{14, 4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7},
+		{0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11,  9,  5,  3,  8},
+		{4, 1, 14,  8, 13,  6, 2, 11, 15, 12,  9,  7,  3, 10,  5,  0},
+		{15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
+	 };
+
+	private static int[][] s2 = {
+			{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
+			{3, 13,  4, 7, 15,  2,  8, 14, 12,  0, 1, 10,  6,  9, 11,  5},
+			{0, 14, 7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15},
+			{13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14,  9}
+		 };
+
+	private static int[][] s3 = {
+			{10, 0, 9, 14, 6, 3, 15, 5,  1, 13, 12, 7, 11, 4, 2,  8},
+			{13, 7, 0, 9, 3,  4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
+			{13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14,  7},
+			{1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
+		 };
+
+	private static int[][] s4 = {
+			{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
+			{13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14,  9},
+			{10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
+			{3, 15, 0, 6, 10, 1, 13, 8, 9,  4, 5, 11, 12, 7, 2, 14}
+		 };
+
+	private static int[][] s5 = {
+			{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
+			{14, 11, 2, 12,  4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
+			{4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
+			{11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
+		  };
+
+	private static int[][] s6 = {
+			{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
+			{10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
+			{9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
+			{4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
+		  };
+
+	private static int[][] s7 = {
+			{4, 11, 2, 14, 15,  0, 8, 13 , 3, 12, 9 , 7,  5, 10, 6, 1},
+			{13 , 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
+			{1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
+			{6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
+		  };
+
+	private static int[][] s8 = {
+			{13, 2, 8,  4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
+			{1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6 ,11, 0, 14, 9, 2},
+			{7, 11, 4, 1, 9, 12, 14, 2,  0, 6, 10 ,13, 15, 3, 5, 8},
+			{2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6 ,11}
+		};
+
+	private static int[][][] s = {s1, s2, s3, s4, s5, s6, s7, s8};
+
+    //Permutation table, used in the feistel function post s-box usage
+    static int[] permutation =
+	{
+		16,  7, 20, 21,
+		29, 12, 28, 17,
+		 1, 15, 23, 26,
+		 5, 18, 31, 10,
+		 2,  8, 24, 14,
+		32, 27,  3,  9,
+		19, 13, 30,  6,
+		22, 11,  4, 25
+	};
+
+    //Table used for final inversion of the message box after 16 rounds of Feistel Function
+    static int[] IPinverse =
+	{
+		40, 8, 48, 16, 56, 24, 64, 32,
+	    39, 7, 47, 15, 55, 23, 63, 31,
+	    38, 6, 46, 14, 54, 22, 62, 30,
+	    37, 5, 45, 13, 53, 21, 61, 29,
+	    36, 4, 44, 12, 52, 20, 60, 28,
+	    35, 3, 43 ,11, 51, 19, 59, 27,
+	    34, 2, 42, 10, 50, 18, 58, 26,
+	    33, 1, 41, 9, 49, 17, 57, 25
+	};
+
+    private String[] getSubkeys(String originalKey) {
+        StringBuilder permutedKey = new StringBuilder(); //Initial permutation of keys via PC1
+        int i, j;
+        for (i = 0; i < 56; i++) {
+            permutedKey.append(originalKey.charAt(PC1[i] - 1));
+        }
+        String subKeys[] = new String[16];
+        String initialPermutedKey = permutedKey.toString();
+        String C0 = initialPermutedKey.substring(0, 28), D0 = initialPermutedKey.substring(28);
+
+        //We will now operate on the left and right halves of the permutedKey
+        for (i = 0; i < 16; i++) {
+            String Cn = C0.substring(KEY_SHIFTS[i]) + C0.substring(0, KEY_SHIFTS[i]);
+            String Dn = D0.substring(KEY_SHIFTS[i]) + D0.substring(0, KEY_SHIFTS[i]);
+            subKeys[i] = Cn + Dn;
+            C0 = Cn; //Re-assign the values to create running permutation
+            D0 = Dn;
+        }
+
+        //Let us shrink the keys to 48 bits (well, characters here) using PC2
+        for (i = 0; i < 16; i++) {
+            String key = subKeys[i];
+            permutedKey.setLength(0);
+            for (j = 0; j < 48; j++) {
+                permutedKey.append(key.charAt(PC2[j] - 1));
+            }
+            subKeys[i] = permutedKey.toString();
+        }
+
+        return subKeys;
+    }
+
+    private String XOR(String a, String b) {
+        int i, l = a.length();
+        StringBuilder xor = new StringBuilder();
+        for (i = 0; i < l; i++) {
+            int firstBit = a.charAt(i) - 48; // 48 is '0' in ascii
+            int secondBit = b.charAt(i) - 48;
+            xor.append((firstBit ^ secondBit));
+        }
+        return xor.toString();
+    }
+
+    private String createPaddedString(String s, int desiredLength, char pad) {
+        int i, l = s.length();
+        StringBuilder paddedString = new StringBuilder();
+        int diff = desiredLength - l;
+        for (i = 0; i < diff; i++) {
+            paddedString.append(pad);
+        }
+        return paddedString.toString();
+    }
+
+    private String pad(String s, int desiredLength) {
+        return createPaddedString(s, desiredLength, '0') + s;
+    }
+
+    private String padLast(String s, int desiredLength) {
+        return s + createPaddedString(s, desiredLength, '\u0000');
+    }
+
+    private String feistel(String messageBlock, String key) {
+        int i;
+        StringBuilder expandedKey = new StringBuilder();
+        for (i = 0; i < 48; i++) {
+            expandedKey.append(messageBlock.charAt(expansion[i] - 1));
+        }
+        String mixedKey = XOR(expandedKey.toString(), key);
+        StringBuilder substitutedString = new StringBuilder();
+
+        //Let us now use the s-boxes to transform each 6 bit (length here) block to 4 bits
+        for (i = 0; i < 48; i += 6) {
+            String block = mixedKey.substring(i, i + 6);
+            int row = (block.charAt(0) - 48) * 2 + (block.charAt(5) - 48);
+            int col = (block.charAt(1) - 48) * 8 + (block.charAt(2) - 48) * 4 + (block.charAt(3) - 48) * 2 + (block.charAt(4) - 48);
+            String substitutedBlock = pad(Integer.toBinaryString(s[i / 6][row][col]), 4);
+            substitutedString.append(substitutedBlock);
+        }
+
+        StringBuilder permutedString = new StringBuilder();
+        for (i = 0; i < 32; i++) {
+            permutedString.append(substitutedString.charAt(permutation[i] - 1));
+        }
+
+        return permutedString.toString();
+    }
+
+    private String encryptBlock(String message, String keys[]) {
+        StringBuilder permutedMessage = new StringBuilder();
+        int i;
+        for (i = 0; i < 64; i++) {
+            permutedMessage.append(message.charAt(IP[i] - 1));
+        }
+        String L0 = permutedMessage.substring(0, 32), R0 = permutedMessage.substring(32);
+
+        //Iterate 16 times
+        for (i = 0; i < 16; i++) {
+            String Ln = R0; // Previous Right block
+            String Rn = XOR(L0, feistel(R0, keys[i]));
+            L0 = Ln;
+            R0 = Rn;
+        }
+
+        String combinedBlock = R0 + L0; //Reverse the 16th block
+        permutedMessage.setLength(0);
+        for (i = 0; i < 64; i++) {
+            permutedMessage.append(combinedBlock.charAt(IPinverse[i] - 1));
+        }
+        return permutedMessage.toString();
+    }
+
+    //To decode, we follow the same process as encoding, but with reversed keys
+    private String decryptBlock(String message, String keys[]) {
+        String reversedKeys[] = new String[keys.length];
+        for (int i = 0; i < keys.length; i++) {
+            reversedKeys[i] = keys[keys.length - i - 1];
+        }
+        return encryptBlock(message, reversedKeys);
+    }
+
+    /**
+     * @param message Message to be encrypted
+     * @return The encrypted message, as a binary string
+     */
+    public String encrypt(String message) {
+        StringBuilder encryptedMessage = new StringBuilder();
+        int l = message.length(), i, j;
+        if (l % 8 != 0) {
+            int desiredLength = (l / 8 + 1) * 8;
+            l = desiredLength;
+            message = padLast(message, desiredLength);
+        }
+
+        for (i = 0; i < l; i+= 8) {
+            String block = message.substring(i, i + 8);
+            StringBuilder bitBlock = new StringBuilder();
+            byte[] bytes = block.getBytes();
+            for (j = 0; j < 8; j++) {
+                bitBlock.append(pad(Integer.toBinaryString(bytes[j]), 8));
+            }
+            encryptedMessage.append(encryptBlock(bitBlock.toString(), subKeys));
+        }
+        return encryptedMessage.toString();
+    }
+
+    /**
+     * @param message The encrypted string. Expects it to be a multiple of 64 bits, in binary format
+     * @return The decrypted String, in plain English
+     */
+    public String decrypt(String message) {
+        StringBuilder decryptedMessage = new StringBuilder();
+        int l = message.length(), i, j;
+        if (l % 64 != 0) {
+            throw new IllegalArgumentException("Encrypted message should be a multiple of 64 characters in length");
+        }
+        for (i = 0; i < l; i+= 64) {
+            String block = message.substring(i, i + 64);
+            String result = decryptBlock(block.toString(), subKeys);
+            byte res[] = new byte[8];
+            for (j = 0; j < 64; j+=8) {
+                res[j / 8] = (byte)Integer.parseInt(result.substring(j, j + 8), 2);
+            }
+            decryptedMessage.append(new String(res));
+        }
+        return decryptedMessage.toString().replace("\0", ""); // Get rid of the null bytes used for padding
+    }
+
+}
\ No newline at end of file

<!-- For completed items, change [ ] to [x] -->

- [x] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Java/blob/master/CONTRIBUTING.md).
- [x] This pull request is all my own work -- I have not plagiarized it.
- [x] All filenames are in PascalCase.
- [x] All functions and variable names follow Java naming conventions.
- [x] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.

This closes #4167

def parse_java_mvn(log: str) -> dict[str, str]:
    """Parse Maven logs into {identifier: status}."""
    results = {}

    failure_re = re.compile(
        r"^\[ERROR\]\s+(?P<class>[A-Za-z0-9_$.]+)\.(?P<method>[A-Za-z0-9_$.]+?)(?::(?P<line>\d+))?\b"
    )
    running_re = re.compile(r"^(?:\[INFO\]\s+)?Running\s+([A-Za-z0-9_$.]+)$")
    summary_re = re.compile(
        r"Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)"
    )

    seen_running = set()
    current_running = None

    for raw in log.splitlines():
        line = raw.strip()
        if not line:
            continue
        rm = running_re.match(line)
        if rm:
            running_name = rm.group(1)
            current_running = running_name
            seen_running.add(running_name)
            continue
        fm = failure_re.match(line)
        if fm:
            clazz = fm.group("class")
            method = fm.group("method")
            test_id = f"{clazz}.{method}"
            results[test_id] = "FAILED"
            results.setdefault(clazz, "FAILED")
            continue

        sm = summary_re.search(line)
        if sm:
            _tests_run, failures, errors, skipped = map(int, sm.groups())
            if failures > 0 or errors > 0:
                status = "FAILED"
            elif skipped > 0:
                status = "SKIPPED"
            else:
                status = "PASSED"
            if current_running:
                results.setdefault(current_running, status)
            current_running = None

    if not results and seen_running:
        for clazz in seen_running:
            results.setdefault(clazz, "PASSED")
    return results

bash -c '{ mvn --batch-mode test ; } > /tmp/test.log 2>&1'


diff --git a/.github/workflows/snapshot_release.yml b/.github/workflows/snapshot_release.yml
index 4cd3ce040..9683bf2ab 100644
--- a/.github/workflows/snapshot_release.yml
+++ b/.github/workflows/snapshot_release.yml
@@ -63,7 +63,7 @@ jobs:
           node-version: '16'

       - name: Restore Tests dependency cache
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: |
             ~/cache
@@ -245,7 +245,7 @@ jobs:

     - name: Package and upload to OSSRH
       run: |
-        mvn -B package deploy -DskipTests=true -DrepositoryId=ossrh -Dapple.notarization.username=$APPLE_USERNAME -Dapple.notarization.password=$APPLE_PASSWORD -Dapple.notarization.team.id=$APPLE_TEAM_ID
+        mvn -B deploy -DskipTests=true -DrepositoryId=ossrh -Dapple.notarization.username=$APPLE_USERNAME -Dapple.notarization.password=$APPLE_PASSWORD -Dapple.notarization.team.id=$APPLE_TEAM_ID
       env:
         OSSRH_USER: ${{ secrets.OSSRH_USER }}
         OSSRH_PASS: ${{ secrets.OSSRH_PASS }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3a812db33..31264c060 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,12 +24,12 @@ When browsing our documentation, feel free to use the edit button to suggest imp
 ##  Contributing code

 You can contribute code in various ways:
-- Fix bugs or implement new features. Follow [our guide towards your first code contribution](https://openrefine.org/docs/technical-reference/contributing#your-first-code-pull-request).
+- Fix bugs or implement new features. Follow [our guide towards your first code contribution](https://openrefine.org/docs/technical-reference/code-contributions)
 - Develop an OpenRefine extension
 - Develop a reconciliation service

 All developers including new distributions and plugin developers are invited to leverage the following OpenRefine project management areas to avoid splitting the community in different communication channels.
-- the [official documentation](https://openrefine.org/docs/technical-reference/technical-reference-index) for shared documentation between both user docs and [technical reference](https://docs.openrefine.org/technical-reference/technical-reference-index)
+- the [official documentation](https://openrefine.org/docs/) for shared documentation between both user docs and [technical reference](https://docs.openrefine.org/technical-reference/contributing)
 - the [developer forum](https://forum.openrefine.org/c/dev/8) for technical questions, new feature development and anything code related. We invite you to share you idea there first. Someone may be able to point out to existing development saving you hours of research and development.
 - [OpenRefine github issue tracker](https://github.com/OpenRefine/OpenRefine/issues) for requesting new features and bug reports.
 - [Gitter Chat](https://gitter.im/OpenRefine/OpenRefine)
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
index bf0a74484..7feb82b87 100644
--- a/GOVERNANCE.md
+++ b/GOVERNANCE.md
@@ -90,15 +90,16 @@ Advisory Committee members:
 * Can be part of the Admin team for the project on GitHub

 #### Conflict of Interest
-In the event of any conflict of interest (a Committee Member, their family member, or someone with whom the Committee Member has a close academic or employment relationship is involved in a decision), the Committee Member must immediately notify other Committee Members. The Committee Member will be asked to recuse themselves from ongoing conversations, and decision process regarding the Transaction.
+In the event of any conflict of interest (a Committee Member, their family member, or someone with whom the Committee Member has a close academic or employment relationship is involved in a decision), the Committee Member must immediately notify other Committee Members. The Committee Members will be asked to recuse themselves from ongoing conversations and decision processes regarding the Transaction.

 #### Current list of Advisory Committee members
 * Jan Ainali
 * Antonin Delpeuch
-* [Open Position]
+* Julie Faure-Lacroix
+* Esther Jackson

 ### Project Manager
-OpenRefine's Project Manager (paid position) works closely with OpenRefine’s advisory and steering committees, and with its communities of users and contributors.
+OpenRefine's Project Manager (paid position) works closely with OpenRefine’s advisory and steering committees and with its communities of users and contributors.

 The Project Manager:
 * Helps to improve OpenRefine's governance and community diversity
diff --git a/extensions/database/pom.xml b/extensions/database/pom.xml
index 5826dd92c..66ccf7d4f 100644
--- a/extensions/database/pom.xml
+++ b/extensions/database/pom.xml
@@ -105,7 +105,7 @@
     <dependency>
       <groupId>org.xerial</groupId>
       <artifactId>sqlite-jdbc</artifactId>
-      <version>3.45.0.0</version>
+      <version>3.45.1.0</version>
     </dependency>
     <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java b/extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java
index 277feb032..7d11e18f7 100644
--- a/extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java
@@ -67,6 +67,13 @@ public class DatabaseConfiguration {
     }

     public void setDatabaseHost(String databaseServer) {
+        // forbid setting settings inside the host parameter:
+        // https://dev.mysql.com/doc/connector-j/en/connector-j-reference-jdbc-url-format.html
+        if (databaseServer == null ||
+                databaseServer.contains("(") ||
+                databaseServer.contains("=")) {
+            throw new IllegalArgumentException("Invalid host supplied");
+        }
         this.databaseHost = databaseServer;
     }

diff --git a/extensions/wikibase/module/langs/translation-nl.json b/extensions/wikibase/module/langs/translation-nl.json
index 3191073f6..cc8840267 100644
--- a/extensions/wikibase/module/langs/translation-nl.json
+++ b/extensions/wikibase/module/langs/translation-nl.json
@@ -129,5 +129,9 @@
     "wikibase-account/password-remember-me-title": "Je wachtwoord wordt niet opgeslagen. Vink dit vakje niet aan als je op een publieke computer werkt.",
     "wikibase-account/owner-only-consumer-remember-me-title": "Je inloggegevens worden onversleuteld opgeslagen in cookies. Vink dit vakje niet aan als je op een publieke computer werkt.",
     "perform-wikibase-edits/review-your-edits": "Je gaat zo dadelijk $1 bewerkingen uploaden naar $3. Kijk deze grondig na. Grote batch-bewerkingen moeten eerst via een bot-review gecheckt worden.",
-    "wikibase-account/consumer-token-label": "Consumententoken"
+    "wikibase-account/consumer-token-label": "Consumententoken",
+    "wikibase-management/discover-manifests": "Ontdek Wikibase-instanties…",
+    "wikibase-management/cancel": "Annuleren",
+    "wikibase-management/ok": "OKÉ",
+    "wikibase-addition/version-error": "Manifest versie $1 wordt niet ondersteund. OpenRefine ondersteund alleen manifest versies 1.x en 2.x."
 }
diff --git a/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java b/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java
index 8508e8468..eabf19441 100644
--- a/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java
+++ b/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java
@@ -155,6 +155,7 @@ public class ReconClearOneCellCommand extends Command {
             } else {
                 int newChange = 0;
                 int matchChange = 0;
+                int errorChange = 0;

                 if (oldJudgment == Judgment.New) {
                     newChange--;
@@ -162,11 +163,14 @@ public class ReconClearOneCellCommand extends Command {
                 if (oldJudgment == Judgment.Matched) {
                     matchChange--;
                 }
+                if (oldJudgment == Judgment.Error)
+                    errorChange--;

                 stats = new ReconStats(
                         stats.nonBlanks + 1,
                         stats.newTopics + newChange,
-                        stats.matchedTopics + matchChange);
+                        stats.matchedTopics + matchChange,
+                        stats.errorTopics + errorChange);
             }

             String description = "Clear recon data for single cell on row " + (rowIndex + 1) +
diff --git a/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java b/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java
index 8b83a6691..c093e6cb0 100644
--- a/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java
+++ b/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java
@@ -190,10 +190,18 @@ public class ReconJudgeOneCellCommand extends Command {
             newCell.recon.judgmentBatchSize = 1;

             if (judgment == Judgment.None) {
-                newCell.recon.judgment = Recon.Judgment.None;
+                if (cell.recon.error == null) {
+                    newCell.recon.judgment = Recon.Judgment.None;
+                } else {
+                    newCell.recon.judgment = Recon.Judgment.Error;
+                }
                 newCell.recon.match = null;
-
                 description = "Discard recon judgment for " + cellDescription;
+
+            } else if (judgment == Judgment.Error) {
+
+                throw new IllegalArgumentException("Cannot manually set judgment to 'error'");
+
             } else if (judgment == Judgment.New) {
                 newCell.recon.judgment = Recon.Judgment.New;
                 newCell.recon.match = null;
@@ -222,6 +230,7 @@ public class ReconJudgeOneCellCommand extends Command {
             } else {
                 int newChange = 0;
                 int matchChange = 0;
+                int errorsChange = 0;

                 if (oldJudgment == Judgment.New) {
                     newChange--;
@@ -229,17 +238,24 @@ public class ReconJudgeOneCellCommand extends Command {
                 if (oldJudgment == Judgment.Matched) {
                     matchChange--;
                 }
+                if (oldJudgment == Judgment.Error) {
+                    errorsChange--;
+                }
                 if (newCell.recon.judgment == Judgment.New) {
                     newChange++;
                 }
                 if (newCell.recon.judgment == Judgment.Matched) {
                     matchChange++;
                 }
+                if (newCell.recon.judgment == Judgment.Error) {
+                    errorsChange++;
+                }

                 stats = new ReconStats(
                         stats.nonBlanks,
                         stats.newTopics + newChange,
-                        stats.matchedTopics + matchChange);
+                        stats.matchedTopics + matchChange,
+                        stats.errorTopics + errorsChange);
             }

             Change change = new ReconChange(
diff --git a/main/src/com/google/refine/model/Recon.java b/main/src/com/google/refine/model/Recon.java
index b1a531d23..5d9304b8e 100644
--- a/main/src/com/google/refine/model/Recon.java
+++ b/main/src/com/google/refine/model/Recon.java
@@ -69,7 +69,8 @@ public class Recon implements HasFields {
         @JsonProperty("none")
         None, @JsonProperty("matched")
         Matched, @JsonProperty("new")
-        New
+        New, @JsonProperty("error")
+        Error
     }

     @Deprecated
@@ -78,6 +79,8 @@ public class Recon implements HasFields {
             return "matched";
         } else if (judgment == Judgment.New) {
             return "new";
+        } else if (judgment == Judgment.Error) {
+            return "errors";
         } else {
             return "none";
         }
@@ -89,6 +92,8 @@ public class Recon implements HasFields {
             return Judgment.Matched;
         } else if ("new".equals(s)) {
             return Judgment.New;
+        } else if ("errors".equals(s)) {
+            return Judgment.Error;
         } else {
             return Judgment.None;
         }
@@ -198,6 +203,7 @@ public class Recon implements HasFields {

         r.match = match;
         r.matchRank = matchRank;
+        r.error = error;
     }

     public void addCandidate(ReconCandidate candidate) {
diff --git a/main/src/com/google/refine/model/ReconStats.java b/main/src/com/google/refine/model/ReconStats.java
index 11325e789..ce55c9434 100644
--- a/main/src/com/google/refine/model/ReconStats.java
+++ b/main/src/com/google/refine/model/ReconStats.java
@@ -51,21 +51,26 @@ public class ReconStats {
     final public int newTopics;
     @JsonProperty("matchedTopics")
     final public int matchedTopics;
+    @JsonProperty("errorTopics")
+    final public int errorTopics;

     @JsonCreator
     public ReconStats(
             @JsonProperty("nonBlanks") int nonBlanks,
             @JsonProperty("newTopics") int newTopics,
-            @JsonProperty("matchedTopics") int matchedTopics) {
+            @JsonProperty("matchedTopics") int matchedTopics,
+            @JsonProperty("errorTopics") int errorTopics) {
         this.nonBlanks = nonBlanks;
         this.newTopics = newTopics;
         this.matchedTopics = matchedTopics;
+        this.errorTopics = errorTopics;
     }

     static public ReconStats create(Project project, int cellIndex) {
         int nonBlanks = 0;
         int newTopics = 0;
         int matchedTopics = 0;
+        int errorTopics = 0;

         for (Row row : project.rows) {
             Cell cell = row.getCell(cellIndex);
@@ -77,12 +82,14 @@ public class ReconStats {
                         newTopics++;
                     } else if (cell.recon.judgment == Judgment.Matched) {
                         matchedTopics++;
+                    } else if (cell.recon.judgment == Judgment.Error) {
+                        errorTopics++;
                     }
                 }
             }
         }

-        return new ReconStats(nonBlanks, newTopics, matchedTopics);
+        return new ReconStats(nonBlanks, newTopics, matchedTopics, errorTopics);
     }

     public void save(Writer writer) {
diff --git a/main/src/com/google/refine/model/recon/StandardReconConfig.java b/main/src/com/google/refine/model/recon/StandardReconConfig.java
index 8101facf0..80d0b45cc 100644
--- a/main/src/com/google/refine/model/recon/StandardReconConfig.java
+++ b/main/src/com/google/refine/model/recon/StandardReconConfig.java
@@ -464,12 +464,14 @@ public class StandardReconConfig extends ReconConfig {
         } catch (IOException e) {
             Recon recon = new Recon(historyEntryID, identifierSpace, schemaSpace);
             recon.error = e.getMessage();
+            recon.judgment = Judgment.Error;
             recons.add(recon);
         }

         if (o == null) { // utility method returns null instead of throwing
             Recon recon = new Recon(historyEntryID, identifierSpace, schemaSpace);
             recon.error = "The reconciliation service returned an invalid response";
+            recon.judgment = Judgment.Error;
             recons.add(recon);

         } else {
@@ -488,11 +490,13 @@ public class StandardReconConfig extends ReconConfig {
                     } else {
                         recon = new Recon(historyEntryID, identifierSpace, schemaSpace);
                         recon.error = "The service returned a JSON response without \"result\" field for query " + key;
+                        recon.judgment = Judgment.Error;

                     }
                 } else {
                     recon = new Recon(historyEntryID, identifierSpace, schemaSpace);
                     recon.error = "The service returned a JSON response without \"" + key + "\" field ";
+                    recon.judgment = Judgment.Error;
                 }

                 if (recon != null) {
@@ -506,6 +510,7 @@ public class StandardReconConfig extends ReconConfig {
         while (recons.size() < jobs.size()) {
             Recon recon = new Recon(historyEntryID, identifierSpace, schemaSpace);
             recon.error = "No. of recon objects was less than no. of jobs";
+            recon.judgment = Judgment.Error;
             recons.add(recon);
         }

diff --git a/main/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsOperation.java b/main/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsOperation.java
index 369404f2e..079fd98be 100644
--- a/main/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsOperation.java
+++ b/main/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsOperation.java
@@ -222,8 +222,10 @@ public class ReconJudgeSimilarCellsOperation extends EngineDependentMassCellOper
                                     recon.judgment = Recon.Judgment.New;
                                     recon.match = null;
                                 } else if (_judgment == Judgment.None) {
-                                    recon.judgment = Recon.Judgment.None;
+                                    recon.judgment = recon.error == null ? Recon.Judgment.None : Recon.Judgment.Error;
                                     recon.match = null;
+                                } else if (_judgment == Judgment.Error) {
+                                    throw new IllegalArgumentException("Cannot manually set judgment to 'error'");
                                 }

                                 _dupReconMap.put(cell.recon.id, recon);
diff --git a/packaging/pom.xml b/packaging/pom.xml
index b21c43a56..073ee37f7 100644
--- a/packaging/pom.xml
+++ b/packaging/pom.xml
@@ -26,7 +26,7 @@
     <mac.dmg.path>${project.build.directory}/${project.build.finalName}-mac-${project.version}.dmg</mac.dmg.path>
     <mac.signing.identity>Code for Science and Society, Inc.</mac.signing.identity>

-    <apple.notarization.timeout>10m</apple.notarization.timeout>
+    <apple.notarization.timeout>15m</apple.notarization.timeout>

     <windows.jre.path>${project.build.directory}/windows_jre</windows.jre.path>
     <windows.jre.url>https://api.adoptium.net/v3/binary/latest/${embedded.jre.version}/ga/windows/x64/jre/hotspot/normal/eclipse?project=jdk</windows.jre.url>
diff --git a/pom.xml b/pom.xml
index 4098e5c8d..3d0d5ca85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,7 +75,7 @@
     <jackson.version>2.16.1</jackson.version>
     <servlet-api.version>4.0.1</servlet-api.version>
     <butterfly.version>1.2.5</butterfly.version>
-    <slf4j.version>2.0.11</slf4j.version>
+    <slf4j.version>2.0.12</slf4j.version>
     <log4j.version>2.22.1</log4j.version>
     <jetty.version>10.0.16</jetty.version>
     <okhttp.version>4.12.0</okhttp.version>
@@ -88,7 +88,7 @@
     <commons-validator.version>1.8.0</commons-validator.version>
     <org-jvnet-localizer.version>1.31</org-jvnet-localizer.version>

-    <commons-codec.version>1.16.0</commons-codec.version>
+    <commons-codec.version>1.16.1</commons-codec.version>
     <commons-compress.version>1.25.0</commons-compress.version>
     <velocity.version>2.3</velocity.version>
     <marc4j.version>2.9.5</marc4j.version>

diff --git a/.github/workflows/pull_request_e2e.yml b/.github/workflows/pull_request_e2e.yml
index 391b52c9b..48ab36bef 100644
--- a/.github/workflows/pull_request_e2e.yml
+++ b/.github/workflows/pull_request_e2e.yml
@@ -61,7 +61,7 @@ jobs:
           node-version: '16'

       - name: Restore Tests dependency cache
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: |
             ~/cache
diff --git a/extensions/database/tests/src/com/google/refine/extension/database/DatabaseConfigurationTest.java b/extensions/database/tests/src/com/google/refine/extension/database/DatabaseConfigurationTest.java
index ef101c896..b45f19b58 100644
--- a/extensions/database/tests/src/com/google/refine/extension/database/DatabaseConfigurationTest.java
+++ b/extensions/database/tests/src/com/google/refine/extension/database/DatabaseConfigurationTest.java
@@ -2,6 +2,7 @@
 package com.google.refine.extension.database;

 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;

 import org.testng.annotations.Test;

@@ -19,4 +20,13 @@ public class DatabaseConfigurationTest {
         // the database name is escaped, preventing the exploit
         assertEquals(url, "jdbc:mysql://my.host/test%3FallowLoadLocalInfile=true%23");
     }
+
+    @Test
+    public void testSetMaliciousHost() {
+        DatabaseConfiguration config = new DatabaseConfiguration();
+        config.setDatabaseType("mysql");
+
+        assertThrows(IllegalArgumentException.class,
+                () -> config.setDatabaseHost("127.0.0.1:3306,(allowLoadLocalInfile=true,allowUrlInLocalInfile=true),127.0.0.1"));
+    }
 }
diff --git a/main/tests/cypress/package.json b/main/tests/cypress/package.json
index 0ab6dd681..c5eb1ace6 100644
--- a/main/tests/cypress/package.json
+++ b/main/tests/cypress/package.json
@@ -24,6 +24,6 @@
         "eslint-config-google": "^0.14.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-cypress": "^2.15.1",
-        "prettier": "3.2.4"
+        "prettier": "3.2.5"
     }
 }
diff --git a/main/tests/cypress/yarn.lock b/main/tests/cypress/yarn.lock
index 7a601b5e3..07e07f4cb 100644
--- a/main/tests/cypress/yarn.lock
+++ b/main/tests/cypress/yarn.lock
@@ -1308,10 +1308,10 @@ prelude-ls@^1.2.1:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==

-prettier@3.2.4:
-  version "3.2.4"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.4.tgz#4723cadeac2ce7c9227de758e5ff9b14e075f283"
-  integrity sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==
+prettier@3.2.5:
+  version "3.2.5"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
+  integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==

 pretty-bytes@^5.6.0:
   version "5.6.0"
diff --git a/main/tests/server/src/com/google/refine/model/ColumnTests.java b/main/tests/server/src/com/google/refine/model/ColumnTests.java
index 7aa7c2523..f367dd415 100644
--- a/main/tests/server/src/com/google/refine/model/ColumnTests.java
+++ b/main/tests/server/src/com/google/refine/model/ColumnTests.java
@@ -76,7 +76,8 @@ public class ColumnTests {
                 + "\"reconStats\":{"
                 + "    \"nonBlanks\":299,"
                 + "    \"newTopics\":0,"
-                + "    \"matchedTopics\":222"
+                + "    \"matchedTopics\":222,"
+                + "    \"errorTopics\":0"
                 + "}}";
         TestUtils.isSerializedTo(Column.load(json), json);
     }
diff --git a/main/tests/server/src/com/google/refine/model/ReconStatsTests.java b/main/tests/server/src/com/google/refine/model/ReconStatsTests.java
index aa394f297..2e2f50189 100644
--- a/main/tests/server/src/com/google/refine/model/ReconStatsTests.java
+++ b/main/tests/server/src/com/google/refine/model/ReconStatsTests.java
@@ -35,7 +35,7 @@ public class ReconStatsTests {

     @Test
     public void serializeReconStats() {
-        ReconStats rs = new ReconStats(3, 1, 2);
-        TestUtils.isSerializedTo(rs, "{\"nonBlanks\":3,\"newTopics\":1,\"matchedTopics\":2}");
+        ReconStats rs = new ReconStats(3, 1, 2, 0);
+        TestUtils.isSerializedTo(rs, "{\"nonBlanks\":3,\"newTopics\":1,\"matchedTopics\":2,\"errorTopics\":0}");
     }
 }
diff --git a/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java b/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java
index 967a4a52b..78158431c 100644
--- a/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java
+++ b/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java
@@ -298,6 +298,7 @@ public class StandardReconConfigTests extends RefineTest {
             Cell cell = row.cells.get(1);
             assertNotNull(cell.value);
             assertNotNull(cell.recon.error);
+            assertEquals(cell.recon.judgment, Recon.Judgment.Error);
             // the recon object has error attribute
         }
     }

Fixes #6232

Changes proposed in this pull request:
- The reconciliation errors can now be selected through facets
- A test is also added in the StandardReconConfigTests file that checks that the judgment has been set to error in case of an error.

The reconciliation errors are now selectable by facets

def parser(log: str) -> dict[str, str]:
    import re
    import xml.etree.ElementTree as ET

    results: dict[str, str] = {}

    # Helper: merge statuses with precedence fail > skip > pass
    def merge_status(name: str, new_status: str):
        if new_status not in ("pass", "fail", "skip"):
            return
        old_status = results.get(name)
        if old_status is None:
            results[name] = new_status
            return
        if old_status == new_status:
            return
        # Precedence: fail > skip > pass
        priority = {"fail": 3, "skip": 2, "pass": 1}
        if priority[new_status] > priority[old_status]:
            results[name] = new_status

    # -----------------------
    # XML-based parsing pass
    # -----------------------

    # Non-greedy patterns for JUnit/TestNG XML blocks
    xml_pattern = re.compile(
        r"(<testng-results[\s\S]*?</testng-results>|<testsuite[\s\S]*?</testsuite>)",
        re.IGNORECASE,
    )

    for match in xml_pattern.finditer(log):
        xml_block = match.group(1)
        try:
            root = ET.fromstring(xml_block)
        except ET.ParseError:
            # Skip this block but continue with others
            continue

        tag_lower = root.tag.lower()

        # Handle TestNG summary reports (<testng-results>)
        if tag_lower.endswith("testng-results"):
            # Look for all test-method elements anywhere
            for tm in root.findall(".//test-method"):
                # Skip configuration methods
                if tm.attrib.get("is-config", "false").lower() == "true":
                    continue
                name = tm.attrib.get("name", "")
                if not name:
                    continue
                # Try to get class name from surrounding <class> element
                class_elem = tm.find("..")
                class_name = ""
                # xml.etree doesn't support parent lookup directly, so search ancestors:
                # We instead search for the nearest <class> ancestor manually:
            # Since parent lookup is tricky, fall back to the attribute 'class' on <test-method> if present
            # or to a surrounding <class> element found via xpath with name match, which is usually enough.
            # Re-parse with a simpler loop using suites/tests/classes for better class name resolution.
            for suite in root.findall(".//suite"):
                for test in suite.findall(".//test"):
                    for clazz in test.findall(".//class"):
                        class_name = clazz.attrib.get("name", "")
                        for tm in clazz.findall("test-method"):
                            if tm.attrib.get("is-config", "false").lower() == "true":
                                continue
                            m_name = tm.attrib.get("name", "")
                            if not m_name:
                                continue
                            fq_name = f"{class_name}.{m_name}" if class_name else m_name
                            status_attr = tm.attrib.get("status", "").upper()
                            # Map TestNG status to our statuses
                            if status_attr == "PASS":
                                status = "pass"
                            elif status_attr in ("FAIL", "FAILURE"):
                                status = "fail"
                            elif status_attr == "SKIP":
                                status = "skip"
                            else:
                                # Fallback: if there is an exception element, mark as fail; else pass
                                if tm.find(".//exception") is not None:
                                    status = "fail"
                                else:
                                    status = "pass"
                            merge_status(fq_name, status)
            continue

        # Handle JUnit-style <testsuite> reports
        if tag_lower.endswith("testsuite"):
            for tc in root.findall(".//testcase"):
                class_name = tc.attrib.get("classname", "")
                name = tc.attrib.get("name", "")
                if not name:
                    continue
                fq_name = f"{class_name}.{name}" if class_name else name

                # Determine status via children
                status = "pass"
                if tc.find("skipped") is not None:
                    status = "skip"
                elif tc.find("failure") is not None or tc.find("error") is not None:
                    status = "fail"

                merge_status(fq_name, status)

    # ---------------------------
    # Text-based parsing fallback
    # ---------------------------

    # Compile regexes for common frameworks

    # pytest: path::test_name STATUS
    pytest_re = re.compile(
        r"^(?P<name>\S+::\S+)\s+"
        r"(?P<status>PASSED|FAILED|SKIPPED|ERROR|XFAILED|XPASSED)\b"
    )

    # unittest / nose: ClassName.test_method ... ok/FAIL/ERROR/skipped
    unittest_re = re.compile(
        r"^(?P<class>[A-Za-z_]\w*)\.(?P<method>[A-Za-z_]\w*)\s+\.\.\.\s+"
        r"(?P<status>ok|FAIL|ERROR|skipped)\b"
    )

    # go test: --- STATUS: TestName
    gotest_re = re.compile(
        r"^---\s+(?P<status>PASS|FAIL|SKIP):\s+(?P<name>\S+)"
    )

    # Jest-like symbol lines: ✓/✕/○ test name
    jest_symbol_re = re.compile(r"^\s*([✓✕○])\s+(?P<name>.+)")

    # Map various raw statuses to our canonical ones
    def map_status(raw: str) -> str | None:
        r = raw.upper()
        if r in ("PASSED", "OK", "XPASSED", "PASS"):
            return "pass"
        if r in ("FAILED", "FAIL", "ERROR", "XFAILED"):
            return "fail"
        if r in ("SKIPPED", "SKIP", "SKIPPED (EXPECTED FAILURE)"):
            return "skip"
        return None

    for line in log.splitlines():
        line = line.rstrip("\n")

        # pytest
        m = pytest_re.match(line)
        if m:
            name = m.group("name")
            status = map_status(m.group("status"))
            if status:
                merge_status(name, status)
            continue

        # unittest
        m = unittest_re.match(line)
        if m:
            name = f"{m.group('class')}.{m.group('method')}"
            status = map_status(m.group("status"))
            if status:
                merge_status(name, status)
            continue

        # go test
        m = gotest_re.match(line)
        if m:
            name = m.group("name")
            status = map_status(m.group("status"))
            if status:
                merge_status(name, status)
            continue

        # Jest symbols
        m = jest_symbol_re.match(line)
        if m:
            symbol = m.group(1)
            name = m.group("name").strip()
            if symbol == "✓":
                status = "pass"
            elif symbol == "✕":
                status = "fail"
            elif symbol == "○":
                status = "skip"
            else:
                status = None
            if status:
                merge_status(name, status)
            continue

    return results

export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq nodejs npm
./refine build

find . -maxdepth 6 -path '*surefire-reports/*.xml' -print0 | xargs -0 -I {} sh -c 'echo "===== {} ====="; cat {}'