You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/04/29 16:29:47 UTC

[sis] 02/02: Replace the `` parameterized type by `` in filters and expressions. It is less generic, but the reason is because expressions and filters are often chained. Following a chain of filters become difficult if, when asking parameters of parameters, the `` type become a kind of `` type, which cannot be represented in the Java language and is reported by the compiler as ``. Experience with implementation shows that it is difficult to avo [...]

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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit eef1a6a166661882bec553783355a989a946704c
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 29 17:56:49 2023 +0200

    Replace the `<? super R>` parameterized type by `<R>` in filters and expressions.
    It is less generic, but the reason is because expressions and filters are often chained.
    Following a chain of filters become difficult if, when asking parameters of parameters,
    the `<? super R>` type become a kind of `<? super ? super R>` type, which cannot be
    represented in the Java language and is reported by the compiler as `<? super #CAP1>`.
    Experience with implementation shows that it is difficult to avoid unsafe casts in such cases.
    
    https://issues.apache.org/jira/browse/SIS-578
---
 .../src/main/java/org/apache/sis/cql/CQL.java      |  34 ++---
 .../org/apache/sis/cql/FilterToCQLVisitor.java     |  30 ++--
 .../apache/sis/feature/ExpressionOperation.java    |  26 ++--
 .../org/apache/sis/feature/FeatureOperations.java  |   4 +-
 .../org/apache/sis/filter/ArithmeticFunction.java  |  30 ++--
 .../java/org/apache/sis/filter/BinaryFunction.java |  14 +-
 .../apache/sis/filter/BinaryGeometryFilter.java    |  57 ++++---
 .../org/apache/sis/filter/BinarySpatialFilter.java |  16 +-
 .../org/apache/sis/filter/ComparisonFilter.java    |  64 ++++----
 .../org/apache/sis/filter/ConvertFunction.java     |  10 +-
 .../apache/sis/filter/DefaultFilterFactory.java    | 166 ++++++++++-----------
 .../org/apache/sis/filter/DefaultSortProperty.java |   8 +-
 .../java/org/apache/sis/filter/DistanceFilter.java |  18 +--
 .../java/org/apache/sis/filter/FilterNode.java     |  26 +++-
 .../org/apache/sis/filter/IdentifierFilter.java    |   4 +-
 .../java/org/apache/sis/filter/LeafExpression.java |   6 +-
 .../java/org/apache/sis/filter/LikeFilter.java     |  12 +-
 .../java/org/apache/sis/filter/LogicalFilter.java  |  56 +++----
 .../java/org/apache/sis/filter/Optimization.java   |  70 ++++-----
 .../java/org/apache/sis/filter/TemporalFilter.java |  90 +++++------
 .../java/org/apache/sis/filter/UnaryFunction.java  |  18 +--
 .../java/org/apache/sis/filter/package-info.java   |   2 +-
 .../apache/sis/internal/filter/CopyVisitor.java    |  44 +++---
 .../sis/internal/filter/FunctionRegister.java      |   4 +-
 .../sis/internal/filter/GeometryConverter.java     |  10 +-
 .../java/org/apache/sis/internal/filter/Node.java  |   2 +-
 .../sis/internal/filter/SortByComparator.java      |   8 +-
 .../org/apache/sis/internal/filter/Visitor.java    |  21 +--
 .../internal/filter/sqlmm/FunctionWithSRID.java    |   6 +-
 .../internal/filter/sqlmm/GeometryConstructor.java |  10 +-
 .../sis/internal/filter/sqlmm/GeometryParser.java  |   6 +-
 .../sis/internal/filter/sqlmm/OneGeometry.java     |  18 +--
 .../apache/sis/internal/filter/sqlmm/Registry.java |   4 +-
 .../sis/internal/filter/sqlmm/ST_FromBinary.java   |   6 +-
 .../sis/internal/filter/sqlmm/ST_FromText.java     |   6 +-
 .../apache/sis/internal/filter/sqlmm/ST_Point.java |  10 +-
 .../sis/internal/filter/sqlmm/ST_Transform.java    |  10 +-
 .../sis/internal/filter/sqlmm/SpatialFunction.java |   2 +-
 .../sis/internal/filter/sqlmm/TwoGeometries.java   |  24 +--
 .../sis/internal/filter/sqlmm/package-info.java    |   2 +-
 .../sis/filter/BinarySpatialFilterTestCase.java    |   6 +-
 .../org/apache/sis/filter/LogicalFilterTest.java   |   6 +-
 .../org/apache/sis/filter/TemporalFilterTest.java  |   4 +-
 .../sis/internal/filter/FilterFactoryMock.java     | 160 ++++++++++----------
 .../apache/sis/internal/filter/FunctionMock.java   |   6 +-
 .../internal/filter/sqlmm/RegistryTestCase.java    |   4 +-
 .../org/apache/sis/internal/map/SEPortrayer.java   |   8 +-
 .../sis/internal/sql/feature/FeatureStream.java    |   2 +-
 .../sql/feature/SelectionClauseWriter.java         |  17 +--
 .../sql/feature/SelectionClauseWriterTest.java     |   4 +-
 .../java/org/apache/sis/storage/FeatureQuery.java  |  24 +--
 .../java/org/apache/sis/storage/FeatureSubset.java |   4 +-
 .../sis/storage/aggregate/JoinFeatureSet.java      |   8 +-
 .../org/apache/sis/storage/FeatureQueryTest.java   |  25 ++--
 54 files changed, 609 insertions(+), 623 deletions(-)

diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java b/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
index 8d70de64cd..600395eccc 100644
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
+++ b/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
@@ -96,11 +96,11 @@ public final class CQL {
         return result;
     }
 
-    public static Filter<? super Feature> parseFilter(String cql) throws CQLException {
+    public static Filter<Feature> parseFilter(String cql) throws CQLException {
         return parseFilter(cql, null);
     }
 
-    public static Filter<? super Feature> parseFilter(String cql, FilterFactory<Feature,Object,Object> factory) throws CQLException {
+    public static Filter<Feature> parseFilter(String cql, FilterFactory<Feature,Object,Object> factory) throws CQLException {
         cql = cql.trim();
 
         // Bypass parsing for inclusive filter.
@@ -108,7 +108,7 @@ public final class CQL {
             return Filter.include();
         }
         final Object obj = AntlrCQL.compileFilter(cql);
-        Filter<? super Feature> result = null;
+        Filter<Feature> result = null;
         if (obj instanceof FilterContext) {
             ParseTree tree = (ParseTree) obj;
             if (factory == null) {
@@ -206,8 +206,8 @@ public final class CQL {
             if (tree.getChildCount() == 3) {
                 final String operand = tree.getChild(1).getText();
                 // TODO: unsafe cast.
-                final Expression<? super Feature, ? extends Number> left  = (Expression<? super Feature, ? extends Number>) convertExpression(tree.getChild(0), ff);
-                final Expression<? super Feature, ? extends Number> right = (Expression<? super Feature, ? extends Number>) convertExpression(tree.getChild(2), ff);
+                final Expression<Feature, ? extends Number> left  = (Expression<Feature, ? extends Number>) convertExpression(tree.getChild(0), ff);
+                final Expression<Feature, ? extends Number> right = (Expression<Feature, ? extends Number>) convertExpression(tree.getChild(2), ff);
                 if ("*".equals(operand)) {
                     return ff.multiply(left, right);
                 } else if ("/".equals(operand)) {
@@ -497,7 +497,7 @@ public final class CQL {
     /**
      * Convert the given tree in a Filter.
      */
-    private static Filter<? super Feature> convertFilter(ParseTree tree, FilterFactory<Feature,Object,Object> ff) throws CQLException {
+    private static Filter<Feature> convertFilter(ParseTree tree, FilterFactory<Feature,Object,Object> ff) throws CQLException {
         if (tree instanceof FilterContext) {
             //: filter (AND filter)+
             //| filter (OR filter)+
@@ -520,11 +520,11 @@ public final class CQL {
 
             } else if (!exp.AND().isEmpty()) {
                 //: filter (AND filter)+
-                final List<Filter<? super Feature>> subs = new ArrayList<>();
+                final List<Filter<Feature>> subs = new ArrayList<>();
                 for (FilterContext f : exp.filter()) {
-                    final Filter<? super Feature> sub = convertFilter(f, ff);
+                    final Filter<Feature> sub = convertFilter(f, ff);
                     if (sub.getOperatorType() == LogicalOperatorName.AND) {
-                        subs.addAll(((LogicalOperator<? super Feature>) sub).getOperands());
+                        subs.addAll(((LogicalOperator<Feature>) sub).getOperands());
                     } else {
                         subs.add(sub);
                     }
@@ -532,11 +532,11 @@ public final class CQL {
                 return ff.and(subs);
             } else if (!exp.OR().isEmpty()) {
                 //| filter (OR filter)+
-                final List<Filter<? super Feature>> subs = new ArrayList<>();
+                final List<Filter<Feature>> subs = new ArrayList<>();
                 for (FilterContext f : exp.filter()) {
-                    final Filter<? super Feature> sub = convertFilter(f, ff);
+                    final Filter<Feature> sub = convertFilter(f, ff);
                     if (sub.getOperatorType() == LogicalOperatorName.OR) {
-                        subs.addAll(((LogicalOperator<? super Feature>) sub).getOperands());
+                        subs.addAll(((LogicalOperator<Feature>) sub).getOperands());
                     } else {
                         subs.add(sub);
                     }
@@ -613,7 +613,7 @@ public final class CQL {
                         break;
                     }
                     default: {
-                        final List<Filter<? super Feature>> filters = new ArrayList<>();
+                        final List<Filter<Feature>> filters = new ArrayList<>();
                         for (Expression<Feature,?> e : subexps) {
                             filters.add(ff.equal(val, e));
                         }   selection = ff.or(filters);
@@ -828,9 +828,9 @@ public final class CQL {
             final Query query = new Query();
             if (context.MULT() == null) {
                 for (ProjectionContext pc : projections) {
-                    final Expression<Feature, ?> exp = convertExpression(pc.expression(), ff);
+                    final Expression<Feature,?> exp = convertExpression(pc.expression(), ff);
                     if (pc.AS() != null) {
-                        final Expression<Feature, ?> alias = convertExpression(pc.TEXT(), ff);
+                        final Expression<Feature,?> alias = convertExpression(pc.TEXT(), ff);
                         query.projections.add(new Query.Projection(exp, String.valueOf(( (Literal) alias).getValue())));
                     } else {
                         query.projections.add(new Query.Projection(exp, null));
@@ -848,9 +848,9 @@ public final class CQL {
             }
             if (orderby != null) {
                 for (SortpropContext spc : orderby.sortprop()) {
-                    final Expression<Feature, ?> exp = convertExpression(spc.expression(), ff);
+                    final Expression<Feature,?> exp = convertExpression(spc.expression(), ff);
                     if (exp instanceof ValueReference) {
-                        query.sortby.add(ff.sort((ValueReference<? super Feature, ?>) exp,
+                        query.sortby.add(ff.sort((ValueReference<Feature,?>) exp,
                                 spc.DESC() != null ? SortOrder.DESCENDING : SortOrder.ASCENDING));
                     } else {
                         throw new CQLException("Sort by may be used with property names only");
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java b/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
index b9eeaf7ed8..6d085b812e 100644
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
+++ b/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
@@ -106,7 +106,7 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
         operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    "<=");
         setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE), (f,sb) -> {
             final LikeOperator<Feature> filter = (LikeOperator<Feature>) f;
-            List<Expression<? super Feature, ?>> operands = f.getExpressions();
+            List<Expression<Feature,?>> operands = f.getExpressions();
             format(sb, operands.get(0));
             // TODO: ILIKE is not standard SQL.
             sb.append(filter.isMatchingCase() ? " LIKE " : " ILIKE ");
@@ -120,14 +120,14 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
          */
         setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
             final BinarySpatialOperator<Feature> filter = (BinarySpatialOperator<Feature>) f;
-            final Expression<? super Feature, ?> left  = filter.getOperand1();
-            final Expression<? super Feature, ?> right = filter.getOperand2();
-            final ValueReference<? super Feature, ?> pName =
-                    (left  instanceof ValueReference) ? (ValueReference<? super Feature, ?>) left :
-                    (right instanceof ValueReference) ? (ValueReference<? super Feature, ?>) right : null;
+            final Expression<Feature,?> left  = filter.getOperand1();
+            final Expression<Feature,?> right = filter.getOperand2();
+            final ValueReference<Feature,?> pName =
+                    (left  instanceof ValueReference) ? (ValueReference<Feature,?>) left :
+                    (right instanceof ValueReference) ? (ValueReference<Feature,?>) right : null;
             final Object lit = ((left instanceof Literal)
-                    ? (Literal<? super Feature, ?>) left
-                    : (Literal<? super Feature, ?>) right).getValue();      // TODO: potential classCastException.
+                    ? (Literal<Feature,?>) left
+                    : (Literal<Feature,?>) right).getValue();      // TODO: potential classCastException.
 
             final GeneralEnvelope e = Geometries.wrap(lit).map(GeometryWrapper::getEnvelope).orElse(null);
             if (e != null) {
@@ -221,7 +221,7 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
 
     private void operatorBetweenValues(final ComparisonOperatorName type, final String operator) {
         setFilterHandler(type, (f,sb) -> {
-            final List<Expression<? super Feature, ?>> operands = f.getExpressions();
+            final List<Expression<Feature,?>> operands = f.getExpressions();
             format(sb, operands.get(0));
             final int n = operands.size();
             for (int i=1; i<n; i++) {
@@ -234,7 +234,7 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
     private void operatorBetweenValues(final LogicalOperatorName type, final String operator) {
         setFilterHandler(type, (f,sb) -> {
             final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
-            final List<Filter<? super Feature>> operands = filter.getOperands();
+            final List<Filter<Feature>> operands = filter.getOperands();
             format(sb.append('('), operands.get(0));
             final int n = operands.size();
             for (int i=1; i<n; i++) {
@@ -246,7 +246,7 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
 
     private void function(final CodeList<?> type, final String operator) {
         setFilterHandler(type, (f,sb) -> {
-            final List<Expression<? super Feature, ?>> operands = f.getExpressions();
+            final List<Expression<Feature,?>> operands = f.getExpressions();
             sb.append(operator).append('(');
             final int n = operands.size();
             for (int i=0; i<n; i++) {
@@ -259,7 +259,7 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
 
     private void arithmetic(final String type, final char operator) {
         setExpressionHandler(type, (e,sb) -> {
-            final List<Expression<? super Feature, ?>> parameters = e.getParameters();
+            final List<Expression<Feature,?>> parameters = e.getParameters();
             format(sb, parameters.get(0));
             final int n = parameters.size();
             for (int i=1; i<n; i++) {
@@ -285,7 +285,7 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
      * @throws UnsupportedOperationException if there is no action registered for the given filter.
      */
     @SuppressWarnings("unchecked")
-    private void format(final StringBuilder sb, final Filter<? super Feature> filter) {
+    private void format(final StringBuilder sb, final Filter<Feature> filter) {
         visit((Filter<Feature>) filter, sb);
     }
 
@@ -307,13 +307,13 @@ final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
      * @throws UnsupportedOperationException if there is no action registered for the given expression.
      */
     @SuppressWarnings("unchecked")
-    private void format(final StringBuilder sb, final Expression<? super Feature, ?> expression) {
+    private void format(final StringBuilder sb, final Expression<Feature,?> expression) {
         visit((Expression<Feature,?>) expression, sb);
     }
 
     @Override
     protected void typeNotFound(final String type, final Expression<Feature,?> e, final StringBuilder sb) {
-        final List<Expression<? super Feature,?>> exps = e.getParameters();
+        final List<Expression<Feature,?>> exps = e.getParameters();
         sb.append(type).append('(');
         final int n = exps.size();
         for (int i=0; i<n; i++) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
index 78ecc4b7ad..efbc5cb83c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
@@ -64,7 +64,7 @@ final class ExpressionOperation<V> extends AbstractOperation {
      * The expression on which to delegate the execution of this operation.
      */
     @SuppressWarnings("serial")                         // Not statically typed as serializable.
-    private final Function<? super Feature, ? extends V> expression;
+    private final Function<Feature, ? extends V> expression;
 
     /**
      * The type of result of evaluating the expression.
@@ -88,14 +88,14 @@ final class ExpressionOperation<V> extends AbstractOperation {
      * @param result          type of values computed by the expression.
      */
     ExpressionOperation(final Map<String,?> identification,
-                        final Function<? super Feature, ? extends V> expression,
+                        final Function<Feature, ? extends V> expression,
                         final AttributeType<? super V> result)
     {
         super(identification);
         this.expression = expression;
         this.result     = result;
         if (expression instanceof Expression<?,?>) {
-            dependencies = DependencyFinder.search((Expression<Object,?>) expression);
+            dependencies = DependencyFinder.search((Expression<Feature,?>) expression);
         } else {
             dependencies = Set.of();
         }
@@ -164,10 +164,8 @@ final class ExpressionOperation<V> extends AbstractOperation {
     /**
      * An expression visitor for finding all dependencies of a given expression.
      * The dependencies are feature properties read by {@link ValueReference} nodes.
-     *
-     * @todo The first parameterized type should be {@code Feature} instead of {@code Object}.
      */
-    private static final class DependencyFinder extends Visitor<Object, Collection<String>> {
+    private static final class DependencyFinder extends Visitor<Feature, Collection<String>> {
         /**
          * The unique instance.
          */
@@ -179,7 +177,7 @@ final class ExpressionOperation<V> extends AbstractOperation {
          * @param  expression  the expression for which to get dependencies.
          * @return all dependencies recognized by this method.
          */
-        static Set<String> search(final Expression<Object,?> expression) {
+        static Set<String> search(final Expression<Feature,?> expression) {
             final Set<String> dependencies = new HashSet<>();
             VISITOR.visit(expression, dependencies);
             return Set.copyOf(dependencies);
@@ -190,13 +188,13 @@ final class ExpressionOperation<V> extends AbstractOperation {
          */
         private DependencyFinder() {
             setLogicalHandlers((f, dependencies) -> {
-                final var filter = (LogicalOperator<Object>) f;
-                for (Filter<Object> child : filter.getOperands()) {
+                final var filter = (LogicalOperator<Feature>) f;
+                for (Filter<Feature> child : filter.getOperands()) {
                     visit(child, dependencies);
                 }
             });
             setExpressionHandler(FunctionNames.ValueReference, (e, dependencies) -> {
-                final var expression = (ValueReference<Object,?>) e;
+                final var expression = (ValueReference<Feature,?>) e;
                 final String propName = expression.getXPath();
                 if (!propName.trim().isEmpty()) {
                     dependencies.add(propName);
@@ -208,8 +206,8 @@ final class ExpressionOperation<V> extends AbstractOperation {
          * Fallback for all filters not explicitly handled by the setting applied in the constructor.
          */
         @Override
-        protected void typeNotFound(final CodeList<?> type, final Filter<Object> filter, final Collection<String> dependencies) {
-            for (final Expression<Object,?> f : filter.getExpressions()) {
+        protected void typeNotFound(final CodeList<?> type, final Filter<Feature> filter, final Collection<String> dependencies) {
+            for (final Expression<Feature,?> f : filter.getExpressions()) {
                 visit(f, dependencies);
             }
         }
@@ -218,8 +216,8 @@ final class ExpressionOperation<V> extends AbstractOperation {
          * Fallback for all expressions not explicitly handled by the setting applied in the constructor.
          */
         @Override
-        protected void typeNotFound(final String type, final Expression<Object,?> expression, final Collection<String> dependencies) {
-            for (final Expression<Object,?> p : expression.getParameters()) {
+        protected void typeNotFound(final String type, final Expression<Feature,?> expression, final Collection<String> dependencies) {
+            for (final Expression<Feature,?> p : expression.getParameters()) {
                 visit(p, dependencies);
             }
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
index ad4c132716..b9ab137a05 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
@@ -280,7 +280,7 @@ public final class FeatureOperations extends Static {
      * @since 1.4
      */
     public static <V> Operation expression(final Map<String,?> identification,
-                                           final Function<? super Feature, ? extends V> expression,
+                                           final Function<Feature, ? extends V> expression,
                                            final AttributeType<? super V> result)
     {
         ArgumentChecks.ensureNonNull("expression", expression);
@@ -304,7 +304,7 @@ public final class FeatureOperations extends Static {
      * @since 1.4
      */
     public static <V> Operation expressionToResult(final Map<String,?> identification,
-                                                   final Expression<? super Feature, ?> expression,
+                                                   final Expression<Feature, ?> expression,
                                                    final AttributeType<V> result)
     {
         return expression(identification, expression.toValueType(result.getValueClass()), result);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
index e8ec2b21ae..0cd6fa5a71 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
@@ -39,7 +39,7 @@ import org.opengis.filter.Expression;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  *
@@ -56,8 +56,8 @@ abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
     /**
      * Creates a new arithmetic function.
      */
-    ArithmeticFunction(final Expression<? super R, ? extends Number> expression1,
-                       final Expression<? super R, ? extends Number> expression2)
+    ArithmeticFunction(final Expression<R, ? extends Number> expression1,
+                       final Expression<R, ? extends Number> expression2)
     {
         super(expression1, expression2);
     }
@@ -146,14 +146,14 @@ abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
         @Override protected AttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Add"} operation. */
-        Add(final Expression<? super R, ? extends Number> expression1,
-            final Expression<? super R, ? extends Number> expression2)
+        Add(final Expression<R, ? extends Number> expression1,
+            final Expression<R, ? extends Number> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new expression of the same type but different parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Expression<R,Number> recreate(final Expression<R,?>[] effective) {
             return new Add<>(effective[0].toValueType(Number.class),
                              effective[1].toValueType(Number.class));
         }
@@ -186,14 +186,14 @@ abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
         @Override protected AttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Subtract"} operation. */
-        Subtract(final Expression<? super R, ? extends Number> expression1,
-                 final Expression<? super R, ? extends Number> expression2)
+        Subtract(final Expression<R, ? extends Number> expression1,
+                 final Expression<R, ? extends Number> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new expression of the same type but different parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Expression<R,Number> recreate(final Expression<R,?>[] effective) {
             return new Subtract<>(effective[0].toValueType(Number.class),
                                   effective[1].toValueType(Number.class));
         }
@@ -226,14 +226,14 @@ abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
         @Override protected AttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Multiply"} operation. */
-        Multiply(final Expression<? super R, ? extends Number> expression1,
-                 final Expression<? super R, ? extends Number> expression2)
+        Multiply(final Expression<R, ? extends Number> expression1,
+                 final Expression<R, ? extends Number> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new expression of the same type but different parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Expression<R,Number> recreate(final Expression<R,?>[] effective) {
             return new Multiply<>(effective[0].toValueType(Number.class),
                                   effective[1].toValueType(Number.class));
         }
@@ -266,14 +266,14 @@ abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
         @Override protected AttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Divide"} operation. */
-        Divide(final Expression<? super R, ? extends Number> expression1,
-               final Expression<? super R, ? extends Number> expression2)
+        Divide(final Expression<R, ? extends Number> expression1,
+               final Expression<R, ? extends Number> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new expression of the same type but different parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Expression<R,Number> recreate(final Expression<R,?>[] effective) {
             return new Divide<>(effective[0].toValueType(Number.class),
                                 effective[1].toValueType(Number.class));
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
index 4b8062442d..b7c33e33a2 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
@@ -39,7 +39,7 @@ import org.opengis.filter.Expression;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>   the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <V1>  the type of value computed by the first expression.
@@ -59,7 +59,7 @@ abstract class BinaryFunction<R,V1,V2> extends Node {
      * @see #getExpression1()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected final Expression<? super R, ? extends V1> expression1;
+    protected final Expression<R, ? extends V1> expression1;
 
     /**
      * The second of the two expressions to be used by this function.
@@ -67,7 +67,7 @@ abstract class BinaryFunction<R,V1,V2> extends Node {
      * @see #getExpression2()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected final Expression<? super R, ? extends V2> expression2;
+    protected final Expression<R, ? extends V2> expression2;
 
     /**
      * Creates a new binary function.
@@ -75,8 +75,8 @@ abstract class BinaryFunction<R,V1,V2> extends Node {
      * @param  expression1  the first of the two expressions to be used by this function.
      * @param  expression2  the second of the two expressions to be used by this function.
      */
-    protected BinaryFunction(final Expression<? super R, ? extends V1> expression1,
-                             final Expression<? super R, ? extends V2> expression2)
+    protected BinaryFunction(final Expression<R, ? extends V1> expression1,
+                             final Expression<R, ? extends V2> expression2)
     {
         ArgumentChecks.ensureNonNull("expression1", expression1);
         ArgumentChecks.ensureNonNull("expression2", expression2);
@@ -88,7 +88,7 @@ abstract class BinaryFunction<R,V1,V2> extends Node {
      * Returns the expressions used as parameters by this function.
      * Defined for {@link Expression#getParameters()} implementations.
      */
-    public final List<Expression<? super R, ?>> getParameters() {
+    public final List<Expression<R,?>> getParameters() {
         return getExpressions();
     }
 
@@ -98,7 +98,7 @@ abstract class BinaryFunction<R,V1,V2> extends Node {
      *
      * @return a list of size 2 containing the two expressions.
      */
-    public List<Expression<? super R, ?>> getExpressions() {
+    public List<Expression<R,?>> getExpressions() {
         return List.of(expression1, expression2);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
index b936b9d522..f668f78886 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -48,7 +48,7 @@ import org.opengis.feature.PropertyNotFoundException;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -67,7 +67,7 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
      * @see BinarySpatialOperator#getOperand1()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected final Expression<? super R, GeometryWrapper<G>> expression1;
+    protected final Expression<R, GeometryWrapper<G>> expression1;
 
     /**
      * The second of the two expressions to be used by this function.
@@ -75,7 +75,7 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
      * @see BinarySpatialOperator#getOperand2()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected final Expression<? super R, GeometryWrapper<G>> expression2;
+    protected final Expression<R, GeometryWrapper<G>> expression2;
 
     /**
      * The preferred CRS and other context to use if geometry transformations are needed.
@@ -90,13 +90,13 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
      * @param  systemUnit  if the CRS needs to be in some units of measurement, the {@link Unit#getSystemUnit()} value.
      */
     protected BinaryGeometryFilter(final Geometries<G> library,
-                                   final Expression<? super R, ?> geometry1,
-                                   final Expression<? super R, ?> geometry2,
+                                   final Expression<R,?> geometry1,
+                                   final Expression<R,?> geometry2,
                                    final Unit<?> systemUnit)
     {
         ArgumentChecks.ensureNonNull("expression1", geometry1);
         ArgumentChecks.ensureNonNull("expression2", geometry2);
-        Expression<? super R, GeometryWrapper<G>> expression1, expression2;
+        Expression<R, GeometryWrapper<G>> expression1, expression2;
         expression1 = toGeometryWrapper(library, geometry1);
         expression2 = toGeometryWrapper(library, geometry2);
         /*
@@ -105,14 +105,14 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
          * Otherwise the CRS will be selected on a case-by-case basis at evaluation time.
          */
         final int index;
-        final Literal<? super R, ?> literal;
+        final Literal<R,?> literal;
         final GeometryWrapper<G> value;
         if (geometry2 instanceof Literal<?,?>) {
-            literal = (Literal<? super R, ?>) geometry2;
+            literal = (Literal<R,?>) geometry2;
             value   = expression2.apply(null);
             index   = 1;
         } else if (geometry1 instanceof Literal<?,?>) {
-            literal = (Literal<? super R, ?>) geometry1;
+            literal = (Literal<R,?>) geometry1;
             value   = expression1.apply(null);
             index   = 0;
         } else {
@@ -125,7 +125,7 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
             if (value != null) {
                 final GeometryWrapper<G> gt = context.transform(value);
                 if (gt != value) {
-                    final Expression<? super R, GeometryWrapper<G>> tr = new LeafExpression.Transformed<>(gt, literal);
+                    final Expression<R, GeometryWrapper<G>> tr = new LeafExpression.Transformed<>(gt, literal);
                     switch (index) {
                         case 0:  expression1 = tr; break;
                         case 1:  expression2 = tr; break;
@@ -145,8 +145,8 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
      * This method is invoked when it is possible to simplify or optimize at least one of the expressions that
      * were given in the original call to the constructor.
      */
-    protected abstract BinaryGeometryFilter<R,G> recreate(final Expression<? super R, ?> geometry1,
-                                                          final Expression<? super R, ?> geometry2);
+    protected abstract BinaryGeometryFilter<R,G> recreate(final Expression<R,?> geometry1,
+                                                          final Expression<R,?> geometry2);
 
     /**
      * Returns the original expression specified by the user.
@@ -156,11 +156,10 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
      * @param  expression  the expression to unwrap.
      * @return the unwrapped expression.
      */
-    @SuppressWarnings("unchecked")      // We replace <? super ? super R> by <? super R>.
-    protected static <R,G> Expression<? super R, ?> original(final Expression<R, GeometryWrapper<G>> expression) {
-        Expression<? super R, ?> unwrapped = unwrap(expression);
-        if (unwrapped instanceof LeafExpression.Transformed<?, ?>) {
-            unwrapped = ((LeafExpression.Transformed<R, ?>) unwrapped).original;
+    protected static <R,G> Expression<R,?> original(final Expression<R, GeometryWrapper<G>> expression) {
+        Expression<R,?> unwrapped = unwrap(expression);
+        if (unwrapped instanceof LeafExpression.Transformed<?,?>) {
+            unwrapped = ((LeafExpression.Transformed<R,?>) unwrapped).original;
         }
         return unwrapped;
     }
@@ -169,7 +168,7 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
      * Returns the two expressions used as parameters by this filter.
      */
     @Override
-    public List<Expression<? super R, ?>> getExpressions() {
+    public List<Expression<R,?>> getExpressions() {
         return List.of(original(expression1), original(expression2));
     }
 
@@ -179,25 +178,25 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
      * is a literal and returns {@code null}, then the result is known in advance too.
      */
     @Override
-    public final Filter<? super R> optimize(final Optimization optimization) {
-        Expression<? super R, ?> geometry1  = unwrap(expression1);
-        Expression<? super R, ?> geometry2  = unwrap(expression2);
-        Expression<? super R, ?> effective1 = optimization.apply(geometry1);
-        Expression<? super R, ?> effective2 = optimization.apply(geometry2);
-        Expression<? super R, ?> other;     // The expression which is not literal.
-        Expression<? super R, GeometryWrapper<G>> wrapper;
-        Literal<? super R, ?> literal;
+    public final Filter<R> optimize(final Optimization optimization) {
+        Expression<R,?> geometry1  = unwrap(expression1);
+        Expression<R,?> geometry2  = unwrap(expression2);
+        Expression<R,?> effective1 = optimization.apply(geometry1);
+        Expression<R,?> effective2 = optimization.apply(geometry2);
+        Expression<R,?> other;     // The expression which is not literal.
+        Expression<R, GeometryWrapper<G>> wrapper;
+        Literal<R,?> literal;
         boolean immediate;                  // true if the filter should be evaluated immediately.
         boolean literalIsNull;              // true if one of the literal value is null.
         if (effective2 instanceof Literal<?,?>) {
             other     = effective1;
             wrapper   = expression2;
-            literal   = (Literal<? super R, ?>) effective2;
+            literal   = (Literal<R,?>) effective2;
             immediate = (effective1 instanceof Literal<?,?>);
         } else if (effective1 instanceof Literal<?,?>) {
             other     = effective2;
             wrapper   = expression1;
-            literal   = (Literal<? super R, ?>) effective1;
+            literal   = (Literal<R,?>) effective1;
             immediate = false;
         } else {
             return this;
@@ -233,7 +232,7 @@ abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Spatia
              * If one of the "effective" parameter has been modified, recreate a new filter.
              * If all operands are literal, we can evaluate that filter immediately.
              */
-            Filter<? super R> filter = this;
+            Filter<R> filter = this;
             if ((effective1 != geometry1) || (effective2 != geometry2)) {
                 filter = recreate(effective1, effective2);
             }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java
index 53de6c6039..c9d7795038 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java
@@ -38,7 +38,7 @@ import org.opengis.filter.BinarySpatialOperator;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -64,7 +64,7 @@ final class BinarySpatialFilter<R,G> extends BinaryGeometryFilter<R,G> implement
      * @param  bounds    the bounds to check geometry against.
      * @return a filter checking for interactions of the bounding boxes.
      */
-    BinarySpatialFilter(final Geometries<G> library, final Expression<? super R, ?> geometry,
+    BinarySpatialFilter(final Geometries<G> library, final Expression<R,?> geometry,
                         final Envelope bounds, final WraparoundMethod wraparound)
     {
         super(library, geometry, new LeafExpression.Transformed<>(library.toGeometry2D(bounds, wraparound),
@@ -83,8 +83,8 @@ final class BinarySpatialFilter<R,G> extends BinaryGeometryFilter<R,G> implement
      */
     BinarySpatialFilter(final SpatialOperatorName operatorType,
                         final Geometries<G> library,
-                        final Expression<? super R, ?> geometry1,
-                        final Expression<? super R, ?> geometry2)
+                        final Expression<R,?> geometry1,
+                        final Expression<R,?> geometry2)
     {
         super(library, geometry1, geometry2, null);
         this.operatorType = operatorType;
@@ -97,8 +97,8 @@ final class BinarySpatialFilter<R,G> extends BinaryGeometryFilter<R,G> implement
      * were given in the original call to the constructor.
      */
     @Override
-    protected BinaryGeometryFilter<R,G> recreate(final Expression<? super R, ?> geometry1,
-                                                 final Expression<? super R, ?> geometry2)
+    protected BinaryGeometryFilter<R,G> recreate(final Expression<R,?> geometry1,
+                                                 final Expression<R,?> geometry2)
     {
         return new BinarySpatialFilter<>(operatorType, getGeometryLibrary(expression1), geometry1, geometry2);
     }
@@ -115,7 +115,7 @@ final class BinarySpatialFilter<R,G> extends BinaryGeometryFilter<R,G> implement
      * Returns the first expression to be evaluated.
      */
     @Override
-    public Expression<? super R, ?> getOperand1() {
+    public Expression<R,?> getOperand1() {
         return original(expression1);
     }
 
@@ -123,7 +123,7 @@ final class BinarySpatialFilter<R,G> extends BinaryGeometryFilter<R,G> implement
      * Returns the second expression to be evaluated.
      */
     @Override
-    public Expression<? super R, ?> getOperand2() {
+    public Expression<R,?> getOperand2() {
         return original(expression2);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
index 5e43ed87d3..1853662fe7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
@@ -67,7 +67,7 @@ import org.opengis.filter.BetweenComparisonOperator;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  *
@@ -100,8 +100,8 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
      * @param  isMatchingCase  specifies whether comparisons are case sensitive.
      * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      */
-    ComparisonFilter(final Expression<? super R, ?> expression1,
-                     final Expression<? super R, ?> expression2,
+    ComparisonFilter(final Expression<R,?> expression1,
+                     final Expression<R,?> expression2,
                      final boolean isMatchingCase, final MatchAction matchAction)
     {
         super(expression1, expression2);
@@ -115,7 +115,7 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
      * This is the element at index 0 in the {@linkplain #getExpressions() list of expressions}.
      */
     @Override
-    public final Expression<? super R, ?> getOperand1() {
+    public final Expression<R,?> getOperand1() {
         return expression1;
     }
 
@@ -124,7 +124,7 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
      * This is the element at index 1 in the {@linkplain #getExpressions() list of expressions}.
      */
     @Override
-    public final Expression<? super R, ?> getOperand2() {
+    public final Expression<R,?> getOperand2() {
         return expression2;
     }
 
@@ -545,15 +545,15 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         private static final long serialVersionUID = 6126039112844823196L;
 
         /** Creates a new filter. */
-        LessThan(final Expression<? super R, ?> expression1,
-                 final Expression<? super R, ?> expression2,
+        LessThan(final Expression<R,?> expression1,
+                 final Expression<R,?> expression2,
                  boolean isMatchingCase, MatchAction matchAction)
         {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new LessThan<>(effective[0], effective[1], isMatchingCase, matchAction);
         }
 
@@ -587,15 +587,15 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         private static final long serialVersionUID = 6357459227911760871L;
 
         /** Creates a new filter. */
-        LessThanOrEqualTo(final Expression<? super R, ?> expression1,
-                          final Expression<? super R, ?> expression2,
+        LessThanOrEqualTo(final Expression<R,?> expression1,
+                          final Expression<R,?> expression2,
                           boolean isMatchingCase, MatchAction matchAction)
         {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new LessThanOrEqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
         }
 
@@ -629,15 +629,15 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         private static final long serialVersionUID = 8605517892232632586L;
 
         /** Creates a new filter. */
-        GreaterThan(final Expression<? super R, ?> expression1,
-                    final Expression<? super R, ?> expression2,
+        GreaterThan(final Expression<R,?> expression1,
+                    final Expression<R,?> expression2,
                     boolean isMatchingCase, MatchAction matchAction)
         {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new GreaterThan<>(effective[0], effective[1], isMatchingCase, matchAction);
         }
 
@@ -671,15 +671,15 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         private static final long serialVersionUID = 1514185657159141882L;
 
         /** Creates a new filter. */
-        GreaterThanOrEqualTo(final Expression<? super R, ?> expression1,
-                             final Expression<? super R, ?> expression2,
+        GreaterThanOrEqualTo(final Expression<R,?> expression1,
+                             final Expression<R,?> expression2,
                              boolean isMatchingCase, MatchAction matchAction)
         {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new GreaterThanOrEqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
         }
 
@@ -713,15 +713,15 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         private static final long serialVersionUID = 8502612221498749667L;
 
         /** Creates a new filter. */
-        EqualTo(final Expression<? super R, ?> expression1,
-                final Expression<? super R, ?> expression2,
+        EqualTo(final Expression<R,?> expression1,
+                final Expression<R,?> expression2,
                 boolean isMatchingCase, MatchAction matchAction)
         {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new EqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
         }
 
@@ -755,15 +755,15 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         private static final long serialVersionUID = -3295957142249035362L;
 
         /** Creates a new filter. */
-        NotEqualTo(final Expression<? super R, ?> expression1,
-                   final Expression<? super R, ?> expression2,
+        NotEqualTo(final Expression<R,?> expression1,
+                   final Expression<R,?> expression2,
                    boolean isMatchingCase, MatchAction matchAction)
         {
             super(expression1, expression2, isMatchingCase, matchAction);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new NotEqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
         }
 
@@ -800,13 +800,13 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         /** For cross-version compatibility during (de)serialization. */
         private static final long serialVersionUID = -2434954008425799595L;
 
-        /** The first  operation to apply. */ private final GreaterThanOrEqualTo<? super R> lower;
-        /** The second operation to apply. */ private final LessThanOrEqualTo<? super R> upper;
+        /** The first  operation to apply. */ private final GreaterThanOrEqualTo<R> lower;
+        /** The second operation to apply. */ private final LessThanOrEqualTo<R> upper;
 
         /** Creates a new filter. */
-        Between(final Expression<? super R, ?> expression,
-                final Expression<? super R, ?> lower,
-                final Expression<? super R, ?> upper)
+        Between(final Expression<R,?> expression,
+                final Expression<R,?> lower,
+                final Expression<R,?> upper)
         {
             this.lower = new GreaterThanOrEqualTo<>(expression, lower, true, MatchAction.ANY);
             this.upper = new    LessThanOrEqualTo<>(expression, upper, true, MatchAction.ANY);
@@ -821,14 +821,14 @@ abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         }
 
         /** Returns the expression to be compared by this operator, together with boundaries. */
-        @Override public List<Expression<? super R, ?>> getExpressions() {
+        @Override public List<Expression<R,?>> getExpressions() {
             return List.of(lower.expression1, lower.expression2, upper.expression2);
         }
 
         /** Returns the expression to be compared. */
-        @Override public Expression<? super R, ?> getExpression()    {return lower.expression1;}
-        @Override public Expression<? super R, ?> getLowerBoundary() {return lower.expression2;}
-        @Override public Expression<? super R, ?> getUpperBoundary() {return upper.expression2;}
+        @Override public Expression<R,?> getExpression()    {return lower.expression1;}
+        @Override public Expression<R,?> getLowerBoundary() {return lower.expression2;}
+        @Override public Expression<R,?> getUpperBoundary() {return upper.expression2;}
 
         /** Executes the filter operation. */
         @Override public boolean test(final R object) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
index 43f6f8eb4b..3095f4cb58 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
@@ -37,7 +37,7 @@ import org.opengis.feature.FeatureType;
  * Expression whose results are converted to a different type.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <S>  the type of value computed by the wrapped exception. This is the type to convert.
@@ -74,7 +74,7 @@ final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
      * @param  target      the desired type for the expression result.
      * @throws UnconvertibleObjectException if no converter is found.
      */
-    ConvertFunction(final Expression<? super R, ? extends S> expression, final Class<S> source, final Class<V> target) {
+    ConvertFunction(final Expression<R, ? extends S> expression, final Class<S> source, final Class<V> target) {
         super(expression);
         converter = ObjectConverters.find(source, target);
     }
@@ -85,7 +85,7 @@ final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
      * @param  expression  the expression providing source values.
      * @throws UnconvertibleObjectException if no converter is found.
      */
-    private ConvertFunction(final ConvertFunction<R,S,V> original, final Expression<? super R, ? extends S> expression) {
+    private ConvertFunction(final ConvertFunction<R,S,V> original, final Expression<R, ? extends S> expression) {
         super(expression);
         converter = original.converter;
     }
@@ -95,8 +95,8 @@ final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
      */
     @Override
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public Expression<R,V> recreate(Expression<? super R, ?>[] effective) {
-        final Expression<? super R, ?> e = effective[0];
+    public Expression<R,V> recreate(Expression<R,?>[] effective) {
+        final Expression<R,?> e = effective[0];
         if (e instanceof FeatureExpression<?,?>) {
             final Class<? extends V> target = getValueClass();                          // This is <V>.
             final Class<?> source = ((FeatureExpression<?,?>) e).getValueClass();       // May become <S>.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
index 7b2b90bdf4..5f381105b0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
@@ -48,7 +48,7 @@ import org.opengis.filter.capability.FilterCapabilities;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) to use as inputs.
  * @param  <G>  base class of geometry objects. The implementation-neutral type is GeoAPI {@link Geometry},
@@ -258,8 +258,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
     @Override
-    public BinaryComparisonOperator<R> equal(final Expression<? super R, ?> expression1,
-                                             final Expression<? super R, ?> expression2,
+    public BinaryComparisonOperator<R> equal(final Expression<R,?> expression1,
+                                             final Expression<R,?> expression2,
                                              boolean isMatchingCase, MatchAction matchAction)
     {
         return new ComparisonFilter.EqualTo<>(expression1, expression2, isMatchingCase, matchAction);
@@ -278,8 +278,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
     @Override
-    public BinaryComparisonOperator<R> notEqual(final Expression<? super R, ?> expression1,
-                                                final Expression<? super R, ?> expression2,
+    public BinaryComparisonOperator<R> notEqual(final Expression<R,?> expression1,
+                                                final Expression<R,?> expression2,
                                                 boolean isMatchingCase, MatchAction matchAction)
     {
         return new ComparisonFilter.NotEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
@@ -298,8 +298,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
     @Override
-    public BinaryComparisonOperator<R> less(final Expression<? super R, ?> expression1,
-                                            final Expression<? super R, ?> expression2,
+    public BinaryComparisonOperator<R> less(final Expression<R,?> expression1,
+                                            final Expression<R,?> expression2,
                                             boolean isMatchingCase, MatchAction matchAction)
     {
         return new ComparisonFilter.LessThan<>(expression1, expression2, isMatchingCase, matchAction);
@@ -318,8 +318,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
     @Override
-    public BinaryComparisonOperator<R> greater(final Expression<? super R, ?> expression1,
-                                               final Expression<? super R, ?> expression2,
+    public BinaryComparisonOperator<R> greater(final Expression<R,?> expression1,
+                                               final Expression<R,?> expression2,
                                                boolean isMatchingCase, MatchAction matchAction)
     {
         return new ComparisonFilter.GreaterThan<>(expression1, expression2, isMatchingCase, matchAction);
@@ -338,8 +338,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
     @Override
-    public BinaryComparisonOperator<R> lessOrEqual(final Expression<? super R, ?> expression1,
-                                                   final Expression<? super R, ?> expression2,
+    public BinaryComparisonOperator<R> lessOrEqual(final Expression<R,?> expression1,
+                                                   final Expression<R,?> expression2,
                                                    boolean isMatchingCase, MatchAction matchAction)
     {
         return new ComparisonFilter.LessThanOrEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
@@ -358,8 +358,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
     @Override
-    public BinaryComparisonOperator<R> greaterOrEqual(final Expression<? super R, ?> expression1,
-                                                      final Expression<? super R, ?> expression2,
+    public BinaryComparisonOperator<R> greaterOrEqual(final Expression<R,?> expression1,
+                                                      final Expression<R,?> expression2,
                                                       boolean isMatchingCase, MatchAction matchAction)
     {
         return new ComparisonFilter.GreaterThanOrEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
@@ -376,9 +376,9 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      *                       &amp; ({@code expression} ≤ {@code upperBoundary}).
      */
     @Override
-    public BetweenComparisonOperator<R> between(final Expression<? super R, ?> expression,
-                                                final Expression<? super R, ?> lowerBoundary,
-                                                final Expression<? super R, ?> upperBoundary)
+    public BetweenComparisonOperator<R> between(final Expression<R,?> expression,
+                                                final Expression<R,?> lowerBoundary,
+                                                final Expression<R,?> upperBoundary)
     {
         return new ComparisonFilter.Between<>(expression, lowerBoundary, upperBoundary);
     }
@@ -395,7 +395,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @return a character string comparison operator with pattern matching.
      */
     @Override
-    public LikeOperator<R> like(final Expression<? super R, ?> expression, final String pattern,
+    public LikeOperator<R> like(final Expression<R,?> expression, final String pattern,
             final char wildcard, final char singleChar, final char escape, final boolean isMatchingCase)
     {
         return new LikeFilter<>(expression, pattern, wildcard, singleChar, escape, isMatchingCase);
@@ -409,7 +409,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @return a filter that checks if an expression's value is {@code null}.
      */
     @Override
-    public NullOperator<R> isNull(final Expression<? super R, ?> expression) {
+    public NullOperator<R> isNull(final Expression<R,?> expression) {
         return new UnaryFunction.IsNull<>(expression);
     }
 
@@ -438,7 +438,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see org.apache.sis.xml.NilReason
      */
     @Override
-    public NilOperator<R> isNil(final Expression<? super R, ?> expression, final String nilReason) {
+    public NilOperator<R> isNil(final Expression<R,?> expression, final String nilReason) {
         return new UnaryFunction.IsNil<>(expression, nilReason);
     }
 
@@ -452,7 +452,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see LogicalOperatorName#AND
      */
     @Override
-    public LogicalOperator<R> and(final Filter<? super R> operand1, final Filter<? super R> operand2) {
+    public LogicalOperator<R> and(final Filter<R> operand1, final Filter<R> operand2) {
         ArgumentChecks.ensureNonNull("operand1", operand1);
         ArgumentChecks.ensureNonNull("operand2", operand2);
         return new LogicalFilter.And<>(operand1, operand2);
@@ -468,7 +468,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see LogicalOperatorName#AND
      */
     @Override
-    public LogicalOperator<R> and(final Collection<? extends Filter<? super R>> operands) {
+    public LogicalOperator<R> and(final Collection<? extends Filter<R>> operands) {
         return new LogicalFilter.And<>(operands);
     }
 
@@ -482,7 +482,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see LogicalOperatorName#OR
      */
     @Override
-    public LogicalOperator<R> or(final Filter<? super R> operand1, final Filter<? super R> operand2) {
+    public LogicalOperator<R> or(final Filter<R> operand1, final Filter<R> operand2) {
         ArgumentChecks.ensureNonNull("operand1", operand1);
         ArgumentChecks.ensureNonNull("operand2", operand2);
         return new LogicalFilter.Or<>(operand1, operand2);
@@ -498,7 +498,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see LogicalOperatorName#OR
      */
     @Override
-    public LogicalOperator<R> or(final Collection<? extends Filter<? super R>> operands) {
+    public LogicalOperator<R> or(final Collection<? extends Filter<R>> operands) {
         return new LogicalFilter.Or<>(operands);
     }
 
@@ -511,7 +511,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see LogicalOperatorName#NOT
      */
     @Override
-    public LogicalOperator<R> not(final Filter<? super R> operand) {
+    public LogicalOperator<R> not(final Filter<R> operand) {
         return new LogicalFilter.Not<>(operand);
     }
 
@@ -528,7 +528,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Maybe the expression parameterized type should extend {@link Geometry}.
      */
     @Override
-    public BinarySpatialOperator<R> bbox(final Expression<? super R, ? extends G> geometry, final Envelope bounds) {
+    public BinarySpatialOperator<R> bbox(final Expression<R, ? extends G> geometry, final Envelope bounds) {
         return new BinarySpatialFilter<>(library, geometry, bounds, wraparound);
     }
 
@@ -542,8 +542,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#EQUALS
      */
     @Override
-    public BinarySpatialOperator<R> equals(final Expression<? super R, ? extends G> geometry1,
-                                           final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> equals(final Expression<R, ? extends G> geometry1,
+                                           final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.EQUALS, library, geometry1, geometry2);
     }
@@ -558,8 +558,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#DISJOINT
      */
     @Override
-    public BinarySpatialOperator<R> disjoint(final Expression<? super R, ? extends G> geometry1,
-                                             final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> disjoint(final Expression<R, ? extends G> geometry1,
+                                             final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.DISJOINT, library, geometry1, geometry2);
     }
@@ -574,8 +574,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#INTERSECTS
      */
     @Override
-    public BinarySpatialOperator<R> intersects(final Expression<? super R, ? extends G> geometry1,
-                                               final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> intersects(final Expression<R, ? extends G> geometry1,
+                                               final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.INTERSECTS, library, geometry1, geometry2);
     }
@@ -590,8 +590,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#TOUCHES
      */
     @Override
-    public BinarySpatialOperator<R> touches(final Expression<? super R, ? extends G> geometry1,
-                                            final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> touches(final Expression<R, ? extends G> geometry1,
+                                            final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.TOUCHES, library, geometry1, geometry2);
     }
@@ -606,8 +606,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#CROSSES
      */
     @Override
-    public BinarySpatialOperator<R> crosses(final Expression<? super R, ? extends G> geometry1,
-                                            final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> crosses(final Expression<R, ? extends G> geometry1,
+                                            final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.CROSSES, library, geometry1, geometry2);
     }
@@ -623,8 +623,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#WITHIN
      */
     @Override
-    public BinarySpatialOperator<R> within(final Expression<? super R, ? extends G> geometry1,
-                                           final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> within(final Expression<R, ? extends G> geometry1,
+                                           final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.WITHIN, library, geometry1, geometry2);
     }
@@ -639,8 +639,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#CONTAINS
      */
     @Override
-    public BinarySpatialOperator<R> contains(final Expression<? super R, ? extends G> geometry1,
-                                             final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> contains(final Expression<R, ? extends G> geometry1,
+                                             final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.CONTAINS, library, geometry1, geometry2);
     }
@@ -656,8 +656,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see SpatialOperatorName#OVERLAPS
      */
     @Override
-    public BinarySpatialOperator<R> overlaps(final Expression<? super R, ? extends G> geometry1,
-                                             final Expression<? super R, ? extends G> geometry2)
+    public BinarySpatialOperator<R> overlaps(final Expression<R, ? extends G> geometry1,
+                                             final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.OVERLAPS, library, geometry1, geometry2);
     }
@@ -675,8 +675,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see DistanceOperatorName#BEYOND
      */
     @Override
-    public DistanceOperator<R> beyond(final Expression<? super R, ? extends G> geometry1,
-                                      final Expression<? super R, ? extends G> geometry2,
+    public DistanceOperator<R> beyond(final Expression<R, ? extends G> geometry1,
+                                      final Expression<R, ? extends G> geometry2,
                                       final Quantity<Length> distance)
     {
         return new DistanceFilter<>(DistanceOperatorName.BEYOND, library, geometry1, geometry2, distance);
@@ -695,8 +695,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see DistanceOperatorName#WITHIN
      */
     @Override
-    public DistanceOperator<R> within(final Expression<? super R, ? extends G> geometry1,
-                                      final Expression<? super R, ? extends G> geometry2,
+    public DistanceOperator<R> within(final Expression<R, ? extends G> geometry1,
+                                      final Expression<R, ? extends G> geometry2,
                                       final Quantity<Length> distance)
     {
         return new DistanceFilter<>(DistanceOperatorName.WITHIN, library, geometry1, geometry2, distance);
@@ -712,8 +712,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#AFTER
      */
     @Override
-    public TemporalOperator<R> after(final Expression<? super R, ? extends T> time1,
-                                     final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> after(final Expression<R, ? extends T> time1,
+                                     final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.After<>(time1, time2);
     }
@@ -728,8 +728,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#BEFORE
      */
     @Override
-    public TemporalOperator<R> before(final Expression<? super R, ? extends T> time1,
-                                      final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> before(final Expression<R, ? extends T> time1,
+                                      final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.Before<>(time1, time2);
     }
@@ -744,8 +744,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#BEGINS
      */
     @Override
-    public TemporalOperator<R> begins(final Expression<? super R, ? extends T> time1,
-                                      final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> begins(final Expression<R, ? extends T> time1,
+                                      final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.Begins<>(time1, time2);
     }
@@ -760,8 +760,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#BEGUN_BY
      */
     @Override
-    public TemporalOperator<R> begunBy(final Expression<? super R, ? extends T> time1,
-                                       final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> begunBy(final Expression<R, ? extends T> time1,
+                                       final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.BegunBy<>(time1, time2);
     }
@@ -776,8 +776,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#CONTAINS
      */
     @Override
-    public TemporalOperator<R> tcontains(final Expression<? super R, ? extends T> time1,
-                                         final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> tcontains(final Expression<R, ? extends T> time1,
+                                         final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.Contains<>(time1, time2);
     }
@@ -792,8 +792,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#DURING
      */
     @Override
-    public TemporalOperator<R> during(final Expression<? super R, ? extends T> time1,
-                                      final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> during(final Expression<R, ? extends T> time1,
+                                      final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.During<>(time1, time2);
     }
@@ -808,8 +808,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#EQUALS
      */
     @Override
-    public TemporalOperator<R> tequals(final Expression<? super R, ? extends T> time1,
-                                       final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> tequals(final Expression<R, ? extends T> time1,
+                                       final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.Equals<>(time1, time2);
     }
@@ -824,8 +824,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#OVERLAPS
      */
     @Override
-    public TemporalOperator<R> toverlaps(final Expression<? super R, ? extends T> time1,
-                                         final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> toverlaps(final Expression<R, ? extends T> time1,
+                                         final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.Overlaps<>(time1, time2);
     }
@@ -840,8 +840,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#MEETS
      */
     @Override
-    public TemporalOperator<R> meets(final Expression<? super R, ? extends T> time1,
-                                     final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> meets(final Expression<R, ? extends T> time1,
+                                     final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.Meets<>(time1, time2);
     }
@@ -856,8 +856,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#ENDS
      */
     @Override
-    public TemporalOperator<R> ends(final Expression<? super R, ? extends T> time1,
-                                    final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> ends(final Expression<R, ? extends T> time1,
+                                    final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.Ends<>(time1, time2);
     }
@@ -872,8 +872,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#OVERLAPPED_BY
      */
     @Override
-    public TemporalOperator<R> overlappedBy(final Expression<? super R, ? extends T> time1,
-                                            final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> overlappedBy(final Expression<R, ? extends T> time1,
+                                            final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.OverlappedBy<>(time1, time2);
     }
@@ -888,8 +888,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#MET_BY
      */
     @Override
-    public TemporalOperator<R> metBy(final Expression<? super R, ? extends T> time1,
-                                     final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> metBy(final Expression<R, ? extends T> time1,
+                                     final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.MetBy<>(time1, time2);
     }
@@ -904,8 +904,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#ENDED_BY
      */
     @Override
-    public TemporalOperator<R> endedBy(final Expression<? super R, ? extends T> time1,
-                                       final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> endedBy(final Expression<R, ? extends T> time1,
+                                       final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.EndedBy<>(time1, time2);
     }
@@ -921,8 +921,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @see TemporalOperatorName#ANY_INTERACTS
      */
     @Override
-    public TemporalOperator<R> anyInteracts(final Expression<? super R, ? extends T> time1,
-                                            final Expression<? super R, ? extends T> time2)
+    public TemporalOperator<R> anyInteracts(final Expression<R, ? extends T> time1,
+                                            final Expression<R, ? extends T> time2)
     {
         return new TemporalFilter.AnyInteracts<>(time1, time2);
     }
@@ -937,8 +937,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Should we really restrict the type to {@link Number}?
      */
     @Override
-    public Expression<R,Number> add(final Expression<? super R, ? extends Number> operand1,
-                                    final Expression<? super R, ? extends Number> operand2)
+    public Expression<R,Number> add(final Expression<R, ? extends Number> operand1,
+                                    final Expression<R, ? extends Number> operand2)
     {
         return new ArithmeticFunction.Add<>(operand1, operand2);
     }
@@ -953,8 +953,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Should we really restrict the type to {@link Number}?
      */
     @Override
-    public Expression<R,Number> subtract(final Expression<? super R, ? extends Number> operand1,
-                                         final Expression<? super R, ? extends Number> operand2)
+    public Expression<R,Number> subtract(final Expression<R, ? extends Number> operand1,
+                                         final Expression<R, ? extends Number> operand2)
     {
         return new ArithmeticFunction.Subtract<>(operand1, operand2);
     }
@@ -969,8 +969,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Should we really restrict the type to {@link Number}?
      */
     @Override
-    public Expression<R,Number> multiply(final Expression<? super R, ? extends Number> operand1,
-                                         final Expression<? super R, ? extends Number> operand2)
+    public Expression<R,Number> multiply(final Expression<R, ? extends Number> operand1,
+                                         final Expression<R, ? extends Number> operand2)
     {
         return new ArithmeticFunction.Multiply<>(operand1, operand2);
     }
@@ -985,8 +985,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @todo Should we really restrict the type to {@link Number}?
      */
     @Override
-    public Expression<R,Number> divide(final Expression<? super R, ? extends Number> operand1,
-                                       final Expression<? super R, ? extends Number> operand2)
+    public Expression<R,Number> divide(final Expression<R, ? extends Number> operand1,
+                                       final Expression<R, ? extends Number> operand2)
     {
         return new ArithmeticFunction.Divide<>(operand1, operand2);
     }
@@ -1002,7 +1002,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      *         or if the arguments are illegal for the specified function.
      */
     @Override
-    public Expression<R,?> function(final String name, Expression<? super R, ?>[] parameters) {
+    public Expression<R,?> function(final String name, Expression<R,?>[] parameters) {
         ArgumentChecks.ensureNonNull("name", name);
         ArgumentChecks.ensureNonNull("parameters", parameters);
         parameters = parameters.clone();
@@ -1046,7 +1046,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
      * @return definition of sort order of a property.
      */
     @Override
-    public SortProperty<R> sort(final ValueReference<? super R, ?> property, final SortOrder order) {
+    public SortProperty<R> sort(final ValueReference<R,?> property, final SortOrder order) {
         return new DefaultSortProperty<>(property, order);
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java
index 03c6e46c40..57e40f34c4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java
@@ -34,7 +34,7 @@ import org.opengis.filter.ValueReference;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (typically {@code Feature}) to sort.
  *
@@ -50,7 +50,7 @@ final class DefaultSortProperty<R> implements SortProperty<R>, Serializable {
      * The property on which to apply sorting.
      */
     @SuppressWarnings("serial")     // Most SIS implementations are serializable.
-    private final ValueReference<? super R, ?> property;
+    private final ValueReference<R,?> property;
 
     /**
      * Whether the sorting order is {@code ASCENDING} or {@code DESCENDING}.
@@ -64,7 +64,7 @@ final class DefaultSortProperty<R> implements SortProperty<R>, Serializable {
      * @param property  property on which to apply sorting.
      * @param order     the desired order: {@code ASCENDING} or {@code DESCENDING}.
      */
-    DefaultSortProperty(final ValueReference<? super R, ?> property, final SortOrder order) {
+    DefaultSortProperty(final ValueReference<R,?> property, final SortOrder order) {
         ArgumentChecks.ensureNonNull("property", property);
         ArgumentChecks.ensureNonNull("order",    order);
         this.property = property;
@@ -75,7 +75,7 @@ final class DefaultSortProperty<R> implements SortProperty<R>, Serializable {
      * Returns the property to sort by.
      */
     @Override
-    public ValueReference<? super R, ?> getValueReference() {
+    public ValueReference<R,?> getValueReference() {
         return property;
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
index 739e3988cf..404770b4cd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
@@ -43,7 +43,7 @@ import org.opengis.filter.DistanceOperatorName;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -78,8 +78,8 @@ final class DistanceFilter<R,G> extends BinaryGeometryFilter<R,G> implements Dis
      */
     DistanceFilter(final DistanceOperatorName operatorType,
                    final Geometries<G> library,
-                   final Expression<? super R, ?> geometry1,
-                   final Expression<? super R, ?> geometry2,
+                   final Expression<R,?> geometry1,
+                   final Expression<R,?> geometry2,
                    final Quantity<Length> distance)
     {
         super(library, geometry1, geometry2, distance.getUnit().getSystemUnit());
@@ -94,8 +94,8 @@ final class DistanceFilter<R,G> extends BinaryGeometryFilter<R,G> implements Dis
      * were given in the original call to the constructor.
      */
     @Override
-    protected BinaryGeometryFilter<R,G> recreate(final Expression<? super R, ?> geometry1,
-                                                 final Expression<? super R, ?> geometry2)
+    protected BinaryGeometryFilter<R,G> recreate(final Expression<R,?> geometry1,
+                                                 final Expression<R,?> geometry2)
     {
         return new DistanceFilter<>(operatorType, getGeometryLibrary(expression1), geometry1, geometry2, distance);
     }
@@ -112,7 +112,7 @@ final class DistanceFilter<R,G> extends BinaryGeometryFilter<R,G> implements Dis
      * Returns the two expressions used as parameters by this filter.
      */
     @Override
-    public List<Expression<? super R, ?>> getExpressions() {
+    public List<Expression<R,?>> getExpressions() {
         return List.of(original(expression1), original(expression2),
                        new LeafExpression.Literal<>(distance));
     }
@@ -141,11 +141,11 @@ final class DistanceFilter<R,G> extends BinaryGeometryFilter<R,G> implements Dis
      */
     @Override
     public Geometry getGeometry() {
-        final Literal<? super R, ? extends GeometryWrapper<G>> literal;
+        final Literal<R, ? extends GeometryWrapper<G>> literal;
         if (expression2 instanceof Literal<?,?>) {
-            literal = (Literal<? super R, ? extends GeometryWrapper<G>>) expression2;
+            literal = (Literal<R, ? extends GeometryWrapper<G>>) expression2;
         } else if (expression1 instanceof Literal<?,?>) {
-            literal = (Literal<? super R, ? extends GeometryWrapper<G>>) expression1;
+            literal = (Literal<R, ? extends GeometryWrapper<G>>) expression1;
         } else {
             throw new IllegalStateException();
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
index ac009fc61c..d5a8c25b85 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
@@ -33,7 +33,7 @@ import org.opengis.filter.Filter;
  * implementations implement the {@link Optimization.OnFilter} interface.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  *
@@ -51,6 +51,20 @@ abstract class FilterNode<R> extends Node implements Filter<R> {
     FilterNode() {
     }
 
+    /**
+     * If the given predicate can be casted to a filter of the same parameterized type as the template,
+     * returns {@code other} casted to that type. Otherwise returns {@code null}.
+     *
+     * @param  <R>       desired parameterized type.
+     * @param  template  the filter from which to get the runtime value of {@code <R>}.
+     * @param  other     the predicate to cast to a filter compatible with the target.
+     * @return the casted predicate, or {@code null} if it can not be casted.
+     */
+    static <R> Filter<R> castOrNull(final Filter<R> template, final Predicate<? super R> other) {
+        // TODO
+        return null;
+    }
+
     /**
      * Returns the {@code AND} logical operation between this filter and the given predicate.
      * This method duplicates the {@link Optimization.OnFilter#and(Predicate)} method, but is
@@ -58,8 +72,9 @@ abstract class FilterNode<R> extends Node implements Filter<R> {
      */
     @Override
     public final Predicate<R> and(final Predicate<? super R> other) {
-        if (other instanceof Filter<?>) {
-            return new LogicalFilter.And<>(this, (Filter<? super R>) other);
+        final Filter<R> filter = castOrNull(this, other);
+        if (filter != null) {
+            return new LogicalFilter.And<>(this, filter);
         } else {
             return Filter.super.and(other);
         }
@@ -72,8 +87,9 @@ abstract class FilterNode<R> extends Node implements Filter<R> {
      */
     @Override
     public final Predicate<R> or(final Predicate<? super R> other) {
-        if (other instanceof Filter<?>) {
-            return new LogicalFilter.Or<>(this, (Filter<? super R>) other);
+        final Filter<R> filter = castOrNull(this, other);
+        if (filter != null) {
+            return new LogicalFilter.Or<>(this, filter);
         } else {
             return Filter.super.and(other);
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
index 372ef74877..fbfe274224 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
@@ -33,7 +33,7 @@ import org.opengis.filter.ResourceId;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources used as inputs.
  *
@@ -70,7 +70,7 @@ final class IdentifierFilter<R extends Feature> extends FilterNode<R> implements
      * Returns the parameters of this filter.
      */
     @Override
-    public List<Expression<? super R, ?>> getExpressions() {
+    public List<Expression<R,?>> getExpressions() {
         return List.of(new LeafExpression.Literal<>(identifier));
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
index 1926511b5c..18e6864560 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
@@ -42,7 +42,7 @@ import org.opengis.filter.Expression;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <V>  the type of value computed by the expression.
@@ -66,7 +66,7 @@ abstract class LeafExpression<R,V> extends Node implements FeatureExpression<R,V
      * which is an empty list.
      */
     @Override
-    public final List<Expression<? super R, ?>> getParameters() {
+    public final List<Expression<R,?>> getParameters() {
         return List.of();
     }
 
@@ -200,7 +200,7 @@ abstract class LeafExpression<R,V> extends Node implements FeatureExpression<R,V
          * the transformed value will become visible to users.
          */
         @Override
-        public Expression<? super R, ? extends V> optimize(final Optimization optimization) {
+        public Expression<R, ? extends V> optimize(final Optimization optimization) {
             return Optimization.literal(getValue());
         }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
index 63ed1bbdf4..f4cb6bbc7d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
@@ -32,7 +32,7 @@ import org.opengis.filter.LikeOperator;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  *
@@ -48,7 +48,7 @@ final class LikeFilter<R> extends FilterNode<R> implements LikeOperator<R>, Opti
      * The source of values to compare against the pattern.
      */
     @SuppressWarnings("serial")                         // Most SIS implementations are serializable.
-    private final Expression<? super R, ?> expression;
+    private final Expression<R,?> expression;
 
     /**
      * The pattern to match against expression values. The {@link #wildcard}, {@link #singleChar}
@@ -99,7 +99,7 @@ final class LikeFilter<R> extends FilterNode<R> implements LikeOperator<R>, Opti
      * @param escape          pattern character for indicating that the next character should be matched literally.
      * @param isMatchingCase  specifies how a filter expression processor should perform string comparisons.
      */
-    LikeFilter(final Expression<? super R, ?> expression, final String pattern,
+    LikeFilter(final Expression<R,?> expression, final String pattern,
             final char wildcard, final char singleChar, final char escape, final boolean isMatchingCase)
     {
         ArgumentChecks.ensureNonNull("pattern", pattern);
@@ -143,7 +143,7 @@ final class LikeFilter<R> extends FilterNode<R> implements LikeOperator<R>, Opti
     /**
      * Creates a new filter of the same type but different parameters.
      */
-    private LikeFilter(final LikeFilter<R> original, final Expression<? super R, ?> expression) {
+    private LikeFilter(final LikeFilter<R> original, final Expression<R,?> expression) {
         this.expression     = expression;
         this.pattern        = original.pattern;
         this.wildcard       = original.wildcard;
@@ -157,7 +157,7 @@ final class LikeFilter<R> extends FilterNode<R> implements LikeOperator<R>, Opti
      * Creates a new filter of the same type but different parameters.
      */
     @Override
-    public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+    public Filter<R> recreate(final Expression<R,?>[] effective) {
         return new LikeFilter<>(this, effective[0]);
     }
 
@@ -174,7 +174,7 @@ final class LikeFilter<R> extends FilterNode<R> implements LikeOperator<R>, Opti
      * Returns the expression whose values will be compared by this operator, together with the pattern.
      */
     @Override
-    public List<Expression<? super R, ?>> getExpressions() {
+    public List<Expression<R,?>> getExpressions() {
         return List.of(expression, new LeafExpression.Literal<>(pattern));
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
index 2aeb42d671..4dee42760d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
@@ -35,7 +35,7 @@ import org.opengis.filter.LogicalOperatorName;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  *
@@ -51,7 +51,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
      * The filter on which to apply the logical operator.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected final Filter<? super R>[] operands;
+    protected final Filter<R>[] operands;
 
     /**
      * Creates a new logical operator applied on the given operands.
@@ -59,7 +59,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
      * @param  op  operands of the new operator.
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    LogicalFilter(final Collection<? extends Filter<? super R>> op) {
+    LogicalFilter(final Collection<? extends Filter<R>> op) {
         ArgumentChecks.ensureNonEmpty("operands", op);
         operands = op.toArray(Filter[]::new);
         ArgumentChecks.ensureCountBetween("operands", true, 2, Integer.MAX_VALUE, operands.length);
@@ -74,7 +74,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
      * this check should be already done by the caller.
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    LogicalFilter(final Filter<? super R> operand1, final Filter<? super R> operand2) {
+    LogicalFilter(final Filter<R> operand1, final Filter<R> operand2) {
         operands = new Filter[] {operand1, operand2};
     }
 
@@ -84,13 +84,13 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
      * @param  op  operands of the new operator.
      * @return the new operator.
      */
-    protected abstract LogicalFilter<R> createSameType(Collection<? extends Filter<? super R>> op);
+    protected abstract LogicalFilter<R> createSameType(Collection<? extends Filter<R>> op);
 
     /**
      * Returns a list containing all of the child filters of this object.
      */
     @Override
-    public final List<Filter<? super R>> getOperands() {
+    public final List<Filter<R>> getOperands() {
         return UnmodifiableArrayList.wrap(operands);
     }
 
@@ -114,17 +114,17 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
         private static final long serialVersionUID = 152892064260384713L;
 
         /** Creates a new operator for the given operands. */
-        And(final Collection<? extends Filter<? super R>> op) {
+        And(final Collection<? extends Filter<R>> op) {
             super(op);
         }
 
         /** Creates a new operator for the two given operands. */
-        And(final Filter<? super R> operand1, final Filter<? super R> operand2) {
+        And(final Filter<R> operand1, final Filter<R> operand2) {
             super(operand1, operand2);
         }
 
         /** Creates a new logical operator of the same kind than this operator. */
-        @Override protected LogicalFilter<R> createSameType(Collection<? extends Filter<? super R>> op) {
+        @Override protected LogicalFilter<R> createSameType(Collection<? extends Filter<R>> op) {
             return new And<>(op);
         }
 
@@ -140,7 +140,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
 
         /** Executes the logical operation. */
         @Override public boolean test(final R object) {
-            for (final Filter<? super R> filter : operands) {
+            for (final Filter<R> filter : operands) {
                 if (!filter.test(object)) {
                     return false;
                 }
@@ -149,7 +149,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
         }
 
         /** Tries to optimize this filter. */
-        @Override public Filter<? super R> optimize(final Optimization optimization) {
+        @Override public Filter<R> optimize(final Optimization optimization) {
             return optimize(optimization, Filter.include(), Filter.exclude());
         }
     }
@@ -165,17 +165,17 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
         private static final long serialVersionUID = 3805785720811330282L;
 
         /** Creates a new operator for the given operands. */
-        Or(final Collection<? extends Filter<? super R>> op) {
+        Or(final Collection<? extends Filter<R>> op) {
             super(op);
         }
 
         /** Creates a new operator for the two given operands. */
-        Or(final Filter<? super R> operand1, final Filter<? super R> operand2) {
+        Or(final Filter<R> operand1, final Filter<R> operand2) {
             super(operand1, operand2);
         }
 
         /** Creates a new logical operator of the same kind than this operator. */
-        @Override protected LogicalFilter<R> createSameType(Collection<? extends Filter<? super R>> op) {
+        @Override protected LogicalFilter<R> createSameType(Collection<? extends Filter<R>> op) {
             return new Or<>(op);
         }
 
@@ -191,7 +191,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
 
         /** Executes the logical operation. */
         @Override public boolean test(final R object) {
-            for (Filter<? super R> filter : operands) {
+            for (Filter<R> filter : operands) {
                 if (filter.test(object)) {
                     return true;
                 }
@@ -200,7 +200,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
         }
 
         /** Tries to optimize this filter. */
-        @Override public Filter<? super R> optimize(final Optimization optimization) {
+        @Override public Filter<R> optimize(final Optimization optimization) {
             return optimize(optimization, Filter.exclude(), Filter.include());
         }
     }
@@ -217,10 +217,10 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
 
         /** The filter to negate. */
         @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-        private final Filter<? super R> operand;
+        private final Filter<R> operand;
 
         /** Creates a new operator. */
-        Not(final Filter<? super R> operand) {
+        Not(final Filter<R> operand) {
             ArgumentChecks.ensureNonNull("operand", operand);
             this.operand = operand;
         }
@@ -241,7 +241,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
         }
 
         /** Returns the singleton filter used by this operation. */
-        @Override public List<Filter<? super R>> getOperands() {
+        @Override public List<Filter<R>> getOperands() {
             return List.of(operand);
         }
 
@@ -251,12 +251,12 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
         }
 
         /** Tries to optimize this filter. */
-        @Override public Filter<? super R> optimize(final Optimization optimization) {
-            final Filter<? super R> effective = optimization.apply(operand);
+        @Override public Filter<R> optimize(final Optimization optimization) {
+            final Filter<R> effective = optimization.apply(operand);
             if (effective == Filter.include()) return Filter.exclude();
             if (effective == Filter.exclude()) return Filter.include();
             if (effective instanceof Not<?>) {
-                return ((Not<? super R>) effective).operand;            // NOT(NOT(C)) == C
+                return ((Not<R>) effective).operand;            // NOT(NOT(C)) == C
             } else {
                 /*
                  * TODO:
@@ -277,13 +277,13 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
      * @param  ignore        the filter to ignore (literal "true" or "false").
      * @param  shortCircuit  the filter to use if found (literal "true" or "false").
      */
-    final Filter<? super R> optimize(final Optimization optimization,
+    final Filter<R> optimize(final Optimization optimization,
             final Filter<R> ignore, final Filter<R> shortCircuit)
     {
         boolean unchanged = true;               // Will be `false` if at least one simplification has been applied.
         final Class<?> inline = getClass();     // Filter class for which to expand operands in the optimized filter.
-        final Collection<Filter<? super R>> effective = new LinkedHashSet<>();
-        for (Filter<? super R> f : operands) {
+        final Collection<Filter<R>> effective = new LinkedHashSet<>();
+        for (Filter<R> f : operands) {
             unchanged &= (f == (f = optimization.apply(f)));
             if (f == ignore) {
                 unchanged = false;
@@ -293,7 +293,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
                 unchanged &= effective.add(f);
             } else {
                 unchanged = false;
-                for (Filter<? super R> s : ((LogicalFilter<? super R>) f).operands) {
+                for (Filter<R> s : ((LogicalFilter<R>) f).operands) {
                     if (f != ignore) {
                         if (f == shortCircuit) {
                             return shortCircuit;
@@ -312,7 +312,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
          *     A AND NOT(A) = FALSE
          *     A OR  NOT(A) = TRUE
          */
-        for (Filter<? super R> f : effective) {
+        for (Filter<R> f : effective) {
             if (LogicalOperatorName.NOT.equals(f.getOperatorType())) {
                 if (effective.containsAll(((LogicalOperator<?>) f).getOperands())) {
                     return shortCircuit;
@@ -329,7 +329,7 @@ abstract class LogicalFilter<R> extends FilterNode<R> implements LogicalOperator
         if (unchanged) {
             return this;
         }
-        final Filter<? super R> c = CollectionsExt.singletonOrNull(effective);
+        final Filter<R> c = CollectionsExt.singletonOrNull(effective);
         return (c != null) ? c : createSameType(effective);
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
index 6870fdceb4..d13ef64099 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
@@ -75,7 +75,7 @@ import org.opengis.feature.FeatureType;
  * those effects will disappear in the optimized filter.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public class Optimization {
@@ -142,7 +142,7 @@ public class Optimization {
      * @throws IllegalArgumentException if the given filter is already in process of being optimized
      *         (i.e. there is a recursive call to {@code apply(…)} for the same filter).
      */
-    public <R> Filter<? super R> apply(final Filter<R> filter) {
+    public <R> Filter<R> apply(final Filter<R> filter) {
         if (!(filter instanceof OnFilter<?>)) {
             return filter;
         }
@@ -156,7 +156,7 @@ public class Optimization {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.RecursiveCreateCallForKey_1, filter.getOperatorType()));
             }
             @SuppressWarnings("unchecked")
-            Filter<? super R> result = (Filter<? super R>) previous;
+            Filter<R> result = (Filter<R>) previous;
             if (result == null) {
                 result = ((OnFilter<R>) filter).optimize(this);
                 if (done.put(filter, result) != COMPUTING) {
@@ -195,14 +195,14 @@ public class Optimization {
          * @param  optimization  the simplifications or optimizations to apply on this filter.
          * @return the simplified or optimized filter, or {@code this} if no optimization has been applied.
          */
-        default Filter<? super R> optimize(final Optimization optimization) {
-            final List<Expression<? super R, ?>> expressions = getExpressions();
+        default Filter<R> optimize(final Optimization optimization) {
+            final List<Expression<R,?>> expressions = getExpressions();
             @SuppressWarnings({"unchecked", "rawtypes"})
-            final Expression<? super R, ?>[] effective = new Expression[expressions.size()];
+            final Expression<R,?>[] effective = new Expression[expressions.size()];
             boolean unchanged = true;       // Will be `false` if at least one optimization has been applied.
             boolean immediate = true;
             for (int i=0; i<effective.length; i++) {
-                Expression<? super R, ?> e = expressions.get(i);
+                Expression<R,?> e = expressions.get(i);
                 unchanged &= (e == (e = optimization.apply(e)));
                 immediate &= (e instanceof Literal<?,?>);
                 effective[i] = e;
@@ -227,7 +227,7 @@ public class Optimization {
          * @param  effective  the expressions to use as a replacement of this filter expressions.
          * @return the new filter, or {@code this} if unsupported.
          */
-        default Filter<R> recreate(Expression<? super R, ?>[] effective) {
+        default Filter<R> recreate(Expression<R,?>[] effective) {
             return this;
         }
 
@@ -243,8 +243,9 @@ public class Optimization {
          */
         @Override
         default Predicate<R> and(final Predicate<? super R> other) {
-            if (other instanceof Filter<?>) {
-                return new LogicalFilter.And<>(this, (Filter<? super R>) other);
+            final Filter<R> filter = FilterNode.castOrNull(this, other);
+            if (filter != null) {
+                return new LogicalFilter.And<>(this, filter);
             } else {
                 return Filter.super.and(other);
             }
@@ -262,8 +263,9 @@ public class Optimization {
          */
         @Override
         default Predicate<R> or(final Predicate<? super R> other) {
-            if (other instanceof Filter<?>) {
-                return new LogicalFilter.Or<>(this, (Filter<? super R>) other);
+            final Filter<R> filter = FilterNode.castOrNull(this, other);
+            if (filter != null) {
+                return new LogicalFilter.Or<>(this, filter);
             } else {
                 return Filter.super.and(other);
             }
@@ -294,7 +296,7 @@ public class Optimization {
      * @throws IllegalArgumentException if the given expression is already in process of being optimized
      *         (i.e. there is a recursive call to {@code apply(…)} for the same expression).
      */
-    public <R,V> Expression<? super R, ? extends V> apply(final Expression<R,V> expression) {
+    public <R,V> Expression<R, ? extends V> apply(final Expression<R,V> expression) {
         if (!(expression instanceof OnExpression<?,?>)) {
             return expression;
         }
@@ -308,7 +310,7 @@ public class Optimization {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.RecursiveCreateCallForKey_1, expression.getFunctionName()));
             }
             @SuppressWarnings("unchecked")
-            Expression<? super R, ? extends V> result = (Expression<? super R, ? extends V>) previous;
+            Expression<R, ? extends V> result = (Expression<R, ? extends V>) previous;
             if (result == null) {
                 result = ((OnExpression<R,V>) expression).optimize(this);
                 if (done.put(expression, result) != COMPUTING) {
@@ -348,14 +350,14 @@ public class Optimization {
          * @param  optimization  the simplifications or optimizations to apply on this expression.
          * @return the simplified or optimized expression, or {@code this} if no optimization has been applied.
          */
-        default Expression<? super R, ? extends V> optimize(final Optimization optimization) {
-            final List<Expression<? super R, ?>> parameters = getParameters();
+        default Expression<R, ? extends V> optimize(final Optimization optimization) {
+            final List<Expression<R,?>> parameters = getParameters();
             @SuppressWarnings({"unchecked", "rawtypes"})
-            final Expression<? super R, ?>[] effective = new Expression[parameters.size()];
+            final Expression<R,?>[] effective = new Expression[parameters.size()];
             boolean unchanged = true;       // Will be `false` if at least one optimization has been applied.
             boolean immediate = true;
             for (int i=0; i<effective.length; i++) {
-                Expression<? super R, ?> e = parameters.get(i);
+                Expression<R,?> e = parameters.get(i);
                 unchanged &= (e == (e = optimization.apply(e)));
                 immediate &= (e instanceof Literal<?,?>);
                 effective[i] = e;
@@ -380,7 +382,7 @@ public class Optimization {
          * @param  effective  the expressions to use as a replacement of this expression parameters.
          * @return the new expression, or {@code this} if unsupported.
          */
-        default Expression<R,V> recreate(Expression<? super R, ?>[] effective) {
+        default Expression<R,V> recreate(Expression<R,?>[] effective) {
             return this;
         }
     }
@@ -403,14 +405,8 @@ public class Optimization {
      * @throws ClassCastException if a filter declares the {@code AND}, {@code OR} or {@code NOT} type
      *         without implementing the {@link LogicalOperator} interface.
      */
-    @SuppressWarnings("unchecked")
-    public <R> List<Filter<? super R>> applyAndDecompose(final Filter<R> filter) {
-        /*
-         * This unsafe cast is okay if `toAndOperands(…)` do not invoke any `filter` method having a
-         * return value (directly or indirectly as list elements) restricted to the exact `<R>` type.
-         * Such methods do not exist in the GeoAPI interfaces, so we should be safe.
-         */
-        return toAndOperands((Filter<R>) apply(filter));
+    public <R> List<Filter<R>> applyAndDecompose(final Filter<R> filter) {
+        return toAndOperands(apply(filter));
     }
 
     /**
@@ -423,7 +419,7 @@ public class Optimization {
      * @throws ClassCastException if a filter declares the {@code AND}, {@code OR} or {@code NOT} type
      *         without implementing the {@link LogicalOperator} interface.
      */
-    private static <R> List<Filter<? super R>> toAndOperands(final Filter<R> filter) {
+    private static <R> List<Filter<R>> toAndOperands(final Filter<R> filter) {
         if (filter == null) {
             return List.of();
         }
@@ -432,17 +428,11 @@ public class Optimization {
             return ((LogicalOperator<R>) filter).getOperands();
         }
         if (type == LogicalOperatorName.NOT) {
-            final Filter<? super R> nop = getNotOperand(filter);
+            final Filter<R> nop = getNotOperand(filter);
             if (nop.getOperatorType() == LogicalOperatorName.OR) {
-                /*
-                 * The cast to `<R>` instead of `<? super R>` should be okay because we do not invoke
-                 * any method with a `<R>` return value. All invoked methods return `<? super R>`.
-                 * So what we actually have is a kind of `<? super ? super R>`.
-                 */
-                @SuppressWarnings("unchecked")
-                final List<Filter<? super R>> operands = ((LogicalOperator<R>) nop).getOperands();
-                final List<Filter<? super R>> result = new ArrayList<>(operands.size());
-                for (Filter<? super R> operand : operands) {
+                final List<Filter<R>> operands = ((LogicalOperator<R>) nop).getOperands();
+                final List<Filter<R>> result = new ArrayList<>(operands.size());
+                for (Filter<R> operand : operands) {
                     if (operand.getOperatorType() == LogicalOperatorName.NOT) {
                         operand = getNotOperand(operand);
                     } else {
@@ -464,8 +454,8 @@ public class Optimization {
      * @throws ClassCastException if the filter does not implement the {@link LogicalOperator} interface.
      * @throws IllegalArgumentException if the filter does not have a single operand.
      */
-    private static <R> Filter<? super R> getNotOperand(final Filter<R> filter) {
-        final Filter<? super R> operand = CollectionsExt.singletonOrNull(((LogicalOperator<R>) filter).getOperands());
+    private static <R> Filter<R> getNotOperand(final Filter<R> filter) {
+        final Filter<R> operand = CollectionsExt.singletonOrNull(((LogicalOperator<R>) filter).getOperands());
         if (operand != null) {
             return operand;
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java
index 3275898e2e..c38677e51c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java
@@ -40,7 +40,7 @@ import org.opengis.filter.TemporalOperatorName;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <T>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  *
@@ -60,8 +60,8 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
      * @param  expression1  the first of the two expressions to be used by this function.
      * @param  expression2  the second of the two expressions to be used by this function.
      */
-    TemporalFilter(final Expression<? super T, ?> expression1,
-                   final Expression<? super T, ?> expression2)
+    TemporalFilter(final Expression<T,?> expression1,
+                   final Expression<T,?> expression2)
     {
         super(expression1, expression2);
     }
@@ -218,14 +218,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = -6060822291802339424L;
 
         /** Creates a new filter. */
-        Equals(Expression<? super T, ?> expression1,
-               Expression<? super T, ?> expression2)
+        Equals(Expression<T,?> expression1,
+               Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new Equals<>(effective[0], effective[1]);
         }
 
@@ -273,14 +273,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = -3422629447456003982L;
 
         /** Creates a new filter. */
-        Before(Expression<? super T, ?> expression1,
-               Expression<? super T, ?> expression2)
+        Before(Expression<T,?> expression1,
+               Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new Before<>(effective[0], effective[1]);
         }
 
@@ -326,14 +326,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = 5410476260417497682L;
 
         /** Creates a new filter. */
-        After(Expression<? super T, ?> expression1,
-              Expression<? super T, ?> expression2)
+        After(Expression<T,?> expression1,
+              Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new After<>(effective[0], effective[1]);
         }
 
@@ -377,14 +377,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = -7880699329127762233L;
 
         /** Creates a new filter. */
-        Begins(Expression<? super T, ?> expression1,
-               Expression<? super T, ?> expression2)
+        Begins(Expression<T,?> expression1,
+               Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new Begins<>(effective[0], effective[1]);
         }
 
@@ -414,14 +414,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = -5508229966320563437L;
 
         /** Creates a new filter. */
-        Ends(Expression<? super T, ?> expression1,
-             Expression<? super T, ?> expression2)
+        Ends(Expression<T,?> expression1,
+             Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new Ends<>(effective[0], effective[1]);
         }
 
@@ -452,14 +452,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = -7212413827394364384L;
 
         /** Creates a new filter. */
-        BegunBy(Expression<? super T, ?> expression1,
-                Expression<? super T, ?> expression2)
+        BegunBy(Expression<T,?> expression1,
+                Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new BegunBy<>(effective[0], effective[1]);
         }
 
@@ -495,14 +495,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = 8586566103462153666L;
 
         /** Creates a new filter. */
-        EndedBy(Expression<? super T, ?> expression1,
-                Expression<? super T, ?> expression2)
+        EndedBy(Expression<T,?> expression1,
+                Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new EndedBy<>(effective[0], effective[1]);
         }
 
@@ -537,14 +537,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = -3534843269384858443L;
 
         /** Creates a new filter. */
-        Meets(Expression<? super T, ?> expression1,
-              Expression<? super T, ?> expression2)
+        Meets(Expression<T,?> expression1,
+              Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new Meets<>(effective[0], effective[1]);
         }
 
@@ -583,14 +583,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = 5358059498707330482L;
 
         /** Creates a new filter. */
-        MetBy(Expression<? super T, ?> expression1,
-              Expression<? super T, ?> expression2)
+        MetBy(Expression<T,?> expression1,
+              Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new MetBy<>(effective[0], effective[1]);
         }
 
@@ -629,14 +629,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = -4674319635076886196L;
 
         /** Creates a new filter. */
-        During(Expression<? super T, ?> expression1,
-               Expression<? super T, ?> expression2)
+        During(Expression<T,?> expression1,
+               Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new During<>(effective[0], effective[1]);
         }
 
@@ -672,14 +672,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = 9107531246948034411L;
 
         /** Creates a new filter. */
-        Contains(Expression<? super T, ?> expression1,
-                 Expression<? super T, ?> expression2)
+        Contains(Expression<T,?> expression1,
+                 Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new Contains<>(effective[0], effective[1]);
         }
 
@@ -720,14 +720,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = 1517443045593389773L;
 
         /** Creates a new filter. */
-        Overlaps(Expression<? super T, ?> expression1,
-                 Expression<? super T, ?> expression2)
+        Overlaps(Expression<T,?> expression1,
+                 Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new Overlaps<>(effective[0], effective[1]);
         }
 
@@ -760,14 +760,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = 2228673820507226463L;
 
         /** Creates a new filter. */
-        OverlappedBy(Expression<? super T, ?> expression1,
-                     Expression<? super T, ?> expression2)
+        OverlappedBy(Expression<T,?> expression1,
+                     Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new OverlappedBy<>(effective[0], effective[1]);
         }
 
@@ -798,14 +798,14 @@ abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
         private static final long serialVersionUID = 5972351564286442392L;
 
         /** Creates a new filter. */
-        AnyInteracts(Expression<? super T, ?> expression1,
-                     Expression<? super T, ?> expression2)
+        AnyInteracts(Expression<T,?> expression1,
+                     Expression<T,?> expression2)
         {
             super(expression1, expression2);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<T> recreate(final Expression<? super T, ?>[] effective) {
+        @Override public Filter<T> recreate(final Expression<T,?>[] effective) {
             return new AnyInteracts<>(effective[0], effective[1]);
         }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
index cb51eaa56d..4cc5327b6c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
@@ -36,7 +36,7 @@ import org.opengis.filter.NullOperator;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <V>  the type of value computed by the expression.
@@ -55,12 +55,12 @@ class UnaryFunction<R,V> extends Node {
      * @see #getExpression()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected final Expression<? super R, ? extends V> expression;
+    protected final Expression<R, ? extends V> expression;
 
     /**
      * Creates a new unary operator.
      */
-    UnaryFunction(final Expression<? super R, ? extends V> expression) {
+    UnaryFunction(final Expression<R, ? extends V> expression) {
         ArgumentChecks.ensureNonNull("expression", expression);
         this.expression = expression;
     }
@@ -69,7 +69,7 @@ class UnaryFunction<R,V> extends Node {
      * Returns the expression used as parameter by this function.
      * Defined for {@link Expression#getParameters()} implementations.
      */
-    public final List<Expression<? super R, ?>> getParameters() {
+    public final List<Expression<R,?>> getParameters() {
         return getExpressions();
     }
 
@@ -79,7 +79,7 @@ class UnaryFunction<R,V> extends Node {
      *
      * @return a list of size 1 containing the singleton expression.
      */
-    public final List<Expression<? super R, ?>> getExpressions() {
+    public final List<Expression<R,?>> getExpressions() {
         return List.of(expression);
     }
 
@@ -107,12 +107,12 @@ class UnaryFunction<R,V> extends Node {
         private static final long serialVersionUID = 2960285515924533419L;
 
         /** Creates a new operator. */
-        IsNull(final Expression<? super R,?> expression) {
+        IsNull(final Expression<R,?> expression) {
             super(expression);
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new IsNull<>(effective[0]);
         }
 
@@ -145,13 +145,13 @@ class UnaryFunction<R,V> extends Node {
         private final String nilReason;
 
         /** Creates a new operator. */
-        IsNil(final Expression<? super R,?> expression, final String nilReason) {
+        IsNil(final Expression<R,?> expression, final String nilReason) {
             super(expression);
             this.nilReason = nilReason;
         }
 
         /** Creates a new filter of the same type but different parameters. */
-        @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new IsNil<>(effective[0], nilReason);
         }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
index dcee614f32..2fa11028a1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
@@ -57,7 +57,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @since 1.1
  */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/CopyVisitor.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/CopyVisitor.java
index a02cf00328..4541529bfd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/CopyVisitor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/CopyVisitor.java
@@ -106,8 +106,8 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         BinaryComparisonOperator<R> create(
                 FilterFactory<R,?,?> factory,
-                Expression<? super R, ?> expression1,
-                Expression<? super R, ?> expression2,
+                Expression<R,?> expression1,
+                Expression<R,?> expression2,
                 boolean isMatchingCase, MatchAction matchAction);
     }
 
@@ -137,8 +137,8 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         TemporalOperator<R> create(
                 FilterFactory<R,?,T> factory,
-                Expression<? super R, ? extends T> time1,
-                Expression<? super R, ? extends T> time2);
+                Expression<R, ? extends T> time1,
+                Expression<R, ? extends T> time2);
     }
 
     /**
@@ -167,8 +167,8 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         BinarySpatialOperator<R> create(
                 FilterFactory<R,G,?> factory,
-                Expression<? super R, ? extends G> geometry1,
-                Expression<? super R, ? extends G> geometry2);
+                Expression<R, ? extends G> geometry1,
+                Expression<R, ? extends G> geometry2);
 
         /**
          * Bridge for the "bbox" operation, because it uses a different method signature.
@@ -189,8 +189,8 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         static <R,G> BinarySpatialOperator<R> bbox(
                 final FilterFactory<R,G,?> factory,
-                final Expression<? super R, ? extends G> geometry1,
-                final Expression<? super R, ? extends G> geometry2)
+                final Expression<R, ? extends G> geometry1,
+                final Expression<R, ? extends G> geometry2)
         {
             if (geometry2 instanceof Literal<?,?>) {
                 final Object bounds = ((Literal<?,?>) geometry2).getValue();
@@ -229,8 +229,8 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         DistanceOperator<R> create(
                 FilterFactory<R,G,?> factory,
-                Expression<? super R, ? extends G> geometry1,
-                Expression<? super R, ? extends G> geometry2,
+                Expression<R, ? extends G> geometry1,
+                Expression<R, ? extends G> geometry2,
                 Quantity<Length> distance);
     }
 
@@ -258,7 +258,7 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         LogicalOperator<R> create(
                 FilterFactory<R,?,?> factory,
-                Collection<? extends Filter<? super R>> operands);
+                Collection<? extends Filter<R>> operands);
 
         /**
          * Bridge for the "not" operation, because it uses a different method signature.
@@ -276,9 +276,9 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         static <R> LogicalOperator<R> not(
                 final FilterFactory<R,?,?> factory,
-                final Collection<? extends Filter<? super R>> operands)
+                final Collection<? extends Filter<R>> operands)
         {
-            Filter<? super R> op = CollectionsExt.singletonOrNull(operands);
+            Filter<R> op = CollectionsExt.singletonOrNull(operands);
             return (op != null) ? factory.not(op) : null;
         }
     }
@@ -308,8 +308,8 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
          */
         Expression<R,Number> create(
                 FilterFactory<R,?,?> factory,
-                Expression<? super R, ? extends Number> operand1,
-                Expression<? super R, ? extends Number> operand2);
+                Expression<R, ? extends Number> operand1,
+                Expression<R, ? extends Number> operand2);
     }
 
     /**
@@ -609,10 +609,10 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
      * @return the copies expressions, or {@code null} if no copy is needed.
      */
     @SuppressWarnings({"unchecked","rawtypes"})
-    private <V> List<Expression<TR,V>> copyExpressions(final List<Expression<? super SR, ?>> source) {
+    private <V> List<Expression<TR,V>> copyExpressions(final List<Expression<SR,?>> source) {
         final List<Object> results = new ArrayList<>(source.size());
-        for (final Expression<? super SR, ?> e : source) {
-            visit((Expression<SR,?>) e, results);
+        for (final Expression<SR,?> e : source) {
+            visit(e, results);
         }
         // Cast to <TR> is safe because of factory method signatures.
         return !forceNew && results.equals(source) ? null : (List) results;
@@ -628,10 +628,10 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
      * @return the copies filters, or {@code null} if no copy is needed.
      */
     @SuppressWarnings({"unchecked","rawtypes"})
-    private List<Filter<TR>> copyFilters(final List<Filter<? super SR>> source) {
+    private List<Filter<TR>> copyFilters(final List<Filter<SR>> source) {
         final var results = new ArrayList<Object>(source.size());
-        for (final Filter<? super SR> e : source) {
-            visit((Filter<SR>) e, results);
+        for (final Filter<SR> e : source) {
+            visit(e, results);
         }
         // Cast to <TR> is safe because of factory method signatures.
         return !forceNew && results.equals(source) ? null : (List) results;
@@ -702,7 +702,7 @@ public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
      */
     @Override
     @SuppressWarnings("unchecked")
-    protected void typeNotFound(final String name, Expression<SR, ?> expression, final List<Object> accumulator) {
+    protected void typeNotFound(final String name, Expression<SR,?> expression, final List<Object> accumulator) {
         var exps = copyExpressions(expression.getParameters());
         if (exps != null) {
             expression = factory.function(name, exps.toArray(Expression[]::new));
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java
index 426b490844..109a55da51 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java
@@ -32,7 +32,7 @@ import org.opengis.filter.Expression;
  * in any future Apache SIS version.</p>
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.0
  *
  * @see org.opengis.filter.FilterFactory#function(String, Expression...)
@@ -65,5 +65,5 @@ public interface FunctionRegister {
      * @return function for the given name and parameters.
      * @throws IllegalArgumentException if function name is unknown or some parameters are illegal.
      */
-    <R> Expression<R,?> create(String name, Expression<? super R, ?>[] parameters) throws IllegalArgumentException;
+    <R> Expression<R,?> create(String name, Expression<R,?>[] parameters) throws IllegalArgumentException;
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
index fc204d038a..edb9f26946 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
@@ -43,7 +43,7 @@ import org.opengis.filter.InvalidFilterValueException;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the geometry implementation type.
@@ -74,7 +74,7 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres
      * @see #getParameters()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final Expression<? super R, ?> expression;
+    final Expression<R,?> expression;
 
     /**
      * Creates a new converter expression.
@@ -82,7 +82,7 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres
      * @param  library     the geometry library to use.
      * @param  expression  the expression providing source values.
      */
-    public GeometryConverter(final Geometries<G> library, final Expression<? super R, ?> expression) {
+    public GeometryConverter(final Geometries<G> library, final Expression<R,?> expression) {
         ArgumentChecks.ensureNonNull("expression", expression);
         ArgumentChecks.ensureNonNull("library",    library);
         this.expression = expression;
@@ -94,7 +94,7 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R, GeometryWrapper<G>> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R, GeometryWrapper<G>> recreate(final Expression<R,?>[] effective) {
         return new GeometryConverter<>(library, effective[0]);
     }
 
@@ -111,7 +111,7 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres
      * This is the value specified at construction time.
      */
     @Override
-    public List<Expression<? super R, ?>> getParameters() {
+    public List<Expression<R,?>> getParameters() {
         return List.of(expression);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
index 014679a90d..f48299a752 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
@@ -178,7 +178,7 @@ public abstract class Node implements Serializable {
      * @param  expression  the expression to unwrap.
      * @return the unwrapped expression.
      */
-    protected static <R,G> Expression<? super R, ?> unwrap(final Expression<R, GeometryWrapper<G>> expression) {
+    protected static <R,G> Expression<R,?> unwrap(final Expression<R, GeometryWrapper<G>> expression) {
         if (expression instanceof GeometryConverter<?,?>) {
             return ((GeometryConverter<R,?>) expression).expression;
         } else {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java
index 42425aa7a1..8b0e9ae9be 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java
@@ -36,7 +36,7 @@ import org.opengis.filter.ValueReference;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (typically {@code Feature}) to sort.
  *
@@ -89,7 +89,7 @@ public final class SortByComparator<R> implements SortBy<R>, Serializable {
             case 0: return null;
             case 1: return new SortByComparator<>(properties[0]);
         }
-        final Map<ValueReference<? super R, ?>, SortProperty<R>> merged = new LinkedHashMap<>();
+        final Map<ValueReference<R,?>, SortProperty<R>> merged = new LinkedHashMap<>();
         addAll(Arrays.asList(properties), merged);
         return new SortByComparator<>(merged);
     }
@@ -137,7 +137,7 @@ public final class SortByComparator<R> implements SortBy<R>, Serializable {
      * @return concatenation of the two comparators.
      */
     public static <R> SortBy<R> concatenate(final SortBy<R> s1, final SortBy<R> s2) {
-        final Map<ValueReference<? super R, ?>, SortProperty<R>> merged = new LinkedHashMap<>();
+        final Map<ValueReference<R,?>, SortProperty<R>> merged = new LinkedHashMap<>();
         addAll(s1.getSortProperties(), merged);
         addAll(s2.getSortProperties(), merged);
         return new SortByComparator<>(merged);
@@ -149,7 +149,7 @@ public final class SortByComparator<R> implements SortBy<R>, Serializable {
      * then only the first occurrence is retained.
      */
     private static <R> void addAll(final List<SortProperty<R>> properties,
-            final Map<ValueReference<? super R, ?>, SortProperty<R>> merged)
+            final Map<ValueReference<R,?>, SortProperty<R>> merged)
     {
         final int size = properties.size();
         for (int i=0; i<size; i++) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java
index 2e71bfa311..60945dfea5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java
@@ -27,7 +27,6 @@ import org.apache.sis.internal.feature.Resources;
 // Branch-dependent imports
 import org.opengis.filter.Filter;
 import org.opengis.filter.Expression;
-import org.opengis.filter.LogicalOperator;
 import org.opengis.filter.LogicalOperatorName;
 import org.opengis.filter.SpatialOperatorName;
 import org.opengis.filter.DistanceOperatorName;
@@ -50,7 +49,7 @@ import org.opengis.filter.ComparisonOperatorName;
  * {@code Visitor} instances are thread-safe if protected methods are invoked at construction time only.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <A>  type of the accumulator object where actions will write their results.
@@ -260,8 +259,8 @@ public abstract class Visitor<R,A> {
      * Actions are registered by calls to {@code setFooHandler(…)} before the call to this {@code visit(…)} method.
      *
      * <h4>Note on parameterized type</h4>
-     * This method often needs to be invoked with instances of {@code Filter<? super R>},
-     * because this is the type of filters returned by GeoAPI methods such as {@link LogicalOperator#getOperands()}.
+     * This method sometimes needs to be invoked with instances of {@code Filter<? super R>},
+     * because this is the type of predicate expected by {@link java.util.function} and {@link java.util.stream}.
      * But the parameterized type expected by this method matches the parameterized type of handlers registered by
      * {@link #setFilterHandler(CodeList, BiConsumer)} and similar methods, which use the exact {@code <R>} type.
      * This restriction exists because when doing otherwise, parameterized types become hard to express in Java
@@ -291,20 +290,6 @@ public abstract class Visitor<R,A> {
      * Executes the registered action for the given expression.
      * Actions are registered by calls to {@code setFooHandler(…)} before the call to this {@code visit(…)} method.
      *
-     * <h4>Note on parameterized type</h4>
-     * This method often needs to be invoked with instances of {@code Expression<? super R, ?>},
-     * because this is the type of filters returned by GeoAPI methods such as {@link Expression#getParameters()}.
-     * But the parameterized type expected by this method matches the parameterized type of handlers registered by
-     * {@link #setExpressionHandler(String, BiConsumer)} and similar methods, which use the exact {@code <R>} type.
-     * This restriction exists because when doing otherwise, parameterized types become hard to express in Java
-     * (we get a cascade of {@code super} keywords, something like {@code <? super ? super R>}).
-     * However, doing the {@code (Expression<R>,?) expression} cast is actually safe if the handlers do not invoke
-     * any {@code expression} method having a return value (directly or indirectly as list elements) restricted to
-     * the exact {@code <R>} type. Such methods do not exist in the GeoAPI interfaces, so the cast is safe
-     * if the {@link BiConsumer} handlers do not invoke implementation-specific methods.
-     * Since only subclasses known the details of registered handlers,
-     * the decision to cast or not is left to those subclasses.
-     *
      * @param  expression   the expression for which to execute an action based on its type.
      * @param  accumulator  where to write the result of all actions.
      * @throws UnsupportedOperationException if there is no action registered for the given expression.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
index 8a7ed8afe3..daa6f702d8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
@@ -44,7 +44,7 @@ import org.opengis.filter.InvalidFilterValueException;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  *
@@ -60,7 +60,7 @@ abstract class FunctionWithSRID<R> extends SpatialFunction<R> {
      * The expression giving the spatial reference system identifier, or {@code null} if none.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final Expression<? super R, ?> srid;
+    final Expression<R,?> srid;
 
     /**
      * Identifier of the coordinate reference system in which to represent the geometry.
@@ -98,7 +98,7 @@ abstract class FunctionWithSRID<R> extends SpatialFunction<R> {
      * @todo The {@code MAYBE} flag could be removed if we know the type of value evaluated by the expression.
      *       For now it exists mostly because the last parameter given to {@code ST_Point} can be of various types.
      */
-    FunctionWithSRID(final SQLMM operation, final Expression<? super R, ?>[] parameters, int hasSRID) {
+    FunctionWithSRID(final SQLMM operation, final Expression<R,?>[] parameters, int hasSRID) {
         super(operation, parameters);
         if (hasSRID == MAYBE && parameters.length < operation.maxParamCount) {
             hasSRID = ABSENT;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
index 6f781e1cf6..a687d91527 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
@@ -35,7 +35,7 @@ import org.opengis.filter.InvalidFilterValueException;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -52,7 +52,7 @@ class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
      * The expression giving the geometry.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final Expression<? super R, ?> geometry;
+    final Expression<R,?> geometry;
 
     /**
      * The library to use for creating geometry objects.
@@ -62,7 +62,7 @@ class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
     /**
      * Creates a new function for the given parameters.
      */
-    GeometryConstructor(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    GeometryConstructor(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(operation, parameters, parameters.length >= operation.maxParamCount ? PRESENT : ABSENT);
         this.geometry = parameters[0];
         this.library  = library;
@@ -73,7 +73,7 @@ class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
         return new GeometryConstructor<>(operation, effective, getGeometryLibrary());
     }
 
@@ -81,7 +81,7 @@ class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
     @Override
-    public final List<Expression<? super R, ?>> getParameters() {
+    public final List<Expression<R,?>> getParameters() {
         return (srid != null) ? List.of(geometry, srid) : List.of(geometry);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java
index 29cfb13914..425f02a9c8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java
@@ -33,7 +33,7 @@ import org.opengis.filter.InvalidFilterValueException;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -49,7 +49,7 @@ abstract class GeometryParser<R,G> extends GeometryConstructor<R,G> {
     /**
      * Creates a new function for the given parameters.
      */
-    GeometryParser(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    GeometryParser(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(operation, parameters, library);
     }
 
@@ -58,7 +58,7 @@ abstract class GeometryParser<R,G> extends GeometryConstructor<R,G> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public abstract Expression<R,Object> recreate(final Expression<? super R, ?>[] effective);
+    public abstract Expression<R,Object> recreate(final Expression<R,?>[] effective);
 
     /**
      * Returns a Backus-Naur Form (BNF) of this function.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
index 50c299ca57..b1c7432548 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
@@ -31,7 +31,7 @@ import org.opengis.filter.Expression;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -48,12 +48,12 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
      * The expression giving the geometry.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final Expression<? super R, GeometryWrapper<G>> geometry;
+    final Expression<R, GeometryWrapper<G>> geometry;
 
     /**
      * Creates a new function for a geometry represented by the given parameter.
      */
-    OneGeometry(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    OneGeometry(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(operation, parameters);
         geometry = toGeometryWrapper(library, parameters[0]);
     }
@@ -63,7 +63,7 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
         return new OneGeometry<>(operation, effective, getGeometryLibrary());
     }
 
@@ -79,7 +79,7 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
     @Override
-    public List<Expression<? super R, ?>> getParameters() {
+    public List<Expression<R,?>> getParameters() {
         return List.of(unwrap(geometry));
     }
 
@@ -108,12 +108,12 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
          * The first argument after the geometry.
          */
         @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-        final Expression<? super R, ?> argument;
+        final Expression<R,?> argument;
 
         /**
          * Creates a new function for a geometry represented by the given parameter.
          */
-        WithArgument(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+        WithArgument(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
             super(operation, parameters, library);
             argument = parameters[1];
         }
@@ -123,7 +123,7 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
          * The optimization may be a geometry computed immediately if all operator parameters are literals.
          */
         @Override
-        public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+        public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
             return new WithArgument<>(operation, effective, getGeometryLibrary());
         }
 
@@ -131,7 +131,7 @@ class OneGeometry<R,G> extends SpatialFunction<R> {
          * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
          */
         @Override
-        public List<Expression<? super R, ?>> getParameters() {
+        public List<Expression<R,?>> getParameters() {
             return List.of(unwrap(geometry), argument);
         }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java
index 87cf874b2b..37371d0870 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java
@@ -32,7 +32,7 @@ import org.opengis.filter.Expression;
  * Information technology — Database languages — SQL multimedia and application packages — Part 3: Spatial</a>.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public final class Registry implements FunctionRegister {
@@ -78,7 +78,7 @@ public final class Registry implements FunctionRegister {
      * @throws IllegalArgumentException if function name is unknown or some parameters are illegal.
      */
     @Override
-    public <R> Expression<R,?> create(final String name, Expression<? super R, ?>[] parameters) {
+    public <R> Expression<R,?> create(final String name, Expression<R,?>[] parameters) {
         final SQLMM operation = SQLMM.valueOf(name);
         switch (operation) {
             case ST_PointFromWKB:       // Fallthrough
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java
index c79ad894fc..ef835b17c7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java
@@ -29,7 +29,7 @@ import org.opengis.filter.Expression;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -45,7 +45,7 @@ final class ST_FromBinary<R,G> extends GeometryParser<R,G> {
     /**
      * Creates a new function for the given parameters.
      */
-    ST_FromBinary(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    ST_FromBinary(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(operation, parameters, library);
     }
 
@@ -54,7 +54,7 @@ final class ST_FromBinary<R,G> extends GeometryParser<R,G> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
         return new ST_FromBinary<>(operation, effective, getGeometryLibrary());
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java
index 9f9cb1841f..e8f919dedc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java
@@ -28,7 +28,7 @@ import org.opengis.filter.Expression;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -44,7 +44,7 @@ final class ST_FromText<R,G> extends GeometryParser<R,G> {
     /**
      * Creates a new function for the given parameters.
      */
-    ST_FromText(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    ST_FromText(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(operation, parameters, library);
     }
 
@@ -53,7 +53,7 @@ final class ST_FromText<R,G> extends GeometryParser<R,G> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
         return new ST_FromText<>(operation, effective, getGeometryLibrary());
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
index 5be5872f8b..19d0e6ca91 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
@@ -45,7 +45,7 @@ import org.opengis.filter.InvalidFilterValueException;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -62,7 +62,7 @@ final class ST_Point<R,G> extends FunctionWithSRID<R> {
      * The expression giving the coordinate values. May include the SRID as last parameter.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private final Expression<? super R, ?>[] parameters;
+    private final Expression<R,?>[] parameters;
 
     /**
      * The library to use for creating geometry objects.
@@ -75,7 +75,7 @@ final class ST_Point<R,G> extends FunctionWithSRID<R> {
      *
      * @throws IllegalArgumentException if the number of arguments is less then two.
      */
-    ST_Point(final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    ST_Point(final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(SQLMM.ST_Point, parameters, MAYBE);
         this.parameters = parameters;
         this.library = library;
@@ -86,7 +86,7 @@ final class ST_Point<R,G> extends FunctionWithSRID<R> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
         return new ST_Point<>(effective, getGeometryLibrary());
     }
 
@@ -102,7 +102,7 @@ final class ST_Point<R,G> extends FunctionWithSRID<R> {
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
     @Override
-    public List<Expression<? super R, ?>> getParameters() {
+    public List<Expression<R,?>> getParameters() {
         return UnmodifiableArrayList.wrap(parameters);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
index 9861c9baf5..463b1e2660 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
@@ -58,7 +58,7 @@ import org.opengis.filter.InvalidFilterValueException;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 final class ST_Transform<R,G> extends FunctionWithSRID<R> {
@@ -71,7 +71,7 @@ final class ST_Transform<R,G> extends FunctionWithSRID<R> {
      * The expression giving the geometry.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private final Expression<? super R, GeometryWrapper<G>> geometry;
+    private final Expression<R, GeometryWrapper<G>> geometry;
 
     /**
      * Creates a new function with the given parameters. It is caller's responsibility to ensure
@@ -79,7 +79,7 @@ final class ST_Transform<R,G> extends FunctionWithSRID<R> {
      *
      * @throws InvalidFilterValueException if CRS cannot be constructed from the second expression.
      */
-    ST_Transform(final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    ST_Transform(final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(SQLMM.ST_Transform, parameters, PRESENT);
         geometry = toGeometryWrapper(library, parameters[0]);
     }
@@ -89,7 +89,7 @@ final class ST_Transform<R,G> extends FunctionWithSRID<R> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
         return new ST_Transform<>(effective, getGeometryLibrary());
     }
 
@@ -105,7 +105,7 @@ final class ST_Transform<R,G> extends FunctionWithSRID<R> {
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
     @Override
-    public List<Expression<? super R, ?>> getParameters() {
+    public List<Expression<R,?>> getParameters() {
         return List.of(unwrap(geometry), srid);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
index 9d46610846..0c3c2670c3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
@@ -82,7 +82,7 @@ abstract class SpatialFunction<R> extends Node implements FeatureExpression<R,Ob
      * @param  parameters  sub-expressions that will be evaluated to provide the parameters to the function.
      * @throws IllegalArgumentException if the number of parameters is not in the expected range.
      */
-    SpatialFunction(final SQLMM operation, final Expression<? super R, ?>[] parameters) {
+    SpatialFunction(final SQLMM operation, final Expression<R,?>[] parameters) {
         this.operation = operation;
         ArgumentChecks.ensureCountBetween("parameters", true,
                 operation.minParamCount, operation.maxParamCount, parameters.length);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
index 7b81f2c337..e394a62f31 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
@@ -37,7 +37,7 @@ import org.opengis.filter.ValueReference;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
@@ -54,12 +54,12 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
      * The expression giving the geometries.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final Expression<? super R, GeometryWrapper<G>> geometry1, geometry2;
+    final Expression<R, GeometryWrapper<G>> geometry1, geometry2;
 
     /**
      * Creates a new function for geometries represented by the given parameter.
      */
-    TwoGeometries(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+    TwoGeometries(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
         super(operation, parameters);
         geometry1 = toGeometryWrapper(library, parameters[0]);
         geometry2 = toGeometryWrapper(library, parameters[1]);
@@ -70,7 +70,7 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
      * The optimization may be a geometry computed immediately if all operator parameters are literals.
      */
     @Override
-    public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+    public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
         return new TwoGeometries<>(operation, effective, getGeometryLibrary());
     }
 
@@ -81,10 +81,10 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
      * executed in the CRS of the first argument.
      */
     @Override
-    public Expression<? super R, ?> optimize(final Optimization optimization) {
+    public Expression<R,?> optimize(final Optimization optimization) {
         final FeatureType featureType = optimization.getFeatureType();
         if (featureType != null) {
-            final Expression<? super R, ?> p1 = unwrap(geometry1);
+            final Expression<R,?> p1 = unwrap(geometry1);
             if (p1 instanceof ValueReference<?,?> && unwrap(geometry2) instanceof Literal<?,?>) try {
                 final CoordinateReferenceSystem targetCRS = AttributeConvention.getCRSCharacteristic(
                         featureType, featureType.getProperty(((ValueReference<?,?>) p1).getXPath()));
@@ -94,7 +94,7 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
                         final GeometryWrapper<G> tr = literal.transform(targetCRS);
                         if (tr != literal) {
                             @SuppressWarnings({"unchecked","rawtypes"})
-                            final Expression<? super R, ?>[] effective = getParameters().toArray(Expression[]::new);
+                            final Expression<R,?>[] effective = getParameters().toArray(Expression[]::new);
                             effective[1] = Optimization.literal(tr);
                             return recreate(effective);
                         }
@@ -119,7 +119,7 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
      * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
      */
     @Override
-    public List<Expression<? super R, ?>> getParameters() {
+    public List<Expression<R,?>> getParameters() {
         return List.of(unwrap(geometry1), unwrap(geometry2));
     }
 
@@ -153,12 +153,12 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
          * The first argument after the geometries.
          */
         @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-        final Expression<? super R, ?> argument;
+        final Expression<R,?> argument;
 
         /**
          * Creates a new function for geometries represented by the given parameter.
          */
-        WithArgument(final SQLMM operation, final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
+        WithArgument(final SQLMM operation, final Expression<R,?>[] parameters, final Geometries<G> library) {
             super(operation, parameters, library);
             argument = parameters[2];
         }
@@ -168,7 +168,7 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
          * The optimization may be a geometry computed immediately if all operator parameters are literals.
          */
         @Override
-        public Expression<R,Object> recreate(final Expression<? super R, ?>[] effective) {
+        public Expression<R,Object> recreate(final Expression<R,?>[] effective) {
             return new WithArgument<>(operation, effective, getGeometryLibrary());
         }
 
@@ -176,7 +176,7 @@ class TwoGeometries<R,G> extends SpatialFunction<R> {
          * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
          */
         @Override
-        public List<Expression<? super R, ?>> getParameters() {
+        public List<Expression<R,?>> getParameters() {
             return List.of(unwrap(geometry1), unwrap(geometry2), argument);
         }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/package-info.java
index 576b0eb55d..a2f59a3b4b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/package-info.java
@@ -31,7 +31,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   1.1
  */
 package org.apache.sis.internal.filter.sqlmm;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
index 69bf3408ee..9256708be1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
@@ -57,7 +57,7 @@ import org.opengis.filter.BinarySpatialOperator;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @param  <G> root class of geometry implementation.
  *
@@ -135,10 +135,10 @@ public abstract class BinarySpatialFilterTestCase<G> extends TestCase {
     @Test
     public void bbox_preserve_expression_type() {
         final BinarySpatialOperator<Feature> bbox = factory.bbox(literal(Polygon.RIGHT), new Envelope2D(null, 0, 0, 1, 1));
-        final Expression<? super Feature, ?> arg2 = bbox.getOperand2();
+        final Expression<Feature,?> arg2 = bbox.getOperand2();
         assertSame("The two ways to acquire the second argument return different values.", arg2, bbox.getExpressions().get(1));
         assertInstanceOf("Second argument value should be an envelope.", Envelope.class,
-                         ((Literal<? super Feature, ?>) arg2).getValue());
+                         ((Literal<Feature,?>) arg2).getValue());
     }
 
     /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
index 89fb8eb1b7..c48f45871c 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
@@ -42,7 +42,7 @@ import org.opengis.filter.LogicalOperator;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   1.1
  */
 public final class LogicalFilterTest extends TestCase {
@@ -107,8 +107,8 @@ public final class LogicalFilterTest extends TestCase {
      * @param  anyArity  the function creating a logical operator from an arbitrary number of operands.
      * @param  expected  expected evaluation result.
      */
-    private void create(final BiFunction<Filter<? super Feature>, Filter<? super Feature>, LogicalOperator<Feature>> binary,
-                        final Function<Collection<Filter<? super Feature>>, LogicalOperator<Feature>> anyArity,
+    private void create(final BiFunction<Filter<Feature>, Filter<Feature>, LogicalOperator<Feature>> binary,
+                        final Function<Collection<Filter<Feature>>, LogicalOperator<Feature>> anyArity,
                         final boolean expected)
     {
         final Filter<Feature> f1 = factory.isNull(factory.literal("text"));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java
index 99e8cf840d..ead5d8fd3e 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java
@@ -37,7 +37,7 @@ import org.opengis.filter.TemporalOperatorName;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public final class TemporalFilterTest extends TestCase {
@@ -78,7 +78,7 @@ public final class TemporalFilterTest extends TestCase {
     private void validate(final TemporalOperatorName name) {
         assertInstanceOf("Expected SIS implementation.", TemporalFilter.class, filter);
         assertEquals("name", name, filter.getOperatorType());
-        final List<Expression<? super Feature, ?>> operands = filter.getExpressions();
+        final List<Expression<Feature,?>> operands = filter.getExpressions();
         assertEquals(2, operands.size());
         assertSame("expression1", expression1, operands.get(0));
         assertSame("expression2", expression2, operands.get(1));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FilterFactoryMock.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FilterFactoryMock.java
index 195672c98c..df293521a6 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FilterFactoryMock.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FilterFactoryMock.java
@@ -66,7 +66,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      * Creates a dummy function with an arbitrary number of parameters.
      */
     @Override
-    public Expression<Map<String,?>, ?> function(String name, Expression<? super Map<String,?>, ?>[] parameters) {
+    public Expression<Map<String,?>, ?> function(String name, Expression<Map<String,?>, ?>[] parameters) {
         return new FunctionMock(name, Arrays.asList(parameters));
     }
 
@@ -101,8 +101,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinaryComparisonOperator<Map<String,?>> equal(
-            Expression<? super Map<String,?>, ?> expression1,
-            Expression<? super Map<String,?>, ?> expression2,
+            Expression<Map<String,?>, ?> expression1,
+            Expression<Map<String,?>, ?> expression2,
             boolean isMatchingCase, MatchAction matchAction)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -113,8 +113,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinaryComparisonOperator<Map<String,?>> notEqual(
-            Expression<? super Map<String,?>, ?> expression1,
-            Expression<? super Map<String,?>, ?> expression2,
+            Expression<Map<String,?>, ?> expression1,
+            Expression<Map<String,?>, ?> expression2,
             boolean isMatchingCase, MatchAction matchAction)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -125,8 +125,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinaryComparisonOperator<Map<String,?>> less(
-            Expression<? super Map<String,?>, ?> expression1,
-            Expression<? super Map<String,?>, ?> expression2,
+            Expression<Map<String,?>, ?> expression1,
+            Expression<Map<String,?>, ?> expression2,
             boolean isMatchingCase, MatchAction matchAction)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -137,8 +137,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinaryComparisonOperator<Map<String,?>> greater(
-            Expression<? super Map<String,?>, ?> expression1,
-            Expression<? super Map<String,?>, ?> expression2,
+            Expression<Map<String,?>, ?> expression1,
+            Expression<Map<String,?>, ?> expression2,
             boolean isMatchingCase, MatchAction matchAction)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -149,8 +149,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinaryComparisonOperator<Map<String,?>> lessOrEqual(
-            Expression<? super Map<String,?>, ?> expression1,
-            Expression<? super Map<String,?>, ?> expression2,
+            Expression<Map<String,?>, ?> expression1,
+            Expression<Map<String,?>, ?> expression2,
             boolean isMatchingCase, MatchAction matchAction)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -161,8 +161,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinaryComparisonOperator<Map<String,?>> greaterOrEqual(
-            Expression<? super Map<String,?>, ?> expression1,
-            Expression<? super Map<String,?>, ?> expression2,
+            Expression<Map<String,?>, ?> expression1,
+            Expression<Map<String,?>, ?> expression2,
             boolean isMatchingCase, MatchAction matchAction)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -173,9 +173,9 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BetweenComparisonOperator<Map<String,?>> between(
-            Expression<? super Map<String,?>, ?> expression,
-            Expression<? super Map<String,?>, ?> lowerBoundary,
-            Expression<? super Map<String,?>, ?> upperBoundary)
+            Expression<Map<String,?>, ?> expression,
+            Expression<Map<String,?>, ?> lowerBoundary,
+            Expression<Map<String,?>, ?> upperBoundary)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -185,7 +185,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public LikeOperator<Map<String,?>> like(
-            Expression<? super Map<String,?>, ?> expression,
+            Expression<Map<String,?>, ?> expression,
             String pattern, char wildcard, char singleChar, char escape, boolean isMatchingCase)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -195,7 +195,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      * Unsupported operation.
      */
     @Override
-    public NullOperator<Map<String,?>> isNull(Expression<? super Map<String,?>, ?> expression) {
+    public NullOperator<Map<String,?>> isNull(Expression<Map<String,?>, ?> expression) {
         throw new UnsupportedOperationException("Not supported.");
     }
 
@@ -203,7 +203,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      * Unsupported operation.
      */
     @Override
-    public NilOperator<Map<String,?>> isNil(Expression<? super Map<String,?>, ?> expression, String nilReason) {
+    public NilOperator<Map<String,?>> isNil(Expression<Map<String,?>, ?> expression, String nilReason) {
         throw new UnsupportedOperationException("Not supported.");
     }
 
@@ -211,7 +211,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      * Unsupported operation.
      */
     @Override
-    public LogicalOperator<Map<String,?>> and(Collection<? extends Filter<? super Map<String,?>>> operands) {
+    public LogicalOperator<Map<String,?>> and(Collection<? extends Filter<Map<String,?>>> operands) {
         throw new UnsupportedOperationException("Not supported.");
     }
 
@@ -219,7 +219,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      * Unsupported operation.
      */
     @Override
-    public LogicalOperator<Map<String,?>> or(Collection<? extends Filter<? super Map<String,?>>> operands) {
+    public LogicalOperator<Map<String,?>> or(Collection<? extends Filter<Map<String,?>>> operands) {
         throw new UnsupportedOperationException("Not supported.");
     }
 
@@ -227,7 +227,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      * Unsupported operation.
      */
     @Override
-    public LogicalOperator<Map<String,?>> not(Filter<? super Map<String,?>> operand) {
+    public LogicalOperator<Map<String,?>> not(Filter<Map<String,?>> operand) {
         throw new UnsupportedOperationException("Not supported.");
     }
 
@@ -236,7 +236,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> bbox(
-            Expression<? super Map<String,?>, ? extends Object> geometry,
+            Expression<Map<String,?>, ? extends Object> geometry,
             Envelope bounds)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -247,8 +247,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> equals(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -258,8 +258,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> disjoint(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -269,8 +269,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> intersects(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -280,8 +280,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> touches(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -291,8 +291,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> crosses(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -302,8 +302,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> within(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -313,8 +313,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> contains(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -324,8 +324,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public BinarySpatialOperator<Map<String,?>> overlaps(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2)
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -335,8 +335,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public DistanceOperator<Map<String,?>> beyond(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2,
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2,
             Quantity<Length> distance)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -347,8 +347,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public DistanceOperator<Map<String,?>> within(
-            Expression<? super Map<String,?>, ? extends Object> geometry1,
-            Expression<? super Map<String,?>, ? extends Object> geometry2,
+            Expression<Map<String,?>, ? extends Object> geometry1,
+            Expression<Map<String,?>, ? extends Object> geometry2,
             Quantity<Length> distance)
     {
         throw new UnsupportedOperationException("Not supported.");
@@ -359,8 +359,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> after(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -370,8 +370,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> before(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -381,8 +381,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> begins(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -392,8 +392,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> begunBy(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -403,8 +403,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> tcontains(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -414,8 +414,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> during(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -425,8 +425,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> tequals(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -436,8 +436,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> toverlaps(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -447,8 +447,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> meets(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -458,8 +458,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> ends(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -469,8 +469,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> overlappedBy(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -480,8 +480,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> metBy(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -491,8 +491,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> endedBy(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -502,8 +502,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public TemporalOperator<Map<String,?>> anyInteracts(
-            Expression<? super Map<String,?>, ? extends Object> time1,
-            Expression<? super Map<String,?>, ? extends Object> time2)
+            Expression<Map<String,?>, ? extends Object> time1,
+            Expression<Map<String,?>, ? extends Object> time2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -513,8 +513,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public Expression<Map<String,?>, Number> add(
-            Expression<? super Map<String,?>, ? extends Number> operand1,
-            Expression<? super Map<String,?>, ? extends Number> operand2)
+            Expression<Map<String,?>, ? extends Number> operand1,
+            Expression<Map<String,?>, ? extends Number> operand2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -524,8 +524,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public Expression<Map<String,?>, Number> subtract(
-            Expression<? super Map<String,?>, ? extends Number> operand1,
-            Expression<? super Map<String,?>, ? extends Number> operand2)
+            Expression<Map<String,?>, ? extends Number> operand1,
+            Expression<Map<String,?>, ? extends Number> operand2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -535,8 +535,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public Expression<Map<String,?>, Number> multiply(
-            Expression<? super Map<String,?>, ? extends Number> operand1,
-            Expression<? super Map<String,?>, ? extends Number> operand2)
+            Expression<Map<String,?>, ? extends Number> operand1,
+            Expression<Map<String,?>, ? extends Number> operand2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -546,8 +546,8 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      */
     @Override
     public Expression<Map<String,?>, Number> divide(
-            Expression<? super Map<String,?>, ? extends Number> operand1,
-            Expression<? super Map<String,?>, ? extends Number> operand2)
+            Expression<Map<String,?>, ? extends Number> operand1,
+            Expression<Map<String,?>, ? extends Number> operand2)
     {
         throw new UnsupportedOperationException("Not supported.");
     }
@@ -556,7 +556,7 @@ final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Ob
      * Unsupported operation.
      */
     @Override
-    public SortProperty<Map<String,?>> sort(ValueReference<? super Map<String,?>, ?> property, SortOrder order) {
+    public SortProperty<Map<String,?>> sort(ValueReference<Map<String,?>, ?> property, SortOrder order) {
         throw new UnsupportedOperationException("Not supported.");
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java
index cae63d9653..dc25a40a7c 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionMock.java
@@ -43,7 +43,7 @@ final class FunctionMock implements Expression<Map<String,?>, Object> {
     /**
      * The function parameters.
      */
-    private final List<Expression<? super Map<String,?>, ?>> parameters;
+    private final List<Expression<Map<String,?>, ?>> parameters;
 
     /**
      * Creates a new dummy function.
@@ -51,7 +51,7 @@ final class FunctionMock implements Expression<Map<String,?>, Object> {
      * @param  name        the local part of the function name.
      * @param  parameters  the function parameters.
      */
-    FunctionMock(final String name, final List<Expression<? super Map<String,?>, ?>> parameters) {
+    FunctionMock(final String name, final List<Expression<Map<String,?>, ?>> parameters) {
         this.name = name;
         this.parameters = parameters;
     }
@@ -69,7 +69,7 @@ final class FunctionMock implements Expression<Map<String,?>, Object> {
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public List<Expression<? super Map<String,?>, ?>> getParameters() {
+    public List<Expression<Map<String,?>, ?>> getParameters() {
         return parameters;
     }
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
index f3f685e4e3..2d5e60e320 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
@@ -470,7 +470,7 @@ public abstract class RegistryTestCase<G> extends TestCase {
         /*
          * Optimization should evaluate the point immediately.
          */
-        final Expression<? super Feature, ?> optimized = new Optimization().apply(function);
+        final Expression<Feature,?> optimized = new Optimization().apply(function);
         assertNotSame("Optimization should produce a new expression.", function, optimized);
         assertInstanceOf("Expected immediate expression evaluation.", Literal.class, optimized);
         assertPointEquals(((Literal) optimized).getValue(), HardCodedCRS.WGS84_LATITUDE_FIRST, 30, 10);
@@ -489,7 +489,7 @@ public abstract class RegistryTestCase<G> extends TestCase {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(library.pointClass).setName(P_NAME).setCRS(HardCodedCRS.WGS84);
         optimization.setFeatureType(ftb.setName("Test").build());
-        final Expression<? super Feature, ?> optimized = optimization.apply(function);
+        final Expression<Feature,?> optimized = optimization.apply(function);
         assertNotSame("Optimization should produce a new expression.", function, optimized);
         /*
          * Get the second parameter, which should be a literal, and get the point coordinates.
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
index ddb6fa5eda..61da8fd80e 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
@@ -426,7 +426,7 @@ public final class SEPortrayer {
             return query;
         }
         final Envelope bbox = optimizeBBox(canvas, symbolsMargin);
-        Filter<? super Feature> filter;
+        Filter<Feature> filter;
         // Make a bbox filter.
         if (geomProperties.size() == 1) {
             final Expression<Feature,?> geomExp = geomProperties.iterator().next();
@@ -472,10 +472,10 @@ public final class SEPortrayer {
 //                rules.set(0, rule);
                 combined = rulefilters.get(0);
             } else {
-                combined = filterFactory.or((List) rulefilters);        // TODO
+                combined = filterFactory.or(rulefilters);
             }
-            if (filter != Filter.include()) {
-                filter = filterFactory.and((Filter) filter, combined);  // TODO
+            if (filter != Filter.<Feature>include()) {
+                filter = filterFactory.and(filter, combined);
             } else {
                 filter = combined;
             }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java
index 676296a51f..28649cb1c4 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java
@@ -55,7 +55,7 @@ import org.opengis.filter.SortBy;
  *
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 final class FeatureStream extends DeferredStream<Feature> {
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java
index 6cdc62a800..0d847b22c5 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java
@@ -60,7 +60,7 @@ import org.opengis.filter.BetweenComparisonOperator;
  *
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> {
@@ -89,7 +89,7 @@ public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> {
             sql.append(" AND ");         write(sql, filter.getUpperBoundary());
         });
         setNullAndNilHandlers((filter, sql) -> {
-            final List<Expression<? super Feature, ?>> expressions = filter.getExpressions();
+            final List<Expression<Feature, ?>> expressions = filter.getExpressions();
             if (expressions.size() == 1) {
                 write(sql, expressions.get(0));
                 sql.append(" IS NULL");
@@ -239,17 +239,12 @@ public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> {
     /**
      * Executes the registered action for the given expression.
      *
-     * <h4>Note on type safety</h4>
-     * This method applies a theoretically unsafe cast, which is okay in the context of this class.
-     * See <cite>Note on parameterized type</cite> section in {@link Visitor#visit(Filter, Object)}.
-     *
      * @param  sql         where to write the result of all actions.
      * @param  expression  the expression for which to execute an action based on its type.
      * @return value of {@link SelectionClause#isInvalid} flag, for allowing caller to short-circuit.
      */
-    @SuppressWarnings("unchecked")
-    private boolean write(final SelectionClause sql, final Expression<? super Feature, ?> expression) {
-        visit((Expression<Feature, ?>) expression, sql);
+    private boolean write(final SelectionClause sql, final Expression<Feature, ?> expression) {
+        visit(expression, sql);
         return sql.isInvalid;
     }
 
@@ -273,7 +268,7 @@ public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> {
      * @param separator    the separator to insert between expression.
      * @param binary       whether the list of expressions shall contain exactly 2 elements.
      */
-    private void writeParameters(final SelectionClause sql, final List<Expression<? super Feature, ?>> expressions,
+    private void writeParameters(final SelectionClause sql, final List<Expression<Feature,?>> expressions,
                                  final String separator, final boolean binary)
     {
         final int n = expressions.size();
@@ -320,7 +315,7 @@ public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> {
         /** Invoked when a logical filter needs to be converted to SQL clause. */
         @Override public void accept(final Filter<Feature> f, final SelectionClause sql) {
             final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
-            final List<Filter<? super Feature>> operands = filter.getOperands();
+            final List<Filter<Feature>> operands = filter.getOperands();
             final int n = operands.size();
             if (unary ? (n != 1) : (n == 0)) {
                 sql.invalidate();
diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java
index c3e7a23553..d89eb696de 100644
--- a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java
+++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java
@@ -44,7 +44,7 @@ import org.opengis.filter.SpatialOperator;
  *
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 public final class SelectionClauseWriterTest extends TestCase implements SchemaModifier {
@@ -142,7 +142,7 @@ public final class SelectionClauseWriterTest extends TestCase implements SchemaM
      * Formats the given filter as a SQL {@code WHERE} statement body
      * and verifies that the result is equal to the expected string.
      */
-    private void verifySQL(final Filter<? super Feature> filter, final String expected) {
+    private void verifySQL(final Filter<Feature> filter, final String expected) {
         final SelectionClause sql = new SelectionClause(table);
         assertTrue(sql.tryAppend(SelectionClauseWriter.DEFAULT, filter));
         assertEquals(expected, sql.toString());
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
index 3fdbb0720a..0459d9af4f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
@@ -116,7 +116,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
      * @see #setSelection(Filter)
      */
     @SuppressWarnings("serial")                 // Most SIS implementations are serializable.
-    private Filter<? super Feature> selection;
+    private Filter<Feature> selection;
 
     /**
      * The number of feature instances to skip from the beginning.
@@ -197,12 +197,12 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
      * @throws IllegalArgumentException if a property is duplicated.
      */
     @SafeVarargs
-    public final void setProjection(final Expression<? super Feature, ?>... properties) {
+    public final void setProjection(final Expression<Feature, ?>... properties) {
         NamedExpression[] wrappers = null;
         if (properties != null) {
             wrappers = new NamedExpression[properties.length];
             for (int i=0; i<wrappers.length; i++) {
-                final Expression<? super Feature, ?> e = properties[i];
+                final Expression<Feature, ?> e = properties[i];
                 ArgumentChecks.ensureNonNullElement("properties", i, e);
                 wrappers[i] = new NamedExpression(e);
             }
@@ -263,7 +263,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
      */
     @Override
     public void setSelection(final Envelope domain) {
-        Filter<? super Feature> filter = null;
+        Filter<Feature> filter = null;
         if (domain != null) {
             final FilterFactory<Feature,Object,?> ff = DefaultFilterFactory.forFeatures();
             filter = ff.bbox(ff.property(AttributeConvention.GEOMETRY), domain);
@@ -278,7 +278,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
      *
      * @param  selection  the filter, or {@code null} if none.
      */
-    public void setSelection(final Filter<? super Feature> selection) {
+    public void setSelection(final Filter<Feature> selection) {
         this.selection = selection;
     }
 
@@ -289,7 +289,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
      *
      * @return the filter, or {@code null} if none.
      */
-    public Filter<? super Feature> getSelection() {
+    public Filter<Feature> getSelection() {
         return selection;
     }
 
@@ -483,7 +483,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
          * Never {@code null}.
          */
         @SuppressWarnings("serial")
-        public final Expression<? super Feature, ?> expression;
+        public final Expression<Feature,?> expression;
 
         /**
          * The name to assign to the expression result, or {@code null} if unspecified.
@@ -506,7 +506,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
          *
          * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
          */
-        public NamedExpression(final Expression<? super Feature, ?> expression) {
+        public NamedExpression(final Expression<Feature,?> expression) {
             this(expression, (GenericName) null);
         }
 
@@ -516,7 +516,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
          * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
          * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
          */
-        public NamedExpression(final Expression<? super Feature, ?> expression, final GenericName alias) {
+        public NamedExpression(final Expression<Feature,?> expression, final GenericName alias) {
             this(expression, alias, ProjectionType.STORED);
         }
 
@@ -527,7 +527,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
          * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
          * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
          */
-        public NamedExpression(final Expression<? super Feature, ?> expression, final String alias) {
+        public NamedExpression(final Expression<Feature,?> expression, final String alias) {
             ArgumentChecks.ensureNonNull("expression", expression);
             this.expression = expression;
             this.alias = (alias != null) ? Names.createLocalName(null, null, alias) : null;
@@ -543,7 +543,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
          *
          * @since 1.4
          */
-        public NamedExpression(final Expression<? super Feature, ?> expression, final GenericName alias, ProjectionType type) {
+        public NamedExpression(final Expression<Feature,?> expression, final GenericName alias, ProjectionType type) {
             ArgumentChecks.ensureNonNull("expression", expression);
             ArgumentChecks.ensureNonNull("type", type);
             this.expression = expression;
@@ -687,7 +687,7 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
              * For each property, get the expected type (mandatory) and its name (optional).
              * A default name will be computed if no alias were explicitly given by user.
              */
-            final Expression<? super Feature,?> expression = item.expression;
+            final Expression<Feature,?> expression = item.expression;
             final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(expression);
             final PropertyTypeBuilder resultType;
             if (fex == null || (resultType = fex.expectedType(valueType, ftb)) == null) {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
index be35261e5c..920c859c43 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
@@ -110,7 +110,7 @@ final class FeatureSubset extends AbstractFeatureSet {
         /*
          * Apply filter.
          */
-        final Filter<? super Feature> selection = query.getSelection();
+        final Filter<Feature> selection = query.getSelection();
         if (selection != null && !selection.equals(Filter.include())) {
             stream = stream.filter(selection);
         }
@@ -142,7 +142,7 @@ final class FeatureSubset extends AbstractFeatureSet {
         final FeatureQuery.NamedExpression[] projection = query.getProjection();
         if (projection != null) {
             @SuppressWarnings({"unchecked", "rawtypes"})
-            final Expression<? super Feature, ?>[] expressions = new Expression[projection.length];
+            final Expression<Feature,?>[] expressions = new Expression[projection.length];
             for (int i=0; i<expressions.length; i++) {
                 expressions[i] = projection[i].expression;
             }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/JoinFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/JoinFeatureSet.java
index 898e02089e..c4a98d419b 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/JoinFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/JoinFeatureSet.java
@@ -176,7 +176,7 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
      * This condition specifies also if the comparison is {@linkplain BinaryComparisonOperator#isMatchingCase() case
      * sensitive} and {@linkplain BinaryComparisonOperator#getMatchAction() how to compare multi-values}.
      */
-    public final BinaryComparisonOperator<? super Feature> condition;
+    public final BinaryComparisonOperator<Feature> condition;
 
     /**
      * The factory to use for creating {@code Query} expressions for retrieving subsets of feature sets.
@@ -208,7 +208,7 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
     public JoinFeatureSet(final Resource parent,
                           final FeatureSet left,  String leftAlias,
                           final FeatureSet right, String rightAlias,
-                          final Type joinType, final BinaryComparisonOperator<? super Feature> condition,
+                          final Type joinType, final BinaryComparisonOperator<Feature> condition,
                           Map<String,?> featureInfo)
             throws DataStoreException
     {
@@ -464,7 +464,7 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
          * The filtering condition is determined by the current {@link #mainFeature}.
          */
         private void createFilteredIterator() {
-            final Expression<? super Feature, ?> expression1, expression2;
+            final Expression<Feature,?> expression1, expression2;
             final FeatureSet filteredSet;
             if (swapSides) {
                 expression1 = condition.getOperand2();
@@ -476,7 +476,7 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
                 filteredSet = right;
             }
             final Object mainValue = expression1.apply(mainFeature);
-            final Filter<? super Feature> filter;
+            final Filter<Feature> filter;
             if (mainValue != null) {
                 filter = factory.equal(expression2, factory.literal(mainValue),
                             condition.isMatchingCase(), condition.getMatchAction());
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
index ca2dfb0321..b4aa6f3786 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
@@ -35,6 +35,7 @@ import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
 import org.opengis.feature.AttributeType;
+import org.opengis.feature.IdentifiedType;
 import org.opengis.feature.Operation;
 import org.opengis.filter.Expression;
 import org.opengis.filter.Filter;
@@ -296,10 +297,10 @@ public final class FeatureQueryTest extends TestCase {
         assertEquals(2, resultType.getProperties(true).size());
         final PropertyType pt1 = resultType.getProperty("value1");
         final PropertyType pt2 = resultType.getProperty("unexpected");
-        assertTrue(pt1 instanceof AttributeType);
-        assertTrue(pt2 instanceof AttributeType);
-        assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
-        assertEquals(Object.class,  ((AttributeType) pt2).getValueClass());
+        assertTrue(pt1 instanceof AttributeType<?>);
+        assertTrue(pt2 instanceof AttributeType<?>);
+        assertEquals(Integer.class, ((AttributeType<?>) pt1).getValueClass());
+        assertEquals(Object.class,  ((AttributeType<?>) pt2).getValueClass());
 
         // Check feature property values.
         assertEquals(3,    instance.getPropertyValue("value1"));
@@ -326,7 +327,7 @@ public final class FeatureQueryTest extends TestCase {
     /**
      * Shortcut for creating expression for a projection computed on-the-fly.
      */
-    private static FeatureQuery.NamedExpression virtualProjection(final Expression<? super Feature, ?> expression, final String alias) {
+    private static FeatureQuery.NamedExpression virtualProjection(final Expression<Feature, ?> expression, final String alias) {
         return new FeatureQuery.NamedExpression(expression, Names.createLocalName(null, null, alias), FeatureQuery.ProjectionType.VIRTUAL);
     }
 
@@ -351,14 +352,16 @@ public final class FeatureQueryTest extends TestCase {
         final PropertyType pt1 = resultType.getProperty("value1");
         final PropertyType pt2 = resultType.getProperty("renamed1");
         final PropertyType pt3 = resultType.getProperty("computed");
-        assertTrue(pt1 instanceof AttributeType);
+        assertTrue(pt1 instanceof AttributeType<?>);
         assertTrue(pt2 instanceof Operation);
         assertTrue(pt3 instanceof Operation);
-        assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
-        assertTrue(((Operation) pt2).getResult() instanceof AttributeType);
-        assertTrue(((Operation) pt3).getResult() instanceof AttributeType);
-        assertEquals(Integer.class, ((AttributeType)((Operation) pt2).getResult()).getValueClass());
-        assertEquals(String.class,  ((AttributeType)((Operation) pt3).getResult()).getValueClass());
+        final IdentifiedType result2 = ((Operation) pt2).getResult();
+        final IdentifiedType result3 = ((Operation) pt3).getResult();
+        assertEquals(Integer.class, ((AttributeType<?>) pt1).getValueClass());
+        assertTrue(result2 instanceof AttributeType<?>);
+        assertTrue(result3 instanceof AttributeType<?>);
+        assertEquals(Integer.class, ((AttributeType<?>) result2).getValueClass());
+        assertEquals(String.class,  ((AttributeType<?>) result3).getValueClass());
 
         // Check feature instance.
         assertEquals(3, instance.getPropertyValue("value1"));