Fix Fingerprinter

Change-Id: Ie6ad71a184aef656781837ebb2e9b5031bef500a
diff --git a/Changes b/Changes
index fbe8c4c..606d9aa 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,5 @@
     - [performance] Add leaf cache. (diewald)
+    - [bugfix] Fix fingerprinter (wasn't threadsafe; diewald)
 
 0.64.5 2025-12-03
     - [maintenance] Update to Java 21 (diewald)
diff --git a/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java b/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java
index 52d0d57..980894d 100644
--- a/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java
+++ b/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java
@@ -12,22 +12,15 @@
     private final static Logger log = LoggerFactory
             .getLogger(Fingerprinter.class);
 
-    private static MessageDigest md;
-
     public static String create (String key) {
         try {
-            md = MessageDigest.getInstance("MD5");
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            md.update(key.getBytes());
+            return new String(Base64.getUrlEncoder().encode(md.digest()));
         }
         catch (NoSuchAlgorithmException e) {
             log.error(e.getMessage());
             return e.getMessage();
-        };
-
-        md.update(key.getBytes());
-        String code = new String(Base64.getUrlEncoder().encode(md.digest()));
-        
-        md.reset();
-        return code;
-
+        }
     }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/util/TestKrillFingerprint.java b/src/test/java/de/ids_mannheim/korap/util/TestKrillFingerprint.java
new file mode 100644
index 0000000..c0f53a3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/util/TestKrillFingerprint.java
@@ -0,0 +1,60 @@
+package de.ids_mannheim.korap.util;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.Test;
+
+public class TestKrillFingerprint {
+
+    @Test
+    public void testMessageDigestThreadSafety() throws Exception {
+
+        int threads = 16;
+        int iterationsPerThread = 5_000;
+
+        ExecutorService pool = Executors.newFixedThreadPool(threads);
+
+        CountDownLatch start = new CountDownLatch(1);
+        CountDownLatch done  = new CountDownLatch(threads);
+
+        List<Throwable> errors = new ArrayList<>();
+
+        for (int t = 0; t < threads; t++) {
+            pool.submit(() -> {
+                try {
+                    start.await();
+                    for (int i = 0; i < iterationsPerThread; i++) {
+                        Fingerprinter.create("key-" + i);
+                    }
+                }
+                catch (Throwable e) {
+                    synchronized (errors) {
+                        errors.add(e);
+                    }
+                }
+                finally {
+                    done.countDown();
+                }
+            });
+        }
+
+        start.countDown();
+        done.await();
+        pool.shutdown();
+
+        if (!errors.isEmpty()) {
+            errors.forEach(Throwable::printStackTrace);
+        }
+
+        assertTrue(
+            "Thread-safety violation detected: " + errors,
+            errors.isEmpty()
+        );
+    }
+}