This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit c829fba30a8de6dce8128dd97dac1033e8007a44
Author: Andy Seaborne <[email protected]>
AuthorDate: Mon Jan 5 21:34:55 2026 +0000

    GH-3685: Update string functions for RDF 1.2 base direction
---
 .../apache/jena/riot/writer/JsonLD11Writer.java    |   9 +-
 .../jena/sparql/expr/nodevalue/NodeValueOps.java   |  84 ++--
 .../jena/sparql/expr/nodevalue/XSDFuncOp.java      |  82 ++--
 .../org/apache/jena/arq/junit/OmittedTest.java     |   4 +-
 .../org/apache/jena/rdfs/TestDatasetGraphRDFS.java |   4 +-
 .../java/org/apache/jena/sparql/expr/TS_Expr.java  |   3 +-
 .../org/apache/jena/sparql/expr/TestFunctions.java | 141 +-----
 ...tions2.java => TestSparqlKeywordFunctions.java} | 500 +++++++++++++--------
 .../sparql/expr/TestStringArgCompatibility.java    |  83 ++++
 .../main/java/org/apache/jena/atlas/lib/Lib.java   |   8 +-
 .../org/apache/jena/fuseki/servlets/ActionLib.java |   4 +-
 11 files changed, 507 insertions(+), 415 deletions(-)

diff --git 
a/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java 
b/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java
index 9b4d261474..40ce746117 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java
@@ -18,6 +18,8 @@
 
 package org.apache.jena.riot.writer;
 
+import static org.apache.jena.atlas.lib.Lib.equalsOrNulls;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.Writer;
@@ -37,7 +39,6 @@ import com.apicatalog.rdf.api.RdfConsumerException;
 
 import jakarta.json.*;
 import jakarta.json.stream.JsonGenerator;
-import org.apache.jena.atlas.lib.Lib;
 import org.apache.jena.atlas.logging.FmtLog;
 import org.apache.jena.graph.Node;
 import org.apache.jena.riot.Lang;
@@ -96,11 +97,11 @@ public class JsonLD11Writer implements WriterDatasetRIOT {
             boolean indented = true;
 
             // Choose algorithms
-            if ( Lib.equals(variant, RDFFormat.PRETTY) ) {
+            if ( equalsOrNulls(variant, RDFFormat.PRETTY) ) {
                 writeThis = writePretty(array, dsg);
-            } else if ( variant == null || Lib.equals(variant, 
RDFFormat.PLAIN) ) {
+            } else if ( variant == null || equalsOrNulls(variant, 
RDFFormat.PLAIN) ) {
                 writeThis = writePlain(array, dsg);
-            } else if ( Lib.equals(variant, RDFFormat.FLAT) ) {
+            } else if ( equalsOrNulls(variant, RDFFormat.FLAT) ) {
                 writeThis = writePlain(array, dsg);
                 indented = false;
             } else {
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java
index 5b397efb6f..2a5534ee3f 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java
@@ -27,14 +27,17 @@ import java.util.GregorianCalendar;
 import javax.xml.datatype.Duration;
 import javax.xml.datatype.XMLGregorianCalendar;
 
+import org.apache.jena.atlas.lib.Lib;
+import org.apache.jena.datatypes.RDFDatatype;
 import org.apache.jena.datatypes.xsd.XSDDatatype;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.graph.TextDirection;
 import org.apache.jena.sparql.expr.ExprEvalException;
 import org.apache.jena.sparql.expr.ExprEvalTypeException;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.expr.ValueSpace;
-import org.apache.jena.sparql.util.NodeUtils;
+import org.apache.jena.vocabulary.RDF;
 
 /**
  * Operations relating to {@link NodeValue NodeValues}.
@@ -301,18 +304,36 @@ public class NodeValueOps {
         Node n = nv.asNode();
         if ( !n.isLiteral() )
             throw new ExprEvalException(label + ": Not a literal: " + nv);
-        String lang = n.getLiteralLanguage();
 
-        if ( NodeUtils.isLangString(n) )
-            // Language tag. Legal.
+        if ( nv.isString() )
+            // Includes derived types of xsd:string.
             return n;
 
-        // No language tag : either no datatype or a datatype of xsd:string
-        // Includes the case of rdf:langString and no language ==> Illegal as a
-        // compatible string.
+        RDFDatatype dt = n.getLiteralDatatype();
+        if ( ! RDF.dtLangString.equals(dt) && ! RDF.dtDirLangString.equals(dt) 
)
+            throw new ExprEvalException(label + ": Not a string literal: " + 
nv);
 
-        if ( nv.isString() )
+        // Check for malformed:
+        // e.g. "abc"^^rdf:langString, and "abc"^^rdf:dirLangString
+
+        // Must have a language.
+        String lang = n.getLiteralLanguage();
+        if ( lang == null || lang.isEmpty() )
+            throw new ExprEvalException(label + ": Not a string literal (no 
langtag): " + nv);
+        if ( RDF.dtLangString.equals(dt) ) {
+            // Must not have a text direction
+            if ( n.getLiteralBaseDirection() != null )
+                throw new ExprEvalException(label + ": Not a string literal 
(rdf:langString + text direction): " + nv);
             return n;
+        }
+        if ( RDF.dtDirLangString.equals(dt) ) {
+            // Must have a text direction
+            if ( n.getLiteralBaseDirection() == null )
+                throw new ExprEvalException(label + ": Not a string literal 
(no text direction): " + nv);
+            return n;
+        }
+
+        // Should not get here.
         throw new ExprEvalException(label + ": Not a string literal: " + nv);
     }
 
@@ -322,19 +343,36 @@ public class NodeValueOps {
      * is not symmetric.
      * <ul>
      * <li>"abc"@en is compatible with "abc"
+     * <li>"abc"@en--ltr is compatible with "abc"
      * <li>"abc" is NOT compatible with "abc"@en
+     * <li>"abc"@en--ltr is NOT compatible with "abc"@en
      * </ul>
      */
     public static void checkTwoArgumentStringLiterals(String label, NodeValue 
arg1, NodeValue arg2) {
-        /* Quote the spec:
-         * Compatibility of two arguments is defined as:
-         *    The arguments are simple literals or literals typed as xsd:string
-         *    The arguments are plain literals with identical language tags
-         *    The first argument is a plain literal with language tag and the 
second argument is a simple literal or literal typed as xsd:string
+        /* Compatibility of two arguments:
+         *    The arguments are both xsd:string
+         *    The arguments are rdf:langString with identical language tags
+         *    The arguments are rdf:dirLangString with identical language tags 
and text direction
+         *    The first argument a string literal (rdf:langString, 
rdf:dirLangString) and the second argument is an xsd:string
+         *
+         * which simplifies to
+         *     Both arguments are string literals
+         *     The second argument is an xsd:string.
+         *     The first and second arguments have the same lang and text 
direction.
          */
 
+        // Common case
+        if ( arg1.isString() && arg2.isString() )
+            // Includes derived datatypes of xsd:string.
+            return;
+
+        // Robust checking.
         Node n1 = checkAndGetStringLiteral(label, arg1);
         Node n2 = checkAndGetStringLiteral(label, arg2);
+        if ( arg2.isString() )
+            // args1 is some kind of string literal.
+            return;
+        // same lane, same text direction.
         String lang1 = n1.getLiteralLanguage();
         String lang2 = n2.getLiteralLanguage();
         if ( lang1 == null )
@@ -342,21 +380,13 @@ public class NodeValueOps {
         if ( lang2 == null )
             lang2 = "";
 
-        // Case 1
-        if ( lang1.equals("") ) {
-            if ( lang2.equals("") )
-                return;
+        if ( ! Lib.equalsOrNulls(lang1, lang2) )
+            // Different languages.
             throw new ExprEvalException(label + ": Incompatible: " + arg1 + " 
and " + arg2);
-        }
 
-        // Case 2
-        if ( lang1.equalsIgnoreCase(lang2) )
-            return;
-
-        // Case 3
-        if ( lang2.equals("") )
-            return;
-
-        throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and 
" + arg2);
+        TextDirection textDir1 = n1.getLiteralBaseDirection();
+        TextDirection textDir2 = n2.getLiteralBaseDirection();
+        if ( ! Lib.equalsOrNulls(textDir1, textDir2) )
+            throw new ExprEvalException(label + ": Incompatible: " + arg1 + " 
and " + arg2);
     }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java
index 6fd848817d..aef4a5ecc5 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java
@@ -42,11 +42,13 @@ import javax.xml.datatype.Duration;
 import javax.xml.datatype.XMLGregorianCalendar;
 
 import org.apache.jena.atlas.lib.IRILib;
+import org.apache.jena.atlas.lib.Lib;
 import org.apache.jena.atlas.lib.StrUtils;
 import org.apache.jena.datatypes.RDFDatatype;
 import org.apache.jena.datatypes.xsd.XSDDatatype;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.graph.TextDirection;
 import org.apache.jena.rdf.model.impl.Util;
 import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.SystemARQ;
@@ -745,7 +747,7 @@ public class XSDFuncOp
 
     /** Build a NodeValue with lexical form, and same language and datatype as 
the Node argument */
     private static NodeValue calcReturn(String result, Node arg) {
-        Node n2 = NodeFactory.createLiteral(result, arg.getLiteralLanguage(), 
arg.getLiteralDatatype());
+        Node n2 = NodeFactory.createLiteral(result, arg.getLiteralLanguage(), 
arg.getLiteralBaseDirection(), arg.getLiteralDatatype());
         return NodeValue.makeNode(n2);
     }
 
@@ -823,57 +825,57 @@ public class XSDFuncOp
     }
 
     /** SPARQL CONCAT (no implicit casts to strings) */
-    public static NodeValue strConcat(List<NodeValue> args) {
-        // Step 1 : Choose type.
-        // One lang tag -> that lang tag
-        String lang = null;
-        boolean mixedLang = false;
-        boolean xsdString = false;
-        boolean simpleLiteral = false;
+    public static NodeValue /*XSDFuncOp*/strConcat(List<NodeValue> args) {
+        // Is this list of argument known to result in xsd:string by being 
mixed or seen an xsd:string?
+        boolean outputSimpleString = false;
+
+        // A candidate has been set (happens at first argument)
+        boolean candidateSet = false;
+        String concatLang = null;
+        TextDirection concatTextDir = null;
 
         StringBuilder sb = new StringBuilder();
 
         for (NodeValue nv : args) {
+            // ExprEvalException is is not a string/langString or 
dirLangString.
             Node n = NodeValueOps.checkAndGetStringLiteral("CONCAT", nv);
-            String lang1 = n.getLiteralLanguage();
-            if ( !lang1.equals("") ) {
-                if ( lang != null && !lang1.equals(lang) )
-                    // throw new
-                    // ExprEvalException("CONCAT: Mixed language tags: "+args);
-                    mixedLang = true;
-                lang = lang1;
-            } else if ( n.getLiteralDatatype() != null )
-                xsdString = true;
-            else
-                simpleLiteral = true;
-
             sb.append(n.getLiteralLexicalForm());
+            if ( outputSimpleString )
+                continue;
+            String nLang = n.getLiteralLanguage();
+            TextDirection nTextDir = n.getLiteralBaseDirection();
+
+            // Not mixed,
+            if ( ! candidateSet ) {
+                if ( nLang.isEmpty() ) {
+                    // No language => outcome is an xsd:string
+                    outputSimpleString = nLang.isEmpty();
+                    continue;
+                }
+                // It is the first argument, then set candidate
+                concatLang = nLang;
+                concatTextDir = nTextDir;
+                candidateSet = true;
+                continue;
+            }
+            // candidateSet is true
+            // Possible lang/textDir
+            if ( ! Lib.equalsOrNulls(concatLang, nLang) || ! 
Lib.equalsOrNulls(concatTextDir, nTextDir) ) {
+                // Different => outcome is an xsd:string
+                outputSimpleString = true;
+                candidateSet = false;
+            }
         }
 
-        if ( mixedLang )
-            return NodeValue.makeString(sb.toString());
+        String string = sb.toString();
 
-        // Must be all one lang.
-        if ( lang != null ) {
-            if ( !xsdString && !simpleLiteral )
-                return NodeValue.makeNode(sb.toString(), lang, (String)null);
-            else
-                // Lang and one or more of xsd:string or simpleLiteral.
-                return NodeValue.makeString(sb.toString());
-        }
-
-        if ( simpleLiteral && xsdString )
-            return NodeValue.makeString(sb.toString());
-        // All xsdString
-        if ( xsdString )
-            return NodeValue.makeNode(sb.toString(), XSDDatatype.XSDstring);
-        if ( simpleLiteral )
+        if ( outputSimpleString )
             return NodeValue.makeString(sb.toString());
 
-        // No types - i.e. no arguments
-        return NodeValue.makeString(sb.toString());
+        // Handles "textDir == null"
+        Node nOut = NodeFactory.createLiteralDirLang(string, concatLang, 
concatTextDir);
+        return NodeValue.makeNode(nOut);
     }
-
     /** fn:normalizeSpace */
     public static NodeValue strNormalizeSpace(NodeValue v){
         String str = v.asString();
diff --git a/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java 
b/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java
index 0dd1098c44..c21a3ff273 100644
--- a/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java
+++ b/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java
@@ -21,7 +21,7 @@ package org.apache.jena.arq.junit;
 import org.apache.jena.arq.junit.manifest.ManifestEntry;
 
 /**
- * Omitted test -= no marked as "suppressed" but not run.
+ * Omitted test = not marked as "suppressed" but not run.
  */
 public class OmittedTest extends SkipTest {
     public final boolean verbose;
@@ -38,7 +38,7 @@ public class OmittedTest extends SkipTest {
 
     @Override
     public void runTest() {
-        //if ( verbose )
+        if ( verbose )
             System.err.println("Omitted test: " + manifestEntry.getName());
     }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java 
b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
index 095a366c3e..6588923d2c 100644
--- a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
@@ -19,6 +19,7 @@
 package org.apache.jena.rdfs;
 
 import static org.apache.jena.atlas.iterator.Iter.iter;
+import static org.apache.jena.atlas.lib.Lib.equalsOrNulls;
 import static org.apache.jena.graph.Node.ANY;
 import static org.apache.jena.rdfs.LibTestRDFS.node;
 import static org.apache.jena.rdfs.engine.ConstRDFS.rdfType;
@@ -36,7 +37,6 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import org.apache.jena.atlas.iterator.Iter;
-import org.apache.jena.atlas.lib.Lib;
 import org.apache.jena.atlas.lib.ListUtils;
 import org.apache.jena.atlas.lib.StrUtils;
 import org.apache.jena.graph.Graph;
@@ -136,7 +136,7 @@ public class TestDatasetGraphRDFS {
     }
 
     private static boolean hasNG(List<Quad> quads, Node graphName) {
-        return quads.stream().map(Quad::getGraph).anyMatch(gn -> 
Lib.equals(gn, graphName));
+        return quads.stream().map(Quad::getGraph).anyMatch(gn -> 
equalsOrNulls(gn, graphName));
     }
 
     private void testContains(Node g, Node s, Node p, Node o, boolean 
expected) {
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java
index 06efb7eee7..1ef5eb3696 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java
@@ -37,7 +37,8 @@ import 
org.apache.jena.sparql.expr.nodevalue.TestNodeValueSortKey;
     , TestNodeFunctions.class
     , TestExpressionsMath.class
     , TestFunctions.class
-    , TestFunctions2.class
+    , TestStringArgCompatibility.class
+    , TestSparqlKeywordFunctions.class
     , TestFunctionsByURI.class
     , TestExprTripleTerms.class
     , TestLeviathanFunctions.class
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java
index df04ffe1cf..fa873d2997 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java
@@ -19,7 +19,6 @@
 package org.apache.jena.sparql.expr;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -31,15 +30,13 @@ import java.util.function.Predicate;
 
 import org.junit.jupiter.api.Test;
 
-import org.apache.jena.datatypes.xsd.XSDDatatype;
-import org.apache.jena.graph.Node;
-import org.apache.jena.graph.NodeFactory;
 import org.apache.jena.sparql.ARQConstants;
 import org.apache.jena.sparql.util.ExprUtils;
 
 public class TestFunctions
 {
     // Test of fn;* are in org.apache.jena.sparql.function.library.*
+    // TestFunctions2 has the SPARQl keyword functions.
 
     private static final NodeValue TRUE     = NodeValue.TRUE;
     private static final NodeValue FALSE    = NodeValue.FALSE;
@@ -121,150 +118,14 @@ public class TestFunctions
     // Timezone +11:00 can be a day behind
     @Test public void exprSprintf_25() { 
test_exprSprintf_tz_possibilites("2005-10-14T10:09:43+11:00",  "10 13,2005", 
"10 14,2005"); }
 
-    @Test public void exprStrStart10() { test("STRSTARTS('abc', 'abcd')", 
FALSE); }
-    @Test public void exprStrStart11() { test("STRSTARTS('abc'@en, 'ab')", 
TRUE); }
-    @Test public void exprStrStart12() { test("STRSTARTS('abc'^^xsd:string, 
'ab')", TRUE); }
-    @Test public void exprStrStart13() { test("STRSTARTS('abc'^^xsd:string, 
'ab'^^xsd:string)", TRUE); }
-    @Test public void exprStrStart14() { test("STRSTARTS('abc', 
'ab'^^xsd:string)", TRUE); }
-    @Test public void exprStrStart15() { test("STRSTARTS('abc'@en, 'ab'@en)", 
TRUE); }
-
-    @Test public void exprStrStart16() { testEvalException("STRSTARTS('ab'@en, 
'ab'@fr)"); }
-    @Test public void exprStrStart17() { testEvalException("STRSTARTS(123, 
'ab'@fr)"); }
-    @Test public void exprStrStart18() { 
testEvalException("STRSTARTS('123'^^xsd:string, 12.3)"); }
-
-    @Test public void exprStrBefore0() { test("STRBEFORE('abc', 'abcd')", 
NodeValue.nvEmptyString); }
-    @Test public void exprStrBefore1() { test("STRBEFORE('abc'@en, 'b')", 
NodeValue.makeNode("a", "en", (String)null)); }
-    @Test public void exprStrBefore2() { test("STRBEFORE('abc'^^xsd:string, 
'c')", NodeValue.makeNode("ab", XSDDatatype.XSDstring)); }
-    @Test public void exprStrBefore3() { test("STRBEFORE('abc'^^xsd:string, 
''^^xsd:string)", NodeValue.makeNode("", XSDDatatype.XSDstring)); }
-    @Test public void exprStrBefore4() { test("STRBEFORE('abc', 
'ab'^^xsd:string)", NodeValue.nvEmptyString); }
-    @Test public void exprStrBefore5() { test("STRBEFORE('abc'@en, 'b'@en)", 
NodeValue.makeNode("a", "en", (String)null)); }
-
-    @Test public void exprStrBefore6() { testEvalException("STRBEFORE('ab'@en, 
'ab'@fr)"); }
-    @Test public void exprStrBefore7() { testEvalException("STRBEFORE(123, 
'ab'@fr)"); }
-    @Test public void exprStrBefore8() { 
testEvalException("STRBEFORE('123'^^xsd:string, 12.3)"); }
-    // No match case
-    @Test public void exprStrBefore9() { test("STRBEFORE('abc'^^xsd:string, 
'z')", NodeValue.nvEmptyString); }
-    // Empty string case
-    @Test public void exprStrBefore10() { test("STRBEFORE('abc'^^xsd:string, 
'')", NodeValue.makeNode("", XSDDatatype.XSDstring)); }
-
-    @Test public void exprStrAfter0() { test("STRAFTER('abc', 'abcd')", 
NodeValue.nvEmptyString); }
-    @Test public void exprStrAfter1() { test("STRAFTER('abc'@en, 'b')", 
NodeValue.makeNode("c", "en", (String)null)); }
-    @Test public void exprStrAfter2() { test("STRAFTER('abc'^^xsd:string, 
'a')", NodeValue.makeNode("bc", XSDDatatype.XSDstring)); }
-    @Test public void exprStrAfter3() { test("STRAFTER('abc'^^xsd:string, 
''^^xsd:string)", NodeValue.makeNode("abc", XSDDatatype.XSDstring)); }
-    @Test public void exprStrAfter4() { test("STRAFTER('abc', 
'bc'^^xsd:string)", NodeValue.nvEmptyString); }
-    @Test public void exprStrAfter5() { test("STRAFTER('abc'@en, 'b'@en)", 
NodeValue.makeNode("c", "en", (String)null)); }
-
-    @Test public void exprStrAfter6() { testEvalException("STRAFTER('ab'@en, 
'ab'@fr)"); }
-    @Test public void exprStrAfter7() { testEvalException("STRAFTER(123, 
'ab'@fr)"); }
-    @Test public void exprStrAfter8() { 
testEvalException("STRAFTER('123'^^xsd:string, 12.3)"); }
-    // No match case
-    @Test public void exprStrAfter9() { test("STRAFTER('abc'^^xsd:string, 
'z')", NodeValue.nvEmptyString); }
-    // Empty string case
-    @Test public void exprStrAfter10() { test("STRAFTER('abc'^^xsd:string, 
'')", NodeValue.makeNode("abc", XSDDatatype.XSDstring)); }
-
-    @Test public void exprStrEnds10() { test("STRENDS('abc', 'abcd')", FALSE); 
}
-    @Test public void exprStrEnds11() { test("STRENDS('abc'@en, 'bc')", TRUE); 
}
-    @Test public void exprStrEnds12() { test("STRENDS('abc'^^xsd:string, 
'c')", TRUE); }
-    @Test public void exprStrEnds13() { test("STRENDS('abc'^^xsd:string, 
'c'^^xsd:string)", TRUE); }
-    @Test public void exprStrEnds14() { test("STRENDS('abc', 
'ab'^^xsd:string)", FALSE); }
-    @Test public void exprStrEnds15() { test("STRENDS('abc'@en, 'abc'@en)", 
TRUE); }
-
-    @Test public void exprStrEnds16() { testEvalException("STRENDS('ab'@en, 
'ab'@fr)"); }
-    @Test public void exprStrEnds17() { testEvalException("STRENDS(123, 
'ab'@fr)"); }
-    @Test public void exprStrEnds18() { 
testEvalException("STRENDS('123'^^xsd:string, 12.3)"); }
-
-    @Test public void exprContains10() { test("Contains('abc', 'abcd')", 
FALSE); }
-    @Test public void exprContains11() { test("Contains('abc'@en, 'bc')", 
TRUE); }
-    @Test public void exprContains12() { test("Contains('abc'^^xsd:string, 
'c')", TRUE); }
-    @Test public void exprContains13() { test("Contains('abc'^^xsd:string, 
'c'^^xsd:string)", TRUE); }
-    @Test public void exprContains14() { test("Contains('abc', 
'z'^^xsd:string)", FALSE); }
-    @Test public void exprContains15() { test("Contains('abc'@en, 'abc'@en)", 
TRUE); }
-
-    @Test public void exprContains16() { testEvalException("Contains('ab'@en, 
'ab'@fr)"); }
-    @Test public void exprContains17() { testEvalException("Contains(123, 
'ab'@fr)"); }
-    @Test public void exprContains18() { 
testEvalException("STRENDS('123'^^xsd:string, 12.3)"); }
-
-    @Test public void exprReplace01()  { test("REPLACE('abc', 'b', 'Z')", 
NodeValue.makeString("aZc")); }
-    @Test public void exprReplace02()  { test("REPLACE('abc', 'b.', 'Z')", 
NodeValue.makeString("aZ")); }
-    @Test public void exprReplace03()  { test("REPLACE('abcbd', 'b.', 'Z')", 
NodeValue.makeString("aZZ")); }
-
-    @Test public void exprReplace04()  { test("REPLACE('abcbd'^^xsd:string, 
'b.', 'Z')", NodeValue.makeNode("aZZ", XSDDatatype.XSDstring)); }
-    @Test public void exprReplace05()  { test("REPLACE('abcbd'@en, 'b.', 
'Z')", NodeValue.makeNode("aZZ", "en", (String)null)); }
-    @Test public void exprReplace06()  { test("REPLACE('abcbd', 'B.', 'Z', 
'i')", NodeValue.makeString("aZZ")); }
-
-    // See JENA-740
-    // ARQ provides replacement of the potentially empty string.
-    @Test public void exprReplace07()  { test("REPLACE('abc', '.*', 'Z')", 
NodeValue.makeString("Z")); }
-    @Test public void exprReplace08()  { test("REPLACE('', '.*', 'Z')",    
NodeValue.makeString("Z")); }
-    @Test public void exprReplace09()  { test("REPLACE('abc', '.?', 'Z')", 
NodeValue.makeString("ZZZ")); }
-
-    @Test public void exprReplace10()  { test("REPLACE('abc', 'XXX', 'Z')", 
NodeValue.makeString("abc")); }
-    @Test public void exprReplace11()  { test("REPLACE('', '.', 'Z')",      
NodeValue.makeString("")); }
-    @Test public void exprReplace12()  { test("REPLACE('', '(a|b)?', 'Z')", 
NodeValue.makeString("Z")); }
-
-    // Bad group
-    @Test
-    public void exprReplace13() {
-        testEvalException("REPLACE('abc', '.*', '$1')");
-    }
-
-    // Bad pattern; static (parse or build time) compilation.
-    @Test
-    public void exprReplace14() {
-               assertThrows(ExprException.class,
-                            ()-> ExprUtils.parse("REPLACE('abc', '^(a){-9}', 
'ABC')")
-                            );
-    }
-
     // Better name!
     @Test public void localTimezone_2() { test("afn:timezone()", 
nv->nv.isDayTimeDuration()); }
-
     @Test public void localDateTime_1() { test("afn:nowtz()", nv-> 
nv.isDateTime()); }
     // Test field defined.
     @Test public void localDateTime_2() { test("afn:nowtz()", nv-> 
nv.getDateTime().getTimezone() >= -14 * 60 ); }
 
     @Test public void localDateTime_3() { test("afn:nowtz() = NOW()", 
NodeValue.TRUE); }
 
-    @Test public void exprSameTerm1()     { test("sameTerm(1,1)",           
TRUE); }
-    @Test public void exprSameTerm2()     { test("sameTerm(1,1.0)",         
FALSE); }
-    @Test public void exprSameTerm3()     { test("sameTerm(1,1e0)",         
FALSE); }
-    @Test public void exprSameTerm4()     { test("sameTerm(<_:a>, <_:a>)",  
TRUE); }
-    @Test public void exprSameTerm5()     { test("sameTerm(<x>, <x>)",      
TRUE); }
-    @Test public void exprSameTerm6()     { test("sameTerm(<x>, <y>)",      
FALSE); }
-
-    @Test public void exprOneOf_01()     { test("57 in (xsd:integer, '123')",  
 FALSE); }
-    @Test public void exprOneOf_02()     { test("57 in (57)",                  
 TRUE); }
-    @Test public void exprOneOf_03()     { test("57 in (123, 57)",             
 TRUE); }
-    @Test public void exprOneOf_04()     { test("57 in (57, 456)",             
 TRUE); }
-    @Test public void exprOneOf_05()     { test("57 in (123, 57, 456)",        
 TRUE); }
-    @Test public void exprOneOf_06()     { test("57 in (1,2,3)",               
 FALSE); }
-
-    @Test public void exprNotOneOf_01()  { test("57 not in (xsd:integer, 
'123')",   TRUE); }
-    @Test public void exprNotOneOf_02()  { test("57 not in (57)",              
     FALSE); }
-    @Test public void exprNotOneOf_03()  { test("57 not in (123, 57)",         
     FALSE); }
-    @Test public void exprNotOneOf_04()  { test("57 not in (57, 456)",         
     FALSE); }
-    @Test public void exprNotOneOf_05()  { test("57 not in (123, 57, 456)",    
     FALSE); }
-    @Test public void exprNotOneOf_06()  { test("57 not in (1,2,3)",           
     TRUE); }
-
-
-    static Node xyz_en = NodeFactory.createLiteralLang("xyz", "en");
-    static NodeValue nv_xyz_en = NodeValue.makeNode(xyz_en);
-
-    static Node xyz_xsd_string = NodeFactory.createLiteralDT("xyz", 
XSDDatatype.XSDstring);
-    static NodeValue nv_xyz_string = NodeValue.makeNode(xyz_xsd_string);
-
-
-    @Test public void exprStrLang1()     { test("strlang('xyz', 'en')",        
     nv_xyz_en); }
-    @Test
-    public void exprStrLang2()           { 
assertThrows(ExprEvalException.class, ()-> test("strlang('xyz', '')", x->false) 
); }
-
-    @Test public void exprStrDatatype1()    { test("strdt('123', 
xsd:integer)",     NodeValue.makeInteger(123)); }
-    @Test public void exprStrDatatype2()    { test("strdt('xyz', xsd:string)", 
     nv_xyz_string); }
-    @Test public void exprStrDatatype3()    { testEvalException("strdt('123',  
     'datatype')"); }
-
-    static Node n_uri = NodeFactory.createURI("http://example/";);
-    static NodeValue nv_uri = NodeValue.makeNode(n_uri);
-
     private void test(String exprStr, NodeValue result) {
         Expr expr = ExprUtils.parse(exprStr);
         NodeValue r = expr.eval(null, LibTestExpr.createTest());
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSparqlKeywordFunctions.java
similarity index 53%
rename from 
jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java
rename to 
jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSparqlKeywordFunctions.java
index 44b5ef44e1..b4b06b6362 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSparqlKeywordFunctions.java
@@ -21,6 +21,7 @@ package org.apache.jena.sparql.expr;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
@@ -35,7 +36,7 @@ import org.apache.jena.sparql.util.ExprUtils;
 import org.apache.jena.sparql.util.NodeFactoryExtra;
 import org.apache.jena.sys.JenaSystem;
 
-public class TestFunctions2
+public class TestSparqlKeywordFunctions
 {
     static { JenaSystem.init(); }
     // Some overlap with TestFunctions except those are direct function calls 
and these are via SPARQL 1.1 syntax.
@@ -43,16 +44,21 @@ public class TestFunctions2
 
     static boolean warnOnBadLexicalForms = true;
 
-    @BeforeAll public static void beforeClass()
-    {
+    @BeforeAll
+    public static void beforeClass() {
         warnOnBadLexicalForms = NodeValue.VerboseWarnings;
         NodeValue.VerboseWarnings = false;
     }
-    @AfterAll  public static void afterClass()
-    {
+
+    @AfterAll
+    public static void afterClass() {
         NodeValue.VerboseWarnings = warnOnBadLexicalForms;
     }
 
+    private final static String kwTRUE = "true";
+    private final static String kwFALSE = "false";
+    private final static String kwEmptyString = "''";;
+
     // tests for strings. strlen, substr, strucase, strlcase, contains, concat
     // Some overlap with NodeFunctions.
     // Tests for IRI(..) are in 
TestUpdateOperations.insert_with_iri_function_resolution*
@@ -105,6 +111,7 @@ public class TestFunctions2
     @Test public void round_13()  { test("round('-0'^^xsd:float)",      
"'-0.0'^^xsd:float"); }
     @Test public void round_14()  { test("round('-0'^^xsd:double)",     
"'-0.0e0'^^xsd:double"); }
 
+    // ABS
     @Test public void abs_01()    { test("abs(1)",        "1"); }
     @Test public void abs_02()    { test("abs(1.0)",      "1.0"); }
     @Test public void abs_03()    { test("abs(1.0e0)",    "1.0e0"); }
@@ -132,34 +139,40 @@ public class TestFunctions2
     @Test public void floor_07()  { test("floor(-9.5)",   
"'-10.0'^^xsd:decimal"); }
     @Test public void floor_08()  { test("floor(0)",      "0"); }
 
-
     // CONCAT
-    @Test public void concat_00()    { test("concat()",       "''"); }
+    @Test public void concat_00()    { test("concat()",       kwEmptyString); }
     @Test public void concat_01()    { test("concat('a')",       "'a'"); }
     @Test public void concat_02()    { test("concat('a', 'b')",  "'ab'"); }
     @Test public void concat_03()    { test("concat('a'@en, 'b')",  "'ab'"); }
     @Test public void concat_04()    { test("concat('a'@en, 'b'@en)",  
"'ab'@en"); }
-    //@Test public void concat_05()    { test("concat('a'^^xsd:string, 'b')",  
"'ab'^^xsd:string"); }
-    @Test public void concat_05()    { test("concat('a'^^xsd:string, 'b')",  
"'ab'"); }
-    @Test public void concat_06()    { test("concat('a'^^xsd:string, 
'b'^^xsd:string)",  "'ab'^^xsd:string"); }
+    @Test public void concat_05()    { test("concat('a'@en, 'b'@fr)",  
"'ab'"); }
+    @Test public void concat_06()    { test("concat('a'^^xsd:string, 'b')",  
"'ab'"); }
     @Test public void concat_07()    { test("concat('a'^^xsd:string, 
'b'^^xsd:string)",  "'ab'^^xsd:string"); }
-    //@Test public void concat_08()   { test("concat('a', 'b'^^xsd:string)",  
"'ab'^^xsd:string"); }
-    @Test public void concat_08()    { test("concat('a', 'b'^^xsd:string)",  
"'ab'"); }
-    @Test public void concat_09()    { test("concat('a'@en, 'b'^^xsd:string)", 
 "'ab'"); }
-    @Test public void concat_10()    { test("concat('a'^^xsd:string, 'b'@en)", 
 "'ab'"); }
-    @Test public void concat_11()    { test("concat()",  "''"); }
-
-    @Test
-    public void concat_90()          { assertThrows(ExprEvalException.class, 
()-> test("concat(1)",      "1") ); }
-
-    @Test
-    public void concat_91()          { test("concat('a'@en, 'b'@fr)",  
"'ab'"); }
+    @Test public void concat_08()    { test("concat('a'^^xsd:string, 
'b'^^xsd:string)",  "'ab'^^xsd:string"); }
+    @Test public void concat_09()    { test("concat('a', 'b'^^xsd:string)",  
"'ab'"); }
+    @Test public void concat_10()    { test("concat('a'@en, 'b'^^xsd:string)", 
 "'ab'"); }
+    @Test public void concat_11()    { test("concat('a'^^xsd:string, 'b'@en)", 
 "'ab'"); }
+    @Test public void concat_12()    { test("concat()",  kwEmptyString); }
+
+    @Test public void concat_20()    { test("concat('a', 'b', 'c')",  
"'abc'"); }
+    @Test public void concat_21()    { test("concat('a'@en, 'b'@en, 'c')",  
"'abc'"); }
+    @Test public void concat_22()    { test("concat('a', 'b'@en, 'c')",  
"'abc'"); }
+    @Test public void concat_23()    { test("concat('a', 'b', 'c'@en)",  
"'abc'"); }
+
+    @Test public void concat_30()    { test("concat('a'@en--ltr, 
'b'@en--ltr)",  "'ab'@en--ltr"); }
+    @Test public void concat_31()    { test("concat('a'@en--ltr, 
'b'@en--rtl)",  "'ab'"); }
+    @Test public void concat_32()    { test("concat('a'@en--ltr, 
'b'@fr--ltr)",  "'ab'"); }
+    @Test public void concat_33()    { test("concat('a'@en--ltr, 
'b'@fr--ltr)",  "'ab'"); }
+    @Test public void concat_34()    { test("concat('a'@en--ltr, 'b')",  
"'ab'"); }
+    @Test public void concat_35()    { test("concat('a'@en--ltr, 'b'@en)",  
"'ab'"); }
+    @Test public void concat_36()    { test("concat('a', 'b'@en--ltr)",  
"'ab'"); }
+    @Test public void concat_37()    { test("concat('a'@en, 'b'@en--ltr)",  
"'ab'"); }
 
     // SUBSTR
     @Test public void substr_01()    { test("substr('abc',1)",      "'abc'"); }
     @Test public void substr_02()    { test("substr('abc',2)",      "'bc'"); }
     @Test public void substr_03()    { test("substr('abc',2,1)",    "'b'"); }
-    @Test public void substr_04()    { test("substr('abc',2,0)",    "''"); }
+    @Test public void substr_04()    { test("substr('abc',2,0)",    
kwEmptyString); }
     @Test public void substr_05()    { test("substr('12345',0,3)",  "'12'"); }
     @Test public void substr_06()    { test("substr('12345',-1,3)", "'1'"); }
 
@@ -168,18 +181,15 @@ public class TestFunctions2
     @Test public void substr_11()    { test("substr('metadata', 4, 3)",    
"'ada'"); }
     @Test public void substr_12()    { test("substr('12345', 1.5, 2.6)",   
"'234'"); }
     @Test public void substr_13()    { test("substr('12345', 0, 3)",       
"'12'"); }
-    @Test public void substr_14()    { test("substr('12345', 5, -3)",      
"''"); }
+    @Test public void substr_14()    { test("substr('12345', 5, -3)",      
kwEmptyString); }
     @Test public void substr_15()    { test("substr('12345', -3, 5)",      
"'1'"); }
-    @Test public void substr_16()    { test("substr('12345', 0/0E0, 3)",   
"''"); }
-    @Test public void substr_17()    { test("substr('12345', 1, 0/0E0)",   
"''"); }
-    @Test public void substr_18()    { test("substr('', 1, 3)",            
"''"); }
+    @Test public void substr_16()    { test("substr('12345', 0/0E0, 3)",   
kwEmptyString); }
+    @Test public void substr_17()    { test("substr('12345', 1, 0/0E0)",   
kwEmptyString); }
+    @Test public void substr_18()    { test("substr('', 1, 3)",            
kwEmptyString); }
 
-    @Test
-    public void substr_20()         { assertThrows(ExprEvalException.class, 
()-> test("substr(1, 1, 3)",            "''") ); }
-    @Test
-    public void substr_21()         { assertThrows(ExprEvalException.class, 
()-> test("substr('', 'one', 3)",            "''") ); }
-    @Test
-    public void substr_22()         { assertThrows(ExprEvalException.class, 
()-> test("substr('', 1, 'three')",            "''") ); }
+    @Test public void substr_20()    { testEvalException("substr(1, 1, 3)"); }
+    @Test public void substr_21()    { testEvalException("substr('', 'one', 
3)"); }
+    @Test public void substr_22()    { testEvalException("substr('', 1, 
'three')"); }
 
     // Codepoint outside UTF-16.
     // These are  U+0001F46A 👪 - FAMILY
@@ -192,7 +202,7 @@ public class TestFunctions2
     @Test public void substr_30()   { test("substr('\\uD83D\\uDC6A', 1)",      
     "'\\uD83D\\uDC6A'"); }
     // Same using Java string escapes.
     @Test public void substr_30b()  { test("substr('\uD83D\uDC6A', 1)",        
     "'\uD83D\uDC6A'"); }
-    @Test public void substr_31()   { test("substr('\\uD83D\\uDC6A', 2)",      
                 "''"); }
+    @Test public void substr_31()   { test("substr('\\uD83D\\uDC6A', 2)",      
                 kwEmptyString); }
 
     @Test public void substr_32()   { test("substr('ABC\\uD83D\\uDC6ADEF', 4, 
1)",  "'\\uD83D\\uDC6A'"); }
     @Test public void substr_33()   { test("substr('\\uD83D\\uDC6A!', -1, 3)", 
     "'\\uD83D\\uDC6A'"); }
@@ -206,13 +216,13 @@ public class TestFunctions2
     @Test public void ucase_01()    { test("ucase('abc')",  "'ABC'"); }
     @Test public void ucase_02()    { test("ucase('ABC')",  "'ABC'"); }
     @Test public void ucase_03()    { test("ucase('Ab 123 Cd')", "'AB 123 
CD'"); }
-    @Test public void ucase_04()    { test("ucase('')",     "''"); }
+    @Test public void ucase_04()    { test("ucase('')",     kwEmptyString); }
 
     // LCASE
     @Test public void lcase_01()    { test("lcase('abc')",  "'abc'"); }
     @Test public void lcase_02()    { test("lcase('ABC')",  "'abc'"); }
     @Test public void lcase_03()    { test("lcase('Ab 123 Cd')", "'ab 123 
cd'"); }
-    @Test public void lcase_04()    { test("lcase('')",     "''"); }
+    @Test public void lcase_04()    { test("lcase('')",     kwEmptyString); }
 
     // ENCODE_FOR_URI
     @Test public void encodeURI_01()    { test("encode_for_uri('a:b cd/~')",  
"'a%3Ab%20cd%2F~'"); }
@@ -222,92 +232,210 @@ public class TestFunctions2
     @Test public void encodeURI_05()    { test("encode_for_uri('abc'@en)",  
"'abc'"); }
 
     @Test
-    public void encodeURI_09()          { 
assertThrows(ExprEvalException.class, ()-> test("encode_for_uri(1234)",  
"'1234'") ); }
-
-    /* Compatibility rules
-    # pairs of simple literals,
-    # pairs of xsd:string typed literals
-    # pairs of plain literals with identical language tags
-    # pairs of an xsd:string typed literal (arg1 or arg2) and a simple literal 
(arg2 or arg1)
-    # pairs of a plain literal with language tag (arg1) and a simple literal 
(arg2)
-    # pairs of a plain literal with language tag (arg1) and an xsd:string 
typed literal (arg2)
-    */
-
-    // CONTAINS
-    @Test public void contains_01()     { test("contains('abc', 'a')", 
"true"); }
-    @Test public void contains_02()     { test("contains('abc', 'b')", 
"true"); }
-    @Test public void contains_03()     { test("contains('ABC', 'a')", 
"false"); }
-    @Test public void contains_04()     { test("contains('abc', '')",  
"true"); }
-    @Test public void contains_05()     { test("contains('', '')",     
"true"); }
-    @Test public void contains_06()     { test("contains('', 'a')",    
"false"); }
-    @Test public void contains_07()     { test("contains('12345', '34')",      
  "true"); }
-    @Test public void contains_08()     { test("contains('12345', '123456')",  
  "false"); }
-
-    @Test public void contains_10()     { test("contains('abc', 
'a'^^xsd:string)",          "true"); }
-    @Test public void contains_11()     { 
assertThrows(ExprEvalException.class, ()-> test("contains('abc', 'a'@en)",      
    "true") ); }
-
-    @Test public void contains_12()     { test("contains('abc'@en, 'a')",      
    "true"); }
-    @Test public void contains_13()     { test("contains('abc'@en, 
'a'^^xsd:string)",          "true"); }
-    @Test public void contains_14()     { test("contains('abc'@en, 'a'@en)",   
    "true"); }
-    @Test public void contains_15()     { 
assertThrows(ExprEvalException.class, ()-> test("contains('abc'@en, 'a'@fr)",   
    "true") ); }
-
-    @Test public void contains_16()     { test("contains('abc'^^xsd:string, 
'a')", "true"); }
-
-    @Test public void contains_17()     { 
assertThrows(ExprEvalException.class, ()-> test("contains('abc'^^xsd:string, 
'a'@en)", "true") ); }
-    @Test public void contains_18()     { test("contains('abc'^^xsd:string, 
'a'^^xsd:string)", "true"); }
-
-    @Test public void contains_20()     { 
assertThrows(ExprEvalException.class, ()-> test("contains(1816, 
'a'^^xsd:string)", "true") ); }
-    @Test public void contains_21()     { 
assertThrows(ExprEvalException.class, ()-> test("contains('abc', 1066)", 
"true") ); }
-
-    @Test public void strstarts_01()    { test("strstarts('abc', 'a')", 
"true"); }
-    @Test public void strstarts_02()    { test("strstarts('abc', 'b')", 
"false"); }
-    @Test public void strstarts_03()    { test("strstarts('ABC', 'a')", 
"false"); }
-    @Test public void strstarts_04()    { test("strstarts('abc', '')",  
"true"); }
-    @Test public void strstarts_05()    { test("strstarts('', '')",     
"true"); }
-    @Test public void strstarts_06()    { test("strstarts('', 'a')",    
"false"); }
-
-    @Test public void strstarts_10()    { test("strstarts('abc', 
'a'^^xsd:string)",          "true"); }
-    @Test public void strstarts_11()    { 
assertThrows(ExprEvalException.class, ()-> test("strstarts('abc', 'a'@en)",     
     "true") ); }
-
-    @Test public void strstarts_12()    { test("strstarts('abc'@en, 'a')",     
     "true"); }
-    @Test public void strstarts_13()    { test("strstarts('abc'@en, 
'a'^^xsd:string)",          "true"); }
-    @Test public void strstarts_14()    { test("strstarts('abc'@en, 'a'@en)",  
     "true"); }
-    @Test
-    public void strstarts_15()          { 
assertThrows(ExprEvalException.class, ()-> test("strstarts('abc'@en, 'a'@fr)",  
     "true") ); }
-
-    @Test public void strstarts_16()    { test("strstarts('abc'^^xsd:string, 
'a')", "true"); }
+    public void encodeURI_09()          { 
testEvalException("encode_for_uri(1234)"); }
 
-    @Test
-    public void strstarts_17()          { 
assertThrows(ExprEvalException.class, ()-> test("strstarts('abc'^^xsd:string, 
'a'@en)", "true") ); }
-    @Test public void strstarts_18()    { test("strstarts('abc'^^xsd:string, 
'a'^^xsd:string)", "true"); }
+    /* Compatibility rules */
 
-    @Test public void strstarts_20()    { 
assertThrows(ExprEvalException.class, ()-> test("strstarts(1816, 
'a'^^xsd:string)", "true") ); }
-    @Test public void strstarts_21()    { 
assertThrows(ExprEvalException.class, ()-> test("strstarts('abc', 1066)", 
"true") ); }
+    // CONTAINS
+    @Test public void contains_01()     { test("contains('abc', 'a')", 
kwTRUE); }
+    @Test public void contains_02()     { test("contains('abc', 'b')", 
kwTRUE); }
+    @Test public void contains_03()     { test("contains('ABC', 'a')", 
kwFALSE); }
+    @Test public void contains_04()     { test("contains('abc', '')",  
kwTRUE); }
+    @Test public void contains_05()     { test("contains('', '')",     
kwTRUE); }
+    @Test public void contains_06()     { test("contains('', 'a')",    
kwFALSE); }
+    @Test public void contains_07()     { test("contains('12345', '34')",      
  kwTRUE); }
+    @Test public void contains_08()     { test("contains('12345', '123456')",  
  kwFALSE); }
+
+    @Test public void contains_10() { test("contains('abc', 'abcd')", 
kwFALSE); }
+    @Test public void contains_11() { test("contains('abc'@en, 'bc')", 
kwTRUE); }
+    @Test public void contains_12() { test("contains('abc'^^xsd:string, 'c')", 
kwTRUE); }
+    @Test public void contains_13() { test("contains('abc'^^xsd:string, 
'c'^^xsd:string)", kwTRUE); }
+    @Test public void contains_14() { test("contains('abc', 'z'^^xsd:string)", 
kwFALSE); }
+    @Test public void contains_15() { test("contains('abc'@en, 'abc'@en)", 
kwTRUE); }
+
+    @Test public void contains_16() { testEvalException("contains('ab'@en, 
'ab'@fr)"); }
+    @Test public void contains_17() { testEvalException("contains(123, 
'ab'@fr)"); }
+
+    @Test public void contains_20()     { test("contains('abc', 
'a'^^xsd:string)", kwTRUE); }
+    @Test public void contains_21()     { testEvalException("contains('abc', 
'a'@en)"); }
+
+    @Test public void contains_22()     { test("contains('abc'@en, 'a')",      
    kwTRUE); }
+    @Test public void contains_23()     { test("contains('abc'@en, 
'a'^^xsd:string)",          kwTRUE); }
+    @Test public void contains_24()     { test("contains('abc'@en, 'a'@en)",   
    kwTRUE); }
+    @Test public void contains_25()     { 
testEvalException("contains('abc'@en, 'a'@fr)"); }
+
+    @Test public void contains_26()     { test("contains('abc'^^xsd:string, 
'a')", kwTRUE); }
+
+    @Test public void contains_27()     { 
testEvalException("contains('abc'^^xsd:string, 'a'@en)"); }
+    @Test public void contains_28()     { test("contains('abc'^^xsd:string, 
'a'^^xsd:string)", kwTRUE); }
+
+    @Test public void contains_30()     { testEvalException("contains(1816, 
'a'^^xsd:string)"); }
+    @Test public void contains_31()     { testEvalException("contains('abc', 
1066)"); }
+
+    @Test public void contains_40()     { test("contains('abc'@en--ltr, 'a')", 
kwTRUE); }
+    @Test public void contains_41()     { 
testEvalException("contains('abc'@en--ltr, 'a'@en)"); }
+    @Test public void contains_42()     { 
testEvalException("contains('abc'@en, 'a'@en--ltr)"); }
+    @Test public void contains_43()     { testEvalException("contains('abc', 
'a'@en--ltr)"); }
+
+    // STRSTARTS
+    @Test public void strstarts_01()    { test("strstarts('abc', 'a')", 
kwTRUE); }
+    @Test public void strstarts_02()    { test("strstarts('abc', 'b')", 
kwFALSE); }
+    @Test public void strstarts_03()    { test("strstarts('ABC', 'a')", 
kwFALSE); }
+    @Test public void strstarts_04()    { test("strstarts('abc', '')",  
kwTRUE); }
+    @Test public void strstarts_05()    { test("strstarts('', '')",     
kwTRUE); }
+    @Test public void strstarts_06()    { test("strstarts('', 'a')",    
kwFALSE); }
+    @Test public void strstarts_07()    { test("STRSTARTS('abc', 'abcd')", 
kwFALSE); }
+
+    @Test public void strstarts_10()    { test("strstarts('abc', 
'a'^^xsd:string)",          kwTRUE); }
+    @Test public void strstarts_11()    { testEvalException("strstarts('abc', 
'a'@en)"); }
+    @Test public void strstarts_12()    { test("strstarts('abc'@en, 'a')",     
     kwTRUE); }
+    @Test public void strstarts_13()    { test("strstarts('abc'@en, 
'a'^^xsd:string)",          kwTRUE); }
+    @Test public void strstarts_14()    { test("strstarts('abc'@en, 'a'@en)",  
     kwTRUE); }
+    @Test public void strstarts_15()    { 
testEvalException("strstarts('abc'@en, 'a'@fr)"); }
+
+    @Test public void strstarts_16()    { test("strstarts('abc'^^xsd:string, 
'a')", kwTRUE); }
+    @Test public void strstarts_17()    { 
testEvalException("strstarts('abc'^^xsd:string, 'a'@en)"); }
+    @Test public void strstarts_18()    { test("strstarts('abc'^^xsd:string, 
'a'^^xsd:string)", kwTRUE); }
+
+    @Test public void strstarts_20()    { testEvalException("strstarts(1816, 
'a'^^xsd:string)"); }
+    @Test public void strstarts_21()    { testEvalException("strstarts('abc', 
1066)"); }
+
+    @Test public void strstarts_22()    { 
testEvalException("STRSTARTS('ab'@en, 'ab'@fr)"); }
+    @Test public void strstarts_23()    { testEvalException("STRSTARTS(123, 
'ab'@fr)"); }
+    @Test public void strstarts_24()    { 
testEvalException("STRSTARTS('123'^^xsd:string, 12.3)"); }
+
+    @Test public void strstarts_30()    { test("STRSTARTS('ab'@en--ltr, 'z')", 
kwFALSE); }
+    @Test public void strstarts_31()    { test("STRSTARTS('ab'@en--ltr, 
'z'@en--ltr)", kwFALSE); }
+    @Test public void strstarts_32()    { 
testEvalException("STRSTARTS('ab'@en, 'z'@en--rtl)"); }
 
     // STRENDS
-    @Test public void strends_01()      { test("strends('abc', 'c')", "true"); 
}
-    @Test public void strends_02()      { test("strends('abc', 'b')", 
"false"); }
-    @Test public void strends_03()      { test("strends('ABC', 'c')", 
"false"); }
-    @Test public void strends_04()      { test("strends('abc', '')",  "true"); 
}
-    @Test public void strends_05()      { test("strends('', '')",     "true"); 
}
-    @Test public void strends_06()      { test("strends('', 'a')",    
"false"); }
-
-    @Test public void strends_10()      { test("strends('abc', 
'c'^^xsd:string)",          "true"); }
-    @Test public void strends11()       { 
assertThrows(ExprEvalException.class, ()-> test("strends('abc', 'c'@en)",       
   "true") ); }
-
-    @Test public void strends_12()      { test("strends('abc'@en, 'c')",       
   "true"); }
-    @Test public void strends_13()      { test("strends('abc'@en, 
'c'^^xsd:string)",          "true"); }
-    @Test public void strends_14()      { test("strends('abc'@en, 'c'@en)",    
   "true"); }
-    @Test
-    public void strends_15()            { 
assertThrows(ExprEvalException.class, ()-> test("strends('abc'@en, 'c'@fr)",    
   "true") ); }
-
-    @Test public void strends_16()      { test("strends('abc'^^xsd:string, 
'bc')", "true"); }
-    @Test
-    public void strends_17()            { 
assertThrows(ExprEvalException.class, ()-> test("strends('abc'^^xsd:string, 
'a'@en)", "true") ); }
-    @Test public void strends_18()      { test("strends('abc'^^xsd:string, 
'abc'^^xsd:string)", "true"); }
-
-    @Test public void strends_20()      { 
assertThrows(ExprEvalException.class, ()-> test("strends(1816, 
'6'^^xsd:string)", "true") ); }
-    @Test public void strends_21()      { 
assertThrows(ExprEvalException.class, ()-> test("strends('abc', 1066)", "true") 
); }
+    @Test public void strends_01()      { test("strends('abc', 'c')", kwTRUE); 
}
+    @Test public void strends_02()      { test("strends('abc', 'b')", 
kwFALSE); }
+    @Test public void strends_03()      { test("strends('ABC', 'c')", 
kwFALSE); }
+    @Test public void strends_04()      { test("strends('abc', '')",  kwTRUE); 
}
+    @Test public void strends_05()      { test("strends('', '')",     kwTRUE); 
}
+    @Test public void strends_06()      { test("strends('', 'a')",    
kwFALSE); }
+
+    @Test public void strends_10()      { test("STRENDS('abc', 'abcd')", 
kwFALSE); }
+    @Test public void strends_11()      { test("STRENDS('abc'@en, 'bc')", 
kwTRUE); }
+    @Test public void strends_12()      { test("STRENDS('abc'^^xsd:string, 
'c')", kwTRUE); }
+    @Test public void strends_13()      { test("STRENDS('abc'^^xsd:string, 
'c'^^xsd:string)", kwTRUE); }
+    @Test public void strends_14()      { test("STRENDS('abc', 
'ab'^^xsd:string)", kwFALSE); }
+    @Test public void strends_15()      { test("STRENDS('abc'@en, 'abc'@en)", 
kwTRUE); }
+
+    @Test public void strends_16()      { testEvalException("STRENDS('ab'@en, 
'ab'@fr)"); }
+    @Test public void strends_17()      { testEvalException("STRENDS(123, 
'ab'@fr)"); }
+    @Test public void strends_18()      { 
testEvalException("STRENDS('123'^^xsd:string, 12.3)"); }
+
+    @Test public void strends_20()      { test("strends('abc', 
'c'^^xsd:string)",          kwTRUE); }
+    @Test public void strends_21()      { testEvalException("strends('abc', 
'c'@en)"); }
+
+    @Test public void strends_22()      { 
testEvalException("STRSTARTS('ab'@en, 'ab'@fr)"); }
+    @Test public void strends_23()      { testEvalException("STRSTARTS(123, 
'ab'@fr)"); }
+    @Test public void strends_24()      { 
testEvalException("STRSTARTS('123'^^xsd:string, 12.3)"); }
+
+    @Test public void strends_30()      { test("STRENDS('ab'@en--ltr, 'z')", 
kwFALSE); }
+    @Test public void strends_31()      { test("STRENDS('ab'@en--ltr, 
'z'@en--ltr)", kwFALSE); }
+    @Test public void strends_32()      { testEvalException("STRENDS('ab'@en, 
'z'@en--rtl)"); }
+
+    // STRBEFORE
+    @Test public void strbefore_01()    { test("STRBEFORE('abc', 'abcd')", 
kwEmptyString); }
+    @Test public void strbefore_02()    { test("STRBEFORE('abc'@en, 'b')", 
"'a'@en"); }
+    @Test public void strbefore_03()    { test("STRBEFORE('abc'^^xsd:string, 
'c')", "'ab'^^xsd:string"); }
+    @Test public void strbefore_04()    { test("STRBEFORE('abc'^^xsd:string, 
''^^xsd:string)", "''^^xsd:string"); }
+    @Test public void strbefore_05()    { test("STRBEFORE('abc', 
'ab'^^xsd:string)", kwEmptyString); }
+    @Test public void strbefore_06()    { test("STRBEFORE('abc'@en, 'b'@en)", 
"'a'@en"); }
+
+    @Test public void strbefore_07()    { 
testEvalException("STRBEFORE('ab'@en, 'ab'@fr)"); }
+    @Test public void strbefore_08()    { testEvalException("STRBEFORE(123, 
'ab'@fr)"); }
+    @Test public void strbefore_09()    { 
testEvalException("STRBEFORE('123'^^xsd:string, 12.3)"); }
+    // No match case
+    @Test public void strbefore_10()    { test("STRBEFORE('abc'^^xsd:string, 
'z')", kwEmptyString); }
+    // Empty string case
+    @Test public void strbefore_11()    { test("STRBEFORE('abc'^^xsd:string, 
'')", kwEmptyString); }
+    @Test public void strbefore_12()    { testEvalException("STRBEFORE('abc', 
''@en)"); }
+
+    @Test public void strbefore_30()    { test("STRBEFORE('abc'@en--ltr, 
'a')", "''@en--ltr"); }
+    @Test public void strbefore_31()    { test("STRBEFORE('abc'@en--ltr, 
'a'@en--ltr)", "''@en--ltr"); }
+    @Test public void strbefore_32()    { 
testEvalException("STRBEFORE('abc'@en--ltr, 'a'@en)"); }
+
+    // STRAFTER
+    @Test public void strafter_01()     { test("STRAFTER('abc', 'abcd')", 
kwEmptyString); }
+    @Test public void strafter_02()     { test("STRAFTER('abc'@en, 'b')", 
"'c'@en"); }
+    @Test public void strafter_03()     { test("STRAFTER('abc'^^xsd:string, 
'a')", "'bc'^^xsd:string"); }
+    @Test public void strafter_04()     { test("STRAFTER('abc'^^xsd:string, 
''^^xsd:string)", "'abc'"); }
+    @Test public void strafter_05()     { test("STRAFTER('abc', 
'bc'^^xsd:string)", kwEmptyString); }
+    @Test public void strafter_06()     { test("STRAFTER('abc'@en, 'b'@en)", 
"'c'@en"); }
+
+    @Test public void strafter_07()     { testEvalException("STRAFTER('ab'@en, 
'ab'@fr)"); }
+    @Test public void strafter_08()     { testEvalException("STRAFTER(123, 
'ab'@fr)"); }
+    @Test public void strafter_09()     { 
testEvalException("STRAFTER('123'^^xsd:string, 12.3)"); }
+    // No match case
+    @Test public void strafter_10()     { test("STRAFTER('abc'^^xsd:string, 
'z')", kwEmptyString); }
+    // Empty string case
+    @Test public void strafter_11()     { test("STRAFTER('abc'^^xsd:string, 
'')", "'abc'"); }
+    @Test public void strafter_12()     { testEvalException("STRAFTER('abc', 
''@en)"); }
+
+    @Test public void strafter_30()     { test("STRAFTER('abc'@en--ltr, '')", 
"'abc'@en--ltr"); }
+    @Test public void strafter_31()     { test("STRAFTER('abc'@en--ltr, 
'a'@en--ltr)", "'bc'@en--ltr"); }
+    @Test public void strafter_32()     { 
testEvalException("STRAFTER('abc'@en--ltr, 'a'@en)"); }
+    @Test public void strafter_33()     { 
testEvalException("STRAFTER('abc'@en, 'a'@en--ltr)"); }
+
+    // STRREPLACE
+    @Test public void replace01()       { test("REPLACE('abc', 'b', 'Z')", 
"'aZc'"); }
+    @Test public void replace02()       { test("REPLACE('abc', 'b.', 'Z')", 
"'aZ'"); }
+    @Test public void replace03()       { test("REPLACE('abcbd', 'b.', 'Z')", 
"'aZZ'"); }
+
+    @Test public void replace04()       { test("REPLACE('abcbd'^^xsd:string, 
'b.', 'Z')", "'aZZ'"); }
+    @Test public void replace05()       { test("REPLACE('abcbd'@en, 'b.', 
'Z')", "'aZZ'@en"); }
+    @Test public void replace06()       { test("REPLACE('abcbd', 'B.', 'Z', 
'i')", "'aZZ'"); }
+
+    // See JENA-740
+    // ARQ provides replacement of the potentially empty string.
+    @Test public void replace07()       { test("REPLACE('abc', '.*', 'Z')", 
"'Z'"); }
+    @Test public void replace08()       { test("REPLACE('', '.*', 'Z')",    
"'Z'"); }
+    @Test public void replace09()       { test("REPLACE('abc', '.?', 'Z')", 
"'ZZZ'"); }
+
+    @Test public void replace10()       { test("REPLACE('abc', 'XXX', 'Z')", 
"'abc'"); }
+    @Test public void replace11()       { test("REPLACE('', '.', 'Z')",      
kwEmptyString); }
+    @Test public void replace12()       { test("REPLACE('', '(a|b)?', 'Z')", 
"'Z'"); }
+    @Test public void replace13()       { test("REPLACE('abc'@en, 'XXX', 
'Z')", "'abc'@en"); }
+
+    // Bad group
+    @Test public void replace20()       { testEvalException("REPLACE('abc', 
'.*', '$1')"); }
+    // Bad pattern; static (parse or build time) compilation.
+    @Test public void replace21()       { assertThrows(ExprException.class, 
()-> ExprUtils.parse("REPLACE('abc', '^(a){-9}', 'ABC')")); }
+
+    @Test public void replace30()       { test("REPLACE('b'@en--ltr, '(a|b)?', 
'Z')", "'Z'@en--ltr"); }
+    @Test public void replace31()       { test("REPLACE('b'@en--ltr, '(a|b)?', 
'Z'@en--ltr)", "'Z'@en--ltr"); }
+
+    @Test public void sameTerm1()       { test("sameTerm(1,1)",           
kwTRUE); }
+    @Test public void sameTerm2()       { test("sameTerm(1,1.0)",         
kwFALSE); }
+    @Test public void sameTerm3()       { test("sameTerm(1,1e0)",         
kwFALSE); }
+    @Test public void sameTerm4()       { test("sameTerm(<_:a>, <_:a>)",  
kwTRUE); }
+    @Test public void sameTerm5()       { test("sameTerm(<x>, <x>)",      
kwTRUE); }
+    @Test public void sameTerm6()       { test("sameTerm(<x>, <y>)",      
kwFALSE); }
+
+    @Test public void OneOf_01()        { test("57 in (xsd:integer, '123')",   
kwFALSE); }
+    @Test public void OneOf_02()        { test("57 in (57)",                   
kwTRUE); }
+    @Test public void OneOf_03()        { test("57 in (123, 57)",              
kwTRUE); }
+    @Test public void OneOf_04()        { test("57 in (57, 456)",              
kwTRUE); }
+    @Test public void OneOf_05()        { test("57 in (123, 57, 456)",         
kwTRUE); }
+    @Test public void OneOf_06()        { test("57 in (1,2,3)",                
kwFALSE); }
+
+    @Test public void NotOneOf_01()     { test("57 not in (xsd:integer, 
'123')",   kwTRUE); }
+    @Test public void NotOneOf_02()     { test("57 not in (57)",               
    kwFALSE); }
+    @Test public void NotOneOf_03()     { test("57 not in (123, 57)",          
    kwFALSE); }
+    @Test public void NotOneOf_04()     { test("57 not in (57, 456)",          
    kwFALSE); }
+    @Test public void NotOneOf_05()     { test("57 not in (123, 57, 456)",     
    kwFALSE); }
+    @Test public void NotOneOf_06()     { test("57 not in (1,2,3)",            
    kwTRUE); }
+
+    @Test public void StrLang1()        { test("strlang('xyz', 'en')", 
"'xyz'@en"); }
+    @Test public void StrLang2()        { testEvalException("strlang('xyz', 
'')"); }
+
+    @Test public void StrDatatype1()    { test("strdt('123', xsd:integer)",    
 "123"); }
+    @Test public void StrDatatype2()    { test("strdt('xyz', xsd:string)",  
"'xyz'"); }
+    @Test public void StrDatatype3()    { testEvalException("strdt('123',      
 'datatype')"); }
 
     // YEAR
     @Test public void year_01()         { 
test("year('2010-12-24T16:24:01.123'^^xsd:dateTime)", "2010"); }
@@ -315,106 +443,82 @@ public class TestFunctions2
     @Test public void year_03()         { test("year('2010'^^xsd:gYear)", 
"2010"); }
     @Test public void year_04()         { 
test("year('2010-12'^^xsd:gYearMonth)", "2010"); }
 
-    @Test
-    public void year_05()               { 
assertThrows(ExprEvalException.class, ()-> test("year('--12'^^xsd:gMonth)", 
"2010") ); }
-    @Test
-    public void year_06()               { 
assertThrows(ExprEvalException.class, ()-> 
test("year('--12-24'^^xsd:gMonthDay)", "2010") ); }
-    @Test
-    public void year_07()               { 
assertThrows(ExprEvalException.class, ()-> test("year('---24'^^xsd:gDay)", 
"2010") ); }
+    @Test public void year_05()         { 
testEvalException("year('--12'^^xsd:gMonth)"); }
+    @Test public void year_06()         { 
testEvalException("year('--12-24'^^xsd:gMonthDay)"); }
+    @Test public void year_07()         { 
testEvalException("year('---24'^^xsd:gDay)"); }
 
     @Test public void year_11()         { 
test("year('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "2010"); }
     @Test public void year_12()         { 
test("year('2010-12-24Z'^^xsd:date)", "2010"); }
     @Test public void year_13()         { test("year('2010Z'^^xsd:gYear)", 
"2010"); }
     @Test public void year_14()         { 
test("year('2010-12Z'^^xsd:gYearMonth)", "2010"); }
 
-    @Test
-    public void year_15()               { 
assertThrows(ExprEvalException.class, ()-> test("year('--12Z'^^xsd:gMonth)", 
"2010") ); }
-    @Test
-    public void year_16()               { 
assertThrows(ExprEvalException.class, ()-> 
test("year('--12-24Z'^^xsd:gMonthDay)", "2010") ); }
-    @Test
-    public void year_17()               { 
assertThrows(ExprEvalException.class, ()-> test("year('---24Z'^^xsd:gDay)", 
"2010") ); }
+    @Test public void year_15()         { 
testEvalException("year('--12Z'^^xsd:gMonth)"); }
+    @Test public void year_16()         { 
testEvalException("year('--12-24Z'^^xsd:gMonthDay)"); }
+    @Test public void year_17()         { 
testEvalException("year('---24Z'^^xsd:gDay)"); }
 
     @Test public void year_21()         { 
test("year('2010-12-24T16:24:01.123-08:00'^^xsd:dateTime)", "2010"); }
     @Test public void year_22()         { 
test("year('2010-12-24-08:00'^^xsd:date)", "2010"); }
     @Test public void year_23()         { 
test("year('2010-08:00'^^xsd:gYear)", "2010"); }
     @Test public void year_24()         { 
test("year('2010-12-08:00'^^xsd:gYearMonth)", "2010"); }
-    @Test
-    public void year_25()               { 
assertThrows(ExprEvalException.class, ()-> 
test("year('--12-08:00'^^xsd:gMonth)", "2010") ); }
-    @Test
-    public void year_26()               { 
assertThrows(ExprEvalException.class, ()-> 
test("year('--12-24-08:00'^^xsd:gMonthDay)", "2010") ); }
-    @Test
-    public void year_27()               { 
assertThrows(ExprEvalException.class, ()-> 
test("year('---24-08:00'^^xsd:gDay)", "2010") ); }
+    @Test public void year_25()         { 
testEvalException("year('--12-08:00'^^xsd:gMonth)"); }
+    @Test public void year_26()         { 
testEvalException("year('--12-24-08:00'^^xsd:gMonthDay)"); }
+    @Test public void year_27()         { 
testEvalException("year('---24-08:00'^^xsd:gDay)"); }
 
     @Test public void year_dur_01()     { 
test("year('P1Y2M3DT4H5M6S'^^xsd:duration)", "1"); }
 
-
     // MONTH
     @Test public void month_01()        { 
test("month('2010-12-24T16:24:01.123'^^xsd:dateTime)", "12"); }
     @Test public void month_02()        { 
test("month('2010-12-24'^^xsd:date)", "12"); }
-    @Test
-    public void month_03()              { 
assertThrows(ExprEvalException.class, ()-> test("month('2010'^^xsd:gYear)", 
"12") ); }
+    @Test public void month_03()        { 
testEvalException("month('2010'^^xsd:gYear)"); }
     @Test public void month_04()        { 
test("month('2010-12'^^xsd:gYearMonth)", "12"); }
 
     @Test public void month_05()        { test("month('--12'^^xsd:gMonth)", 
"12"); }
     @Test public void month_06()        { 
test("month('--12-24'^^xsd:gMonthDay)", "12"); }
-    @Test
-    public void month_07()              { 
assertThrows(ExprEvalException.class, ()-> test("month('---24'^^xsd:gDay)", 
"12") ); }
+    @Test public void month_07()        { 
testEvalException("month('---24'^^xsd:gDay)"); }
 
     @Test public void month_11()        { 
test("month('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "12"); }
     @Test public void month_12()        { 
test("month('2010-12-24Z'^^xsd:date)", "12"); }
-    @Test
-    public void month_13()              { 
assertThrows(ExprEvalException.class, ()-> test("month('2010Z'^^xsd:gYear)", 
"12") ); }
+    @Test public void month_13()        { 
testEvalException("month('2010Z'^^xsd:gYear)"); }
     @Test public void month_14()        { 
test("month('2010-12Z'^^xsd:gYearMonth)", "12"); }
 
     @Test public void month_15()        { test("month('--12Z'^^xsd:gMonth)", 
"12"); }
     @Test public void month_16()        { 
test("month('--12-24Z'^^xsd:gMonthDay)", "12"); }
-    @Test
-    public void month_17()              { 
assertThrows(ExprEvalException.class, ()-> test("month('---24Z'^^xsd:gDay)", 
"12") ); }
+    @Test public void month_17()        { 
testEvalException("month('---24Z'^^xsd:gDay)"); }
 
     @Test public void month_21()        { 
test("month('2010-12-24T16:24:01.123-08:00'^^xsd:dateTime)", "12"); }
     @Test public void month_22()        { 
test("month('2010-12-24-08:00'^^xsd:date)", "12"); }
-    @Test
-    public void month_23()              { 
assertThrows(ExprEvalException.class, ()-> 
test("month('2010-08:00'^^xsd:gYear)", "12") ); }
+    @Test public void month_23()        { 
testEvalException("month('2010-08:00'^^xsd:gYear)"); }
     @Test public void month_24()        { 
test("month('2010-12-08:00'^^xsd:gYearMonth)", "12"); }
     @Test public void month_25()        { 
test("month('--12-08:00'^^xsd:gMonth)", "12"); }
-    public void month_26()              { 
test("month('--12-24-08:00'^^xsd:gMonthDay)", "12"); }
-    @Test
-    public void month_27()              { 
assertThrows(ExprEvalException.class, ()-> 
test("month('---24-08:00'^^xsd:gDay)", "12") ); }
+    @Test public void month_26()        { 
test("month('--12-24-08:00'^^xsd:gMonthDay)", "12"); }
+    @Test public void month_27()        { 
testEvalException("month('---24-08:00'^^xsd:gDay)"); }
 
     @Test public void month_dur_01()    { 
test("month('P1Y2M3DT4H5M6S'^^xsd:duration)", "2"); }
 
     // DAY
     @Test public void day_01()          { 
test("day('2010-12-24T16:24:01.123'^^xsd:dateTime)", "24"); }
     @Test public void day_02()          { test("day('2010-12-24'^^xsd:date)", 
"24"); }
-    @Test
-    public void day_03()                { 
assertThrows(ExprEvalException.class, ()-> test("day('2010'^^xsd:gYear)", "24") 
); }
-    @Test
-    public void day_04()                { 
assertThrows(ExprEvalException.class, ()-> 
test("day('2010-12'^^xsd:gYearMonth)", "24") ); }
+    @Test public void day_03()          { 
testEvalException("day('2010'^^xsd:gYear)"); }
+    @Test public void day_04()          { 
testEvalException("day('2010-12'^^xsd:gYearMonth)"); }
 
-    @Test
-    public void day_05()                { 
assertThrows(ExprEvalException.class, ()-> test("day('--12'^^xsd:gMonth)", 
"24") ); }
+    @Test public void day_05()          { 
testEvalException("day('--12'^^xsd:gMonth)"); }
     @Test public void day_06()          { 
test("day('--12-24'^^xsd:gMonthDay)", "24"); }
     @Test public void day_07()          { test("day('---24'^^xsd:gDay)", 
"24"); }
 
     @Test public void day_11()          { 
test("day('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "24"); }
     @Test public void day_12()          { test("day('2010-12-24Z'^^xsd:date)", 
"24"); }
-    @Test
-    public void day_13()                { 
assertThrows(ExprEvalException.class, ()-> test("day('2010Z'^^xsd:gYear)", 
"24") ); }
-    @Test
-    public void day_14()                { 
assertThrows(ExprEvalException.class, ()-> 
test("day('2010-12Z'^^xsd:gYearMonth)", "24") ); }
-    @Test
-    public void day_15()                { 
assertThrows(ExprEvalException.class, ()-> test("day('--12Z'^^xsd:gMonth)", 
"24") ); }
+
+    @Test public void day_13()          { 
testEvalException("day('2010Z'^^xsd:gYear)"); }
+    @Test public void day_14()          { 
testEvalException("day('2010-12Z'^^xsd:gYearMonth)"); }
+    @Test public void day_15()          { 
testEvalException("day('--12Z'^^xsd:gMonth)"); }
     @Test public void day_16()          { 
test("day('--12-24Z'^^xsd:gMonthDay)", "24"); }
     @Test public void day_17()          { test("day('---24Z'^^xsd:gDay)", 
"24"); }
 
     @Test public void day_21()          { 
test("day('2010-12-24T16:24:01.123-08:00'^^xsd:dateTime)", "24"); }
     @Test public void day_22()          { 
test("day('2010-12-24-08:00'^^xsd:date)", "24"); }
-    @Test
-    public void day_23()                { 
assertThrows(ExprEvalException.class, ()-> test("day('2010-08:00'^^xsd:gYear)", 
"24") ); }
-    @Test
-    public void day_24()                { 
assertThrows(ExprEvalException.class, ()-> 
test("day('2010-12-08:00'^^xsd:gYearMonth)", "24") ); }
-    @Test
-    public void day_25()                { 
assertThrows(ExprEvalException.class, ()-> 
test("day('--12-08:00'^^xsd:gMonth)", "24") ); }
+    @Test public void day_23()          { 
testEvalException("day('2010-08:00'^^xsd:gYear)"); }
+    @Test public void day_24()          { 
testEvalException("day('2010-12-08:00'^^xsd:gYearMonth)"); }
+    @Test public void day_25()          { 
testEvalException("day('--12-08:00'^^xsd:gMonth)"); }
     @Test public void day_26()          { 
test("day('--12-24-08:00'^^xsd:gMonthDay)", "24"); }
     @Test public void day_27()          { test("day('---24-08:00'^^xsd:gDay)", 
"24"); }
 
@@ -423,8 +527,7 @@ public class TestFunctions2
     // HOURS
 
     @Test public void hours_01()        { 
test("hours('2010-12-24T16:24:01.123'^^xsd:dateTime)", "16"); }
-    @Test
-    public void hours_02()              { 
assertThrows(ExprEvalException.class, ()-> 
test("hours('2010-12-24'^^xsd:date)", "16") ); }
+    @Test public void hours_02()        { 
testEvalException("hours('2010-12-24'^^xsd:date)"); }
     @Test public void hours_03()        { test("hours('16:24:01'^^xsd:time)", 
"16"); }
 
     @Test public void hours_10()        { 
test("hours('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "16"); }
@@ -437,7 +540,7 @@ public class TestFunctions2
 
     // MINUTES
     @Test public void minutes_01()      { 
test("minutes('2010-12-24T16:24:01.123'^^xsd:dateTime)", "24"); }
-    @Test public void minutes_02()      { 
assertThrows(ExprEvalException.class, ()-> 
test("minutes('2010-12-24'^^xsd:date)", "") ); }
+    @Test public void minutes_02()      { 
testEvalException("minutes('2010-12-24'^^xsd:date)"); }
     @Test public void minutes_03()      { 
test("minutes('16:24:01'^^xsd:time)", "24"); }
 
     @Test public void minutes_10()      { 
test("minutes('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "24"); }
@@ -450,8 +553,7 @@ public class TestFunctions2
 
     // SECONDS
     @Test public void seconds_01()      { 
test("seconds('2010-12-24T16:24:01.123'^^xsd:dateTime)", "01.123"); }
-    @Test
-    public void seconds_02()            { 
assertThrows(ExprEvalException.class, ()-> 
test("seconds('2010-12-24'^^xsd:date)", "") ); }
+    @Test public void seconds_02()            { 
testEvalException("seconds('2010-12-24'^^xsd:date)"); }
     @Test public void seconds_03()      { 
test("seconds('16:24:01'^^xsd:time)", "'01'^^xsd:decimal"); }
 
     @Test public void seconds_10()      { 
test("seconds('2010-12-24T16:24:31.123Z'^^xsd:dateTime)", "31.123"); }
@@ -469,9 +571,9 @@ public class TestFunctions2
     @Test public void timezone_04()     { 
test("timezone('2010-12-24T16:24:35.123-00:00'^^xsd:dateTime)", 
"'-PT0S'^^xsd:dayTimeDuration"); }
     @Test public void timezone_05()     { 
test("timezone('2010-12-24T16:24:35.123+00:00'^^xsd:dateTime)", 
"'PT0S'^^xsd:dayTimeDuration"); }
 
-    @Test public void timezone_09()     { 
assertThrows(ExprEvalException.class, ()-> 
test("timezone('2010-12-24T16:24:35'^^xsd:dateTime)", 
"'PT0S'^^xsd:dayTimeDuration") ); }
-    @Test public void timezone_10()     { 
assertThrows(ExprEvalException.class, ()-> test("timezone(2010)", 
"'PT0S'^^xsd:dayTimeDuration") ); }
-    @Test public void timezone_11()     { 
assertThrows(ExprEvalException.class, ()-> 
test("timezone('2010-junk'^^xsd:gYear)", "'PT0S'^^xsd:dayTimeDuration") ); }
+    @Test public void timezone_09()     { 
testEvalException("timezone('2010-12-24T16:24:35'^^xsd:dateTime)"); }
+    @Test public void timezone_10()     { testEvalException("timezone(2010)"); 
}
+    @Test public void timezone_11()     { 
testEvalException("timezone('2010-junk'^^xsd:gYear)"); }
 
     // General "adjust-to-timezone"
     @Test public void adjust_1() {
@@ -480,9 +582,7 @@ public class TestFunctions2
 
     @Test
     public void adjust_2() {
-               assertThrows(ExprEvalException.class, ()->
-                   test("adjust('2022-12-21T05:05:07', 
'PT08H'^^xsd:duration)", "'2022-12-21T05:05:07+08:00'^^xsd:dateTime")
-                   );
+        test("adjust('2022-12-21T05:05:07'^^xsd:dateTime, 
'PT08H'^^xsd:duration)", "'2022-12-21T05:05:07+08:00'^^xsd:dateTime");
     }
 
     // General "adjust-to-timezone"
@@ -494,63 +594,62 @@ public class TestFunctions2
         test("adjust(xsd:dateTime('2022-12-21T05:05:07'))", 
"'2022-12-21T05:05:07Z'^^xsd:dateTime");
     }
 
-
     // TZ
     @Test public void tz_01()           { 
test("tz('2010-12-24T16:24:35.123Z'^^xsd:dateTime)", "'Z'"); }
     @Test public void tz_02()           { 
test("tz('2010-12-24T16:24:35.123-08:00'^^xsd:dateTime)", "'-08:00'"); }
     @Test public void tz_03()           { 
test("tz('2010-12-24T16:24:35.123+01:00'^^xsd:dateTime)", "'+01:00'"); }
     @Test public void tz_04()           { 
test("tz('2010-12-24T16:24:35.123-00:00'^^xsd:dateTime)", "'-00:00'"); }
     @Test public void tz_05()           { 
test("tz('2010-12-24T16:24:35.123+00:00'^^xsd:dateTime)", "'+00:00'"); }
-    @Test public void tz_06()           { 
test("tz('2010-12-24T16:24:35.123'^^xsd:dateTime)", "''"); }
+    @Test public void tz_06()           { 
test("tz('2010-12-24T16:24:35.123'^^xsd:dateTime)", kwEmptyString); }
 
-    @Test public void tz_10()           { 
assertThrows(ExprEvalException.class, ()-> test("tz(2010)", "''") ); }
-    @Test public void tz_11()           { 
assertThrows(ExprEvalException.class, ()-> test("tz('2010-junk'^^xsd:gYear)", 
"''") ); }
+    @Test public void tz_10()           { testEvalException("tz(2010)"); }
+    @Test public void tz_11()           { 
testEvalException("tz('2010-junk'^^xsd:gYear)"); }
 
     // NOW
-    //@Test public void now_01()        { test("now() > 
'2010-12-24T16:24:35.123-08:00'^^xsd:dateTime", "true"); }
+    //@Test public void now_01()        { test("now() > 
'2010-12-24T16:24:35.123-08:00'^^xsd:dateTime", kwTRUE); }
 
     // MD5
     @Test public void md5_01()          { 
test("md5('abcd')","'e2fc714c4727ee9395f324cd2e7f331f'"); }
     @Test public void md5_02()          { 
test("md5('abcd'^^xsd:string)","'e2fc714c4727ee9395f324cd2e7f331f'"); }
-    @Test public void md5_03()          { 
assertThrows(ExprEvalException.class, ()-> 
test("md5('abcd'@en)","'e2fc714c4727ee9395f324cd2e7f331f'") ); }
-    @Test public void md5_04()          { 
assertThrows(ExprEvalException.class, ()-> 
test("md5(1234)","'e2fc714c4727ee9395f324cd2e7f331f'") ); }
+    @Test public void md5_03()          { testEvalException("md5('abcd'@en)"); 
}
+    @Test public void md5_04()          { testEvalException("md5(1234)"); }
 
     // SHA1
     @Test public void sha1_01()         { 
test("sha1('abcd')","'81fe8bfe87576c3ecb22426f8e57847382917acf'"); }
     @Test public void sha1_02()         { 
test("sha1('abcd'^^xsd:string)","'81fe8bfe87576c3ecb22426f8e57847382917acf'"); }
-    @Test public void sha1_03()         { 
assertThrows(ExprEvalException.class, ()-> 
test("sha1('abcd'@en)","'81fe8bfe87576c3ecb22426f8e57847382917acf'") ); }
-    @Test public void sha1_04()         { 
assertThrows(ExprEvalException.class, ()-> 
test("sha1(123)","'81fe8bfe87576c3ecb22426f8e57847382917acf'") ); }
+    @Test public void sha1_03()         { 
testEvalException("sha1('abcd'@en)"); }
+    @Test public void sha1_04()         { testEvalException("sha1(123)"); }
 
 //    // SHA224
 //    @Test public void sha224_01()      { 
test("sha224('abcd')","'e2fc714c4727ee9395f324cd2e7f331f'"); }
 //
 //    @Test
-//    public void sha224_02()            { 
assertThrows(ExprEvalException.class, ()-> 
test("sha224('abcd'^^xsd:string)","'e2fc714c4727ee9395f324cd2e7f331f'") ); }
+//    public void sha224_02()            { 
testEvalException("sha224('abcd'^^xsd:string)"); }
 //
 //    @Test
-//    public void sha224_03()            { 
assertThrows(ExprEvalException.class, ()-> 
test("sha224('abcd'@en)","'e2fc714c4727ee9395f324cd2e7f331f'") ); }
+//    public void sha224_03()            { 
testEvalException("sha224('abcd'@en)"); }
 //
 //  @Test
-//  public void sha224_04()            { assertThrows(ExprEvalException.class, 
()-> test("sha224(1234)","'e2fc714c4727ee9395f324cd2e7f331f'") ); }
+//  public void sha224_04()            { testEvalException("sha224(1234)"); }
 
     // SHA256
 
     @Test public void sha256_01()       { 
test("sha256('abcd')","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'");
 }
     @Test public void sha256_02()       { 
test("sha256('abcd'^^xsd:string)","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'");
 }
-    @Test public void sha256_03()       { 
assertThrows(ExprEvalException.class, ()-> 
test("sha256('abcd'@en)","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'")
 ); }
-    @Test public void sha256_04()       { 
assertThrows(ExprEvalException.class, ()-> 
test("sha256(<uri>)","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'")
 ); }
+    @Test public void sha256_03()       { 
testEvalException("sha256('abcd'@en)"); }
+    @Test public void sha256_04()       { testEvalException("sha256(<uri>)"); }
 
     // SHA384
     @Test public void sha384_01()       { 
test("sha384('abcd')","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'");
 }
     @Test public void sha384_02()       { 
test("sha384('abcd'^^xsd:string)","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'");
 }
-    @Test public void sha384_03()       { 
assertThrows(ExprEvalException.class, ()-> 
test("sha384('abcd'@en)","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'")
 ); }
-    @Test public void sha384_04()       { 
assertThrows(ExprEvalException.class, ()-> 
test("sha384(123.45)","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'")
 ); }
+    @Test public void sha384_03()       { 
testEvalException("sha384('abcd'@en)"); }
+    @Test public void sha384_04()       { testEvalException("sha384(123.45)"); 
}
 
     // SHA512
     @Test public void sha512_01()       { 
test("sha512('abcd')","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'");
 }
     @Test public void sha512_02()       { 
test("sha512('abcd'^^xsd:string)","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'");
 }
-    @Test public void sha512_03()       { 
assertThrows(ExprEvalException.class, ()-> 
test("md5('abcd'@en)","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'")
 ); }
-    @Test public void sha512_04()       { 
assertThrows(ExprEvalException.class, ()-> 
test("md5(0.0e0)","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'")
 ); }
+    @Test public void sha512_03()       { testEvalException("md5('abcd'@en)"); 
}
+    @Test public void sha512_04()       { testEvalException("md5(0.0e0)"); }
 
     // --------
 
@@ -576,4 +675,13 @@ public class TestFunctions2
         // test result must be lexical form exact.
         assertEquals(r, nv.asNode());
     }
+
+    private void testEvalException(String exprStr) {
+        Expr expr = ExprUtils.parse(exprStr);
+        try {
+            NodeValue r = expr.eval(null, LibTestExpr.createTest());
+            fail("No exception raised");
+        }
+        catch (ExprEvalException ex) {}
+    }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestStringArgCompatibility.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestStringArgCompatibility.java
new file mode 100644
index 0000000000..5623d80317
--- /dev/null
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestStringArgCompatibility.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *     SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.apache.jena.sparql.expr;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.sparql.expr.nodevalue.NodeValueOps;
+import org.apache.jena.sparql.sse.SSE;
+
+public class TestStringArgCompatibility {
+
+    @Test public void arg_01()     { testArgGood("'abc'", "'abc'"); }
+    @Test public void arg_02()     { testArgGood("'abc'@en", "'abc'@en"); }
+    @Test public void arg_03()     { testArgGood("'abc'@en--ltr", 
"'abc'@en--ltr"); }
+
+    @Test public void arg_04()     { testArgBad("123"); }
+    @Test public void arg_05()     { testArgBad("<123>"); }
+
+    @Test public void args2_01()   { test2ArgsGood("'abc'", "''"); }
+    @Test public void args2_02()   { test2ArgsGood("'abc'", "'ab'"); }
+    @Test public void args2_03()   { test2ArgsGood("'abc'@en", "'ab'"); }
+    @Test public void args2_04()   { test2ArgsGood("'abc'@en--ltr", "'ab'"); }
+    @Test public void args2_05()   { test2ArgsGood("'abc'@en", "'ab'@en"); }
+    @Test public void args2_06()   { test2ArgsGood("'abc'@en--ltr", 
"'ab'@en--ltr"); }
+
+    @Test public void args2_20()   { test2ArgsBad("123", "123"); }  // Not 
strings.
+    @Test public void args2_21()   { test2ArgsBad("'abc'@en", "'a'@fr"); }
+    @Test public void args2_22()   { test2ArgsBad("'abc'@en--ltr", "'a'@en"); }
+    @Test public void args2_23()   { test2ArgsBad("'abc'@en", "'a'@en--ltr"); }
+
+    private void testArgGood(String arg, String expected) {
+        Node n = SSE.parseNode(arg);
+        NodeValue nv = NodeValue.makeNode(n);
+        Node r = NodeValueOps.checkAndGetStringLiteral("test", nv);
+        Node nExpected = SSE.parseNode(expected);
+        assertEquals(nExpected, r);
+    }
+
+    private void testArgBad(String arg) {
+        Node n = SSE.parseNode(arg);
+        NodeValue nv = NodeValue.makeNode(n);
+        assertThrows(ExprEvalException.class, 
()->NodeValueOps.checkAndGetStringLiteral("test", nv));
+    }
+
+    private void test2ArgsGood(String arg1, String arg2) {
+        Node n1 = SSE.parseNode(arg1);
+        Node n2 = SSE.parseNode(arg2);
+        NodeValue nv1 = NodeValue.makeNode(n1);
+        NodeValue nv2 = NodeValue.makeNode(n2);
+        NodeValueOps.checkTwoArgumentStringLiterals("test", nv1, nv2);
+    }
+
+    private void test2ArgsBad(String arg1, String arg2) {
+        Node n1 = SSE.parseNode(arg1);
+        Node n2 = SSE.parseNode(arg2);
+        NodeValue nv1 = NodeValue.makeNode(n1);
+        NodeValue nv2 = NodeValue.makeNode(n2);
+        assertThrows(ExprEvalException.class, 
()->NodeValueOps.checkTwoArgumentStringLiterals("test", nv1, nv2));
+    }
+
+}
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java 
b/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java
index e31eee664d..504362cb42 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java
@@ -65,13 +65,19 @@ public class Lib
 
     /**
      * Return true if obj1 and obj are both null or are .equals, else return 
false
-     * Prefer {@link Objects#equals(Object, Object)}
+     * @deprecated Use {@link #equalsOrNulls(Object, Object)} {@link 
Objects#equals(Object, Object)}
      */
+    @Deprecated(forRemoval = true)
     public static final <T> boolean equals(T obj1, T obj2) {
         // Include because other equals* operations are here.
         return Objects.equals(obj1, obj2);
     }
 
+    public static final <T> boolean equalsOrNulls(T obj1, T obj2) {
+        // Include because static import works.
+        return Objects.equals(obj1, obj2);
+    }
+
     /** Return true if obj1 and obj are both null or are .equals, else return 
false */
     public static final boolean equalsIgnoreCase(String str1, String str2) {
         if ( str1 == null )
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
index 099a6afea9..e8c3feeae0 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
@@ -19,6 +19,7 @@
 package org.apache.jena.fuseki.servlets;
 
 import static java.lang.String.format;
+import static org.apache.jena.atlas.lib.Lib.equalsOrNulls;
 import static org.apache.jena.riot.web.HttpNames.METHOD_POST;
 
 import java.io.ByteArrayOutputStream;
@@ -35,7 +36,6 @@ import jakarta.servlet.http.HttpServletResponse;
 
 import org.apache.jena.atlas.RuntimeIOException;
 import org.apache.jena.atlas.io.IO;
-import org.apache.jena.atlas.lib.Lib;
 import org.apache.jena.atlas.web.AcceptList;
 import org.apache.jena.atlas.web.ContentType;
 import org.apache.jena.atlas.web.MediaType;
@@ -193,7 +193,7 @@ public class ActionLib {
 
     public static boolean splitContains(String[] elts, String str) {
         for ( int i = 0 ; i < elts.length ; i++ ) {
-            if ( Lib.equals(elts[i],  str) )
+            if ( equalsOrNulls(elts[i],  str) )
                 return true;
         }
         return false;

Reply via email to