diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java
index ea5ebdca14..4e0a51268d 100644
--- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java
+++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java
@@ -11,12 +11,15 @@
package org.eclipse.rdf4j.sail.shacl;
+import java.util.NoSuchElementException;
+
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.repository.RepositoryResult;
+import org.eclipse.rdf4j.sail.InterruptedSailException;
import org.eclipse.rdf4j.sail.SailConnection;
import org.eclipse.rdf4j.sail.SailException;
@@ -55,8 +58,23 @@ static void transferStatements(SailConnection from, TransferStatement transfer)
try (CloseableIteration extends Statement> statements = from
.getStatements(null, null, null, false)) {
- while (statements.hasNext()) {
- Statement next = statements.next();
+ while (true) {
+ throwIfInterrupted();
+ boolean hasNext = statements.hasNext();
+ throwIfInterrupted();
+ if (!hasNext) {
+ break;
+ }
+
+ Statement next;
+ try {
+ next = statements.next();
+ } catch (NoSuchElementException e) {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedSailException("Thread was interrupted while transferring statements.", e);
+ }
+ throw e;
+ }
transfer.transfer(next.getSubject(), next.getPredicate(),
next.getObject(), next.getContext());
@@ -66,6 +84,12 @@ static void transferStatements(SailConnection from, TransferStatement transfer)
}
+ private static void throwIfInterrupted() {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedSailException("Thread was interrupted while transferring statements.");
+ }
+ }
+
static boolean isEmpty(SailConnection connection) {
return !connection.hasStatement(null, null, null, false);
}
diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelperTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelperTest.java
new file mode 100644
index 0000000000..7613f8fcf5
--- /dev/null
+++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelperTest.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+// Some portions generated by Codex
+
+package org.eclipse.rdf4j.sail.shacl;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.NoSuchElementException;
+
+import org.eclipse.rdf4j.common.iteration.CloseableIteration;
+import org.eclipse.rdf4j.model.Statement;
+import org.eclipse.rdf4j.sail.InterruptedSailException;
+import org.eclipse.rdf4j.sail.SailConnection;
+import org.junit.jupiter.api.Test;
+
+class ConnectionHelperTest {
+
+ @Test
+ void transferStatementsConvertsInterruptedIterationToInterruptedSailException() {
+ SailConnection connection = mock(SailConnection.class);
+ @SuppressWarnings("unchecked")
+ CloseableIteration extends Statement> statements = mock(CloseableIteration.class);
+ doReturn(statements).when(connection).getStatements(isNull(), isNull(), isNull(), anyBoolean());
+ when(statements.hasNext()).thenReturn(true);
+ when(statements.next()).thenAnswer(invocation -> {
+ Thread.currentThread().interrupt();
+ throw new NoSuchElementException("The iteration has been interrupted.");
+ });
+
+ try {
+ assertThatExceptionOfType(InterruptedSailException.class)
+ .isThrownBy(() -> ConnectionHelper.transferStatements(connection, (subject, predicate, object,
+ context) -> {
+ throw new AssertionError("No statement should be transferred after interrupt");
+ }))
+ .withCauseInstanceOf(NoSuchElementException.class);
+ verify(statements).close();
+ } finally {
+ Thread.interrupted();
+ }
+ }
+
+ @Test
+ void transferStatementsKeepsNonInterruptNoSuchElementException() {
+ SailConnection connection = mock(SailConnection.class);
+ @SuppressWarnings("unchecked")
+ CloseableIteration extends Statement> statements = mock(CloseableIteration.class);
+ NoSuchElementException exception = new NoSuchElementException("empty");
+ doReturn(statements).when(connection).getStatements(isNull(), isNull(), isNull(), anyBoolean());
+ when(statements.hasNext()).thenReturn(true);
+ when(statements.next()).thenThrow(exception);
+
+ assertThatExceptionOfType(NoSuchElementException.class)
+ .isThrownBy(() -> ConnectionHelper.transferStatements(connection, (subject, predicate, object,
+ context) -> {
+ throw new AssertionError("No statement should be transferred");
+ }))
+ .isSameAs(exception);
+ verify(statements).close();
+ verify(statements, never()).remove();
+ }
+}
diff --git a/site/content/download.md b/site/content/download.md
index c37aa0080b..ebd406bc2d 100644
--- a/site/content/download.md
+++ b/site/content/download.md
@@ -5,15 +5,15 @@ toc: true
You can either retrieve RDF4J via Apache Maven, or download the SDK or onejar directly.
-## RDF4J 5.3.0 (latest)
+## RDF4J 5.3.1 (latest)
-RDF4J 5.3.0 is our latest stable release. It requires Java 11 minimally.
-For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/5.3.0).
+RDF4J 5.3.1 is our latest stable release. It requires Java 11 minimally.
+For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/5.3.1).
-- [RDF4J 5.3.0 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.0-sdk.zip)
+- [RDF4J 5.3.1 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.1-sdk.zip)
Full Eclipse RDF4J SDK, containing all libraries, RDF4J Server, Workbench, and Console applications, and Javadoc API.
-- [RDF4J 5.3.0 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.0-onejar.jar)
+- [RDF4J 5.3.1 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.1-onejar.jar)
Single jar file for easy inclusion of the full RDF4J toolkit in your Java project.
- [RDF4J artifacts](https://search.maven.org/search?q=org.eclipse.rdf4j) on the [Maven Central Repository](http://search.maven.org/)
@@ -28,7 +28,7 @@ You can include RDF4J as a Maven dependency in your Java project by including th
org.eclipse.rdf4j
rdf4j-bom
- 5.3.0
+ 5.3.1
pom
import
diff --git a/site/content/news/rdf4j-531.md b/site/content/news/rdf4j-531.md
new file mode 100644
index 0000000000..9b8cd10db3
--- /dev/null
+++ b/site/content/news/rdf4j-531.md
@@ -0,0 +1,14 @@
+---
+title: "RDF4J 5.3.1 released"
+date: 2026-04-23T21:24:25+0200
+layout: "single"
+categories: ["news"]
+---
+RDF4J 5.3.1 is now available. This is a patch release fixing 2 bugs.
+
+For more details, have a look at the [release notes](/release-notes/5.3.1).
+
+### Links
+
+- [Download RDF4J](/download/)
+- [release notes](/release-notes/5.3.1)
diff --git a/site/content/release-notes/5.3.1.md b/site/content/release-notes/5.3.1.md
new file mode 100644
index 0000000000..3a59fddebb
--- /dev/null
+++ b/site/content/release-notes/5.3.1.md
@@ -0,0 +1,11 @@
+---
+title: "5.3.1"
+toc: true
+---
+RDF4J 5.3.1 is a patch release that fixes 2 performance issues.
+
+For a complete overview, see [all issues fixed in 5.3.1](https://github.com/eclipse/rdf4j/milestone/129?closed=1).
+
+### Acknowledgements
+
+This release was made possible by contributions from [Håvard M. Ottestad](https://github.com/hmottestad).
diff --git a/site/static/javadoc/5.3.1.tar.xz b/site/static/javadoc/5.3.1.tar.xz
new file mode 100644
index 0000000000..9a1e0809ca
Binary files /dev/null and b/site/static/javadoc/5.3.1.tar.xz differ
diff --git a/site/static/javadoc/latest.tar.xz b/site/static/javadoc/latest.tar.xz
index 89adac4264..9a1e0809ca 100644
Binary files a/site/static/javadoc/latest.tar.xz and b/site/static/javadoc/latest.tar.xz differ