Merge branch 'geoapi-3.1'
diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..aaf336f
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,22 @@
+#
+# Configuration file for Apache SIS project.
+#
+# Documentation:  https://s.apache.org/asfyaml
+# Active setting: https://gitbox.apache.org/schemes.cgi?sis-site
+#
+github:
+  description: Java language library for developing geospatial applications following OGC/ISO standards.
+  homepage:    https://sis.apache.org/
+  labels:
+    - java
+    - geospatial
+    - standards
+notifications:
+  commits:              commits@sis.apache.org
+  issues:               dev@sis.apache.org
+  issues_status:        issues@sis.apache.org
+  issues_comment:       issues@sis.apache.org
+  pullrequests:         dev@sis.apache.org
+  pullrequests_status:  issues@sis.apache.org
+  pullrequests_comment: issues@sis.apache.org
+  jira_options:         worklog
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index aab14f3..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,4 +0,0 @@
-[submodule "geoapi/snapshot"]
-	path = geoapi/snapshot
-	url = https://github.com/opengeospatial/geoapi
-	branch = 3.1.x
diff --git a/NOTICE b/NOTICE
index e31e488..acd2096 100644
--- a/NOTICE
+++ b/NOTICE
@@ -7,7 +7,7 @@
 The Javadoc contains documentation from the Open Geospatial Consortium (OGC®)
 specifications (https://www.ogc.org/standards/), also known as OpenGIS®.
 
-Apache SIS depends on GeoAPI published by OGC under Apache 2.0 license.
+Apache SIS depends on GeoAPI published by OGC under BSD-style license.
 https://www.ogc.org/about-ogc/policies/software-licenses/
 
 Apache SIS depends on JSR-385 (API only) published under BSD license.
diff --git a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
index 5abc18a..1ac9152 100644
--- a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
+++ b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
@@ -71,7 +71,6 @@
         Map.entry("cql",                      "core:sis-cql"),                      // Incubator.
         Map.entry("storage.shapefile",        "core:sis-shapefile"),
         Map.entry("storage.coveragejson",     "core:sis-coveragejson"),
-        Map.entry("portrayal.map",            "core:sis-portrayal-map"),
         Map.entry("webapp",                   "application:sis-webapp")
     );
 
diff --git a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJAR.java b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJAR.java
index 8863453..f6468c5 100644
--- a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJAR.java
+++ b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJAR.java
@@ -47,7 +47,7 @@
      *
      * @todo should be specified in a property.
      */
-    private static final String GEOAPI_VERSION = "3.1-SNAPSHOT";
+    private static final String GEOAPI_VERSION = "3.0.2";
 
     /**
      * Attributes where values are class names. Those attributes can be assigned
diff --git a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJavadoc.java b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJavadoc.java
index 00a55a2..b5d31bf 100644
--- a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJavadoc.java
+++ b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/ModularJavadoc.java
@@ -69,7 +69,7 @@
                      "todo:a:\"TODO:\"");
         options.links(
                 "https://docs.oracle.com/en/java/javase/11/docs/api",
-                "http://www.geoapi.org/snapshot/javadoc",
+                "http://www.geoapi.org/3.0/javadoc",
                 "https://openjfx.io/javadoc/21/",
                 "http://unitsofmeasurement.github.io/unit-api/site/apidocs");
         /*
diff --git a/buildSrc/src/main/resources/org/apache/sis/buildtools/book/GEOAPI.lst b/buildSrc/src/main/resources/org/apache/sis/buildtools/book/GEOAPI.lst
index 05fe6dc..1959531 100644
--- a/buildSrc/src/main/resources/org/apache/sis/buildtools/book/GEOAPI.lst
+++ b/buildSrc/src/main/resources/org/apache/sis/buildtools/book/GEOAPI.lst
@@ -79,8 +79,10 @@
 Formula
 GCP
 GCPCollection
+GeneralDerivedCRS
 GeneralParameterDescriptor
 GeneralParameterValue
+GeocentricCRS
 GeodeticCRS
 GeodeticDatum
 GeographicBoundingBox
diff --git a/endorsed/build.gradle.kts b/endorsed/build.gradle.kts
index 0872b95..8343ca9 100644
--- a/endorsed/build.gradle.kts
+++ b/endorsed/build.gradle.kts
@@ -97,7 +97,6 @@
  */
 var srcDir = file("src")            // Must be the same as the hard-coded value in `BuildHelper.java`.
 tasks.compileJava {
-    dependsOn(":geoapi:rebuild")
     options.release.set(11)         // The version of both Java source code and compiled byte code.
 }
 tasks.compileTestJava {
diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/IdentifierCommand.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/IdentifierCommand.java
index b048d9a..3f80b9f 100644
--- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/IdentifierCommand.java
+++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/IdentifierCommand.java
@@ -36,6 +36,10 @@
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.resources.Vocabulary;
 
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
+
 
 /**
  * The "identifier" sub-command.
@@ -126,11 +130,11 @@
         }
         if (metadata != null) {
             final List<Row> rows;
-            if (metadata instanceof Metadata) {
+            if (metadata instanceof DefaultMetadata) {
                 rows = new ArrayList<>();
-                final Identifier id = ((Metadata) metadata).getMetadataIdentifier();
-                if (id != null) {
-                    CharSequence desc = id.getDescription();
+                final Identifier id = ((DefaultMetadata) metadata).getMetadataIdentifier();
+                if (id instanceof DefaultIdentifier) {
+                    CharSequence desc = ((DefaultIdentifier) id).getDescription();
                     if (desc == null && !files.isEmpty()) {
                         final Object c = files.get(0);
                         if (c instanceof CharSequence) {
diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
index 7d87734..810cfc1 100644
--- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
+++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
@@ -82,8 +82,9 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.geometry.MismatchedDimensionException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.apache.sis.referencing.DefaultObjectDomain;
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -356,7 +357,7 @@
         printHeader(Vocabulary.Keys.Destination); printNameAndIdentifier(getEffectiveCRS(true),  false);
         printHeader(Vocabulary.Keys.Operations);  printOperations(operation, false);
         outHeader.nextLine();
-        printDomainOfValidity(operation.getDomains());
+        printDomainOfValidity(Legacy.getDomains(operation));
         printAccuracy(CRS.getLinearAccuracy(operation));
         if (options.containsKey(Option.VERBOSE)) {
             printDetails();
@@ -499,8 +500,8 @@
      *
      * <blockquote>Canada - onshore and offshore</blockquote>
      */
-    private void printDomainOfValidity(final Collection<ObjectDomain> domains) throws IOException {
-        for (final ObjectDomain domain : domains) {
+    private void printDomainOfValidity(final Collection<DefaultObjectDomain> domains) throws IOException {
+        for (final DefaultObjectDomain domain : domains) {
             final Extent extent = domain.getDomainOfValidity();
             final InternationalString description = extent.getDescription();
             if (description != null) {
diff --git a/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/MetadataCommandTest.java b/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/MetadataCommandTest.java
index 65c9b47..cfd7d2d 100644
--- a/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/MetadataCommandTest.java
+++ b/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/MetadataCommandTest.java
@@ -24,8 +24,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCaseWithLogs;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
+// Specific to the main branch:
+import org.junit.jupiter.api.Disabled;
 
 
 /**
@@ -47,8 +47,9 @@
      * @throws Exception if an error occurred while creating the command.
      */
     @Test
+    @Disabled("Requires GeoAPI 3.1")
     public void testNetCDF() throws Exception {
-        final URL url = TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = new URL("Cube2D_geographic_packed.nc"); // TestData.NETCDF_2D_GEOGRAPHIC.location();
         var test = new MetadataCommand(0, new String[] {CommandRunner.TEST, url.toString()});
         test.run();
         verifyNetCDF("Metadata", test.outputBuffer.toString());
@@ -73,8 +74,9 @@
      * @throws Exception if an error occurred while creating the command.
      */
     @Test
+    @Disabled("Requires GeoAPI 3.1")
     public void testFormatXML() throws Exception {
-        final URL url = TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = new URL("Cube2D_geographic_packed.nc") ; // TestData.NETCDF_2D_GEOGRAPHIC.location();
         var test = new MetadataCommand(0, new String[] {CommandRunner.TEST, url.toString(), "--format", "XML"});
         test.run();
         verifyNetCDF("<?xml", test.outputBuffer.toString());
diff --git a/endorsed/src/org.apache.sis.feature/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/module-info.java
index 6daf162..80ebe49 100644
--- a/endorsed/src/org.apache.sis.feature/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/module-info.java
@@ -41,12 +41,16 @@
     exports org.apache.sis.filter;
     exports org.apache.sis.index.tree;
 
-    exports org.apache.sis.filter.privy to
+    exports org.apache.sis.pending.geoapi.filter to
             org.apache.sis.storage,
             org.apache.sis.storage.sql,
             org.apache.sis.storage.shapefile,       // In the "incubator" sub-project.
-            org.apache.sis.cql,                     // In the "incubator" sub-project.
-            org.apache.sis.portrayal.map;           // In the "incubator" sub-project.
+            org.apache.sis.portrayal;
+
+    exports org.apache.sis.filter.privy to
+            org.apache.sis.storage,
+            org.apache.sis.storage.sql,
+            org.apache.sis.storage.shapefile;       // In the "incubator" sub-project.
 
     exports org.apache.sis.feature.privy to
             org.apache.sis.storage,
@@ -54,7 +58,6 @@
             org.apache.sis.storage.netcdf,
             org.apache.sis.storage.shapefile,       // In the "incubator" sub-project.
             org.apache.sis.portrayal,
-            org.apache.sis.portrayal.map,           // In the "incubator" sub-project.
             org.apache.sis.gui;                     // In the "optional" sub-project.
 
     exports org.apache.sis.geometry.wrapper to
@@ -62,17 +65,11 @@
             org.apache.sis.storage.xml,
             org.apache.sis.storage.sql,
             org.apache.sis.storage.netcdf,
-            org.apache.sis.storage.shapefile,       // In the "incubator" sub-project.
-            org.apache.sis.portrayal.map,           // In the "incubator" sub-project.
-            org.apache.sis.cql;                     // In the "incubator" sub-project.
+            org.apache.sis.storage.shapefile;       // In the "incubator" sub-project.
 
     exports org.apache.sis.geometry.wrapper.j2d to
             org.apache.sis.gui;                     // In the "optional" sub-project.
 
-    exports org.apache.sis.geometry.wrapper.jts to
-            org.apache.sis.portrayal.map,           // In the "incubator" sub-project.
-            org.apache.sis.cql;                     // In the "incubator" sub-project.
-
     exports org.apache.sis.coverage.privy to
             org.apache.sis.storage,
             org.apache.sis.storage.sql,
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/BandedCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/BandedCoverage.java
index e735e1b..68de7fe 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/BandedCoverage.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/BandedCoverage.java
@@ -23,10 +23,6 @@
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
-
 
 /**
  * A coverage where all sample values at a given location can be provided in an array of primitive type.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CannotEvaluateException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CannotEvaluateException.java
new file mode 100644
index 0000000..9dbc699
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CannotEvaluateException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage;
+
+
+/**
+ * Thrown when a quantity can not be evaluated.
+ *
+ * <div class="note"><b>Upcoming API change:</b>
+ * this class may move to GeoAPI in a future version. If that move happens,
+ * the {@code org.apache.sis.coverage} package name would become {@code org.opengis.coverage}.</div>
+ */
+public class CannotEvaluateException extends RuntimeException {
+    /**
+     * Creates an exception with no message.
+     */
+    public CannotEvaluateException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message  the detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public CannotEvaluateException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message  the detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     * @param  cause  the cause for this exception. The cause is saved
+     *         for later retrieval by the {@link #getCause()} method.
+     */
+    public CannotEvaluateException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CategoryList.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CategoryList.java
index 8a82893..d817549 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CategoryList.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CategoryList.java
@@ -758,7 +758,7 @@
         } else {
             ArgumentChecks.ensureDimensionMatches("ptDst", 1, ptDst);
         }
-        ptDst.setCoordinate(0, transform(ptSrc.getCoordinate(0)));
+        ptDst.setOrdinate(0, transform(ptSrc.getOrdinate(0)));
         return ptDst;
     }
 
@@ -769,7 +769,7 @@
     public final Matrix derivative(final DirectPosition point) throws TransformException {
         ArgumentChecks.ensureNonNull("point", point);
         ArgumentChecks.ensureDimensionMatches("point", 1, point);
-        return new Matrix1(derivative(point.getCoordinate(0)));
+        return new Matrix1(derivative(point.getOrdinate(0)));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
index 33c7c8e..59142d9 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
@@ -43,9 +43,6 @@
 import static org.apache.sis.util.privy.Numerics.saturatingAdd;
 import static org.apache.sis.util.privy.Numerics.saturatingSubtract;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * Combines an arbitrary number of coverages into a single one.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/MismatchedCoverageRangeException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/MismatchedCoverageRangeException.java
index a9cc4d1..92d18e3 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/MismatchedCoverageRangeException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/MismatchedCoverageRangeException.java
@@ -24,7 +24,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  *
- * @see org.opengis.coordinate.MismatchedDimensionException
+ * @see org.opengis.geometry.MismatchedDimensionException
  *
  * @since 1.0
  */
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/PointOutsideCoverageException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/PointOutsideCoverageException.java
new file mode 100644
index 0000000..e464db6
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/PointOutsideCoverageException.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage;
+
+
+/**
+ * Thrown when an evaluate method is invoked for a location outside the domain of the coverage.
+ *
+ * <div class="note"><b>Upcoming API change:</b>
+ * this class may move to GeoAPI in a future version. If that move happens,
+ * the {@code org.apache.sis.coverage} package name would become {@code org.opengis.coverage}.</div>
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ */
+public class PointOutsideCoverageException extends CannotEvaluateException {
+    /**
+     * Creates an exception with no message.
+     */
+    public PointOutsideCoverageException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message  the detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public PointOutsideCoverageException(final String message) {
+        super(message);
+    }
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/RegionOfInterest.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/RegionOfInterest.java
index 82ca8fa..65a1370 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/RegionOfInterest.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/RegionOfInterest.java
@@ -111,7 +111,7 @@
             }
             crsToGrid = MathTransforms.bidimensional(tr);
         } catch (IllegalArgumentException | FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(null, e);
         }
         return crsToGrid.createTransformedShape(geometry);
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
index ae04583..88a9c0c 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
@@ -63,7 +63,7 @@
  * In this example, sample values in range [10…210] define a quantitative category, while all others categories are qualitative.
  *
  * <h2>Relationship with metadata</h2>
- * This class provides the same information as ISO 19115 {@link org.opengis.metadata.content.SampleDimension},
+ * This class provides the same information as ISO 19115 {@code org.opengis.metadata.content.SampleDimension},
  * but organized in a different way. The use of the same name may seem a risk, but those two types are typically
  * not used at the same time.
  *
@@ -76,9 +76,6 @@
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Alexis Manin (Geomatys)
  * @version 1.2
- *
- * @see org.opengis.metadata.content.SampleDimension
- *
  * @since 1.0
  */
 public class SampleDimension implements Serializable {
@@ -518,7 +515,7 @@
      *
      * <p>A <dfn>quantitative category</dfn> is a range of sample values associated to numbers with units of measurement.
      * For example, 10 = 1.0°C, 11 = 1.1°C, 12 = 1.2°C, <i>etc</i>. A quantitative category has a
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getTransferFunctionType() transfer function}
+     * transfer function
      * (typically a scale factor and an offset) for converting sample values to values expressed
      * in the unit of measurement.</p>
      *
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SubspaceNotSpecifiedException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
index 6b589dc..c328917 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
@@ -16,9 +16,6 @@
  */
 package org.apache.sis.coverage;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * Thrown when an operation can only be applied on a subspace of a multi-dimensional coverage,
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BufferedGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BufferedGridCoverage.java
index 179aade..e5f3dbd 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BufferedGridCoverage.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BufferedGridCoverage.java
@@ -37,10 +37,10 @@
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.image.DataType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -351,7 +351,7 @@
                             return null;
                         }
                         throw new PointOutsideCoverageException(
-                                gc.pointOutsideCoverage(getCoverage().gridGeometry.extent), point);
+                                gc.pointOutsideCoverage(getCoverage().gridGeometry.extent));
                     }
                     /*
                      * Following should never overflow, otherwise BufferedGridCoverage
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ConvertedGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index 72b5631..5a5f5c8 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -32,8 +32,8 @@
 import org.apache.sis.image.DataType;
 import org.apache.sis.image.ImageProcessor;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
index 599b188..9c38ab9 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
@@ -45,9 +45,9 @@
 import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.util.logging.Logging;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -369,11 +369,10 @@
             } catch (ArithmeticException | IndexOutOfBoundsException | DisjointExtentException ex) {
                 if (!nullIfOutside) {
                     throw (PointOutsideCoverageException) new PointOutsideCoverageException(
-                            gc.pointOutsideCoverage(gridGeometry.extent), point).initCause(ex);
+                            gc.pointOutsideCoverage(gridGeometry.extent)).initCause(ex);
                 }
             }
         } catch (PointOutsideCoverageException ex) {
-            ex.setOffendingLocation(point);
             throw ex;
         } catch (RuntimeException | FactoryException | TransformException ex) {
             throw new CannotEvaluateException(ex.getMessage(), ex);
@@ -456,7 +455,7 @@
         if (result != position) {
             // Should not happen, but be paranoiac.
             final double[] coordinates = position.coordinates;
-            System.arraycopy(result.getCoordinates(), 0, coordinates, 0, coordinates.length);
+            System.arraycopy(result.getCoordinate(), 0, coordinates, 0, coordinates.length);
         }
         /*
          * In most cases, the work of this method ends here. The remaining code in this method
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java
index 1264dfc..a35e1a6 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java
@@ -24,9 +24,9 @@
 import org.apache.sis.feature.internal.Resources;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
-import org.apache.sis.util.ArgumentChecks;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
@@ -162,7 +162,9 @@
         if (sliceExtent != null) {
             final int sourceDim = source.getGridGeometry().getDimension();
             final int dimension = dimToAdd.getDimension() + sourceDim;
-            ArgumentChecks.ensureDimensionMatches("sliceExtent", dimension, sliceExtent);
+            if (dimension != sliceExtent.getDimension()) {
+                throw new MismatchedDimensionException();
+            }
             for (int i=sourceDim; i<dimension; i++) {
                 final long size = sliceExtent.getSize(i);
                 if (size != 1) {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
index 6815cbe..f393222 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
@@ -83,7 +83,7 @@
         }
         final GeneralDirectPosition position = new GeneralDirectPosition(reducedCRS);
         for (int i=0; i < dimensions.length; i++) {
-            position.coordinates[i] = target.getCoordinate(dimensions[i]);
+            position.coordinates[i] = target.getOrdinate(dimensions[i]);
         }
         return position;
     }
@@ -101,7 +101,7 @@
         final GeneralEnvelope envelope = new GeneralEnvelope(reducedCRS);
         for (int i=0; i < dimensions.length; i++) {
             final int s = dimensions[i];
-            envelope.setRange(i, lowerCorner.getCoordinate(s), upperCorner.getCoordinate(s));
+            envelope.setRange(i, lowerCorner.getOrdinate(s), upperCorner.getOrdinate(s));
         }
         return envelope;
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java
index 51fe89b..2d1750d 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java
@@ -43,9 +43,9 @@
 import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.PointOutsideCoverageException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -650,7 +650,7 @@
                     while (++dim == removedAxis) {
                         removedAxis = toRemovedDimension(++remCounter);
                     }
-                    reduced.coordinates[i] = source.getCoordinate(dim);
+                    reduced.coordinates[i] = source.getOrdinate(dim);
                 }
                 return reduced;
             }
@@ -940,7 +940,6 @@
         Objects.requireNonNull(point);
         final GridExtent extent = sourceGeometry.getExtent();
         final int sourceDim = extent.getDimension();
-        ArgumentChecks.ensureDimensionMatches("slicePoint", sourceDim, extent);
         final Map<Integer,Long> slices = new HashMap<>();
         for (int dim=0; dim < sourceDim; dim++) {
             final long low   = extent.getLow (dim);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DomainLinearizer.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DomainLinearizer.java
index fc4990e..de6a0a9 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DomainLinearizer.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DomainLinearizer.java
@@ -193,7 +193,7 @@
             if (cause instanceof TransformException) {
                 throw (TransformException) cause;
             }
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
         return gg;
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/EvaluatorWrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/EvaluatorWrapper.java
index 12e7e65..0e26e69 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/EvaluatorWrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/EvaluatorWrapper.java
@@ -20,8 +20,8 @@
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.operation.TransformException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/FractionalGridCoordinates.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
index 3f7ed38..ff597fd 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
@@ -26,10 +26,10 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.PointOutsideCoverageException;
-import org.opengis.coverage.grid.GridCoordinates;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -54,7 +54,7 @@
  *
  * @since 1.1
  */
-public class FractionalGridCoordinates implements GridCoordinates, Serializable {
+public class FractionalGridCoordinates implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -93,7 +93,6 @@
      *
      * @return  the number of dimensions.
      */
-    @Override
     public int getDimension() {
         return coordinates.length;
     }
@@ -108,7 +107,6 @@
      * @throws ArithmeticException if a coordinate value is outside the range
      *         of values representable as a 64-bits integer value.
      */
-    @Override
     public long[] getCoordinateValues() {
         final long[] indices = new long[coordinates.length];
         for (int i=0; i<indices.length; i++) {
@@ -131,7 +129,6 @@
      * @throws ArithmeticException if the coordinate value is outside the range
      *         of values representable as a 64-bits integer value.
      */
-    @Override
     public long getCoordinateValue(final int dimension) {
         final double value = coordinates[dimension];
         /*
@@ -169,7 +166,6 @@
      * @throws ArithmeticException if this method cannot store the given grid coordinate
      *         without precision lost.
      */
-    @Override
     public void setCoordinateValue(final int dimension, final long value) {
         if ((coordinates[dimension] = value) != value) {
             throw new ArithmeticException(Resources.format(Resources.Keys.UnconvertibleGridCoordinate_2, "double", value));
@@ -247,7 +243,7 @@
         if (bounds != null) {
             final int bd = bounds.getDimension();
             if (bd != dimension) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, "bounds", dimension, bd));
             }
         }
@@ -382,10 +378,26 @@
         }
 
         /**
+         * Returns the direct position, which is this object itself.
+         */
+        @Override
+        public DirectPosition getDirectPosition() {
+            return this;
+        }
+
+        /**
+         * Grid coordinates have no coordinate reference system.
+         */
+        @Override
+        public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+            return null;
+        }
+
+        /**
          * Returns all coordinate values.
          */
         @Override
-        public double[] getCoordinates() {
+        public double[] getCoordinate() {
             return coordinates.clone();
         }
 
@@ -393,7 +405,7 @@
          * Returns the coordinate value at the given dimension.
          */
         @Override
-        public double getCoordinate(int dimension) {
+        public double getOrdinate(int dimension) {
             return coordinates[dimension];
         }
 
@@ -401,7 +413,7 @@
          * Sets the coordinate value at the given dimension.
          */
         @Override
-        public void setCoordinate(final int dimension, final double value) {
+        public void setOrdinate(final int dimension, final double value) {
             coordinates[dimension] = value;
         }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoordinatesView.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoordinatesView.java
index 5288541..d8e064a 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoordinatesView.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoordinatesView.java
@@ -19,18 +19,18 @@
 import java.util.Arrays;
 import java.util.Objects;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.GridCoordinates;
-import org.apache.sis.util.resources.Errors;
-
 
 /**
  * A view over the low or high grid envelope coordinates.
  * This is not a general-purpose grid coordinates since it assumes a {@link GridExtent} coordinates layout.
  *
+ * <h2>Upcoming API generalization</h2>
+ * this class may implement the {@code GridCoordinates} interface in a future Apache SIS version.
+ * This is pending GeoAPI update.
+ *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class GridCoordinatesView implements GridCoordinates {
+final class GridCoordinatesView {
     /**
      * A reference to the coordinate array of the enclosing grid envelope.
      */
@@ -53,7 +53,6 @@
     /**
      * Returns the number of dimension.
      */
-    @Override
     public final int getDimension() {
         return coordinates.length >>> 1;
     }
@@ -61,7 +60,6 @@
     /**
      * Returns all coordinate values.
      */
-    @Override
     public final long[] getCoordinateValues() {
         return Arrays.copyOfRange(coordinates, offset, offset + getDimension());
     }
@@ -69,7 +67,6 @@
     /**
      * Returns the coordinate value for the specified dimension.
      */
-    @Override
     public final long getCoordinateValue(final int index) {
         return coordinates[offset + Objects.checkIndex(index, getDimension())];
     }
@@ -77,10 +74,9 @@
     /**
      * Do not allow modification of grid coordinates since they are backed by {@link GridExtent}.
      */
-    @Override
-    public void setCoordinateValue(final int index, long value) {
-        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "GridCoordinates"));
-    }
+//  public void setCoordinateValue(final int index, long value) {
+//      throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "GridCoordinates"));
+//  }
 
     /**
      * Returns a string representation of this grid coordinates for debugging purpose.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
index 5ea04a7..5a86922 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
@@ -44,9 +44,9 @@
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
@@ -502,7 +502,7 @@
      * @throws DisjointExtentException if the given extent does not intersect this grid coverage.
      * @throws CannotEvaluateException if this method cannot produce the rendered image for another reason.
      */
-    public abstract RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException;
+    public abstract RenderedImage render(GridExtent sliceExtent);
 
     /**
      * Returns a string representation of this grid coverage for debugging purpose.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java
index 9d22ed1..a6d3b0d 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -54,10 +54,10 @@
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -528,10 +528,9 @@
                         return null;
                     }
                     throw (PointOutsideCoverageException) new PointOutsideCoverageException(
-                            gc.pointOutsideCoverage(gridGeometry.extent), point).initCause(ex);
+                            gc.pointOutsideCoverage(gridGeometry.extent)).initCause(ex);
                 }
             } catch (PointOutsideCoverageException ex) {
-                ex.setOffendingLocation(point);
                 throw ex;
             } catch (RuntimeException | FactoryException | TransformException ex) {
                 throw new CannotEvaluateException(ex.getMessage(), ex);
@@ -571,7 +570,7 @@
             final int expected = gridGeometry.getDimension();
             final int dimension = sliceExtent.getDimension();
             if (expected != dimension) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, "sliceExtent", expected, dimension));
             }
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
index 8e4383a..750a6f4 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
@@ -53,8 +53,8 @@
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.math.MathFunctions;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.PointOutsideCoverageException;
+// Specific to the main branch:
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
index 8a2805c..899c049 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
@@ -66,12 +66,10 @@
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.system.Modules;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
-import org.opengis.coverage.grid.GridEnvelope;
-import org.opengis.coverage.grid.GridCoordinates;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -88,13 +86,17 @@
  * <p>{@code GridExtent} instances are immutable and thread-safe.
  * The same instance can be shared by different {@link GridGeometry} instances.</p>
  *
+ * <div class="note"><b>Upcoming API generalization:</b>
+ * this class may implement the {@code GridEnvelope} interface in a future Apache SIS version.
+ * This is pending GeoAPI update.</div>
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Alexis Manin (Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @version 1.5
  * @since   1.0
  */
-public class GridExtent implements GridEnvelope, LenientComparable, Serializable {
+public class GridExtent implements Serializable, LenientComparable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -664,49 +666,12 @@
     }
 
     /**
-     * Creates a new grid extent as a copy of the given one.
-     *
-     * @param  extent  the grid extent to copy.
-     * @throws IllegalArgumentException if a coordinate value in the low part is
-     *         greater than the corresponding coordinate value in the high part.
-     *
-     * @see #castOrCopy(GridEnvelope)
-     */
-    protected GridExtent(final GridEnvelope extent) {
-        final int dimension = extent.getDimension();
-        coordinates = allocate(dimension);
-        for (int i=0; i<dimension; i++) {
-            coordinates[i] = extent.getLow(i);
-            coordinates[i + dimension] = extent.getHigh(i);
-        }
-        types = (extent instanceof GridExtent) ? ((GridExtent) extent).types : null;
-        validateCoordinates();
-    }
-
-    /**
-     * Returns the given grid extent as a {@code GridExtent} implementation.
-     * If the given extent is already a {@code GridExtent} instance or is null, then it is returned as-is.
-     * Otherwise a new extent is created using the {@linkplain #GridExtent(GridEnvelope) copy constructor}.
-     *
-     * @param  extent  the grid extent to cast or copy, or {@code null}.
-     * @return the grid extent as a {@code GridExtent}, or {@code null} if the given extent was null.
-     */
-    public static GridExtent castOrCopy(final GridEnvelope extent) {
-        if (extent == null || extent instanceof GridExtent) {
-            return (GridExtent) extent;
-        } else {
-            return new GridExtent(extent);
-        }
-    }
-
-    /**
      * Returns the number of dimensions.
      *
      * @return the number of dimensions.
      *
      * @see #selectDimensions(int[])
      */
-    @Override
     public final int getDimension() {
         return coordinates.length >>> 1;
     }
@@ -764,8 +729,7 @@
      *
      * @see #getLow(int)
      */
-    @Override
-    public GridCoordinates getLow() {
+    GridCoordinatesView getLow() {
         return new GridCoordinatesView(coordinates, 0);
     }
 
@@ -777,8 +741,7 @@
      *
      * @see #getHigh(int)
      */
-    @Override
-    public GridCoordinates getHigh() {
+    GridCoordinatesView getHigh() {
         return new GridCoordinatesView(coordinates, getDimension());
     }
 
@@ -790,11 +753,9 @@
      * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater
      *         than the {@linkplain #getDimension() grid dimension}.
      *
-     * @see #getLow()
      * @see #getHigh(int)
      * @see #withRange(int, long, long)
      */
-    @Override
     public long getLow(final int index) {
         return coordinates[Objects.checkIndex(index, getDimension())];
     }
@@ -807,11 +768,9 @@
      * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater
      *         than the {@linkplain #getDimension() grid dimension}.
      *
-     * @see #getHigh()
      * @see #getLow(int)
      * @see #withRange(int, long, long)
      */
-    @Override
     public long getHigh(final int index) {
         final int dimension = getDimension();
         return coordinates[Objects.checkIndex(index, dimension) + dimension];
@@ -890,7 +849,6 @@
      * @see #getHigh(int)
      * @see #resize(long...)
      */
-    @Override
     public long getSize(final int index) {
         final int dimension = getDimension();
         Objects.checkIndex(index, dimension);
@@ -1240,7 +1198,7 @@
         if (gridToCRS != null && Matrices.isAffine(gridToCRS)) try {
             envelope.setCoordinateReferenceSystem(GridExtentCRS.forExtentAlone(gridToCRS, getAxisTypes()));
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
         return envelope;
     }
@@ -1733,10 +1691,10 @@
      */
     final GridExtent sliceByRatio(final DirectPosition slicePoint, final double sliceRatio, final int[] dimensionsToKeep) {
         for (int i=slicePoint.getDimension(); --i >= 0;) {
-            slicePoint.setCoordinate(i, Math.fma(sliceRatio, getSize(i, true), getLow(i)));
+            slicePoint.setOrdinate(i, Math.fma(sliceRatio, getSize(i, true), getLow(i)));
         }
         for (int i=0; i<dimensionsToKeep.length; i++) {
-            slicePoint.setCoordinate(dimensionsToKeep[i], Double.NaN);
+            slicePoint.setOrdinate(dimensionsToKeep[i], Double.NaN);
         }
         return slice(slicePoint, null);
     }
@@ -1761,7 +1719,7 @@
         final int n = slicePoint.getDimension();
         final int m = getDimension();
         for (int k=0; k<n; k++) {
-            double p = slicePoint.getCoordinate(k);
+            double p = slicePoint.getOrdinate(k);
             if (!Double.isNaN(p)) {
                 final long c = Math.round(p);
                 final int i = (modifiedDimensions != null) ? modifiedDimensions[k] : k;
@@ -1773,7 +1731,7 @@
                     final StringBuilder b = new StringBuilder();
                     for (int j=0; j<n; j++) {
                         if (j != 0) b.append(", ");
-                        p = slicePoint.getCoordinate(j);
+                        p = slicePoint.getOrdinate(j);
                         if (Double.isNaN(p)) b.append("NaN");
                         else b.append(Math.round(p));
                     }
@@ -1974,7 +1932,7 @@
         final int n = coordinates.length;
         final int m = n >>> 1;
         if (n != other.coordinates.length) {
-            throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+            throw new MismatchedDimensionException(Errors.format(
                     Errors.Keys.MismatchedDimension_3, param, m, other.getDimension()));
         }
         // First condition below is a fast check for a common case.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtentCRS.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtentCRS.java
index ce159e2..0db5fec 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtentCRS.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtentCRS.java
@@ -61,9 +61,6 @@
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-
 
 /**
  * Builder for coordinate reference system which is derived from the coverage CRS by the inverse
@@ -183,9 +180,9 @@
         final var properties = new HashMap<String,Object>(8);
         properties.put(IdentifiedObject.NAME_KEY, METHOD.getName());
         properties.put(DefaultConversion.LOCALE_KEY, locale);
-        properties.put(ObjectDomain.SCOPE_KEY, SCOPE);
+        properties.put(Conversion.SCOPE_KEY, SCOPE);
         gg.getGeographicExtent().ifPresent((domain) -> {
-            properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY,
+            properties.put(Conversion.DOMAIN_OF_VALIDITY_KEY,
                     new DefaultExtent(null, domain, null, null));
         });
         final ParameterValueGroup params = METHOD.getParameters().createValue();
@@ -298,7 +295,7 @@
                     abbreviation = "t"; direction = AxisDirection.FUTURE; hasTime = true;
                 } else {
                     abbreviation = abbreviation(target);
-                    direction = AxisDirection.UNSPECIFIED;
+                    direction = AxisDirections.UNSPECIFIED;
                     hasOther = true;
                 }
                 /*
@@ -310,7 +307,7 @@
                     final CoordinateSystemAxis previous = axes[k];
                     if (previous != null) {
                         if (direction.equals(AxisDirections.absolute(previous.getDirection()))) {
-                            direction = AxisDirection.UNSPECIFIED;
+                            direction = AxisDirections.UNSPECIFIED;
                             hasOther = true;
                         }
                         if (abbreviation.equals(previous.getAbbreviation())) {
@@ -333,7 +330,7 @@
             if (axes[j] == null) {
                 final String name = Vocabulary.forLocale(locale).getString(Vocabulary.Keys.Dimension_1, j);
                 final String abbreviation = abbreviation(j);
-                axes[j] = axis(csFactory, name, abbreviation, AxisDirection.UNSPECIFIED);
+                axes[j] = axis(csFactory, name, abbreviation, AxisDirections.UNSPECIFIED);
             }
         }
         /*
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index d563bec..ec9b623 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
@@ -83,8 +83,8 @@
 import org.apache.sis.xml.NilReason;
 import static org.apache.sis.referencing.CRS.findOperation;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -570,7 +570,7 @@
         if (extent != null) {
             final int dimension = extent.getDimension();
             if (dimension != expected) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, "extent", expected, dimension));
             }
         }
@@ -1061,7 +1061,7 @@
             }
             return env;
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(null, e);
         }
         throw incomplete(bitmask, errorKey);
     }
@@ -1698,7 +1698,7 @@
         try {
             tr = finder.inverse();
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
         return MathTransforms.concatenate(getGridToCRS(anchor), tr);
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java
index 0e94ee0..cd33d04 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.coverage.grid;
 
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.metadata.spatial.PixelOrientation;
 
 
@@ -32,7 +31,7 @@
  *
  * @since 1.5
  */
-public enum PixelInCell implements ControlledVocabulary {
+public enum PixelInCell {
     /**
      * "Real world" coordinates give the location of the cell center.
      *
@@ -100,7 +99,6 @@
      *
      * @return the legacy ISO/OGC identifier for this constant.
      */
-    @Override
     public String identifier() {
         return identifier;
     }
@@ -111,19 +109,7 @@
      *
      * @return all names of this constant. This array is never null and never empty.
      */
-    @Override
     public String[] names() {
         return new String[] {name(), identifier};
     }
-
-    /**
-     * Returns the enumeration of the same kind as this item.
-     * This is equivalent to {@link #values()}.
-     *
-     * @return the enumeration of the same kind as this item.
-     */
-    @Override
-    public ControlledVocabulary[] family() {
-        return values();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ResampledGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 6487144..cda0c4d 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -40,8 +40,8 @@
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
index 9747ebb..40edc87 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
@@ -18,8 +18,8 @@
 
 import java.awt.image.RenderedImage;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/CommonDomainFinder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/CommonDomainFinder.java
index b2527b9..d87d5f8 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/CommonDomainFinder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/CommonDomainFinder.java
@@ -36,8 +36,8 @@
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.privy.Numerics;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -166,7 +166,7 @@
                 return;
             }
             // Arbitrary criterion (may be revisited in any future version).
-            if (fallback == null && expected.getLow().equals(actual.getLow())) {
+            if (fallback == null && sameLow(expected, actual)) {
                 location = expected;
                 fallback = item;
             }
@@ -183,6 +183,15 @@
         }
     }
 
+    private static boolean sameLow(final GridExtent e1, final GridExtent e2) {
+        final int dim = e1.getDimension();
+        if (dim != e2.getDimension()) return false;
+        for (int i=0; i<dim; i++) {
+            if (e1.getLow(i) != e2.getLow(i)) return false;
+        }
+        return true;
+    }
+
     /**
      * Given a grid geometry with the desired "grid to CRS", saves its index in {@link #sourceOfGridToCRS}.
      * This method updates all previously computed translations for making them relative to the new reference.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAssociation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAssociation.java
index eec56f1..8f27632 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAssociation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAssociation.java
@@ -23,14 +23,6 @@
 import org.opengis.metadata.quality.DataQuality;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole feature association role} containing the associated feature.
@@ -53,7 +45,7 @@
  *
  * @since 0.5
  */
-public abstract class AbstractAssociation extends Field<Feature> implements FeatureAssociation, Cloneable, Serializable {
+public abstract class AbstractAssociation extends Field<AbstractFeature> implements Cloneable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -62,17 +54,16 @@
     /**
      * Information about the association.
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final FeatureAssociationRole role;
+    final DefaultAssociationRole role;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      *
-     * @see #create(FeatureAssociationRole)
+     * @see #create(DefaultAssociationRole)
      */
-    protected AbstractAssociation(final FeatureAssociationRole role) {
+    protected AbstractAssociation(final DefaultAssociationRole role) {
         this.role = role;
     }
 
@@ -84,7 +75,7 @@
      *
      * @see DefaultAssociationRole#newInstance()
      */
-    public static AbstractAssociation create(final FeatureAssociationRole role) {
+    public static AbstractAssociation create(final DefaultAssociationRole role) {
         return isSingleton(role.getMaximumOccurs())
                ? new SingletonAssociation(role)
                : new MultiValuedAssociation(role);
@@ -97,15 +88,15 @@
      * @param  value  the initial value (may be {@code null}).
      * @return the new association.
      */
-    static AbstractAssociation create(final FeatureAssociationRole role, final Object value) {
+    static AbstractAssociation create(final DefaultAssociationRole role, final Object value) {
         return isSingleton(role.getMaximumOccurs())
-               ? new SingletonAssociation(role, (Feature) value)
+               ? new SingletonAssociation(role, (AbstractFeature) value)
                : new MultiValuedAssociation(role, value);
     }
 
     /**
      * Returns the name of this association as defined by its {@linkplain #getRole() role}.
-     * This convenience method delegates to {@link FeatureAssociationRole#getName()}.
+     * This convenience method delegates to {@link DefaultAssociationRole#getName()}.
      *
      * @return the association name specified by its role.
      */
@@ -117,10 +108,12 @@
     /**
      * Returns information about the association.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.AssociationRole}. This change is pending GeoAPI revision.</div>
+     *
      * @return information about the association.
      */
-    @Override
-    public FeatureAssociationRole getRole() {
+    public DefaultAssociationRole getRole() {
         return role;
     }
 
@@ -129,13 +122,16 @@
      * the common case where the {@linkplain DefaultAssociationRole#getMaximumOccurs() maximum number} of
      * features is restricted to 1 or 0.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.Feature}. This change is pending GeoAPI revision.</div>
+     *
      * @return the associated feature (may be {@code null}).
-     * @throws MultiValuedPropertyException if this association contains more than one value.
+     * @throws IllegalStateException if this association contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract Feature getValue() throws MultiValuedPropertyException;
+    public abstract AbstractFeature getValue() throws IllegalStateException;
 
     /**
      * Returns all features, or an empty collection if none.
@@ -148,13 +144,16 @@
      * @return the features in a <em>live</em> collection.
      */
     @Override
-    public Collection<Feature> getValues() {
+    public Collection<AbstractFeature> getValues() {
         return super.getValues();
     }
 
     /**
      * Sets the associated feature.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument type may be changed
+     * to {@code org.opengis.feature.Feature}. This change is pending GeoAPI revision.</div>
+     *
      * <h4>Validation</h4>
      * The number of validations performed by this method is implementation dependent.
      * Usually, only the most basic constraints are verified. This is so for performance reasons
@@ -162,24 +161,24 @@
      * A more exhaustive verification can be performed by invoking the {@link #quality()} method.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
+     * @throws IllegalArgumentException if the given feature is not valid for this association.
      *
      * @see AbstractFeature#setPropertyValue(String, Object)
      */
     @Override
-    public abstract void setValue(final Feature value) throws InvalidPropertyValueException;
+    public abstract void setValue(final AbstractFeature value) throws IllegalArgumentException;
 
     /**
      * Sets the features. All previous values are replaced by the given collection.
      *
      * <p>The default implementation ensures that the given collection contains at most one element,
-     * then delegates to {@link #setValue(Feature)}.</p>
+     * then delegates to {@link #setValue(AbstractFeature)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends Feature> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends AbstractFeature> values) throws IllegalArgumentException {
         super.setValues(values);
     }
 
@@ -187,9 +186,9 @@
      * Ensures that storing a feature of the given type is valid for an association
      * expecting the given base type.
      */
-    final void ensureValid(final FeatureType base, final FeatureType type) {
+    final void ensureValid(final DefaultFeatureType base, final DefaultFeatureType type) {
         if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-            throw new InvalidPropertyValueException(
+            throw new IllegalArgumentException(
                     Resources.format(Resources.Keys.IllegalFeatureType_3, getName(), base.getName(), type.getName()));
         }
     }
@@ -224,7 +223,7 @@
     @Override
     public String toString() {
         final String pt = DefaultAssociationRole.getTitleProperty(role);
-        final Iterator<Feature> it = getValues().iterator();
+        final Iterator<AbstractFeature> it = getValues().iterator();
         return FieldType.toString(isDeprecated(role), "FeatureAssociation", role.getName(),
                 DefaultAssociationRole.getValueTypeName(role), new Iterator<Object>()
         {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAttribute.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAttribute.java
index 819d50c..2193353 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAttribute.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractAttribute.java
@@ -29,12 +29,6 @@
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.util.Classes;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAttributeType attribute type} containing the value of an attribute in a feature.
@@ -76,7 +70,7 @@
  *
  * @since 0.5
  */
-public abstract class AbstractAttribute<V> extends Field<V> implements Attribute<V>, Serializable {
+public abstract class AbstractAttribute<V> extends Field<V> implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -85,8 +79,7 @@
     /**
      * Information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    @SuppressWarnings("serial")     // Most SIS implementations are serializable.
-    final AttributeType<V> type;
+    final DefaultAttributeType<V> type;
 
     /**
      * Other attributes that describes this attribute, or {@code null} if not yet created.
@@ -101,16 +94,16 @@
      *
      * @see #characteristics()
      */
-    private transient Map<String,Attribute<?>> characteristics;
+    private transient Map<String,AbstractAttribute<?>> characteristics;
 
     /**
      * Creates a new attribute of the given type.
      *
      * @param type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      *
-     * @see #create(AttributeType)
+     * @see #create(DefaultAttributeType)
      */
-    protected AbstractAttribute(final AttributeType<V> type) {
+    protected AbstractAttribute(final DefaultAttributeType<V> type) {
         this.type = type;
     }
 
@@ -124,7 +117,7 @@
      *
      * @see DefaultAttributeType#newInstance()
      */
-    public static <V> AbstractAttribute<V> create(final AttributeType<V> type) {
+    public static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type) {
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type)
                : new MultiValuedAttribute<>(type);
@@ -139,7 +132,7 @@
      * @param  value  the initial value (may be {@code null}).
      * @return the new attribute.
      */
-    static <V> AbstractAttribute<V> create(final AttributeType<V> type, final Object value) {
+    static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type, final Object value) {
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type, value)
                : new MultiValuedAttribute<>(type, value);
@@ -153,9 +146,9 @@
      */
     private void writeObject(final ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
-        final Attribute<?>[] characterizedBy;
+        final AbstractAttribute<?>[] characterizedBy;
         if (characteristics instanceof CharacteristicMap) {
-            characterizedBy = characteristics.values().toArray(Attribute[]::new);
+            characterizedBy = characteristics.values().toArray(AbstractAttribute[]::new);
         } else {
             characterizedBy = null;
         }
@@ -172,7 +165,7 @@
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final Attribute<?>[] characterizedBy = (Attribute<?>[]) in.readObject();
+            final AbstractAttribute<?>[] characterizedBy = (AbstractAttribute<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = newCharacteristicsMap();
                 Collections.addAll(characteristics.values(), characterizedBy);
@@ -185,7 +178,7 @@
 
     /**
      * Returns the name of this attribute as defined by its {@linkplain #getType() type}.
-     * This convenience method delegates to {@link AttributeType#getName()}.
+     * This convenience method delegates to {@link DefaultAttributeType#getName()}.
      *
      * @return the attribute name specified by its type.
      */
@@ -197,10 +190,12 @@
     /**
      * Returns information about the attribute (base Java class, domain of values, <i>etc.</i>).
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.AttributeType}. This change is pending GeoAPI revision.</div>
+     *
      * @return information about the attribute.
      */
-    @Override
-    public AttributeType<V> getType() {
+    public DefaultAttributeType<V> getType() {
         return type;
     }
 
@@ -210,12 +205,12 @@
      * of attribute values is restricted to 1 or 0.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this attribute contains more than one value.
+     * @throws IllegalStateException if this attribute contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws MultiValuedPropertyException;
+    public abstract V getValue() throws IllegalStateException;
 
     /**
      * Returns all attribute values, or an empty collection if none.
@@ -242,13 +237,13 @@
      * A more exhaustive verification can be performed by invoking the {@link #quality()} method.
      *
      * @param  value  the new value, or {@code null} for removing all values from this attribute.
-     * @throws InvalidPropertyValueException if this method verifies argument validity and the given value
+     * @throws IllegalArgumentException if this method verifies argument validity and the given value
      *         does not met the attribute constraints.
      *
      * @see AbstractFeature#setPropertyValue(String, Object)
      */
     @Override
-    public abstract void setValue(final V value) throws InvalidPropertyValueException;
+    public abstract void setValue(final V value) throws IllegalArgumentException;
 
     /**
      * Sets the attribute values. All previous values are replaced by the given collection.
@@ -257,10 +252,10 @@
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
         super.setValues(values);
     }
 
@@ -339,9 +334,8 @@
      *
      * @see DefaultAttributeType#characteristics()
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Map<String,Attribute<?>> characteristics() {
+    public Map<String,AbstractAttribute<?>> characteristics() {
         if (characteristics == null) {
             characteristics = newCharacteristicsMap();
         }
@@ -353,13 +347,13 @@
      * This method does not store the new map in the {@link #characteristics} field;
      * it is caller responsibility to do so if desired.
      */
-    private Map<String,Attribute<?>> newCharacteristicsMap() {
+    private Map<String,AbstractAttribute<?>> newCharacteristicsMap() {
         if (type instanceof DefaultAttributeType<?>) {
-            Map<String, AttributeType<?>> map = ((DefaultAttributeType<?>) type).characteristics();
+            Map<String, DefaultAttributeType<?>> map = type.characteristics();
             if (!map.isEmpty()) {
                 if (!(map instanceof CharacteristicTypeMap)) {
-                    final Collection<AttributeType<?>> types = map.values();
-                    map = CharacteristicTypeMap.create(type, types.toArray(AttributeType<?>[]::new));
+                    final Collection<DefaultAttributeType<?>> types = map.values();
+                    map = CharacteristicTypeMap.create(type, types.toArray(DefaultAttributeType<?>[]::new));
                 }
                 return new CharacteristicMap(this, (CharacteristicTypeMap) map);
             }
@@ -372,7 +366,7 @@
      * Contrarily to {@link #characteristics()}, this method does not create the map. This method
      * is suitable when then caller only wants to read the map and does not plan to write anything.
      */
-    final Map<String,Attribute<?>> characteristicsReadOnly() {
+    final Map<String,AbstractAttribute<?>> characteristicsReadOnly() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -467,7 +461,7 @@
         if (characteristics != null && !characteristics.isEmpty()) {
             buffer.append(System.lineSeparator());
             String separator = "└─ characteristics: ";
-            for (final Map.Entry<String,Attribute<?>> entry : characteristics.entrySet()) {
+            for (final Map.Entry<String,AbstractAttribute<?>> entry : characteristics.entrySet()) {
                 buffer.append(separator).append(entry.getKey()).append('=').append(entry.getValue().getValue());
                 separator = ", ";
             }
@@ -492,7 +486,7 @@
     @SuppressWarnings({"CloneInNonCloneableClass", "unchecked"})
     public AbstractAttribute<V> clone() throws CloneNotSupportedException {
         final AbstractAttribute<V> clone = (AbstractAttribute<V>) super.clone();
-        final Map<String,Attribute<?>> c = clone.characteristics;
+        final Map<String,AbstractAttribute<?>> c = clone.characteristics;
         if (c instanceof CharacteristicMap) {
             clone.characteristics = ((CharacteristicMap) c).clone();
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
index c073f80..14f7691 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
@@ -33,20 +33,6 @@
 import org.apache.sis.util.privy.CheckedArrayList;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-
 
 /**
  * An instance of a {@linkplain DefaultFeatureType feature type} containing values for a real-world phenomena.
@@ -68,8 +54,8 @@
  * </ul>
  *
  * <h2>Operations</h2>
- * Properties that are instances of {@link Operation} are usually not stored in {@code Feature} instances.
- * Instead, the {@link Operation#apply Operation.apply(…)} method is invoked every times that the property
+ * Properties that are instances of {@code Operation} are usually not stored in {@code Feature} instances.
+ * Instead, the {@link AbstractOperation#apply Operation.apply(…)} method is invoked every times that the property
  * value is requested. {@code AbstractFeature} does not cache operation results.
  * Those results are usually read-only, but may be writable under the conditions documented in
  * {@link #setOperationValue(String, Object)}.
@@ -92,7 +78,7 @@
  *
  * @since 0.5
  */
-public abstract class AbstractFeature implements Feature, Serializable {
+public abstract class AbstractFeature implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -108,8 +94,7 @@
     /**
      * Information about the feature (name, characteristics, <i>etc.</i>).
      */
-    @SuppressWarnings("serial")     // Most SIS implementations are serializable.
-    final FeatureType type;
+    final DefaultFeatureType type;
 
     /**
      * Creates a new feature of the given type.
@@ -118,7 +103,7 @@
      *
      * @see DefaultFeatureType#newInstance()
      */
-    protected AbstractFeature(final FeatureType type) {
+    protected AbstractFeature(final DefaultFeatureType type) {
         this.type = Objects.requireNonNull(type);
     }
 
@@ -133,10 +118,12 @@
     /**
      * Returns information about the feature (name, characteristics, <i>etc.</i>).
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.FeatureType}. This change is pending GeoAPI revision.</div>
+     *
      * @return information about the feature.
      */
-    @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -161,15 +148,17 @@
      * Implementers are encouraged to override this method if they can provide a more efficient implementation.
      * Note that this is already the case when using implementations created by {@link DefaultFeatureType#newInstance()}.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
+     *
      * @param  name  the property name.
      * @return the property of the given name (never {@code null}).
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      *
      * @see #getPropertyValue(String)
      * @see DefaultFeatureType#getProperty(String)
      */
-    @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         return PropertyView.create(this, type.getProperty(name));
     }
 
@@ -204,21 +193,23 @@
      * Implementers are encouraged to override this method if they can provide a better implementation.
      * Note that this is already the case when using implementations created by {@link DefaultFeatureType#newInstance()}.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument may be changed
+     * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
+     *
      * @param  property  the property to set.
-     * @throws PropertyNotFoundException if the name of the given property is not a property name of this feature.
-     * @throws InvalidPropertyValueException if the value of the given property is not valid.
+     * @throws IllegalArgumentException if the name of the given property is not a property name of this feature.
+     * @throws IllegalArgumentException if the value of the given property is not valid.
      * @throws IllegalArgumentException if the property cannot be set for another reason.
      *
      * @see #setPropertyValue(String, Object)
      */
-    @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
-        if (property instanceof Attribute<?> && !Containers.isNullOrEmpty(((Attribute<?>) property).characteristics())) {
+    public void setProperty(final Object property) throws IllegalArgumentException {
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
+        if (property instanceof AbstractAttribute<?> && !Containers.isNullOrEmpty(((AbstractAttribute<?>) property).characteristics())) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.CanNotAssignCharacteristics_1, name));
         }
-        setPropertyValue(name, property.getValue());
+        setPropertyValue(name, ((Property) property).getValue());
     }
 
     /**
@@ -230,11 +221,11 @@
      * @return a {@code Property} wrapping the given value.
      */
     final Property createProperty(final String name, final Object value) {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
-            return AbstractAttribute.create((AttributeType<?>) pt, value);
-        } else if (pt instanceof FeatureAssociationRole) {
-            return AbstractAssociation.create((FeatureAssociationRole) pt, value);
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return AbstractAttribute.create((DefaultAttributeType<?>) pt, value);
+        } else if (pt instanceof DefaultAssociationRole) {
+            return AbstractAssociation.create((DefaultAssociationRole) pt, value);
         } else {
             // Should never happen, unless the user gave us some mutable FeatureType.
             throw new CorruptedObjectException(Errors.format(Errors.Keys.UnknownType_1, pt));
@@ -246,15 +237,15 @@
      *
      * @param  name  the name of the property to create.
      * @return a {@code Property} of the given name.
-     * @throws PropertyNotFoundException if the given argument is not the name of an attribute or
+     * @throws IllegalArgumentException if the given argument is not the name of an attribute or
      *         feature association of this feature.
      */
-    final Property createProperty(final String name) throws PropertyNotFoundException {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
-            return ((AttributeType<?>) pt).newInstance();
-        } else if (pt instanceof FeatureAssociationRole) {
-            return ((FeatureAssociationRole) pt).newInstance();
+    final Property createProperty(final String name) throws IllegalArgumentException {
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return ((DefaultAttributeType<?>) pt).newInstance();
+        } else if (pt instanceof DefaultAssociationRole) {
+            return ((DefaultAssociationRole) pt).newInstance();
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
         }
@@ -265,14 +256,14 @@
      *
      * @see #getOperationValue(String)
      */
-    final Property getOperationResult(final String name) {
+    final Object getOperationResult(final String name) {
         /*
          * The (Operation) cast below should never fail (unless the DefaultFeatureType in not really immutable,
          * which would be a contract violation) because all callers shall ensure that this method is invoked in
          * a context where the following assertion holds.
          */
-        assert DefaultFeatureType.OPERATION_INDEX.equals(((DefaultFeatureType) type).indices().get(name)) : name;
-        return ((Operation) type.getProperty(name)).apply(this, null);
+        assert DefaultFeatureType.OPERATION_INDEX.equals(type.indices().get(name)) : name;
+        return ((AbstractOperation) type.getProperty(name)).apply(this, null);
     }
 
     /**
@@ -281,14 +272,14 @@
      *
      * @param  name  the name of the property for which to get the default value.
      * @return the default value for the {@code Property} of the given name.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
-    final Object getDefaultValue(final String name) throws PropertyNotFoundException {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
-            return getDefaultValue((AttributeType<?>) pt);
-        } else if (pt instanceof FeatureAssociationRole) {
-            final int maximumOccurs = ((FeatureAssociationRole) pt).getMaximumOccurs();
+    final Object getDefaultValue(final String name) throws IllegalArgumentException {
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return getDefaultValue((DefaultAttributeType<?>) pt);
+        } else if (pt instanceof DefaultAssociationRole) {
+            final int maximumOccurs = ((DefaultAssociationRole) pt).getMaximumOccurs();
             return maximumOccurs > 1 ? Collections.EMPTY_LIST : null;       // No default value for associations.
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -298,7 +289,7 @@
     /**
      * Returns the default value to be returned by {@link #getPropertyValue(String)} for the given attribute type.
      */
-    private static <V> Object getDefaultValue(final AttributeType<V> attribute) {
+    private static <V> Object getDefaultValue(final DefaultAttributeType<V> attribute) {
         final V defaultValue = attribute.getDefaultValue();
         if (Field.isSingleton(attribute.getMaximumOccurs())) {
             return defaultValue;
@@ -319,10 +310,10 @@
      * <table class="sis">
      *   <caption>Class of returned value</caption>
      *   <tr><th>Property type</th>                  <th>max. occurs</th> <th>Method invoked</th>                         <th>Return type</th></tr>
-     *   <tr><td>{@link AttributeType}</td>          <td>0 or 1</td>      <td>{@link Attribute#getValue()}</td>           <td>{@link Object}</td></tr>
-     *   <tr><td>{@code AttributeType}</td>          <td>2 or more</td>   <td>{@link Attribute#getValues()}</td>          <td>{@code Collection<?>}</td></tr>
-     *   <tr><td>{@link FeatureAssociationRole}</td> <td>0 or 1</td>      <td>{@link FeatureAssociation#getValue()}</td>  <td>{@link Feature}</td></tr>
-     *   <tr><td>{@code FeatureAssociationRole}</td> <td>2 or more</td>   <td>{@link FeatureAssociation#getValues()}</td> <td>{@code Collection<Feature>}</td></tr>
+     *   <tr><td>{@code AttributeType}</td>          <td>0 or 1</td>      <td>{@code Attribute.getValue()}</td>           <td>{@link Object}</td></tr>
+     *   <tr><td>{@code AttributeType}</td>          <td>2 or more</td>   <td>{@code Attribute.getValues()}</td>          <td>{@code Collection<?>}</td></tr>
+     *   <tr><td>{@code FeatureAssociationRole}</td> <td>0 or 1</td>      <td>{@code FeatureAssociation.getValue()}</td>  <td>{@code Feature}</td></tr>
+     *   <tr><td>{@code FeatureAssociationRole}</td> <td>2 or more</td>   <td>{@code FeatureAssociation.getValues()}</td> <td>{@code Collection<Feature>}</td></tr>
      * </table>
      *
      * <div class="note"><b>Note:</b> “max. occurs” is the {@linkplain DefaultAttributeType#getMaximumOccurs() maximum
@@ -344,12 +335,11 @@
      * @param  name  the property name.
      * @return value of the specified property, or the
      *         {@linkplain DefaultAttributeType#getDefaultValue() default value} (which may be {@code null}} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      *
      * @see AbstractAttribute#getValue()
      */
-    @Override
-    public abstract Object getPropertyValue(final String name) throws PropertyNotFoundException;
+    public abstract Object getPropertyValue(final String name) throws IllegalArgumentException;
 
     /**
      * Sets the value for the property of the given name.
@@ -362,13 +352,12 @@
      *
      * @param  name   the attribute name.
      * @param  value  the new value for the given attribute (may be {@code null}).
-     * @throws PropertyNotFoundException if the given name is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given name is not an attribute or association name of this feature.
      * @throws ClassCastException if the value is not assignable to the expected value class.
-     * @throws InvalidPropertyValueException if the given value is not valid for a reason other than its type.
+     * @throws IllegalArgumentException if the given value is not valid for a reason other than its type.
      *
      * @see AbstractAttribute#setValue(Object)
      */
-    @Override
     public abstract void setPropertyValue(final String name, final Object value) throws IllegalArgumentException;
 
     /**
@@ -395,7 +384,6 @@
      *
      * @since 1.1
      */
-    @Override
     public abstract Object getValueOrFallback(final String name, Object missingPropertyFallback);
 
     /**
@@ -420,21 +408,21 @@
      * }
      *
      * @param  name  the name of the operation to execute. The caller is responsible to ensure that the
-     *               property type for that name is an instance of {@link Operation}.
+     *               property type for that name is an instance of {@link AbstractOperation}.
      * @return the result value of the given operation, or {@code null} if none.
      *
      * @since 0.8
      */
     protected Object getOperationValue(final String name) {
-        final Operation operation = (Operation) type.getProperty(name);
+        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             return getPropertyValue(((LinkOperation) operation).referentName);
         }
-        final Property result = operation.apply(this, null);
-        if (result instanceof Attribute<?>) {
-            return getAttributeValue((Attribute<?>) result);
-        } else if (result instanceof FeatureAssociation) {
-            return getAssociationValue((FeatureAssociation) result);
+        final Object result = operation.apply(this, null);
+        if (result instanceof AbstractAttribute<?>) {
+            return getAttributeValue((AbstractAttribute<?>) result);
+        } else if (result instanceof AbstractAssociation) {
+            return getAssociationValue((AbstractAssociation) result);
         } else {
             return null;
         }
@@ -447,23 +435,23 @@
      * but the {@linkplain FeatureOperations#link link} and
      * {@linkplain FeatureOperations#compound compound} operations (for instances) do.
      * Whether an operation is writable or not depends on whether the computed {@link Property}
-     * supports {@link Attribute#setValue(Object)} or {@link FeatureAssociation#setValue(Feature)}.
+     * supports {@link AbstractAttribute#setValue(Object)} or {@link AbstractAssociation#setValue(Feature)}.
      *
      * @param  name   the name of the operation to execute. The caller is responsible to ensure that the
-     *                property type for that name is an instance of {@link Operation}.
+     *                property type for that name is an instance of {@link AbstractOperation}.
      * @param  value  the value to assign to the result of the named operation.
      * @throws IllegalStateException if the operation of the given name does not accept assignment.
      *
      * @since 0.8
      */
     protected void setOperationValue(final String name, final Object value) {
-        final Operation operation = (Operation) type.getProperty(name);
+        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             setPropertyValue(((LinkOperation) operation).referentName, value);
         } else {
-            final Property result = operation.apply(this, null);
-            if (result != null) {
-                setPropertyValue(result, value);
+            final Object result = operation.apply(this, null);
+            if (result instanceof Property) {
+                setPropertyValue((Property) result, value);
             } else {
                 throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSetPropertyValue_1, name));
             }
@@ -474,7 +462,7 @@
      * Returns the value of the given attribute, as a singleton or as a collection depending
      * on the maximum number of occurrences.
      */
-    static Object getAttributeValue(final Attribute<?> property) {
+    static Object getAttributeValue(final AbstractAttribute<?> property) {
         return Field.isSingleton(property.getType().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -482,7 +470,7 @@
      * Returns the value of the given association, as a singleton or as a collection depending
      * on the maximum number of occurrences.
      */
-    static Object getAssociationValue(final FeatureAssociation property) {
+    static Object getAssociationValue(final AbstractAssociation property) {
         return Field.isSingleton(property.getRole().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -490,10 +478,10 @@
      * Sets the value of the given property, with some minimal checks.
      */
     static void setPropertyValue(final Property property, final Object value) {
-        if (property instanceof Attribute<?>) {
-            setAttributeValue((Attribute<?>) property, value);
-        } else if (property instanceof FeatureAssociation) {
-            setAssociationValue((FeatureAssociation) property, value);
+        if (property instanceof AbstractAttribute<?>) {
+            setAttributeValue((AbstractAttribute<?>) property, value);
+        } else if (property instanceof AbstractAssociation) {
+            setAssociationValue((AbstractAssociation) property, value);
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
         }
@@ -505,9 +493,9 @@
      * use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static <V> void setAttributeValue(final Attribute<V> attribute, final Object value) {
+    private static <V> void setAttributeValue(final AbstractAttribute<V> attribute, final Object value) {
         if (value != null) {
-            final AttributeType<V> pt = attribute.getType();
+            final DefaultAttributeType<V> pt = attribute.getType();
             final Class<?> base = pt.getValueClass();
             if (!base.isInstance(value)) {
                 Object element = value;
@@ -518,7 +506,7 @@
                      */
                     final Iterator<?> it = ((Collection<?>) value).iterator();
                     do if (!it.hasNext()) {
-                        ((Attribute) attribute).setValues((Collection) value);
+                        ((AbstractAttribute) attribute).setValues((Collection) value);
                         return;
                     } while ((element = it.next()) == null || base.isInstance(element));
                     // Found an illegal value. Exeption is thrown below.
@@ -526,7 +514,7 @@
                 throw new ClassCastException(illegalValueClass(pt, base, element));         // `element` cannot be null here.
             }
         }
-        ((Attribute) attribute).setValue(value);
+        ((AbstractAttribute) attribute).setValue(value);
     }
 
     /**
@@ -534,24 +522,24 @@
      * For a more exhaustive validation, use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static void setAssociationValue(final FeatureAssociation association, final Object value) {
+    private static void setAssociationValue(final AbstractAssociation association, final Object value) {
         if (value != null) {
-            final FeatureAssociationRole role = association.getRole();
-            final FeatureType base = role.getValueType();
-            if (value instanceof Feature) {
-                final FeatureType actual = ((Feature) value).getType();
+            final DefaultAssociationRole role = association.getRole();
+            final DefaultFeatureType base = role.getValueType();
+            if (value instanceof AbstractFeature) {
+                final DefaultFeatureType actual = ((AbstractFeature) value).getType();
                 if (base != actual && !DefaultFeatureType.maybeAssignableFrom(base, actual)) {
-                    throw new InvalidPropertyValueException(illegalFeatureType(role, base, actual));
+                    throw new IllegalArgumentException(illegalFeatureType(role, base, actual));
                 }
             } else if (value instanceof Collection<?>) {
                 verifyAssociationValues(role, (Collection<?>) value);
-                association.setValues((Collection<? extends Feature>) value);
+                association.setValues((Collection<? extends AbstractFeature>) value);
                 return;                                 // Skip the setter at the end of this method.
             } else {
-                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
             }
         }
-        association.setValue((Feature) value);
+        association.setValue((AbstractFeature) value);
     }
 
     /**
@@ -569,7 +557,7 @@
             if (value == null) {
                 return true;
             }
-            if (previous.getClass() == value.getClass() && !(value instanceof Feature)) {
+            if (previous.getClass() == value.getClass() && !(value instanceof AbstractFeature)) {
                 return true;
             }
         }
@@ -583,19 +571,19 @@
      * @param  property  the property to verify.
      */
     final void verifyPropertyType(final String name, final Property property) {
-        final PropertyType pt, base = type.getProperty(name);
-        if (property instanceof Attribute<?>) {
-            pt = ((Attribute<?>) property).getType();
-        } else if (property instanceof FeatureAssociation) {
-            pt = ((FeatureAssociation) property).getRole();
+        final AbstractIdentifiedType pt, base = type.getProperty(name);
+        if (property instanceof AbstractAttribute<?>) {
+            pt = ((AbstractAttribute<?>) property).getType();
+        } else if (property instanceof AbstractAssociation) {
+            pt = ((AbstractAssociation) property).getRole();
         } else {
-            throw new InvalidPropertyValueException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
+            throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
         }
         if (pt != base) {
             if (base == null) {
-                throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
             } else {
-                throw new InvalidPropertyValueException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
             }
         }
     }
@@ -605,14 +593,14 @@
      * The returned value is usually the same as the given one, except in the case of collections.
      */
     final Object verifyPropertyValue(final String name, final Object value) {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
             if (value != null) {
-                return verifyAttributeValue((AttributeType<?>) pt, value);
+                return verifyAttributeValue((DefaultAttributeType<?>) pt, value);
             }
-        } else if (pt instanceof FeatureAssociationRole) {
+        } else if (pt instanceof DefaultAssociationRole) {
             if (value != null) {
-                return verifyAssociationValue((FeatureAssociationRole) pt, value);
+                return verifyAssociationValue((DefaultAssociationRole) pt, value);
             }
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -630,7 +618,7 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static <T> Object verifyAttributeValue(final AttributeType<T> type, final Object value) {
+    private static <T> Object verifyAttributeValue(final DefaultAttributeType<T> type, final Object value) {
         final Class<T> valueClass = type.getValueClass();
         final boolean isSingleton = Field.isSingleton(type.getMaximumOccurs());
         if (valueClass.isInstance(value)) {
@@ -652,42 +640,42 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static Object verifyAssociationValue(final FeatureAssociationRole role, final Object value) {
+    private static Object verifyAssociationValue(final DefaultAssociationRole role, final Object value) {
         final boolean isSingleton = Field.isSingleton(role.getMaximumOccurs());
-        if (value instanceof Feature) {
+        if (value instanceof AbstractFeature) {
             /*
              * If the user gave us a single value, first verify its validity.
              * Then wrap it in a list of 1 element if this property is multi-valued.
              */
-            final FeatureType valueType = ((Feature) value).getType();
-            final FeatureType base = role.getValueType();
+            final DefaultFeatureType valueType = ((AbstractFeature) value).getType();
+            final DefaultFeatureType base = role.getValueType();
             if (base == valueType || DefaultFeatureType.maybeAssignableFrom(base, valueType)) {
-                return isSingleton ? value : singletonList(Feature.class, role.getMinimumOccurs(), value);
+                return isSingleton ? value : singletonList(AbstractFeature.class, role.getMinimumOccurs(), value);
             } else {
-                throw new InvalidPropertyValueException(illegalFeatureType(role, base, valueType));
+                throw new IllegalArgumentException(illegalFeatureType(role, base, valueType));
             }
         } else if (!isSingleton && value instanceof Collection<?>) {
             verifyAssociationValues(role, (Collection<?>) value);
-            return CheckedArrayList.castOrCopy((Collection<?>) value, Feature.class);
+            return CheckedArrayList.castOrCopy((Collection<?>) value, AbstractFeature.class);
         } else {
-            throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+            throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
         }
     }
 
     /**
      * Verifies if all values in the given collection are valid instances of feature for the given association role.
      */
-    private static void verifyAssociationValues(final FeatureAssociationRole role, final Collection<?> values) {
-        final FeatureType base = role.getValueType();
+    private static void verifyAssociationValues(final DefaultAssociationRole role, final Collection<?> values) {
+        final DefaultFeatureType base = role.getValueType();
         int index = 0;
         for (final Object value : values) {
             ArgumentChecks.ensureNonNullElement("values", index, value);
-            if (!(value instanceof Feature)) {
-                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+            if (!(value instanceof AbstractFeature)) {
+                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
             }
-            final FeatureType type = ((Feature) value).getType();
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
             if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-                throw new InvalidPropertyValueException(illegalFeatureType(role, base, type));
+                throw new IllegalArgumentException(illegalFeatureType(role, base, type));
             }
             index++;
         }
@@ -713,7 +701,7 @@
      */
     static String propertyNotFound(final FeatureType type, final Object feature, final String property) {
         GenericName ambiguous = null;
-        for (final IdentifiedType p : type.getProperties(true)) {
+        for (final AbstractIdentifiedType p : type.getProperties(true)) {
             final GenericName next = p.getName();
             GenericName name = next;
             do {
@@ -731,7 +719,7 @@
 
     /**
      * Returns the exception message for a property type which is neither an attribute or an association.
-     * This method is invoked after a {@link PropertyType} has been found for the user supplied name,
+     * This method is invoked after a {@code PropertyType} has been found for the user supplied name,
      * but that property cannot be stored in or extracted from a {@link Property} instance.
      */
     static String unsupportedPropertyType(final GenericName name) {
@@ -743,7 +731,7 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static String illegalValueClass(final IdentifiedType property, final Class<?> expected, final Object value) {
+    private static String illegalValueClass(final AbstractIdentifiedType property, final Class<?> expected, final Object value) {
         return Resources.format(Resources.Keys.IllegalPropertyValueClass_3,
                                 property.getName(), expected, value.getClass());
     }
@@ -752,7 +740,7 @@
      * Returns the exception message for an association value of wrong type.
      */
     private static String illegalFeatureType(
-            final FeatureAssociationRole association, final FeatureType expected, final FeatureType actual)
+            final DefaultAssociationRole association, final FeatureType expected, final FeatureType actual)
     {
         return Resources.format(Resources.Keys.IllegalFeatureType_3,
                                 association.getName(), expected.getName(), actual.getName());
@@ -883,7 +871,7 @@
     public int hashCode() {
         int code = type.hashCode() * 37;
         if (comparisonStart()) try {
-            for (final PropertyType pt : type.getProperties(true)) {
+            for (final AbstractIdentifiedType pt : type.getProperties(true)) {
                 final String name = pt.getName().toString();
                 if (name != null) {                                             // Paranoiac check.
                     final Object value = getPropertyValue(name);
@@ -931,7 +919,7 @@
                 return false;
             }
             if (comparisonStart()) try {
-                for (final PropertyType pt : type.getProperties(true)) {
+                for (final AbstractIdentifiedType pt : type.getProperties(true)) {
                     final String name = pt.getName().toString();
                     if (!Objects.equals(getPropertyValue(name), that.getPropertyValue(name))) {
                         return false;
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractIdentifiedType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractIdentifiedType.java
index 1d99dbb..a406296 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractIdentifiedType.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractIdentifiedType.java
@@ -31,18 +31,20 @@
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.IdentifiedType;
-
 
 /**
  * Identification and description information inherited by property types and feature types.
  *
+ * <div class="warning"><b>Warning:</b>
+ * This class is expected to implement a GeoAPI {@code IdentifiedType} interface in a future version.
+ * When such interface will be available, most references to {@code AbstractIdentifiedType} in the API
+ * will be replaced by references to the {@code IdentifiedType} interface.</div>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.5
  * @since   0.5
  */
-public class AbstractIdentifiedType implements IdentifiedType, Deprecable, Serializable {
+public class AbstractIdentifiedType implements Deprecable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -262,7 +264,6 @@
      *
      * @return the type name.
      */
-    @Override
     public final GenericName getName() {
         return name;
     }
@@ -272,7 +273,6 @@
      *
      * @return concise definition of the element.
      */
-    @Override
     public InternationalString getDefinition() {
         return definition;
     }
@@ -283,7 +283,6 @@
      *
      * @return natural language designator for the element.
      */
-    @Override
     public Optional<InternationalString> getDesignation() {
         return Optional.ofNullable(designation);
     }
@@ -297,7 +296,6 @@
      *
      * @return information beyond that required for concise definition of the element, or {@code null} if none.
      */
-    @Override
     public Optional<InternationalString> getDescription() {
         return Optional.ofNullable(description);
     }
@@ -387,7 +385,9 @@
      * @param  index      index of the characteristics having the given name.
      * @throws IllegalArgumentException if the given name is null or have an empty string representation.
      */
-    static String toString(final GenericName name, final IdentifiedType container, final String argument, final int index) {
+    static String toString(final GenericName name, final AbstractIdentifiedType container,
+            final String argument, final int index)
+    {
         short key = Errors.Keys.MissingValueForProperty_1;
         if (name != null) {
             final String s = name.toString();
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java
index 2ce24a1..24efcf6 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java
@@ -31,16 +31,6 @@
 import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.util.Classes;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureOperationException;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-
 
 /**
  * Describes the behaviour of a feature type as a function or a method.
@@ -54,8 +44,8 @@
  * <div class="note"><b>Example:</b> a mutator operation may raise the height of a dam. This changes
  * may affect other properties like the watercourse and the reservoir associated with the dam.</div>
  *
- * The value is computed, or the operation is executed, by {@link #apply(Feature, ParameterValueGroup)}.
- * If the value is modifiable, new value can be set by call to {@link Attribute#setValue(Object)}.
+ * The value is computed, or the operation is executed, by {@code apply(Feature, ParameterValueGroup)}.
+ * If the value is modifiable, new value can be set by call to {@code Attribute.setValue(Object)}.
  *
  * <div class="warning"><b>Warning:</b> this class is experimental and may change after we gained more
  * experience on this aspect of ISO 19109.</div>
@@ -67,8 +57,8 @@
  *
  * @since 0.6
  */
-public abstract class AbstractOperation extends AbstractIdentifiedType implements Operation,
-        BiFunction<Feature, ParameterValueGroup, Property>
+public abstract class AbstractOperation extends AbstractIdentifiedType
+        implements BiFunction<AbstractFeature, ParameterValueGroup, Object>
 {
     /**
      * For cross-version compatibility.
@@ -156,16 +146,17 @@
      *
      * @return description of the input parameters.
      */
-    @Override
     public abstract ParameterDescriptorGroup getParameters();
 
     /**
      * Returns the expected result type, or {@code null} if none.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.IdentifiedType}. This change is pending GeoAPI revision.</div>
+     *
      * @return the type of the result, or {@code null} if none.
      */
-    @Override
-    public abstract IdentifiedType getResult();
+    public abstract AbstractIdentifiedType getResult();
 
     /**
      * Executes the operation on the specified feature with the specified parameters.
@@ -174,11 +165,11 @@
      * <ul>
      *   <li>If {@code getResult()} returns {@code null},
      *       then this method should return {@code null}.</li>
-     *   <li>If {@code getResult()} returns an instance of {@link AttributeType},
-     *       then this method shall return an instance of {@link Attribute}
+     *   <li>If {@code getResult()} returns an instance of {@code AttributeType},
+     *       then this method shall return an instance of {@code Attribute}
      *       and the {@code Attribute.getType() == getResult()} relation should hold.</li>
-     *   <li>If {@code getResult()} returns an instance of {@link org.opengis.feature.FeatureAssociationRole},
-     *       then this method shall return an instance of {@link FeatureAssociation}
+     *   <li>If {@code getResult()} returns an instance of {@code FeatureAssociationRole},
+     *       then this method shall return an instance of {@code FeatureAssociation}
      *       and the {@code FeatureAssociation.getRole() == getResult()} relation should hold.</li>
      * </ul>
      *
@@ -188,15 +179,18 @@
      * in the Java language, and may be {@code null} if the operation does not need a feature instance
      * (like static methods in the Java language).</div>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the parameter type and return value may
+     * be changed to {@code org.opengis.feature.Feature} and {@code org.opengis.feature.Property} respectively.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param  feature     the feature on which to execute the operation.
      *                     Can be {@code null} if the operation does not need feature instance.
      * @param  parameters  the parameters to use for executing the operation.
      *                     Can be {@code null} if the operation does not take any parameters.
      * @return the operation result, or {@code null} if this operation does not produce any result.
-     * @throws FeatureOperationException if the operation execution cannot complete.
      */
     @Override
-    public abstract Property apply(Feature feature, ParameterValueGroup parameters) throws FeatureOperationException;
+    public abstract Object apply(AbstractFeature feature, ParameterValueGroup parameters);
 
     /**
      * Returns the names of feature properties that this operation needs for performing its task.
@@ -267,11 +261,11 @@
         if (name != null) {
             buffer.append('”');
         }
-        final IdentifiedType result = getResult();
+        final AbstractIdentifiedType result = getResult();
         if (result != null) {
             final Object type;
-            if (result instanceof AttributeType<?>) {
-                type = Classes.getShortName(((AttributeType<?>) result).getValueClass());
+            if (result instanceof DefaultAttributeType<?>) {
+                type = Classes.getShortName(((DefaultAttributeType<?>) result).getValueClass());
             } else {
                 type = result.getName();
             }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AssociationView.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AssociationView.java
index bb406cd..0e037f8 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AssociationView.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AssociationView.java
@@ -19,10 +19,8 @@
 import java.util.Collection;
 import org.opengis.util.GenericName;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
+// Specific to the main branch:
+import java.util.Objects;
 
 
 /**
@@ -36,24 +34,30 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-class AssociationView extends PropertyView<Feature> implements FeatureAssociation {
+class AssociationView extends AbstractAssociation {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = -148100967531766909L;
 
     /**
-     * The role of this association. Must be one of the properties listed in the {@link #feature}.
+     * The feature from which to read and where to write the attribute or association value.
      */
-    @SuppressWarnings("serial")                     // Most SIS implementations are serializable.
-    private final FeatureAssociationRole role;
+    final AbstractFeature feature;
+
+    /**
+     * The string representation of the property name. This is the value to be given in calls to
+     * {@code Feature.getPropertyValue(String)} and {@code Feature.setPropertyValue(String, Object)}.
+     */
+    final String name;
 
     /**
      * Creates a new association which will delegate its work to the given feature.
      */
-    private AssociationView(final Feature feature, final FeatureAssociationRole role) {
-        super(feature, role.getName().toString());
-        this.role = role;
+    private AssociationView(final AbstractFeature feature, final DefaultAssociationRole role) {
+        super(role);
+        this.feature = feature;
+        this.name = role.getName().toString();
     }
 
     /**
@@ -63,7 +67,7 @@
      * @param role     the role of this association. Must be one of the properties listed in the
      *                 {@link #feature} (this is not verified by this constructor).
      */
-    static FeatureAssociation create(final Feature feature, final FeatureAssociationRole role) {
+    static AbstractAssociation create(final AbstractFeature feature, final DefaultAssociationRole role) {
         if (isSingleton(role.getMaximumOccurs())) {
             return new Singleton(feature, role);
         } else {
@@ -79,20 +83,24 @@
         return role.getName();
     }
 
-    /**
-     * Returns the role specified at construction time.
-     */
     @Override
-    public final FeatureAssociationRole getRole() {
-        return role;
+    public AbstractFeature getValue() {
+        return (AbstractFeature) PropertyView.getValue(feature, name);
     }
 
-    /**
-     * Returns the class of values.
-     */
     @Override
-    final Class<Feature> getValueClass() {
-        return Feature.class;
+    public void setValue(final AbstractFeature value) {
+        PropertyView.setValue(feature, name, value);
+    }
+
+    @Override
+    public Collection<AbstractFeature> getValues() {
+        return PropertyView.getValues(feature, name, AbstractFeature.class);
+    }
+
+    @Override
+    public final void setValues(final Collection<? extends AbstractFeature> values) {
+        PropertyView.setValues(feature, name, values);
     }
 
     /**
@@ -109,7 +117,7 @@
         /**
          * Creates a new association which will delegate its work to the given feature.
          */
-        Singleton(final Feature feature, final FeatureAssociationRole role) {
+        Singleton(final AbstractFeature feature, final DefaultAssociationRole role) {
             super(feature, role);
         }
 
@@ -117,17 +125,17 @@
          * Returns the single value, or {@code null} if none.
          */
         @Override
-        public Feature getValue() {
-            return (Feature) this.feature.getPropertyValue(this.name);
+        public AbstractFeature getValue() {
+            return (AbstractFeature) this.feature.getPropertyValue(this.name);
         }
 
         /**
          * Sets the value of this association. This method assumes that the
-         * {@link Feature#setPropertyValue(String, Object)} implementation
+         * {@code Feature.setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
-        public void setValue(final Feature value) {
+        public void setValue(final AbstractFeature value) {
             this.feature.setPropertyValue(this.name, value);
         }
 
@@ -135,8 +143,30 @@
          * Wraps the property value in a set.
          */
         @Override
-        public Collection<Feature> getValues() {
-            return singletonOrEmpty(getValue());
+        public Collection<AbstractFeature> getValues() {
+            return PropertyView.singletonOrEmpty(getValue());
         }
     }
+
+    @Override
+    public final int hashCode() {
+        return PropertyView.hashCode(feature, name);
+    }
+
+    @Override
+    public final boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj != null && obj.getClass() == getClass()) {
+            final AssociationView that = (AssociationView) obj;
+            return feature == that.feature && Objects.equals(name, that.name);
+        }
+        return false;
+    }
+
+    @Override
+    public final String toString() {
+        return PropertyView.toString(getClass(), AbstractFeature.class, getName(), getValues());
+    }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AttributeView.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AttributeView.java
index b0eb039..c576eee 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AttributeView.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AttributeView.java
@@ -21,10 +21,8 @@
 import java.util.Collections;
 import org.opengis.util.GenericName;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import java.util.Objects;
 
 
 /**
@@ -41,24 +39,30 @@
  * @param <V> the type of attribute values. If the attribute supports multi-occurrences,
  *            then this is the type of elements (not the collection type).
  */
-class AttributeView<V> extends PropertyView<V> implements Attribute<V> {
+class AttributeView<V> extends AbstractAttribute<V> {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 3617999929561826634L;
 
     /**
-     * The type of this attribute. Must be one of the properties listed in the {@link #feature}.
+     * The feature from which to read and where to write the attribute or association value.
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final AttributeType<V> type;
+    final AbstractFeature feature;
+
+    /**
+     * The string representation of the property name. This is the value to be given in calls to
+     * {@code Feature.getPropertyValue(String)} and {@code Feature.setPropertyValue(String, Object)}.
+     */
+    final String name;
 
     /**
      * Creates a new attribute which will delegate its work to the given feature.
      */
-    private AttributeView(final Feature feature, final AttributeType<V> type) {
-        super(feature, type.getName().toString());
-        this.type = type;
+    private AttributeView(final AbstractFeature feature, final DefaultAttributeType<V> type) {
+        super(type);
+        this.feature = feature;
+        this.name = type.getName().toString();
     }
 
     /**
@@ -68,7 +72,7 @@
      * @param type     the type of this attribute. Must be one of the properties listed in the
      *                 {@link #feature} (this is not verified by this constructor).
      */
-    static <V> Attribute<V> create(final Feature feature, final AttributeType<V> type) {
+    static <V> AbstractAttribute<V> create(final AbstractFeature feature, final DefaultAttributeType<V> type) {
         if (isSingleton(type.getMaximumOccurs())) {
             return new Singleton<>(feature, type);
         } else {
@@ -84,27 +88,31 @@
         return type.getName();
     }
 
-    /**
-     * Returns the type specified at construction time.
-     */
     @Override
-    public final AttributeType<V> getType() {
-        return type;
+    public V getValue() {
+        return type.getValueClass().cast(PropertyView.getValue(feature, name));
     }
 
-    /**
-     * Returns the class of values.
-     */
     @Override
-    final Class<V> getValueClass() {
-        return type.getValueClass();
+    public void setValue(final V value) {
+        PropertyView.setValue(feature, name, value);
+    }
+
+    @Override
+    public Collection<V> getValues() {
+        return PropertyView.getValues(feature, name, type.getValueClass());
+    }
+
+    @Override
+    public final void setValues(final Collection<? extends V> values) {
+        PropertyView.setValues(feature, name, values);
     }
 
     /**
      * Returns an empty map since this simple view does not support characteristics.
      */
     @Override
-    public final Map<String,Attribute<?>> characteristics() {
+    public final Map<String,AbstractAttribute<?>> characteristics() {
         return Collections.emptyMap();
     }
 
@@ -125,7 +133,7 @@
         /**
          * Creates a new attribute which will delegate its work to the given feature.
          */
-        Singleton(final Feature feature, final AttributeType<V> type) {
+        Singleton(final AbstractFeature feature, final DefaultAttributeType<V> type) {
             super(feature, type);
         }
 
@@ -139,7 +147,7 @@
 
         /**
          * Sets the value of this attribute. This method assumes that the
-         * {@link Feature#setPropertyValue(String, Object)} implementation
+         * {@code Feature.setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
@@ -152,7 +160,29 @@
          */
         @Override
         public Collection<V> getValues() {
-            return singletonOrEmpty(getValue());
+            return PropertyView.singletonOrEmpty(getValue());
         }
     }
+
+    @Override
+    public final int hashCode() {
+        return PropertyView.hashCode(feature, name);
+    }
+
+    @Override
+    public final boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj != null && obj.getClass() == getClass()) {
+            final AttributeView<?> that = (AttributeView<?>) obj;
+            return feature == that.feature && Objects.equals(name, that.name);
+        }
+        return false;
+    }
+
+    @Override
+    public final String toString() {
+        return PropertyView.toString(getClass(), type.getValueClass(), getName(), getValues());
+    }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicMap.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicMap.java
index 8c32f92..8b579e0 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicMap.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicMap.java
@@ -24,12 +24,6 @@
 import org.apache.sis.util.privy.AbstractMapEntry;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Implementation of {@link AbstractAttribute#characteristics()} map.
@@ -37,16 +31,16 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class CharacteristicMap extends AbstractMap<String,Attribute<?>> implements CloneAccess {
+final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> implements CloneAccess {
     /**
      * The attribute source for which to provide characteristics.
      */
-    private final Attribute<?> source;
+    private final AbstractAttribute<?> source;
 
     /**
      * Characteristics of the {@code source} attribute, created when first needed.
      */
-    Attribute<?>[] characterizedBy;
+    AbstractAttribute<?>[] characterizedBy;
 
     /**
      * Description of the attribute characteristics.
@@ -59,7 +53,7 @@
      * @param  source  the attribute which is characterized by {@code characterizedBy}.
      * @param  types   description of the characteristics of {@code source}.
      */
-    CharacteristicMap(final Attribute<?> source, final CharacteristicTypeMap types) {
+    CharacteristicMap(final AbstractAttribute<?> source, final CharacteristicTypeMap types) {
         this.source = source;
         this.types  = types;
     }
@@ -72,14 +66,14 @@
     @Override
     public CharacteristicMap clone() throws CloneNotSupportedException {
         final CharacteristicMap clone = (CharacteristicMap) super.clone();
-        Attribute<?>[] c = clone.characterizedBy;
+        AbstractAttribute<?>[] c = clone.characterizedBy;
         if (c != null) {
             clone.characterizedBy = c = c.clone();
             final Cloner cloner = new Cloner();
             for (int i=0; i<c.length; i++) {
-                final Attribute<?> attribute = c[i];
+                final AbstractAttribute<?> attribute = c[i];
                 if (attribute instanceof Cloneable) {
-                    c[i] = (Attribute<?>) cloner.clone(attribute);
+                    c[i] = (AbstractAttribute<?>) cloner.clone(attribute);
                 }
             }
         }
@@ -105,7 +99,7 @@
     @Override
     public boolean isEmpty() {
         if (characterizedBy != null) {
-            for (final Attribute<?> attribute : characterizedBy) {
+            for (final AbstractAttribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     return false;
                 }
@@ -121,7 +115,7 @@
     public int size() {
         int n = 0;
         if (characterizedBy != null) {
-            for (final Attribute<?> attribute : characterizedBy) {
+            for (final AbstractAttribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     n++;
                 }
@@ -134,7 +128,7 @@
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public Attribute<?> get(final Object key) {
+    public AbstractAttribute<?> get(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
@@ -148,11 +142,11 @@
      * Removes the attribute characteristic for the given name.
      */
     @Override
-    public Attribute<?> remove(final Object key) {
+    public AbstractAttribute<?> remove(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
-                final Attribute<?> previous = characterizedBy[index];
+                final AbstractAttribute<?> previous = characterizedBy[index];
                 characterizedBy[index] = null;
                 return previous;
             }
@@ -165,12 +159,12 @@
      *
      * @param  key  the name for which to get the characteristic index.
      * @return the index for the characteristic of the given name.
-     * @throws PropertyNotFoundException if the given key is not the name of a characteristic in this map.
+     * @throws IllegalArgumentException if the given key is not the name of a characteristic in this map.
      */
     private int indexOf(final String key) {
         final Integer index = types.indices.get(key);
         if (index == null) {
-            throw new PropertyNotFoundException(Resources.format(
+            throw new IllegalArgumentException(Resources.format(
                     Resources.Keys.CharacteristicsNotFound_2, source.getName(), key));
         }
         return index;
@@ -184,12 +178,12 @@
      * @param  index  index of the expected attribute type.
      * @param  type   the actual attribute type.
      */
-    final void verifyAttributeType(final int index, final AttributeType<?> type) {
-        final AttributeType<?> expected = types.characterizedBy[index];
+    final void verifyAttributeType(final int index, final DefaultAttributeType<?> type) {
+        final DefaultAttributeType<?> expected = types.characterizedBy[index];
         if (!expected.equals(type)) {
             final GenericName en = expected.getName();
             final GenericName an = type.getName();
-            throw new InvalidPropertyValueException(String.valueOf(en).equals(String.valueOf(an))
+            throw new IllegalArgumentException(String.valueOf(en).equals(String.valueOf(an))
                     ? Resources.format(Resources.Keys.MismatchedPropertyType_1, en)
                     : Resources.format(Resources.Keys.CanNotSetCharacteristics_2, en.push(source.getName()), an));
         }
@@ -202,13 +196,13 @@
      * @throws IllegalArgumentException if the given key is not the name of a characteristic in this map.
      */
     @Override
-    public Attribute<?> put(final String key, final Attribute<?> value) {
+    public AbstractAttribute<?> put(final String key, final AbstractAttribute<?> value) {
         final int index = indexOf(key);
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
-        final Attribute<?> previous = characterizedBy[index];
+        final AbstractAttribute<?> previous = characterizedBy[index];
         characterizedBy[index] = value;
         return previous;
     }
@@ -225,7 +219,7 @@
     protected boolean addKey(final String name) {
         final int index = indexOf(name);
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
         if (characterizedBy[index] == null) {
             characterizedBy[index] = types.characterizedBy[index].newInstance();
@@ -243,13 +237,13 @@
      * @throws IllegalStateException if another characteristic already exists for the characteristic name.
      */
     @Override
-    protected boolean addValue(final Attribute<?> value) {
+    protected boolean addValue(final AbstractAttribute<?> value) {
         final int index = indexOf(value.getName().toString());
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
-        final Attribute<?> previous = characterizedBy[index];
+        final AbstractAttribute<?> previous = characterizedBy[index];
         if (previous == null) {
             characterizedBy[index] = value;
             return true;
@@ -265,16 +259,16 @@
      * Returns an iterator over the entries.
      */
     @Override
-    protected EntryIterator<String, Attribute<?>> entryIterator() {
+    protected EntryIterator<String, AbstractAttribute<?>> entryIterator() {
         if (characterizedBy == null) {
             return null;
         }
-        return new EntryIterator<String, Attribute<?>>() {
+        return new EntryIterator<String, AbstractAttribute<?>>() {
             /** Index of the current element to return in the iteration. */
             private int index = -1;
 
             /** The element to return, or {@code null} if we reached the end of iteration. */
-            private Attribute<?> value;
+            private AbstractAttribute<?> value;
 
             /** Returns {@code true} if there is more entries in the iteration. */
             @Override protected boolean next() {
@@ -292,12 +286,12 @@
             }
 
             /** Returns the attribute characteristic (never {@code null}). */
-            @Override protected Attribute<?> getValue() {
+            @Override protected AbstractAttribute<?> getValue() {
                 return value;
             }
 
             /** Creates and return the next entry. */
-            @Override protected Map.Entry<String, Attribute<?>> getEntry() {
+            @Override protected Map.Entry<String, AbstractAttribute<?>> getEntry() {
                 return new Entry(index, value);
             }
 
@@ -311,17 +305,17 @@
     /**
      * An entry returned by the {@link CharacteristicMap#entrySet()} iterator.
      * The key and value are never null, even in case of concurrent modification.
-     * This entry supports the {@link #setValue(Attribute)} operation.
+     * This entry supports the {@code setValue(Attribute)} operation.
      */
-    private final class Entry extends AbstractMapEntry<String, Attribute<?>> {
+    private final class Entry extends AbstractMapEntry<String, AbstractAttribute<?>> {
         /** Index of the attribute characteristics represented by this entry. */
         private final int index;
 
         /** The current attribute value, which is guaranteed to be non-null. */
-        private Attribute<?> value;
+        private AbstractAttribute<?> value;
 
         /** Creates a new entry for the characteristic at the given index. */
-        Entry(final int index, final Attribute<?> value) {
+        Entry(final int index, final AbstractAttribute<?> value) {
             this.index = index;
             this.value = value;
         }
@@ -332,14 +326,14 @@
         }
 
         /** Returns the attribute characteristic (never {@code null}). */
-        @Override public Attribute<?> getValue() {
+        @Override public AbstractAttribute<?> getValue() {
             return value;
         }
 
         /** Sets the attribute characteristic. */
-        @Override public Attribute<?> setValue(final Attribute<?> value) {
+        @Override public AbstractAttribute<?> setValue(final AbstractAttribute<?> value) {
             verifyAttributeType(index, value.getType());
-            final Attribute<?> previous = this.value;
+            final AbstractAttribute<?> previous = this.value;
             characterizedBy[index] = value;
             this.value = value;
             return previous;
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicTypeMap.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicTypeMap.java
index 99f9006..58348c7 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicTypeMap.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CharacteristicTypeMap.java
@@ -27,9 +27,6 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.pending.jdk.JDK19;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Implementation of the map returned by {@link DefaultAttributeType#characteristics()}.
@@ -46,17 +43,17 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class CharacteristicTypeMap extends AbstractMap<String,AttributeType<?>> {
+final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeType<?>> {
     /**
      * For sharing the same {@code CharacteristicTypeMap} instances among the attribute types
      * having the same characteristics.
      */
     @SuppressWarnings("unchecked")
-    private static final WeakValueHashMap<AttributeType<?>[],CharacteristicTypeMap> SHARED =
-            new WeakValueHashMap<>((Class) AttributeType[].class);
+    private static final WeakValueHashMap<DefaultAttributeType<?>[],CharacteristicTypeMap> SHARED =
+            new WeakValueHashMap<>((Class) DefaultAttributeType[].class);
 
     /*
-     * This class has intentionally no reference to the AttributeType for which we are providing characteristics.
+     * This class has intentionally no reference to the DefaultAttributeType for which we are providing characteristics.
      * This allows us to use the same CharacteristicTypeMap instance for various attribute types having the same
      * characteristic (e.g. many measurements may have an "accuracy" characteristic).
      */
@@ -65,7 +62,7 @@
      * Characteristics of another attribute type (the {@code source} attribute given to the constructor).
      * This array shall not be modified.
      */
-    final AttributeType<?>[] characterizedBy;
+    final DefaultAttributeType<?>[] characterizedBy;
 
     /**
      * The names of attribute types listed in the {@link #characterizedBy} array,
@@ -84,7 +81,7 @@
      * @return a map for this given characteristics.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    static CharacteristicTypeMap create(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
+    static CharacteristicTypeMap create(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] characterizedBy) {
         CharacteristicTypeMap map;
         synchronized (SHARED) {
             map = SHARED.get(characterizedBy);
@@ -106,14 +103,14 @@
      * @param  characterizedBy  characteristics of {@code source}. Should not be empty.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    private CharacteristicTypeMap(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
+    private CharacteristicTypeMap(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] characterizedBy) {
         this.characterizedBy = characterizedBy;
         int index = 0;
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         final Map<String,Integer> indices = JDK19.newHashMap(characterizedBy.length);
         final Map<String,Integer> aliases = new HashMap<>();
         for (int i=0; i<characterizedBy.length; i++) {
-            final AttributeType<?> attribute = characterizedBy[i];
+            final DefaultAttributeType<?> attribute = characterizedBy[i];
             ArgumentChecks.ensureNonNullElement("characterizedBy", i, attribute);
             GenericName name = attribute.getName();
             String key = AbstractIdentifiedType.toString(name, source, "characterizedBy", i);
@@ -178,7 +175,7 @@
      */
     @Override
     public boolean containsValue(final Object key) {
-        for (final AttributeType<?> type : characterizedBy) {
+        for (final DefaultAttributeType<?> type : characterizedBy) {
             if (type.equals(key)) {
                 return true;
             }
@@ -190,7 +187,7 @@
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public AttributeType<?> get(final Object key) {
+    public DefaultAttributeType<?> get(final Object key) {
         final Integer index = indices.get(key);
         return (index != null) ? characterizedBy[index] : null;
     }
@@ -200,13 +197,13 @@
      * This is not the iterator returned by public API like {@code Map.entrySet().iterator()}.
      */
     @Override
-    protected EntryIterator<String, AttributeType<?>> entryIterator() {
-        return new EntryIterator<String, AttributeType<?>>() {
+    protected EntryIterator<String, DefaultAttributeType<?>> entryIterator() {
+        return new EntryIterator<String, DefaultAttributeType<?>>() {
             /** Index of the next element to return in the iteration. */
             private int index;
 
             /** Value of current entry. */
-            private AttributeType<?> value;
+            private DefaultAttributeType<?> value;
 
             /**
              * Returns {@code true} if there is more entries in the iteration.
@@ -232,7 +229,7 @@
              * Returns the attribute characteristic contained in this entry.
              */
             @Override
-            protected AttributeType<?> getValue() {
+            protected DefaultAttributeType<?> getValue() {
                 return value;
             }
         };
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CommonParentFinder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CommonParentFinder.java
index 4c929ac..1686282 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CommonParentFinder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/CommonParentFinder.java
@@ -20,13 +20,10 @@
 import java.util.LinkedHashMap;
 import java.util.function.Predicate;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Finds a feature type common to all given types. This is either one of the given types, or a parent common to all types.
- * A feature <var>F</var> is considered a common parent if <code>F.{@link DefaultFeatureType#isAssignableFrom(FeatureType)
+ * A feature <var>F</var> is considered a common parent if <code>F.{@link DefaultFeatureType#isAssignableFrom(DefaultFeatureType)
  * isAssignableFrom}(type)</code> returns {@code true} for all elements <var>type</var> in the given array.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -38,16 +35,16 @@
      * @param  types  types for which to find a common type.
      * @return a feature type which is assignable from all given types, or {@code null} if none.
      */
-    static FeatureType select(final Iterable<? extends FeatureType> types) {
+    static DefaultFeatureType select(final Iterable<? extends DefaultFeatureType> types) {
         /*
          * Get a set of unique feature types. Since the process done in this method may be relatively costly,
          * we want to avoid doing the same work twice. The purpose of Boolean value will be explained later.
          */
-        final Map<FeatureType,Boolean> allTypes = new LinkedHashMap<>();
+        final Map<DefaultFeatureType,Boolean> allTypes = new LinkedHashMap<>();
         types.forEach((type) -> allTypes.putIfAbsent(type, Boolean.FALSE));
         allTypes.remove(null);
         int count = allTypes.size();
-        final FeatureType[] required = allTypes.keySet().toArray(new FeatureType[count]);
+        final DefaultFeatureType[] required = allTypes.keySet().toArray(new DefaultFeatureType[count]);
         /*
          * We are going to iterate over the `required` array many times. For performance reason, this array should be as
          * short as possible. We can make it shorter by removing any element A which is assignable to another element B,
@@ -55,7 +52,7 @@
          * If this simplification results in an array of only one element, we are done.
          */
         for (int i=0; i<count; i++) {
-            final FeatureType parent = required[i];
+            final DefaultFeatureType parent = required[i];
             for (int j=count; --j >= 0;) {                 // Reverse order so that removed elements do not impact index j.
                 if (j != i && parent.isAssignableFrom(required[j])) {
                     System.arraycopy(required, j+1, required, j, --count - j);      // If A is assignable from B, remove B.
@@ -81,13 +78,13 @@
      * updated after we find that a type {@linkplain #isAssignableFromAll is assignable from
      * all required types}.
      */
-    private final Map<FeatureType,Boolean> allTypes;
+    private final Map<DefaultFeatureType,Boolean> allTypes;
 
     /**
      * The features types which must be assignable to the common parent.
      * This array may not contain all feature types given by user, since we try to remove redundant elements.
      */
-    private final FeatureType[] required;
+    private final DefaultFeatureType[] required;
 
     /**
      * Number of valid elements in the {@link #required} array.
@@ -98,7 +95,7 @@
      * Creates a finder for a common parent of the given types.
      * Invokes {@link #select()} after construction time for getting the parent.
      */
-    private CommonParentFinder(final Map<FeatureType,Boolean> allTypes, final FeatureType[] required, final int count) {
+    private CommonParentFinder(final Map<DefaultFeatureType,Boolean> allTypes, final DefaultFeatureType[] required, final int count) {
         this.allTypes = allTypes;
         this.required = required;
         this.count    = count;
@@ -111,9 +108,9 @@
      * Returns {@code true} if the given parent candidate is assignable from all required types.
      * The feature type to be returned by {@link #select()} must met that condition.
      */
-    private boolean isAssignableFromAll(final FeatureType parent) {
+    private boolean isAssignableFromAll(final DefaultFeatureType parent) {
         for (int i=0; i<count; i++) {
-            final FeatureType type = required[i];
+            final DefaultFeatureType type = required[i];
             if (type != parent && !parent.isAssignableFrom(type)) {
                 return false;
             }
@@ -126,8 +123,8 @@
      * This method verifies recursively parents of parents, skipping types that have already
      * been examined in a previous invocation of this method.
      */
-    private void scanParents(final FeatureType type) {
-        for (final FeatureType parent : type.getSuperTypes()) {
+    private void scanParents(final DefaultFeatureType type) {
+        for (final DefaultFeatureType parent : type.getSuperTypes()) {
             if (allTypes.putIfAbsent(parent, Boolean.FALSE) == null) {
                 if (isAssignableFromAll(parent)) {
                     allTypes.put(parent, Boolean.TRUE);     // Found a candidate.
@@ -143,9 +140,9 @@
      * Invoked when the given feature type is assignable from all required types.
      * There is no need to verify the parents since they are not going to be a better match.
      */
-    private void skipParents(final FeatureType type) {
+    private void skipParents(final DefaultFeatureType type) {
         assert isAssignableFromAll(type);
-        for (final FeatureType parent : type.getSuperTypes()) {
+        for (final DefaultFeatureType parent : type.getSuperTypes()) {
             if (Boolean.TRUE.equals(allTypes.put(parent, Boolean.FALSE))) {
                 // If `parent` was previously a candidate, its parents have already been set to `FALSE`.
             } else {
@@ -158,11 +155,11 @@
      * Invoked after all feature types have been examined. This method removes all features types that
      * are not parent of required types, then select the one having the greatest number of properties.
      */
-    FeatureType select() {
+    DefaultFeatureType select() {
         allTypes.values().removeIf(Predicate.isEqual(Boolean.FALSE));
-        FeatureType best = null;
+        DefaultFeatureType best = null;
         int numProperties = 0;
-        for (final FeatureType type : allTypes.keySet()) {
+        for (final DefaultFeatureType type : allTypes.keySet()) {
             final int n = type.getProperties(true).size();
             if (best == null || n > numProperties) {
                 best = type;
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java
index ab1fbb4..fd126ca 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java
@@ -28,14 +28,6 @@
 import org.apache.sis.feature.internal.Resources;
 import org.apache.sis.feature.privy.AttributeConvention;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Indicates the role played by the association between two features.
@@ -62,7 +54,7 @@
  *
  * @since 0.5
  */
-public class DefaultAssociationRole extends FieldType implements FeatureAssociationRole {
+public class DefaultAssociationRole extends FieldType {
     /**
      * For cross-version compatibility.
      */
@@ -80,8 +72,6 @@
      * The name of the property to use as a title for the associated feature, or an empty string if none.
      * This field is initially null, then computed when first needed.
      * This information is used only by {@link AbstractAssociation#toString()} implementation.
-     *
-     * @see #getTitleProperty(FeatureAssociationRole)
      */
     private transient volatile String titleProperty;
 
@@ -132,7 +122,7 @@
      *
      * @see org.apache.sis.feature.builder.AssociationRoleBuilder
      */
-    public DefaultAssociationRole(final Map<String,?> identification, final FeatureType valueType,
+    public DefaultAssociationRole(final Map<String,?> identification, final DefaultFeatureType valueType,
             final int minimumOccurs, final int maximumOccurs)
     {
         super(identification, minimumOccurs, maximumOccurs);
@@ -220,7 +210,7 @@
      *         invoking that method on {@code creating} may cause a failure with user code.
      * @return {@code true} if this association references a resolved feature type after this method call.
      */
-    final boolean resolve(final DefaultFeatureType creating, final Collection<PropertyType> properties) {
+    final boolean resolve(final DefaultFeatureType creating, final Collection<AbstractIdentifiedType> properties) {
         final FeatureType type = valueType;
         if (type instanceof NamedFeatureType) {
             FeatureType resolved = ((NamedFeatureType) type).resolved;
@@ -234,7 +224,7 @@
                      * this desired feature in an association of the `creating` feature, instead of beeing
                      * the `creating` feature itself. This is a little bit unusual, but not illegal.
                      */
-                    final List<FeatureType> deferred = new ArrayList<>();
+                    final List<DefaultFeatureType> deferred = new ArrayList<>();
                     resolved = search(creating, properties, name, deferred);
                     if (resolved == null) {
                         /*
@@ -268,8 +258,8 @@
      * @param  deferred    where to store {@code FeatureType}s to be eventually used for a deep search.
      * @return the feature of the given name, or {@code null} if none.
      */
-    private static FeatureType search(final FeatureType feature, Collection<? extends PropertyType> properties,
-            final GenericName name, final List<FeatureType> deferred)
+    private static DefaultFeatureType search(final DefaultFeatureType feature, Collection<? extends AbstractIdentifiedType> properties,
+            final GenericName name, final List<DefaultFeatureType> deferred)
     {
         /*
          * Search only in associations declared in the given feature, not in inherited associations.
@@ -279,21 +269,17 @@
         if (properties == null) {
             properties = feature.getProperties(false);
         }
-        for (final PropertyType property : properties) {
-            if (property instanceof FeatureAssociationRole) {
+        for (final AbstractIdentifiedType property : properties) {
+            if (property instanceof DefaultAssociationRole) {
                 final FeatureType valueType;
-                if (property instanceof DefaultAssociationRole) {
-                    valueType = ((DefaultAssociationRole) property).valueType;
-                    if (valueType instanceof NamedFeatureType) {
-                        continue;                                   // Skip unresolved feature types.
-                    }
-                } else {
-                    valueType = ((FeatureAssociationRole) property).getValueType();
+                valueType = ((DefaultAssociationRole) property).valueType;
+                if (valueType instanceof NamedFeatureType) {
+                    continue;                                       // Skip unresolved feature types.
                 }
                 if (name.equals(valueType.getName())) {
-                    return valueType;
+                    return (DefaultFeatureType) valueType;
                 }
-                deferred.add(valueType);
+                deferred.add((DefaultFeatureType) valueType);
             }
         }
         /*
@@ -302,7 +288,7 @@
          * but not necessarily the same feature type (may be a subtype). This is equivalent to
          * "covariant return type" in the Java language.
          */
-        for (FeatureType type : feature.getSuperTypes()) {
+        for (DefaultFeatureType type : feature.getSuperTypes()) {
             if (name.equals(type.getName())) {
                 return type;
             }
@@ -315,7 +301,7 @@
     }
 
     /**
-     * Potentially invoked after {@link #search(FeatureType, Collection, GenericName, List)} for searching
+     * Potentially invoked after {@code search(FeatureType, Collection, GenericName, List)} for searching
      * in associations of associations.
      *
      * <p>Current implementation does not check that there are no duplicated names. Even if we did so,
@@ -323,14 +309,14 @@
      * later. We rather put a warning in {@link #DefaultAssociationRole(Map, GenericName, int, int)}
      * javadoc.</p>
      *
-     * @param  deferred  the feature types collected by {@link #search(FeatureType, Collection, GenericName, List)}.
+     * @param  deferred  the feature types collected by {@code search(FeatureType, Collection, GenericName, List)}.
      * @param  name      the name of the feature to search.
      * @return the feature of the given name, or {@code null} if none.
      */
-    private static FeatureType deepSearch(final List<FeatureType> deferred, final GenericName name) {
+    private static DefaultFeatureType deepSearch(final List<DefaultFeatureType> deferred, final GenericName name) {
         final Map<FeatureType,Boolean> done = new IdentityHashMap<>(8);
         for (int i=0; i<deferred.size();) {
-            FeatureType valueType = deferred.get(i++);
+            DefaultFeatureType valueType = deferred.get(i++);
             if (done.put(valueType, Boolean.TRUE) == null) {
                 deferred.subList(0, i).clear();                 // Discard previous value for making more room.
                 valueType = search(valueType, null, name, deferred);
@@ -347,19 +333,20 @@
      * Returns the type of feature values.
      *
      * <p>This method cannot be invoked if {@link #isResolved()} returns {@code false}.
-     * However, it is still possible to {@linkplain Features#getValueTypeName(PropertyType)
+     * However, it is still possible to {@linkplain Features#getValueTypeName
      * get the associated feature type name}.</p>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.FeatureType}. This change is pending GeoAPI revision.</div>
+     *
      * @return the type of feature values.
      * @throws IllegalStateException if the feature type has been specified
      *         {@linkplain #DefaultAssociationRole(Map, GenericName, int, int) only by its name}
      *         and not yet resolved.
      *
      * @see #isResolved()
-     * @see Features#getValueTypeName(PropertyType)
      */
-    @Override
-    public final FeatureType getValueType() {
+    public final DefaultFeatureType getValueType() {
         /*
          * This method shall be final for consistency with other methods in this classes
          * which use the `valueType` field directly. Furthermore, this method is invoked
@@ -373,15 +360,16 @@
             }
             valueType = type;
         }
-        return type;
+        return (DefaultFeatureType) type;
     }
 
     /**
      * Returns the name of the feature type. This information is always available
      * even when the name has not yet been {@linkplain #resolve resolved}.
      */
-    static GenericName getValueTypeName(final FeatureAssociationRole role) {
-        return (role instanceof DefaultAssociationRole ? ((DefaultAssociationRole) role).valueType : role.getValueType()).getName();
+    static GenericName getValueTypeName(final DefaultAssociationRole role) {
+        // Method is static for compatibility with branches on GeoAPI snapshots.
+        return role.valueType.getName();
     }
 
     /**
@@ -399,34 +387,35 @@
      *
      * This method should be used only for display purpose, not as a reliable or stable way to get the identifier.
      * The heuristic rules implemented in this method may change in any future Apache SIS version.
+     *
+     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
+     * However this method needs to be static in other SIS branches, because they work with interfaces
+     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    static String getTitleProperty(final FeatureAssociationRole role) {
-        if (role instanceof DefaultAssociationRole) {
-            String p = ((DefaultAssociationRole) role).titleProperty;       // No synchronization - not a big deal if computed twice.
-            if (p != null) {
-                return p.isEmpty() ? null : p;
-            }
-            p = searchTitleProperty(role.getValueType());
-            ((DefaultAssociationRole) role).titleProperty = (p != null) ? p : "";
-            return p;
+    static String getTitleProperty(final DefaultAssociationRole role) {
+        String p = role.titleProperty;       // No synchronization - not a big deal if computed twice.
+        if (p != null) {
+            return p.isEmpty() ? null : p;
         }
-        return searchTitleProperty(role.getValueType());
+        p = searchTitleProperty(role.getValueType());
+        role.titleProperty = (p != null) ? p : "";
+        return p;
     }
 
     /**
-     * Implementation of {@link #getTitleProperty(FeatureAssociationRole)} for first search,
+     * Implementation of {@link #getTitleProperty(DefaultAssociationRole)} for first search,
      * or for non-SIS {@code FeatureAssociationRole} implementations.
      */
-    private static String searchTitleProperty(final FeatureType ft) {
+    private static String searchTitleProperty(final DefaultFeatureType ft) {
         String fallback = null;
         try {
             return ft.getProperty(AttributeConvention.IDENTIFIER).getName().toString();
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             // Ignore.
         }
-        for (final PropertyType type : ft.getProperties(true)) {
-            if (type instanceof AttributeType<?>) {
-                final AttributeType<?> pt = (AttributeType<?>) type;
+        for (final AbstractIdentifiedType type : ft.getProperties(true)) {
+            if (type instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) type;
                 final Class<?> valueClass = pt.getValueClass();
                 if (CharSequence.class.isAssignableFrom(valueClass) ||
                     GenericName .class.isAssignableFrom(valueClass) ||
@@ -473,10 +462,9 @@
      *
      * @return a new association instance.
      *
-     * @see AbstractAssociation#create(FeatureAssociationRole)
+     * @see AbstractAssociation#create(DefaultAssociationRole)
      */
-    @Override
-    public FeatureAssociation newInstance() {
+    public AbstractAssociation newInstance() {
         return AbstractAssociation.create(this);
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAttributeType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAttributeType.java
index b5b5c12..fdbd22e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAttributeType.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAttributeType.java
@@ -28,10 +28,6 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArgumentChecks;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Definition of an attribute in a feature type.
@@ -45,6 +41,11 @@
  * Attribute characterization (discussed below) is similar to {@link java.lang.annotation.Annotation}.
  * </div>
  *
+ * <div class="warning"><b>Warning:</b>
+ * This class is expected to implement a GeoAPI {@code AttributeType} interface in a future version.
+ * When such interface will be available, most references to {@code DefaultAttributeType} in current
+ * API will be replaced by references to the {@code AttributeType} interface.</div>
+ *
  * <h2>Value type</h2>
  * Attributes can be used for both spatial and non-spatial properties.
  * Some examples are:
@@ -102,7 +103,7 @@
  *
  * @since 0.5
  */
-public class DefaultAttributeType<V> extends FieldType implements AttributeType<V> {
+public class DefaultAttributeType<V> extends FieldType {
     /**
      * For cross-version compatibility.
      */
@@ -186,7 +187,7 @@
     @SuppressWarnings("this-escape")        // Okay because used only in package-private class.
     public DefaultAttributeType(final Map<String,?> identification, final Class<V> valueClass,
             final int minimumOccurs, final int maximumOccurs, final V defaultValue,
-            final AttributeType<?>... characterizedBy)
+            final DefaultAttributeType<?>... characterizedBy)
     {
         super(identification, minimumOccurs, maximumOccurs);
         ArgumentChecks.ensureNonNull("valueClass",   valueClass);
@@ -219,7 +220,7 @@
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final AttributeType<?>[] characterizedBy = (AttributeType<?>[]) in.readObject();
+            final DefaultAttributeType<?>[] characterizedBy = (DefaultAttributeType<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = CharacteristicTypeMap.create(this, characterizedBy);
             }
@@ -233,10 +234,7 @@
      * Returns the type of attribute values.
      *
      * @return the type of attribute values.
-     *
-     * @see Features#getValueClass(PropertyType)
      */
-    @Override
     public final Class<V> getValueClass() {
         return valueClass;
     }
@@ -288,7 +286,6 @@
      *
      * @return the default value for the attribute, or {@code null} if none.
      */
-    @Override
     public V getDefaultValue() {
         return defaultValue;
     }
@@ -310,8 +307,7 @@
      *
      * @see AbstractAttribute#characteristics()
      */
-    @Override
-    public Map<String,AttributeType<?>> characteristics() {
+    public Map<String,DefaultAttributeType<?>> characteristics() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -320,10 +316,9 @@
      *
      * @return a new attribute instance.
      *
-     * @see AbstractAttribute#create(AttributeType)
+     * @see AbstractAttribute#create(DefaultAttributeType)
      */
-    @Override
-    public Attribute<V> newInstance() {
+    public AbstractAttribute<V> newInstance() {
         return AbstractAttribute.create(this);
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java
index 772e341..d8fe643 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java
@@ -40,17 +40,6 @@
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
-import org.opengis.feature.FeatureInstantiationException;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Abstraction of a real-world phenomena. A {@code FeatureType} instance describes the class of all
@@ -60,6 +49,11 @@
  * compared to the Java language, {@code FeatureType} is equivalent to {@link Class} while
  * {@code Feature} instances are equivalent to {@link Object} instances of that class.</div>
  *
+ * <div class="warning"><b>Warning:</b>
+ * This class is expected to implement a GeoAPI {@code FeatureType} interface in a future version.
+ * When such interface will be available, most references to {@code DefaultFeatureType} in the API
+ * will be replaced by references to the {@code FeatureType} interface.</div>
+ *
  * <h2>Naming</h2>
  * The feature type {@linkplain #getName() name} is mandatory and should be unique. Those names are the main
  * criterion used for deciding if a feature type {@linkplain #isAssignableFrom is assignable from} another type.
@@ -92,7 +86,7 @@
  *
  * <h2>Immutability and thread safety</h2>
  * Instances of this class are immutable if all properties ({@link GenericName} and {@link InternationalString}
- * instances) and all arguments ({@link AttributeType} instances) given to the constructor are also immutable.
+ * instances) and all arguments ({@code AttributeType} instances) given to the constructor are also immutable.
  * Such immutable instances can be shared by many objects and passed between threads without synchronization.
  *
  * @author  Johann Sorel (Geomatys)
@@ -139,7 +133,7 @@
      * any unresolved name (i.e. a {@link DefaultAssociationRole#valueType} specified only be the
      * feature type name instead of its actual instance). A value of {@code true} means that all
      * names have been resolved. However, a value of {@code false} only means that we are not sure,
-     * and that {@link #resolve(FeatureType, Map)} should check again.
+     * and that {@code resolve(FeatureType, Map)} should check again.
      *
      * <h4>Implementation note</h4>
      * Strictly speaking, this field should be declared {@code volatile} since the names could
@@ -155,14 +149,14 @@
      *
      * @see #getSuperTypes()
      */
-    @SuppressWarnings("serial")                     // Can be various serializable implementations.
-    private final Set<FeatureType> superTypes;
+    @SuppressWarnings("serial")
+    private final Set<DefaultFeatureType> superTypes;
 
     /**
-     * The names of all parents of this feature type, including parents of parents.
-     * This is used for a more efficient implementation of {@link #isAssignableFrom(FeatureType)}.
+     * The names of all parents of this feature type, including parents of parents. This is used
+     * for a more efficient implementation of {@link #isAssignableFrom(DefaultFeatureType)}.
      *
-     * @see #isAssignableFrom(FeatureType)
+     * @see #isAssignableFrom(DefaultFeatureType)
      */
     private transient Set<GenericName> assignableTo;
 
@@ -174,7 +168,7 @@
      * @see #getProperties(boolean)
      */
     @SuppressWarnings("serial")                     // Can be various serializable implementations.
-    private final List<PropertyType> properties;
+    private final List<AbstractIdentifiedType> properties;
 
     /**
      * All properties, including the ones declared in the super-types.
@@ -182,7 +176,7 @@
      *
      * @see #getProperties(boolean)
      */
-    private transient Collection<PropertyType> allProperties;
+    private transient Collection<AbstractIdentifiedType> allProperties;
 
     /**
      * A lookup table for fetching properties by name, including the properties from super-types.
@@ -190,7 +184,7 @@
      *
      * @see #getProperty(String)
      */
-    private transient Map<String, PropertyType> byName;
+    private transient Map<String, AbstractIdentifiedType> byName;
 
     /**
      * Indices of properties in an array of properties similar to {@link #properties},
@@ -249,6 +243,12 @@
      *   </tr>
      * </table>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the type of array elements may be
+     * changed to {@code org.opengis.feature.FeatureType} and {@code org.opengis.feature.PropertyType}.
+     * This change is pending GeoAPI revision. In the meantime, make sure that the {@code properties}
+     * array contains only attribute types, association roles or operations, <strong>not</strong> other
+     * feature types since the later are not properties in the ISO sense.</div>
+     *
      * @param identification  the name and other information to be given to this feature type.
      * @param isAbstract      if {@code true}, the feature type acts as an abstract super-type.
      * @param superTypes      the parents of this feature type, or {@code null} or empty if none.
@@ -259,7 +259,7 @@
      */
     @SuppressWarnings("this-escape")
     public DefaultFeatureType(final Map<String,?> identification, final boolean isAbstract,
-            final FeatureType[] superTypes, final PropertyType... properties)
+            final DefaultFeatureType[] superTypes, final AbstractIdentifiedType... properties)
     {
         super(identification);
         ArgumentChecks.ensureNonNull("properties", properties);
@@ -280,9 +280,9 @@
          * in case of duplicated values. Opportunistically verify for null values. The same verification could
          * be done in the scanPropertiesFrom(…) method, but doing it here produces a less confusing stacktrace.
          */
-        final List<PropertyType> sourceProperties = new ArrayList<>(properties.length);
+        final List<AbstractIdentifiedType> sourceProperties = new ArrayList<>(properties.length);
         for (int i=0; i<properties.length; i++) {
-            final PropertyType property = properties[i];
+            final AbstractIdentifiedType property = properties[i];
             ArgumentChecks.ensureNonNullElement("properties", i, property);
             sourceProperties.add(property);
         }
@@ -291,13 +291,13 @@
         switch (size) {
             case 0:  this.properties = Collections.emptyList(); break;
             case 1:  this.properties = Collections.singletonList(sourceProperties.get(0)); break;
-            default: this.properties = UnmodifiableArrayList.wrap(sourceProperties.toArray(new PropertyType[size])); break;
+            default: this.properties = UnmodifiableArrayList.wrap(sourceProperties.toArray(new AbstractIdentifiedType[size])); break;
         }
         /*
          * Before to resolve cyclic associations, verify that operations depend only on existing properties.
          * Note: the `allProperties` collection has been created by computeTransientFields(…) above.
          */
-        for (final PropertyType property : allProperties) {
+        for (final AbstractIdentifiedType property : allProperties) {
             if (property instanceof AbstractOperation) {
                 for (final String dependency : ((AbstractOperation) property).getDependencies()) {
                     if (!byName.containsKey(dependency)) {
@@ -345,17 +345,17 @@
      *
      * @param  properties  same content as {@link #properties} (may be the reference to the same list), but
      *         optionally in a temporarily modifiable list if we want to allow removal of duplicated values.
-     *         See {@link #scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
+     *         See {@code scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void computeTransientFields(final List<PropertyType> properties) {
+    private void computeTransientFields(final List<AbstractIdentifiedType> properties) {
         final int capacity = Containers.hashMapCapacity(properties.size());
         byName       = new LinkedHashMap<>(capacity);
         indices      = new LinkedHashMap<>(capacity);
         assignableTo = new HashSet<>(4);
         assignableTo.add(super.getName());
         scanPropertiesFrom(this, properties);
-        allProperties = UnmodifiableArrayList.wrap(byName.values().toArray(PropertyType[]::new));
+        allProperties = UnmodifiableArrayList.wrap(byName.values().toArray(AbstractIdentifiedType[]::new));
         /*
          * Now check if the feature is simple/complex or dense/sparse. We perform this check after we finished
          * to create the list of all properties, because some properties may be overridden and we want to take
@@ -364,16 +364,16 @@
         isSimple = true;
         int index = 0;
         int mandatory = 0;                                                  // Count of mandatory properties.
-        for (final Map.Entry<String,PropertyType> entry : byName.entrySet()) {
+        for (final Map.Entry<String,AbstractIdentifiedType> entry : byName.entrySet()) {
             final int minimumOccurs, maximumOccurs;
-            final PropertyType property = entry.getValue();
-            if (property instanceof AttributeType<?>) {
-                minimumOccurs = ((AttributeType<?>) property).getMinimumOccurs();
-                maximumOccurs = ((AttributeType<?>) property).getMaximumOccurs();
+            final AbstractIdentifiedType property = entry.getValue();
+            if (property instanceof DefaultAttributeType<?>) { // Other SIS branches check for AttributeType instead.
+                minimumOccurs = ((DefaultAttributeType<?>) property).getMinimumOccurs();
+                maximumOccurs = ((DefaultAttributeType<?>) property).getMaximumOccurs();
                 isSimple &= (minimumOccurs == maximumOccurs);
-            } else if (property instanceof FeatureAssociationRole) {
-                minimumOccurs = ((FeatureAssociationRole) property).getMinimumOccurs();
-                maximumOccurs = ((FeatureAssociationRole) property).getMaximumOccurs();
+            } else if (property instanceof FieldType) { // TODO: check for AssociationRole instead (after GeoAPI upgrade).
+                minimumOccurs = ((FieldType) property).getMinimumOccurs();
+                maximumOccurs = ((FieldType) property).getMaximumOccurs();
                 isSimple = false;
             } else {
                 if (isParameterlessOperation(property)) {
@@ -396,8 +396,8 @@
          *
          * In the `aliases` map below, null values will be assigned to ambiguous short names.
          */
-        final Map<String, PropertyType> aliases = new LinkedHashMap<>();
-        for (final PropertyType property : allProperties) {
+        final Map<String, AbstractIdentifiedType> aliases = new LinkedHashMap<>();
+        for (final AbstractIdentifiedType property : allProperties) {
             GenericName name = property.getName();
             while (name instanceof ScopedName) {
                 if (name == (name = ((ScopedName) name).tail())) break;   // Safety against broken implementations.
@@ -406,8 +406,8 @@
                 aliases.put(key, aliases.containsKey(key) ? null : property);
             }
         }
-        for (final Map.Entry<String,PropertyType> entry : aliases.entrySet()) {
-            final PropertyType property = entry.getValue();
+        for (final Map.Entry<String,AbstractIdentifiedType> entry : aliases.entrySet()) {
+            final AbstractIdentifiedType property = entry.getValue();
             if (property != null) {
                 final String tip = entry.getKey();
                 if (byName.putIfAbsent(tip, property) == null) {
@@ -453,7 +453,7 @@
      * <ul>
      *   <li>Avoid a call to the user-overrideable {@link #getProperties(boolean)} method
      *       while this {@code DefaultFeatureType} instance is still under constructor.</li>
-     *   <li>Allow the {@link #DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
+     *   <li>Allow the {@code DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
      *       to pass a temporary modifiable list that allow element removal.</li>
      * </ul>
      *
@@ -461,18 +461,20 @@
      * @param  sourceProperties  {@code source.getProperties(false)} (see above method javadoc).
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void scanPropertiesFrom(final FeatureType source, final Collection<? extends PropertyType> sourceProperties) {
-        for (final FeatureType parent : source.getSuperTypes()) {
+    private void scanPropertiesFrom(final DefaultFeatureType source,
+            final Collection<? extends AbstractIdentifiedType> sourceProperties)
+    {
+        for (final DefaultFeatureType parent : source.getSuperTypes()) {
             if (assignableTo.add(parent.getName())) {
                 scanPropertiesFrom(parent, parent.getProperties(false));
             }
         }
         int index = -1;
-        final Iterator<? extends PropertyType> it = sourceProperties.iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = sourceProperties.iterator();
         while (it.hasNext()) {
-            final PropertyType property = it.next();
+            final AbstractIdentifiedType property = it.next();
             final String name = toString(property.getName(), source, "properties", ++index);
-            final PropertyType previous = byName.put(name, property);
+            final AbstractIdentifiedType previous = byName.put(name, property);
             if (previous != null) {
                 if (previous.equals(property)) {
                     byName.put(name, previous);         // Keep the instance declared in super-type.
@@ -492,14 +494,18 @@
      * Returns the name of the feature which defines the given property, or {@code null} if not found.
      * This method is for information purpose when producing an error message - its implementation does
      * not need to be efficient.
+     *
+     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
+     * However this method needs to be static in other SIS branches, because they work with interfaces
+     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    private static GenericName ownerOf(final FeatureType type, final Collection<? extends PropertyType> properties,
-            final PropertyType toSearch)
+    private static GenericName ownerOf(final DefaultFeatureType type, final Collection<? extends AbstractIdentifiedType> properties,
+            final AbstractIdentifiedType toSearch)
     {
         if (properties.contains(toSearch)) {
             return type.getName();
         }
-        for (final FeatureType superType : type.getSuperTypes()) {
+        for (final DefaultFeatureType superType : type.getSuperTypes()) {
             final GenericName owner = ownerOf(superType, superType.getProperties(false), toSearch);
             if (owner != null) {
                 return owner;
@@ -523,23 +529,18 @@
      * @param  previous  previous results, for avoiding never ending loop.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final FeatureType feature, final Map<FeatureType,Boolean> previous) {
+    private boolean resolve(final DefaultFeatureType feature, final Map<FeatureType,Boolean> previous) {
         /*
          * The isResolved field is used only as a cache for skipping completely the DefaultFeatureType instance if
          * we have determined that there is no unresolved name.  If the given argument is not a DefaultFeatureType
          * instance, conservatively assumes `isSimple`. It may cause more calculation than needed, but should not
          * change the result.
          */
-        if (feature instanceof DefaultFeatureType) {
-            final DefaultFeatureType dt = (DefaultFeatureType) feature;
-            return dt.isResolved = resolve(dt, dt.properties, previous, dt.isResolved);
-        } else {
-            return resolve(feature, feature.getProperties(false), previous, feature.isSimple());
-        }
+        return feature.isResolved = resolve(feature, feature.properties, previous, feature.isResolved);
     }
 
     /**
-     * Implementation of {@link #resolve(FeatureType, Map)}, also to be invoked from the constructor.
+     * Implementation of {@code resolve(FeatureType, Map)}, also to be invoked from the constructor.
      *
      * <p>{@code this} shall be the instance in process of being created, not other instance
      * (i.e. recursive method invocations are performed on the same {@code this} instance).</p>
@@ -550,21 +551,19 @@
      * @param  resolved    {@code true} if we already know that all names are resolved.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final FeatureType feature, final Collection<? extends PropertyType> toUpdate,
+    private boolean resolve(final DefaultFeatureType feature, final Collection<? extends AbstractIdentifiedType> toUpdate,
             Map<FeatureType,Boolean> previous, boolean resolved)
     {
         if (!resolved) {
             resolved = true;
-            for (final FeatureType type : feature.getSuperTypes()) {
+            for (final DefaultFeatureType type : feature.getSuperTypes()) {
                 resolved &= resolve(type, previous);
             }
-            for (final PropertyType property : toUpdate) {
-                if (property instanceof FeatureAssociationRole) {
-                    if (property instanceof DefaultAssociationRole) {
-                        if (!((DefaultAssociationRole) property).resolve(this, properties)) {
-                            resolved = false;
-                            continue;
-                        }
+            for (final AbstractIdentifiedType property : toUpdate) {
+                if (property instanceof DefaultAssociationRole) {
+                    if (!((DefaultAssociationRole) property).resolve(this, properties)) {
+                        resolved = false;
+                        continue;
                     }
                     /*
                      * Resolve recursively the associated features, with a check against infinite recursivity.
@@ -572,7 +571,7 @@
                      * may not be the most accurate answer, but will not cause any more hurt than checking more
                      * often than necessary.
                      */
-                    final FeatureType valueType = ((FeatureAssociationRole) property).getValueType();
+                    final DefaultFeatureType valueType = ((DefaultAssociationRole) property).getValueType();
                     if (valueType != this) {
                         if (previous == null) {
                             previous = new IdentityHashMap<>(8);
@@ -595,11 +594,11 @@
      *
      * @see #OPERATION_INDEX
      */
-    static boolean isParameterlessOperation(final PropertyType type) {
-        if (type instanceof Operation) {
-            final ParameterDescriptorGroup parameters = ((Operation) type).getParameters();
+    static boolean isParameterlessOperation(final AbstractIdentifiedType type) {
+        if (type instanceof AbstractOperation) {
+            final ParameterDescriptorGroup parameters = ((AbstractOperation) type).getParameters();
             return ((parameters == null) || parameters.descriptors().isEmpty())
-                   && ((Operation) type).getResult() != null;
+                   && ((AbstractOperation) type).getResult() != null;
         }
         return false;
     }
@@ -614,7 +613,6 @@
      *
      * @return {@code true} if the feature type acts as an abstract super-type.
      */
-    @Override
     public final boolean isAbstract() {
         return isAbstract;
     }
@@ -634,31 +632,22 @@
      *
      * @return {@code true} if this feature type contains only simple attributes or operations.
      */
-    @Override
     public boolean isSimple() {
         return isSimple;
     }
 
     /**
      * Returns {@code true} if the given base type may be the same or a super-type of the given type, using only
-     * the name as a criterion. This is a faster check than {@link #isAssignableFrom(FeatureType)}.
+     * the name as a criterion. This is a faster check than {@link #isAssignableFrom(DefaultFeatureType)}.
      *
      * <p>Performance note: callers should verify that {@code base != type} before to invoke this method.</p>
+     *
+     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
+     * However this method needs to be static in other SIS branches, because they work with interfaces
+     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    static boolean maybeAssignableFrom(final FeatureType base, final FeatureType type) {
-        if (type instanceof DefaultFeatureType) {
-            return ((DefaultFeatureType) type).assignableTo.contains(base.getName());
-        }
-        // Slower path for non-SIS implementations.
-        if (Objects.equals(base.getName(), type.getName())) {
-            return true;
-        }
-        for (final FeatureType superType : type.getSuperTypes()) {
-            if (base == superType || maybeAssignableFrom(base, superType)) {
-                return true;
-            }
-        }
-        return false;
+    static boolean maybeAssignableFrom(final DefaultFeatureType base, final DefaultFeatureType type) {
+        return type.assignableTo.contains(base.getName());
     }
 
     /**
@@ -681,7 +670,7 @@
      * @return {@code true} if instances of the given type can be assigned to association of this type.
      */
     @Override
-    public boolean isAssignableFrom(final FeatureType type) {
+    public boolean isAssignableFrom(final DefaultFeatureType type) {
         if (type == this) {
             return true;                            // Optimization for a common case.
         }
@@ -692,11 +681,11 @@
          * Ensures that all properties defined in this feature type is also defined
          * in the given property, and that the former is assignable from the latter.
          */
-        for (final Map.Entry<String, PropertyType> entry : byName.entrySet()) {
-            final PropertyType other;
+        for (final Map.Entry<String, AbstractIdentifiedType> entry : byName.entrySet()) {
+            final AbstractIdentifiedType other;
             try {
                 other = type.getProperty(entry.getKey());
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 /*
                  * A property in this FeatureType does not exist in the given FeatureType.
                  * Catching exceptions is not an efficient way to perform this check, but
@@ -717,22 +706,25 @@
      * Returns {@code true} if instances of the {@code other} type are assignable to the given {@code base} type.
      * This method does not compare the names — this verification is presumed already done by the caller.
      */
-    private static boolean isAssignableIgnoreName(final PropertyType base, final PropertyType other) {
+    private static boolean isAssignableIgnoreName(final AbstractIdentifiedType base, final AbstractIdentifiedType other) {
         if (base != other) {
             /*
              * If the base property is an attribute, then the overriding property shall be either an attribute
              * or a parameterless operation producing an attribute.  The parameterless operation is considered
              * has having a [1…1] multiplicity.
+             *
+             * Note: other SIS branches use AttributeType and FeatureAssociationRole
+             *       instead than DefaultAttributeType and DefaultAssociationRole.
              */
-            if (base instanceof AttributeType<?>) {
-                final AttributeType<?> p0 = (AttributeType<?>) base;
-                final AttributeType<?> p1;
-                if (other instanceof AttributeType<?>) {
-                    p1 = (AttributeType<?>) other;
+            if (base instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> p0 = (DefaultAttributeType<?>) base;
+                final DefaultAttributeType<?> p1;
+                if (other instanceof DefaultAttributeType<?>) {
+                    p1 = (DefaultAttributeType<?>) other;
                 } else if (isParameterlessOperation(other)) {
-                    final IdentifiedType result = ((Operation) other).getResult();
-                    if (result instanceof AttributeType<?>) {
-                        p1 = (AttributeType<?>) result;
+                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
+                    if (result instanceof DefaultAttributeType<?>) {
+                        p1 = (DefaultAttributeType<?>) result;
                     } else {
                         return false;
                     }
@@ -753,15 +745,15 @@
              * because an implementation could implement both AttributeType and AssociationRole interfaces.
              * This is not recommended, but if it happen we want a behavior as consistent as possible.
              */
-            if (base instanceof FeatureAssociationRole) {
-                final FeatureAssociationRole p0 = (FeatureAssociationRole) base;
-                final FeatureAssociationRole p1;
-                if (other instanceof FeatureAssociationRole) {
-                    p1 = (FeatureAssociationRole) other;
+            if (base instanceof DefaultAssociationRole) {
+                final DefaultAssociationRole p0 = (DefaultAssociationRole) base;
+                final DefaultAssociationRole p1;
+                if (other instanceof DefaultAssociationRole) {
+                    p1 = (DefaultAssociationRole) other;
                 } else if (isParameterlessOperation(other)) {
-                    final IdentifiedType result = ((Operation) other).getResult();
-                    if (result instanceof FeatureAssociationRole) {
-                        p1 = (FeatureAssociationRole) result;
+                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
+                    if (result instanceof DefaultAssociationRole) {
+                        p1 = (DefaultAssociationRole) result;
                     } else {
                         return false;
                     }
@@ -775,8 +767,8 @@
                 {
                     return false;
                 }
-                final FeatureType f0 = p0.getValueType();
-                final FeatureType f1 = p1.getValueType();
+                final DefaultFeatureType f0 = p0.getValueType();
+                final DefaultFeatureType f1 = p1.getValueType();
                 if (f0 != f1 && !f0.isAssignableFrom(f1)) {
                     return false;
                 }
@@ -786,11 +778,11 @@
              * In the special case of parameterless operations, can also be overridden by
              * AttributeType or FeatureAssociationRole.
              */
-            if (base instanceof Operation) {
-                final Operation p0 = (Operation) base;
-                final IdentifiedType r1;
-                if (other instanceof Operation) {
-                    final Operation p1 = (Operation) other;
+            if (base instanceof AbstractOperation) {
+                final AbstractOperation p0 = (AbstractOperation) base;
+                final AbstractIdentifiedType r1;
+                if (other instanceof AbstractOperation) {
+                    final AbstractOperation p1 = (AbstractOperation) other;
                     if (!Objects.equals(p0.getParameters(), p1.getParameters())) {
                         return false;
                     }
@@ -800,15 +792,14 @@
                 } else {
                     return false;
                 }
-                final IdentifiedType r0 = p0.getResult();
+                final AbstractIdentifiedType r0 = p0.getResult();
                 if (r0 != r1) {
-                    if (r0 instanceof FeatureType) {
-                        if (!(r1 instanceof FeatureType) || !((FeatureType) r0).isAssignableFrom((FeatureType) r1)) {
+                    if (r0 instanceof DefaultFeatureType) {
+                        if (!(r1 instanceof DefaultFeatureType) || !((FeatureType) r0).isAssignableFrom((DefaultFeatureType) r1)) {
                             return false;
                         }
-                    }
-                    if (r0 instanceof PropertyType) {
-                        if (!(r1 instanceof PropertyType) || !isAssignableIgnoreName((PropertyType) r0, (PropertyType) r1)) {
+                    } else if (r0 != null) {
+                        if (r1 == null || !isAssignableIgnoreName(r0, r1)) {
                             return false;
                         }
                     }
@@ -826,6 +817,10 @@
      * if we compare {@code FeatureType} to {@link Class} in the Java language, then this method is equivalent
      * to {@link Class#getSuperclass()} except that feature types allow multi-inheritance.</div>
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of list elements will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * <h4>API note</h4>
      * This method is final because it is invoked (indirectly) by constructors, and invoking a user-overrideable
      * method at construction time is not recommended. Furthermore, many Apache SIS methods need guarantees about
@@ -833,9 +828,8 @@
      *
      * @return  the parents of this feature type, or an empty set if none.
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public final Set<FeatureType> getSuperTypes() {
+    public final Set<DefaultFeatureType> getSuperTypes() {
         return superTypes;      // Immutable
     }
 
@@ -845,32 +839,39 @@
      * inherited from the {@linkplain #getSuperTypes() super-types} only if {@code includeSuperTypes}
      * is {@code true}.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of list elements will be changed to {@code PropertyType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  includeSuperTypes  {@code true} for including the properties inherited from the super-types,
      *         or {@code false} for returning only the properties defined explicitly in this type.
      * @return feature operation, attribute type and association role that carries characteristics of this
      *         feature type (not including parent types).
      */
     @Override
-    public Collection<PropertyType> getProperties(final boolean includeSuperTypes) {
+    public Collection<AbstractIdentifiedType> getProperties(final boolean includeSuperTypes) {
         return includeSuperTypes ? allProperties : properties;
     }
 
     /**
      * Returns the attribute, operation or association role for the given name.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of returned element will be changed to {@code PropertyType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  name  the name of the property to search.
      * @return the property for the given name, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      *
      * @see AbstractFeature#getProperty(String)
      */
-    @Override
-    public PropertyType getProperty(final String name) throws PropertyNotFoundException {
-        final PropertyType pt = byName.get(name);
+    public AbstractIdentifiedType getProperty(final String name) throws IllegalArgumentException {
+        final AbstractIdentifiedType pt = byName.get(name);
         if (pt != null) {
             return pt;
         }
-        throw new PropertyNotFoundException(AbstractFeature.propertyNotFound(this, getName(), name));
+        throw new IllegalArgumentException(AbstractFeature.propertyNotFound(this, getName(), name));
     }
 
     /**
@@ -891,12 +892,11 @@
      * then this method is equivalent to {@link Class#newInstance()}.</div>
      *
      * @return a new feature instance.
-     * @throws FeatureInstantiationException if this feature type {@linkplain #isAbstract() is abstract}.
+     * @throws IllegalStateException if this feature type {@linkplain #isAbstract() is abstract}.
      */
-    @Override
-    public Feature newInstance() throws FeatureInstantiationException {
+    public AbstractFeature newInstance() throws IllegalStateException {
         if (isAbstract) {
-            throw new FeatureInstantiationException(Resources.format(Resources.Keys.AbstractFeatureType_1, getName()));
+            throw new IllegalStateException(Resources.format(Resources.Keys.AbstractFeatureType_1, getName()));
         }
         return isSparse ? new SparseFeature(this) : new DenseFeature(this);
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
index 6cdfb9c..bbdfb52 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
@@ -24,12 +24,6 @@
 import org.apache.sis.util.privy.CloneAccess;
 import org.apache.sis.util.privy.Cloner;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Property;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * A feature in which most properties are expected to be provided. This implementation uses a plain array for
@@ -85,15 +79,15 @@
      * @param  name  the property name.
      * @return the index for the property of the given name,
      *         or a negative value if the property is a parameterless operation.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
-    private int getIndex(final String name) throws PropertyNotFoundException {
+    private int getIndex(final String name) throws IllegalArgumentException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
         ArgumentChecks.ensureNonNull("name", name);
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -101,10 +95,10 @@
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
     @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         // Null value check done by the invoked method.
         final int index = getIndex(name);
         if (index < 0) {
@@ -137,9 +131,9 @@
      *         known to this feature, or if the property cannot be set or another reason.
      */
     @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
+    public void setProperty(final Object property) throws IllegalArgumentException {
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
         if (!(properties instanceof Property[])) {
             wrapValuesInProperties();
         }
@@ -176,13 +170,13 @@
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
+    public Object getPropertyValue(final String name) throws IllegalArgumentException {
         final Object value = getValueOrFallback(name, MISSING);
         if (value != MISSING) return value;
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -207,10 +201,10 @@
             if (element != null) {
                 if (!(properties instanceof Property[])) {
                     return element;                                         // Most common case.
-                } else if (element instanceof Attribute<?>) {
-                    return getAttributeValue((Attribute<?>) element);
-                } else if (element instanceof FeatureAssociation) {
-                    return getAssociationValue((FeatureAssociation) element);
+                } else if (element instanceof AbstractAttribute<?>) {
+                    return getAttributeValue((AbstractAttribute<?>) element);
+                } else if (element instanceof AbstractAssociation) {
+                    return getAssociationValue((AbstractAssociation) element);
                 } else {
                     throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
                 }
@@ -322,10 +316,10 @@
                 for (final Property p : (Property[]) properties) {
                     code = 31 * code;
                     final Object value;
-                    if (p instanceof Attribute<?>) {
-                        value = getAttributeValue((Attribute<?>) p);
-                    } else if (p instanceof FeatureAssociation) {
-                        value = getAssociationValue((FeatureAssociation) p);
+                    if (p instanceof AbstractAttribute<?>) {
+                        value = getAttributeValue((AbstractAttribute<?>) p);
+                    } else if (p instanceof AbstractAssociation) {
+                        value = getAssociationValue((AbstractAssociation) p);
                     } else {
                         continue;
                     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java
index 3ad454a..cf97915 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java
@@ -41,14 +41,6 @@
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * An operation computing the envelope that encompass all geometries found in a list of attributes.
@@ -106,8 +98,8 @@
      * a default CRS.
      *
      * <h4>Performance note</h4>
-     * If this array is {@code null}, then {@link Feature#getProperty(String)} does not need to be invoked at all.
-     * A null array is a signal that invoking only the cheaper {@link Feature#getPropertyValue(String)} method is
+     * If this array is {@code null}, then {@link AbstractFeature#getProperty(String)} does not need to be invoked at all.
+     * A null array is a signal that invoking only the cheaper {@link AbstractFeature#getPropertyValue(String)} method is
      * sufficient. However, this array become non-null as soon as there is at least one CRS characteristic to check.
      * We do not distinguish which particular property may have a CRS characteristic because as of Apache SIS 1.0,
      * implementations of {@link DenseFeature} and {@link SparseFeature} have a "all of nothing" behavior anyway.
@@ -124,8 +116,7 @@
     /**
      * The type of the result returned by the envelope operation.
      */
-    @SuppressWarnings("serial")                         // Most SIS implementations are serializable.
-    private final AttributeType<Envelope> resultType;
+    private final DefaultAttributeType<Envelope> resultType;
 
     /**
      * Creates a new operation computing the envelope of features of the given type.
@@ -135,7 +126,7 @@
      * @param geometryAttributes  the operation or attribute type from which to get geometry values.
      */
     EnvelopeOperation(final Map<String,?> identification, CoordinateReferenceSystem targetCRS,
-            final PropertyType[] geometryAttributes) throws FactoryException
+            final AbstractIdentifiedType[] geometryAttributes) throws FactoryException
     {
         super(identification);
         String defaultGeometry = null;
@@ -149,8 +140,8 @@
          */
         boolean characterizedByCRS = false;
         final Map<String,CoordinateReferenceSystem> names = new LinkedHashMap<>(4);
-        for (final IdentifiedType property : geometryAttributes) {
-            final Optional<AttributeType<?>> at = Features.toAttribute(property);
+        for (final AbstractIdentifiedType property : geometryAttributes) {
+            final Optional<DefaultAttributeType<?>> at = Features.toAttribute(property);
             if (at.isPresent() && Geometries.isKnownType(at.get().getValueClass())) {
                 final GenericName name = property.getName();
                 final String attributeName = (property instanceof LinkOperation)
@@ -165,7 +156,7 @@
                  * "CRS" characteristic. Note that we cannot rely on `attributeCRS` being non-null
                  * because an attribute may be characterized by a CRS without providing default CRS.
                  */
-                final AttributeType<?> ct = at.get().characteristics().get(AttributeConvention.CRS);
+                final DefaultAttributeType<?> ct = at.get().characteristics().get(AttributeConvention.CRS);
                 if (ct != null && CoordinateReferenceSystem.class.isAssignableFrom(ct.getValueClass())) {
                     attributeCRS = (CoordinateReferenceSystem) ct.getDefaultValue();              // May still null.
                     if (targetCRS == null && isDefault) {
@@ -234,7 +225,7 @@
      * @return an {@code AttributeType<Envelope>}.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return resultType;
     }
 
@@ -259,7 +250,7 @@
      * @return the envelope of geometries in feature property values.
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result(feature);
     }
 
@@ -279,7 +270,7 @@
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final Feature feature) {
+        Result(final AbstractFeature feature) {
             super(resultType, feature);
         }
 
@@ -336,7 +327,7 @@
                          * a CRS characteristic is associated to a particular feature, setting `op` to null
                          * will cause a new coordinate operation to be searched.
                          */
-                        final Attribute<?> at = ((Attribute<?>) feature.getProperty(attributeNames[i]))
+                        final AbstractAttribute<?> at = ((AbstractAttribute<?>) feature.getProperty(attributeNames[i]))
                                 .characteristics().get(AttributeConvention.CRS);
                         final Object geomCRS;
                         if (at != null && (geomCRS = at.getValue()) != null) {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/ExpressionOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/ExpressionOperation.java
index 3518b6b..9d56804 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/ExpressionOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/ExpressionOperation.java
@@ -27,16 +27,11 @@
 import org.apache.sis.filter.privy.FunctionNames;
 import org.apache.sis.filter.privy.Visitor;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.CodeList;
-import org.opengis.feature.Feature;
-import org.opengis.feature.Property;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.LogicalOperator;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -63,13 +58,12 @@
      * 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<? super AbstractFeature, ? extends V> expression;
 
     /**
      * The type of result of evaluating the expression.
      */
-    @SuppressWarnings("serial")                         // Apache SIS implementations are serializable.
-    private final AttributeType<V> resultType;
+    private final DefaultAttributeType<V> resultType;
 
     /**
      * The name of all feature properties that are known to be read by the expression.
@@ -87,8 +81,8 @@
      * @param resultType      type of values computed by the expression.
      */
     static <V> AbstractOperation create(final Map<String,?> identification,
-                                        final Function<? super Feature, ? extends V> expression,
-                                        final AttributeType<? super V> resultType)
+                                        final Function<? super AbstractFeature, ? extends V> expression,
+                                        final DefaultAttributeType<? super V> resultType)
     {
         if (expression instanceof ValueReference<?,?>) {
             final String xpath = ((ValueReference<?,?>) expression).getXPath();
@@ -103,15 +97,15 @@
      * Creates a generic operation when no optimized case has been identifier.
      */
     private ExpressionOperation(final Map<String,?> identification,
-                                final Function<? super Feature, ? extends V> expression,
-                                final AttributeType<V> resultType)
+                                final Function<? super AbstractFeature, ? extends V> expression,
+                                final DefaultAttributeType<V> resultType)
     {
         super(identification);
         this.expression = expression;
         this.resultType = resultType;
         if (expression instanceof Expression<?,?>) {
             @SuppressWarnings("unchecked")
-            var c = (Expression<Feature,?>) expression;     // Cast is okay because we will not pass or request any `Feature` instance.
+            var c = (Expression<AbstractFeature,?>) expression;     // Cast is okay because we will not pass or request any `Feature` instance.
             dependencies = DependencyFinder.search(c);
         } else {
             dependencies = Set.of();
@@ -130,7 +124,7 @@
      * Returns the expected result type.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return resultType;
     }
 
@@ -152,7 +146,7 @@
      * @return the computed property from the given feature.
      */
     @Override
-    public Property apply(final Feature feature, ParameterValueGroup parameters) {
+    public Property apply(final AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result(feature);
     }
 
@@ -169,7 +163,7 @@
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final Feature feature) {
+        Result(final AbstractFeature feature) {
             super(resultType, feature);
         }
 
@@ -206,7 +200,7 @@
      * An expression visitor for finding all dependencies of a given expression.
      * The dependencies are feature properties read by {@link ValueReference} nodes.
      */
-    private static final class DependencyFinder extends Visitor<Feature, Collection<String>> {
+    private static final class DependencyFinder extends Visitor<AbstractFeature, Collection<String>> {
         /**
          * The unique instance.
          */
@@ -218,7 +212,7 @@
          * @param  expression  the expression for which to get dependencies.
          * @return all dependencies recognized by this method.
          */
-        static Set<String> search(final Expression<Feature,?> expression) {
+        static Set<String> search(final Expression<AbstractFeature,?> expression) {
             final Set<String> dependencies = new HashSet<>();
             VISITOR.visit(expression, dependencies);
             return Set.copyOf(dependencies);
@@ -229,13 +223,13 @@
          */
         private DependencyFinder() {
             setLogicalHandlers((f, dependencies) -> {
-                final var filter = (LogicalOperator<Feature>) f;
-                for (Filter<Feature> child : filter.getOperands()) {
+                final var filter = (LogicalOperator<AbstractFeature>) f;
+                for (Filter<AbstractFeature> child : filter.getOperands()) {
                     visit(child, dependencies);
                 }
             });
             setExpressionHandler(FunctionNames.ValueReference, (e, dependencies) -> {
-                final var expression = (ValueReference<Feature,?>) e;
+                final var expression = (ValueReference<AbstractFeature,?>) e;
                 final String propName = expression.getXPath();
                 if (!propName.trim().isEmpty()) {
                     dependencies.add(propName);
@@ -247,8 +241,8 @@
          * Fallback for all filters not explicitly handled by the setting applied in the constructor.
          */
         @Override
-        protected void typeNotFound(final CodeList<?> type, final Filter<Feature> filter, final Collection<String> dependencies) {
-            for (final Expression<Feature,?> f : filter.getExpressions()) {
+        protected void typeNotFound(final Enum<?> type, final Filter<AbstractFeature> filter, final Collection<String> dependencies) {
+            for (final Expression<AbstractFeature,?> f : filter.getExpressions()) {
                 visit(f, dependencies);
             }
         }
@@ -257,8 +251,8 @@
          * Fallback for all expressions not explicitly handled by the setting applied in the constructor.
          */
         @Override
-        protected void typeNotFound(final String type, final Expression<Feature,?> expression, final Collection<String> dependencies) {
-            for (final Expression<Feature,?> p : expression.getParameters()) {
+        protected void typeNotFound(final String type, final Expression<AbstractFeature,?> expression, final Collection<String> dependencies) {
+            for (final Expression<AbstractFeature,?> p : expression.getParameters()) {
                 visit(p, dependencies);
             }
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java
index 614f3ce..2f0a746 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java
@@ -50,18 +50,6 @@
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.math.MathFunctions;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
-
 
 /**
  * Formats {@linkplain AbstractFeature features} or {@linkplain DefaultFeatureType feature types} in a tabular format.
@@ -158,7 +146,7 @@
 
     /**
      * Returns the type of objects formatted by this class. This method has to return {@code Object.class}
-     * since it is the only common parent to {@link Feature} and {@link FeatureType}.
+     * since it is the only common parent to {@code Feature} and {@link FeatureType}.
      *
      * @return {@code Object.class}
      */
@@ -225,20 +213,20 @@
     public enum Column {
         /**
          * Natural language designator for the property.
-         * This is the character sequence returned by {@link PropertyType#getDesignation()}.
+         * This is the character sequence returned by {@link AbstractIdentifiedType#getDesignation()}.
          * This column is omitted if no property has a designation.
          */
         DESIGNATION(Vocabulary.Keys.Designation),
 
         /**
          * Name of the property.
-         * This is the character sequence returned by {@link PropertyType#getName()}.
+         * This is the character sequence returned by {@link AbstractIdentifiedType#getName()}.
          */
         NAME(Vocabulary.Keys.Name),
 
         /**
-         * Type of property values. This is the type returned by {@link AttributeType#getValueClass()} or
-         * {@link FeatureAssociationRole#getValueType()}.
+         * Type of property values. This is the type returned by {@link DefaultAttributeType#getValueClass()} or
+         * {@link DefaultAssociationRole#getValueType()}.
          */
         TYPE(Vocabulary.Keys.Type),
 
@@ -246,21 +234,21 @@
          * Cardinality (for attributes) or multiplicity (for attribute types).
          * The cardinality is the actual number of attribute values.
          * The multiplicity is the minimum and maximum occurrences of attribute values.
-         * The multiplicity is made from the numbers returned by {@link AttributeType#getMinimumOccurs()}
-         * and {@link AttributeType#getMaximumOccurs()}.
+         * The multiplicity is made from the numbers returned by {@link DefaultAttributeType#getMinimumOccurs()}
+         * and {@link DefaultAttributeType#getMaximumOccurs()}.
          */
         CARDINALITY(Vocabulary.Keys.Cardinality),
 
         /**
          * Property value (for properties) or default value (for property types).
-         * This is the value returned by {@link Attribute#getValue()}, {@link FeatureAssociation#getValue()}
-         * or {@link AttributeType#getDefaultValue()}.
+         * This is the value returned by {@link AbstractAttribute#getValue()}, {@link AbstractAssociation#getValue()}
+         * or {@link DefaultAttributeType#getDefaultValue()}.
          */
         VALUE(Vocabulary.Keys.Value),
 
         /**
          * Other attributes that describes the attribute.
-         * This is made from the map returned by {@link Attribute#characteristics()}.
+         * This is made from the map returned by {@link AbstractAttribute#characteristics()}.
          * This column is omitted if no property has characteristics.
          */
         CHARACTERISTICS(Vocabulary.Keys.Characteristics),
@@ -297,8 +285,8 @@
      * The object may be an instance of any of the following types:
      *
      * <ul>
-     *   <li>{@link Feature}</li>
-     *   <li>{@link FeatureType}</li>
+     *   <li>{@code Feature}</li>
+     *   <li>{@code FeatureType}</li>
      * </ul>
      *
      * @throws IOException if an error occurred while writing to the given appendable.
@@ -310,13 +298,13 @@
         /*
          * Separate the Feature (optional) and the FeatureType (mandatory) instances.
          */
-        final FeatureType featureType;
-        final Feature feature;
-        if (object instanceof Feature) {
-            feature     = (Feature) object;
+        final DefaultFeatureType featureType;
+        final AbstractFeature feature;
+        if (object instanceof AbstractFeature) {
+            feature     = (AbstractFeature) object;
             featureType = feature.getType();
-        } else if (object instanceof FeatureType) {
-            featureType = (FeatureType) object;
+        } else if (object instanceof DefaultFeatureType) {
+            featureType = (DefaultFeatureType) object;
             feature     = null;
         } else {
             throw new IllegalArgumentException(Errors.forLocale(displayLocale)
@@ -333,12 +321,12 @@
             boolean hasDesignation     = false;
             boolean hasCharacteristics = false;
             boolean hasDeprecatedTypes = false;
-            for (final PropertyType propertyType : featureType.getProperties(true)) {
+            for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
                 if (!hasDesignation) {
                     hasDesignation = propertyType.getDesignation().isPresent();
                 }
-                if (!hasCharacteristics && propertyType instanceof AttributeType<?>) {
-                    hasCharacteristics = !((AttributeType<?>) propertyType).characteristics().isEmpty();
+                if (!hasCharacteristics && propertyType instanceof DefaultAttributeType<?>) {
+                    hasCharacteristics = !((DefaultAttributeType<?>) propertyType).characteristics().isEmpty();
                 }
                 if (!hasDeprecatedTypes && propertyType instanceof Deprecable) {
                     hasDeprecatedTypes = ((Deprecable) propertyType).isDeprecated();
@@ -399,26 +387,26 @@
         final StringBuffer  buffer  = new StringBuffer();
         final FieldPosition dummyFP = new FieldPosition(-1);
         final List<String>  remarks = new ArrayList<>();
-        for (final PropertyType propertyType : featureType.getProperties(true)) {
+        for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
             Object value = null;
             int cardinality = -1;
             if (feature != null) {
-                if (!(propertyType instanceof AttributeType<?>) &&
-                    !(propertyType instanceof FeatureAssociationRole) &&
+                if (!(propertyType instanceof DefaultAttributeType<?>) &&
+                    !(propertyType instanceof DefaultAssociationRole) &&
                     !DefaultFeatureType.isParameterlessOperation(propertyType))
                 {
                     continue;
                 }
                 value = feature.getPropertyValue(propertyType.getName().toString());
                 if (value == null) {
-                    if (propertyType instanceof AttributeType<?>
-                            && ((AttributeType<?>) propertyType).getMinimumOccurs() == 0
-                            && ((AttributeType<?>) propertyType).characteristics().isEmpty())
+                    if (propertyType instanceof DefaultAttributeType<?>
+                            && ((DefaultAttributeType<?>) propertyType).getMinimumOccurs() == 0
+                            && ((DefaultAttributeType<?>) propertyType).characteristics().isEmpty())
                     {
                         continue;                           // If optional, no value and no characteristics, skip the full row.
                     }
-                    if (propertyType instanceof FeatureAssociationRole
-                            && ((FeatureAssociationRole) propertyType).getMinimumOccurs() == 0)
+                    if (propertyType instanceof DefaultAssociationRole
+                            && ((DefaultAssociationRole) propertyType).getMinimumOccurs() == 0)
                     {
                         continue;                           // If optional and no value, skip the full row.
                     }
@@ -428,16 +416,12 @@
                 } else {
                     cardinality = 1;
                 }
-            } else if (propertyType instanceof AttributeType<?>) {
-                value = ((AttributeType<?>) propertyType).getDefaultValue();
-            } else if (propertyType instanceof Operation) {
+            } else if (propertyType instanceof DefaultAttributeType<?>) {
+                value = ((DefaultAttributeType<?>) propertyType).getDefaultValue();
+            } else if (propertyType instanceof AbstractOperation) {
                 buffer.append(" = ");
                 try {
-                    if (propertyType instanceof AbstractOperation) {
-                        ((AbstractOperation) propertyType).formatResultFormula(buffer);
-                    } else {
-                        AbstractOperation.defaultFormula(((Operation) propertyType).getParameters(), buffer);
-                    }
+                    ((AbstractOperation) propertyType).formatResultFormula(buffer);
                 } catch (IOException e) {
                     throw new UncheckedIOException(e);      // Should never happen since we write in a StringBuffer.
                 }
@@ -447,25 +431,25 @@
             final String   valueType;                       // The value to write in the type column.
             final Class<?> valueClass;                      // AttributeType.getValueClass() if applicable.
             final int minimumOccurs, maximumOccurs;         // Negative values mean no cardinality.
-            final IdentifiedType resultType;                // Result of operation if applicable.
-            if (propertyType instanceof Operation) {
-                resultType = ((Operation) propertyType).getResult();                // May be null
+            final AbstractIdentifiedType resultType;        // Result of operation if applicable.
+            if (propertyType instanceof AbstractOperation) {
+                resultType = ((AbstractOperation) propertyType).getResult();        // May be null
             } else {
                 resultType = propertyType;
             }
-            if (resultType instanceof AttributeType<?>) {
-                final AttributeType<?> pt = (AttributeType<?>) resultType;
+            if (resultType instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) resultType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueClass    = pt.getValueClass();
                 valueType     = getFormat(Class.class).format(valueClass, buffer, dummyFP).toString();
                 buffer.setLength(0);
-            } else if (resultType instanceof FeatureAssociationRole) {
-                final FeatureAssociationRole pt = (FeatureAssociationRole) resultType;
+            } else if (resultType instanceof DefaultAssociationRole) {
+                final DefaultAssociationRole pt = (DefaultAssociationRole) resultType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueType     = toString(DefaultAssociationRole.getValueTypeName(pt));
-                valueClass    = Feature.class;
+                valueClass    = AbstractFeature.class;
             } else {
                 valueType  = (resultType != null) ? toString(resultType.getName()) : "";
                 valueClass = null;
@@ -553,10 +537,10 @@
                         while (it.hasNext()) {
                             value = it.next();
                             if (value != null) {
-                                if (propertyType instanceof FeatureAssociationRole) {
-                                    final String p = DefaultAssociationRole.getTitleProperty((FeatureAssociationRole) propertyType);
+                                if (propertyType instanceof DefaultAssociationRole) {
+                                    final String p = DefaultAssociationRole.getTitleProperty((DefaultAssociationRole) propertyType);
                                     if (p != null) {
-                                        value = ((Feature) value).getPropertyValue(p);
+                                        value = ((AbstractFeature) value).getPropertyValue(p);
                                         if (value == null) continue;
                                     }
                                 } else if (format != null && valueClass.isInstance(value)) {    // Null safe because of getFormat(valueClass) contract.
@@ -610,10 +594,10 @@
                      * Characteristics are handled as "attributes of attributes".
                      */
                     case CHARACTERISTICS: {
-                        if (propertyType instanceof AttributeType<?>) {
+                        if (propertyType instanceof DefaultAttributeType<?>) {
                             int length = 0;
                             String separator = "";
-format:                     for (final AttributeType<?> ct : ((AttributeType<?>) propertyType).characteristics().values()) {
+format:                     for (final DefaultAttributeType<?> ct : ((DefaultAttributeType<?>) propertyType).characteristics().values()) {
                                 /*
                                  * Format the characteristic name. We will append the value(s) later.
                                  * We keep trace of the text length in order to stop formatting if the
@@ -631,9 +615,9 @@
                                      * given by the default value `cv`.  Nevertheless we have to check if current
                                      * feature overrides this characteristic.
                                      */
-                                    final Property cp = feature.getProperty(propertyType.getName().toString());
-                                    if (cp instanceof Attribute<?>) {            // Should always be true, but we are paranoiac.
-                                        Attribute<?> ca = ((Attribute<?>) cp).characteristics().get(cn.toString());
+                                    final Object cp = feature.getProperty(propertyType.getName().toString());
+                                    if (cp instanceof AbstractAttribute<?>) {            // Should always be true, but we are paranoiac.
+                                        AbstractAttribute<?> ca = ((AbstractAttribute<?>) cp).characteristics().get(cn.toString());
                                         if (ca != null) cv = ca.getValues();
                                     }
                                 }
@@ -717,8 +701,8 @@
             text = ((InternationalString) value).toString(displayLocale);
         } else if (value instanceof GenericName) {
             text = toString((GenericName) value);
-        } else if (value instanceof IdentifiedType) {
-            text = toString(((IdentifiedType) value).getName());
+        } else if (value instanceof AbstractIdentifiedType) {
+            text = toString(((AbstractIdentifiedType) value).getName());
         } else if (value instanceof IdentifiedObject) {
             text = IdentifiedObjects.getIdentifierOrName((IdentifiedObject) value);
         } else {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
index f88ee70..ef10b00 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
@@ -30,13 +30,8 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.setup.GeometryLibrary;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -120,7 +115,7 @@
     /**
      * The pool of operations or operation dependencies created so far, for sharing exiting instances.
      */
-    static final WeakHashSet<PropertyType> POOL = new WeakHashSet<>(PropertyType.class);
+    static final WeakHashSet<AbstractIdentifiedType> POOL = new WeakHashSet<>(AbstractIdentifiedType.class);
 
     /**
      * Do not allow instantiation of this class.
@@ -156,13 +151,17 @@
      * identified by the {@code referent} argument, the returned property is writable if the referenced
      * property is also writable.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of {@code referent} parameter will be changed to {@code PropertyType}
+     * if and when such interface will be defined in GeoAPI.</div>
+     *
      * @param  identification  the name and other information to be given to the operation.
      * @param  referent        the referenced attribute or feature association.
      * @return an operation which is an alias for the {@code referent} property.
      *
      * @see Features#getLinkTarget(PropertyType)
      */
-    public static Operation link(final Map<String,?> identification, final PropertyType referent) {
+    public static AbstractOperation link(final Map<String,?> identification, final AbstractIdentifiedType referent) {
         ArgumentChecks.ensureNonNull("referent", referent);
         return POOL.unique(new LinkOperation(identification, referent));
     }
@@ -193,6 +192,10 @@
      * operation, the given string value will be split around the {@code delimiter} and each substring will be
      * forwarded to the corresponding single property.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of {@code singleAttributes} elements will be changed to {@code PropertyType}
+     * if and when such interface will be defined in GeoAPI.</div>
+     *
      * @param  identification    the name and other information to be given to the operation.
      * @param  delimiter         the characters to use as delimiter between each single property value.
      * @param  prefix            characters to use at the beginning of the concatenated string, or {@code null} if none.
@@ -207,8 +210,8 @@
      *
      * @see <a href="https://en.wikipedia.org/wiki/Compound_key">Compound key on Wikipedia</a>
      */
-    public static Operation compound(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final PropertyType... singleAttributes)
+    public static AbstractOperation compound(final Map<String,?> identification, final String delimiter,
+            final String prefix, final String suffix, final AbstractIdentifiedType... singleAttributes)
             throws UnconvertibleObjectException
     {
         ArgumentChecks.ensureNonEmpty("delimiter", delimiter);
@@ -219,8 +222,8 @@
         ArgumentChecks.ensureNonEmpty("singleAttributes", singleAttributes);
         if (singleAttributes.length == 1) {
             if (Strings.isNullOrEmpty(prefix) && Strings.isNullOrEmpty(suffix)) {
-                final PropertyType at = singleAttributes[0];
-                if (!(at instanceof FeatureAssociationRole)) {
+                final AbstractIdentifiedType at = singleAttributes[0];
+                if (!(at instanceof DefaultAssociationRole)) {
                     return link(identification, at);
                 }
             }
@@ -253,6 +256,10 @@
      * This operation is read-only. Calls to {@code Attribute.setValue(Envelope)} will result in an
      * {@link UnsupportedOperationException} to be thrown.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of {@code geometryAttributes} elements will be changed to {@code PropertyType}
+     * if and when such interface will be defined in GeoAPI.</div>
+     *
      * @param  identification      the name and other information to be given to the operation.
      * @param  crs                 the Coordinate Reference System in which to express the envelope, or {@code null}.
      * @param  geometryAttributes  the operation or attribute type from which to get geometry values.
@@ -260,8 +267,8 @@
      * @return an operation which will compute the envelope encompassing all geometries in the given attributes.
      * @throws FactoryException if a coordinate operation to the target CRS cannot be created.
      */
-    public static Operation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
-            final PropertyType... geometryAttributes) throws FactoryException
+    public static AbstractOperation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
+            final AbstractIdentifiedType... geometryAttributes) throws FactoryException
     {
         ArgumentChecks.ensureNonNull("geometryAttributes", geometryAttributes);
         return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes));
@@ -297,8 +304,8 @@
      *
      * @since 1.4
      */
-    public static Operation groupAsPolyline(final Map<String,?> identification, final GeometryLibrary library,
-                                            final PropertyType components)
+    public static AbstractOperation groupAsPolyline(final Map<String,?> identification, final GeometryLibrary library,
+                                            final AbstractIdentifiedType components)
     {
         ArgumentChecks.ensureNonNull("library", library);
         ArgumentChecks.ensureNonNull("components", components);
@@ -308,7 +315,7 @@
     /**
      * Creates an operation which delegates the computation to a given expression.
      * The {@code expression} argument should generally be an instance of
-     * {@link org.opengis.filter.Expression},
+     * {@link org.apache.sis.filter.Expression},
      * but more generic functions are accepted as well.
      *
      * <h4>Read/write behavior</h4>
@@ -323,9 +330,9 @@
      *
      * @since 1.4
      */
-    public static <V> Operation function(final Map<String,?> identification,
-                                         final Function<? super Feature, ? extends V> expression,
-                                         final AttributeType<? super V> resultType)
+    public static <V> AbstractOperation function(final Map<String,?> identification,
+                                         final Function<? super AbstractFeature, ? extends V> expression,
+                                         final DefaultAttributeType<? super V> resultType)
     {
         ArgumentChecks.ensureNonNull("expression", expression);
         ArgumentChecks.ensureNonNull("resultType", resultType);
@@ -352,9 +359,9 @@
      *
      * @since 1.4
      */
-    public static <V> Operation expression(final Map<String,?> identification,
-                                           final Expression<? super Feature, ?> expression,
-                                           final AttributeType<V> resultType)
+    public static <V> AbstractOperation expression(final Map<String,?> identification,
+                                           final Expression<? super AbstractFeature, ?> expression,
+                                           final DefaultAttributeType<V> resultType)
     {
         return function(identification, expression.toValueType(resultType.getValueClass()), resultType);
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureType.java
new file mode 100644
index 0000000..d559a69
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureType.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.feature;
+
+import java.util.Collection;
+import org.opengis.util.GenericName;
+
+
+/**
+ * Place-holder for an interface not available in GeoAPI 3.0.
+ * This place-holder will be removed after we upgrade to a later GeoAPI version.
+ *
+ * <p><strong>Do not put this type in public API</strong>. We need to prevent users from using
+ * this type in order to reduce compatibility breaks when we will upgrade the GeoAPI version.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+interface FeatureType {
+    GenericName getName();
+
+    Collection<AbstractIdentifiedType> getProperties(boolean includeSuperTypes);
+
+    boolean isAssignableFrom(DefaultFeatureType type);
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Features.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Features.java
index 32f9da6..ea862f4 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Features.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Features.java
@@ -30,18 +30,6 @@
 import org.apache.sis.util.iso.DefaultNameFactory;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Static methods working on features or attributes.
@@ -73,7 +61,7 @@
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> AttributeType<V> cast(final AttributeType<?> type, final Class<V> valueClass)
+    public static <V> DefaultAttributeType<V> cast(final DefaultAttributeType<?> type, final Class<V> valueClass)
             throws ClassCastException
     {
         if (type != null) {
@@ -87,7 +75,7 @@
                         type.getName(), valueClass, actual));
             }
         }
-        return (AttributeType<V>) type;
+        return (DefaultAttributeType<V>) type;
     }
 
     /**
@@ -104,7 +92,7 @@
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> Attribute<V> cast(final Attribute<?> attribute, final Class<V> valueClass)
+    public static <V> AbstractAttribute<V> cast(final AbstractAttribute<?> attribute, final Class<V> valueClass)
             throws ClassCastException
     {
         if (attribute != null) {
@@ -118,18 +106,18 @@
                         attribute.getName(), valueClass, actual));
             }
         }
-        return (Attribute<V>) attribute;
+        return (AbstractAttribute<V>) attribute;
     }
 
     /**
-     * Returns the given type as an {@link AttributeType} by casting if possible, or by getting the result type
+     * Returns the given type as an {@code AttributeType} by casting if possible, or by getting the result type
      * of an operation. More specifically this method returns the first of the following types which apply:
      *
      * <ul>
-     *   <li>If the given type is an instance of {@link AttributeType}, then it is returned as-is.</li>
-     *   <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
-     *       result type} is an {@link AttributeType}, then that result type is returned.</li>
-     *   <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
+     *   <li>If the given type is an instance of {@code AttributeType}, then it is returned as-is.</li>
+     *   <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
+     *       result type} is an {@code AttributeType}, then that result type is returned.</li>
+     *   <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
      *       result type} is another operation, then the above check is performed recursively.</li>
      * </ul>
      *
@@ -139,19 +127,19 @@
      * @since 1.1
      */
     @SuppressWarnings("unchecked")
-    public static Optional<AttributeType<?>> toAttribute(IdentifiedType type) {
-        return toIdentifiedType(type, (Class) AttributeType.class);
+    public static Optional<DefaultAttributeType<?>> toAttribute(AbstractIdentifiedType type) {
+        return toIdentifiedType(type, (Class) DefaultAttributeType.class);
     }
 
     /**
-     * Returns the given type as a {@link FeatureAssociationRole} by casting if possible, or by getting the result type
+     * Returns the given type as a {@code FeatureAssociationRole} by casting if possible, or by getting the result type
      * of an operation. More specifically this method returns the first of the following types which apply:
      *
      * <ul>
-     *   <li>If the given type is an instance of {@link FeatureAssociationRole}, then it is returned as-is.</li>
-     *   <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
-     *       result type} is an {@link FeatureAssociationRole}, then that result type is returned.</li>
-     *   <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
+     *   <li>If the given type is an instance of {@code FeatureAssociationRole}, then it is returned as-is.</li>
+     *   <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
+     *       result type} is an {@code FeatureAssociationRole}, then that result type is returned.</li>
+     *   <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
      *       result type} is another operation, then the above check is performed recursively.</li>
      * </ul>
      *
@@ -160,21 +148,21 @@
      *
      * @since 1.4
      */
-    public static Optional<FeatureAssociationRole> toAssociation(IdentifiedType type) {
-        return toIdentifiedType(type, FeatureAssociationRole.class);
+    public static Optional<DefaultAssociationRole> toAssociation(AbstractIdentifiedType type) {
+        return toIdentifiedType(type, DefaultAssociationRole.class);
     }
 
     /**
-     * Implementation of {@link #toAttribute(IdentifiedType)} and {@link #toAssociation(IdentifiedType)}.
+     * Implementation of {@code toAttribute(IdentifiedType)} and {@code toAssociation(IdentifiedType)}.
      */
-    private static <T> Optional<T> toIdentifiedType(IdentifiedType type, final Class<T> target) {
+    private static <T> Optional<T> toIdentifiedType(AbstractIdentifiedType type, final Class<T> target) {
         if (!target.isInstance(type)) {
-            if (!(type instanceof Operation)) {
+            if (!(type instanceof AbstractOperation)) {
                 return Optional.empty();
             }
-            type = ((Operation) type).getResult();
+            type = ((AbstractOperation) type).getResult();
             if (!target.isInstance(type)) {
-                if (!(type instanceof Operation)) {
+                if (!(type instanceof AbstractOperation)) {
                     return Optional.empty();
                 }
                 /*
@@ -182,9 +170,9 @@
                  * contain a cycle. However, given that the consequence of an infinite cycle here
                  * would be thread freeze, we check as a safety.
                  */
-                final Map<IdentifiedType,Boolean> done = new IdentityHashMap<>(4);
-                while (!target.isInstance(type = ((Operation) type).getResult())) {
-                    if (!(type instanceof Operation) || done.put(type, Boolean.TRUE) != null) {
+                final Map<AbstractIdentifiedType,Boolean> done = new IdentityHashMap<>(4);
+                while (!target.isInstance(type = ((AbstractOperation) type).getResult())) {
+                    if (!(type instanceof AbstractOperation) || done.put(type, Boolean.TRUE) != null) {
                         return Optional.empty();
                     }
                 }
@@ -202,62 +190,33 @@
      * @param  types  types for which to find a common type, or {@code null}.
      * @return a feature type which is assignable from all given types, or {@code null} if none.
      *
-     * @see FeatureType#isAssignableFrom(FeatureType)
+     * @see DefaultFeatureType#isAssignableFrom(DefaultFeatureType)
      *
      * @since 1.0
      */
-    public static FeatureType findCommonParent(final Iterable<? extends FeatureType> types) {
+    public static DefaultFeatureType findCommonParent(final Iterable<? extends DefaultFeatureType> types) {
         return (types != null) ? CommonParentFinder.select(types) : null;
     }
 
-    /**
-     * Returns the type of values provided by the given property. For {@linkplain AttributeType attributes}
-     * (which is the most common case), the value type is given by {@link AttributeType#getValueClass()}.
-     * For {@linkplain FeatureAssociationRole feature associations}, the value type is {@link Feature}.
-     * For {@linkplain Operation operations}, the value type is determined recursively from the
-     * {@linkplain Operation#getResult() operation result}.
-     * If the value type cannot be determined, then this method returns {@code null}.
+    /*
+     * Following method is omitted on master because it depends on GeoAPI interfaces not yet published:
      *
-     * @param  type  the property for which to get the type of values, or {@code null}.
-     * @return the type of values provided by the given property, or {@code null} if unknown.
-     *
-     * @see AttributeType#getValueClass()
-     *
-     * @since 1.0
+     *     public static Class<?> getValueClass(PropertyType type)
      */
-    public static Class<?> getValueClass(PropertyType type) {
-        while (type instanceof Operation) {
-            final IdentifiedType result = ((Operation) type).getResult();
-            if (result != type && result instanceof PropertyType) {
-                type = (PropertyType) result;
-            } else if (result instanceof FeatureType) {
-                return Feature.class;
-            } else {
-                break;
-            }
-        }
-        if (type instanceof AttributeType<?>) {
-            return ((AttributeType<?>) type).getValueClass();
-        } else if (type instanceof FeatureAssociationRole) {
-            return Feature.class;
-        } else {
-            return null;
-        }
-    }
 
     /**
      * Returns the name of the type of values that the given property can take.
-     * The type of value can be a {@link Class}, a {@link org.opengis.feature.FeatureType}
+     * The type of value can be a {@link Class}, a {@code FeatureType}
      * or another {@code PropertyType} depending on given argument:
      *
      * <ul>
-     *   <li>If {@code property} is an {@link AttributeType}, then this method gets the
+     *   <li>If {@code property} is an {@code AttributeType}, then this method gets the
      *       {@linkplain DefaultAttributeType#getValueClass() value class} and
      *       {@linkplain DefaultNameFactory#toTypeName(Class) maps that class to a name}.</li>
-     *   <li>If {@code property} is a {@link FeatureAssociationRole}, then this method gets
+     *   <li>If {@code property} is a {@code FeatureAssociationRole}, then this method gets
      *       the name of the {@linkplain DefaultAssociationRole#getValueType() value type}.
      *       This methods can work even if the associated {@code FeatureType} is not yet resolved.</li>
-     *   <li>If {@code property} is an {@link Operation}, then this method returns the name of the
+     *   <li>If {@code property} is an {@code Operation}, then this method returns the name of the
      *       {@linkplain AbstractOperation#getResult() result type}.</li>
      * </ul>
      *
@@ -266,14 +225,14 @@
      *
      * @since 0.8
      */
-    public static GenericName getValueTypeName(final PropertyType property) {
-        if (property instanceof FeatureAssociationRole) {
+    public static GenericName getValueTypeName(final AbstractIdentifiedType property) {
+        if (property instanceof DefaultAssociationRole) {
             // Tested first because this is the main interest for this method.
-            return DefaultAssociationRole.getValueTypeName((FeatureAssociationRole) property);
-        } else if (property instanceof AttributeType<?>) {
-            return Names.createTypeName(((AttributeType<?>) property).getValueClass());
-        } else if (property instanceof Operation) {
-            final IdentifiedType result = ((Operation) property).getResult();
+            return DefaultAssociationRole.getValueTypeName((DefaultAssociationRole) property);
+        } else if (property instanceof DefaultAttributeType<?>) {
+            return Names.createTypeName(((DefaultAttributeType<?>) property).getValueClass());
+        } else if (property instanceof AbstractOperation) {
+            final AbstractIdentifiedType result = ((AbstractOperation) property).getResult();
             if (result != null) {
                 return result.getName();
             }
@@ -284,7 +243,7 @@
     /**
      * If the given property is a link, returns the name of the referenced property.
      * A link is an operation created by a call to {@link FeatureOperations#link(Map, PropertyType)},
-     * in which case the value returned by this method is the name of the {@link PropertyType} argument
+     * in which case the value returned by this method is the name of the {@code PropertyType} argument
      * which has been given to that {@code link(…)} method.
      *
      * @param  property  the property to test, or {@code null} if none.
@@ -294,7 +253,7 @@
      *
      * @since 1.1
      */
-    public static Optional<String> getLinkTarget(final PropertyType property) {
+    public static Optional<String> getLinkTarget(final AbstractIdentifiedType property) {
         if (property instanceof LinkOperation) {
             return Optional.of(((LinkOperation) property).referentName);
         }
@@ -315,24 +274,13 @@
      * {@code InvalidPropertyValueException} is thrown. Otherwise this method returns doing nothing.
      *
      * @param  feature  the feature to validate, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is non-null and does not pass validation.
+     * @throws IllegalArgumentException if the given feature is non-null and does not pass validation.
      *
      * @since 0.7
      */
-    public static void validate(final Feature feature) throws InvalidPropertyValueException {
+    public static void validate(final AbstractFeature feature) throws IllegalArgumentException {
         if (feature != null) {
-            /*
-             * Delegate to AbstractFeature.quality() if possible because the user may have overridden the method.
-             * Otherwise fallback on the same code as AbstractFeature.quality() default implementation.
-             */
-            final DataQuality quality;
-            if (feature instanceof AbstractFeature) {
-                quality = ((AbstractFeature) feature).quality();
-            } else {
-                final Validator v = new Validator(ScopeCode.FEATURE);
-                v.validate(feature.getType(), feature);
-                quality = v.quality;
-            }
+            final DataQuality quality = feature.quality();
             /*
              * Loop on quality elements and check conformance results.
              * NOTE: other types of result are ignored for now, since those other
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Field.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Field.java
index a2560fc..4812b5a 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Field.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Field.java
@@ -21,12 +21,6 @@
 import org.apache.sis.util.Deprecable;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.MultiValuedPropertyException;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Base class of property that can be stored in a {@link AbstractFeature} instance.
@@ -36,7 +30,7 @@
  *
  * @param <V> the type of property values.
  */
-abstract class Field<V> implements Property {
+abstract class Field<V> extends Property {
     /**
      * For subclass constructors.
      */
@@ -58,12 +52,12 @@
      * Returns the field feature or attribute value, or {@code null} if none.
      *
      * @return the feature or attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this field contains more than one value.
+     * @throws IllegalStateException if this field contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws MultiValuedPropertyException;
+    public abstract V getValue() throws IllegalStateException;
 
     /**
      * Returns all features or attribute values, or an empty collection if none.
@@ -92,15 +86,15 @@
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
-    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
         V value = null;
         final Iterator<? extends V> it = values.iterator();
         if (it.hasNext()) {
             value = it.next();
             if (it.hasNext()) {
-                throw new InvalidPropertyValueException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
             }
         }
         setValue(value);
@@ -109,7 +103,7 @@
     /**
      * Returns whether the given property is deprecated.
      */
-    static boolean isDeprecated(final PropertyType type) {
+    static boolean isDeprecated(final AbstractIdentifiedType type) {
         return (type instanceof Deprecable) && ((Deprecable) type).isDeprecated();
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FieldType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FieldType.java
index b20be6a..e7c42b3 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FieldType.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FieldType.java
@@ -21,9 +21,6 @@
 import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Base class of property types having a value and a multiplicity.
@@ -36,7 +33,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-abstract class FieldType extends AbstractIdentifiedType implements PropertyType {
+abstract class FieldType extends AbstractIdentifiedType {
     /**
      * For cross-version compatibility.
      */
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/GroupAsPolylineOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/GroupAsPolylineOperation.java
index f0c1d16..de04da0 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/GroupAsPolylineOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/GroupAsPolylineOperation.java
@@ -29,14 +29,6 @@
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 import org.apache.sis.setup.GeometryLibrary;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
-
 
 /**
  * Creates a single (Multi){@code Polyline} instance from a sequence of points or polylines stored in another property.
@@ -86,19 +78,19 @@
      * @param  library         the library providing the implementations of geometry objects to read and write.
      * @param  components      attribute, association or operation providing the geometries to group as a polyline.
      */
-    static Operation create(final Map<String,?> identification, final GeometryLibrary library, PropertyType components) {
+    static AbstractOperation create(final Map<String,?> identification, final GeometryLibrary library, AbstractIdentifiedType components) {
         if (components instanceof LinkOperation) {
             components = ((LinkOperation) components).result;
         }
         final boolean isFeatureAssociation;
-        if (components instanceof AttributeType<?>) {
-            if (((AttributeType<?>) components).getMaximumOccurs() <= 1) {
+        if (components instanceof DefaultAttributeType<?>) {
+            if (((DefaultAttributeType<?>) components).getMaximumOccurs() <= 1) {
                 return new LinkOperation(identification, components);
             }
             isFeatureAssociation = false;
         } else {
-            isFeatureAssociation = (components instanceof FeatureAssociationRole)
-                    && ((FeatureAssociationRole) components).getMaximumOccurs() == 1;
+            isFeatureAssociation = (components instanceof DefaultAssociationRole)
+                    && ((DefaultAssociationRole) components).getMaximumOccurs() == 1;
             if (!isFeatureAssociation) {
                 throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalPropertyType_2,
                                                    components.getName(), components.getClass()));
@@ -113,7 +105,7 @@
      * number of occurrences of attribute values or feature instances is greater than 1.
      */
     private GroupAsPolylineOperation(final Map<String,?> identification, final Geometries<?> geometries,
-                                     final PropertyType components, final boolean isFeatureAssociation)
+                                     final AbstractIdentifiedType components, final boolean isFeatureAssociation)
     {
         super(identification);
         this.geometries = geometries;
@@ -133,7 +125,7 @@
      * Returns the expected result type.
      */
     @Override
-    public final AttributeType<?> getResult() {
+    public final DefaultAttributeType<?> getResult() {
         synchronized (TYPES) {
             return TYPES.computeIfAbsent(geometries.library, (library) -> {
                 var name = Map.of(AbstractIdentifiedType.NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY);
@@ -147,7 +139,7 @@
      */
     @Override
     @SuppressWarnings({"rawtypes", "unchecked"})
-    public final Property apply(Feature feature, ParameterValueGroup parameters) {
+    public final Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result<>(getResult(), feature);
     }
 
@@ -155,7 +147,7 @@
     /**
      * The attribute resulting from execution of the {@link GroupAsPolylineOperation}.
      * The value is computed when first requested, then cached for this {@code Result} instance only.
-     * Note that the cache is not used when {@link #apply(Feature, ParameterValueGroup)} is invoked,
+     * Note that the cache is not used when {@code apply(Feature, ParameterValueGroup)} is invoked,
      * causing a new value to be computed again. The intent is to behave as if the operation has been
      * executed at {@code apply(…)} invocation time, even if we deferred the actual execution.
      *
@@ -176,7 +168,7 @@
          * Creates a new result for an execution on the given feature.
          * The actual computation is deferred to the first call of {@link #getValue()}.
          */
-        Result(final AttributeType<G> resultType, final Feature feature) {
+        Result(final DefaultAttributeType<G> resultType, final AbstractFeature feature) {
             super(resultType, feature);
         }
 
@@ -210,7 +202,7 @@
                     }
 
                     @Override public Object next() {
-                        return ((Feature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
+                        return ((AbstractFeature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
                     }
                 };
             }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/InvalidFeatureException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/InvalidFeatureException.java
index 6aeff94..54c0e15 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/InvalidFeatureException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/InvalidFeatureException.java
@@ -19,23 +19,15 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.LocalizedException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Thrown when a feature fails at least one conformance test.
  *
- * <h2>API design note</h2>
- * This exception extends {@link InvalidPropertyValueException} because an Apache SIS feature
- * can be invalid only if a property is invalid.
- *
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @see Features#validate(Feature)
+ * @see Features#validate(AbstractFeature)
  */
-final class InvalidFeatureException extends InvalidPropertyValueException implements LocalizedException {
+final class InvalidFeatureException extends IllegalArgumentException implements LocalizedException {
     /**
      * For cross-version compatibility.
      */
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/LinkOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/LinkOperation.java
index 6b99151..f5efb4a 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/LinkOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/LinkOperation.java
@@ -24,12 +24,6 @@
 import org.apache.sis.feature.privy.FeatureUtilities;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * A link operation, which is like a redirection or an alias.
@@ -51,8 +45,7 @@
     /**
      * The type of the result.
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final PropertyType result;
+    final AbstractIdentifiedType result;
 
     /**
      * The name of the referenced attribute or feature association.
@@ -64,10 +57,8 @@
      *
      * @param identification  the name of the link, together with optional information.
      * @param referent        the referenced attribute or feature association.
-     *
-     * @see FeatureOperations#link(Map, PropertyType)
      */
-    LinkOperation(final Map<String,?> identification, PropertyType referent) {
+    LinkOperation(final Map<String,?> identification, AbstractIdentifiedType referent) {
         super(identification);
         if (referent instanceof LinkOperation) {
             referent = ((LinkOperation) referent).result;
@@ -92,7 +83,7 @@
      * Returns the expected result type.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return result;
     }
 
@@ -112,7 +103,7 @@
      * @return the linked property from the given feature.
      */
     @Override
-    public Property apply(final Feature feature, final ParameterValueGroup parameters) {
+    public Object apply(final AbstractFeature feature, final ParameterValueGroup parameters) {
         return feature.getProperty(referentName);
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAssociation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAssociation.java
index 1f9b497..ecca415 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAssociation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAssociation.java
@@ -21,12 +21,6 @@
 import org.apache.sis.util.privy.CheckedArrayList;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole association role} containing an arbitrary number of values.
@@ -56,16 +50,16 @@
     /**
      * The association values.
      */
-    private CheckedArrayList<Feature> values;
+    private CheckedArrayList<AbstractFeature> values;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role Information about the association.
      */
-    public MultiValuedAssociation(final FeatureAssociationRole role) {
+    public MultiValuedAssociation(final DefaultAssociationRole role) {
         super(role);
-        values = new CheckedArrayList<>(Feature.class);
+        values = new CheckedArrayList<>(AbstractFeature.class);
     }
 
     /**
@@ -74,12 +68,12 @@
      * @param role   Information about the association.
      * @param values The initial values, or {@code null} for initializing to an empty list.
      */
-    MultiValuedAssociation(final FeatureAssociationRole role, final Object values) {
+    MultiValuedAssociation(final DefaultAssociationRole role, final Object values) {
         super(role);
         if (values == null) {
-            this.values = new CheckedArrayList<>(Feature.class);
+            this.values = new CheckedArrayList<>(AbstractFeature.class);
         } else {
-            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, Feature.class);
+            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, AbstractFeature.class);
         }
     }
 
@@ -87,14 +81,14 @@
      * Returns the feature, or {@code null} if none.
      *
      * @return the feature (may be {@code null}).
-     * @throws MultiValuedPropertyException if this association contains more than one value.
+     * @throws IllegalStateException if this association contains more than one value.
      */
     @Override
-    public Feature getValue() {
+    public AbstractFeature getValue() {
         switch (values.size()) {
             case 0:  return null;
             case 1:  return values.get(0);
-            default: throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
         }
     }
 
@@ -107,7 +101,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Collection<Feature> getValues() {
+    public Collection<AbstractFeature> getValues() {
         return values;
     }
 
@@ -117,7 +111,7 @@
      * @param  value  the new value, or {@code null} for removing all values from this association.
      */
     @Override
-    public void setValue(final Feature value) {
+    public void setValue(final AbstractFeature value) {
         values.clear();
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
@@ -131,12 +125,12 @@
      * @param  newValues  the new values.
      */
     @Override
-    public void setValues(final Collection<? extends Feature> newValues) {
+    public void setValues(final Collection<? extends AbstractFeature> newValues) {
         if (newValues != values) {
             ArgumentChecks.ensureNonNull("values", newValues);      // The parameter name in public API is "values".
-            final FeatureType base = role.getValueType();
+            final DefaultFeatureType base = role.getValueType();
             values.clear();
-            for (final Feature value : newValues) {
+            for (final AbstractFeature value : newValues) {
                 ensureValid(base, value.getType());
                 values.add(value);
             }
@@ -155,7 +149,7 @@
     @SuppressWarnings("unchecked")
     public MultiValuedAssociation clone() throws CloneNotSupportedException {
         final MultiValuedAssociation clone = (MultiValuedAssociation) super.clone();
-        clone.values = (CheckedArrayList<Feature>) clone.values.clone();
+        clone.values = (CheckedArrayList<AbstractFeature>) clone.values.clone();
         return clone;
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAttribute.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAttribute.java
index 482edf2..60161fb 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAttribute.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/MultiValuedAttribute.java
@@ -24,10 +24,6 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAttributeType attribute type} containing an arbitrary number of values.
@@ -70,7 +66,7 @@
      *
      * @param  type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public MultiValuedAttribute(final AttributeType<V> type) {
+    public MultiValuedAttribute(final DefaultAttributeType<V> type) {
         super(type);
         values = new CheckedArrayList<>(type.getValueClass());
         final V value = type.getDefaultValue();
@@ -87,7 +83,7 @@
      * @param  values  the initial values, or {@code null} for initializing to an empty list.
      */
     @SuppressWarnings("unchecked")
-    MultiValuedAttribute(final AttributeType<V> type, final Object values) {
+    MultiValuedAttribute(final DefaultAttributeType<V> type, final Object values) {
         super(type);
         final Class<V> valueClass = type.getValueClass();
         if (values == null) {
@@ -106,14 +102,14 @@
      * Returns the attribute value, or {@code null} if none.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this attribute contains more than one value.
+     * @throws IllegalStateException if this attribute contains more than one value.
      */
     @Override
     public V getValue() {
         switch (values.size()) {
             case 0:  return null;
             case 1:  return values.get(0);
-            default: throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java
index abc94cb..540e63e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java
@@ -22,16 +22,6 @@
 import org.opengis.util.GenericName;
 import org.apache.sis.util.privy.Strings;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Set;
-import org.opengis.util.InternationalString;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.feature.FeatureInstantiationException;
-import org.apache.sis.feature.internal.Resources;
-
 
 /**
  * A feature type identified only by its name. Instances of {@code NamedFeatureType} shall be used only as placeholder
@@ -77,51 +67,10 @@
     }
 
     /**
-     * Undefined.
-     */
-    @Override
-    public InternationalString getDefinition() {
-        return null;
-    }
-
-    /**
-     * Declares that this feature shall not be instantiated.
-     */
-    @Override
-    public boolean isAbstract() {
-        return true;
-    }
-
-    /**
-     * Conservatively assumes that the feature is not simple,
-     * since we do not know what the actual feature will be.
-     */
-    @Override
-    public boolean isSimple() {
-        return false;
-    }
-
-    /**
-     * Always throws {@link PropertyNotFoundException} since this feature type has no declared property yet.
-     */
-    @Override
-    public PropertyType getProperty(final String name) throws PropertyNotFoundException {
-        throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
-    }
-
-    /**
      * Returns an empty set since this feature has no declared property yet.
      */
     @Override
-    public Collection<? extends PropertyType> getProperties(final boolean includeSuperTypes) {
-        return Collections.emptySet();
-    }
-
-    /**
-     * Returns an empty set since this feature has no declared parent yet.
-     */
-    @Override
-    public Set<? extends FeatureType> getSuperTypes() {
+    public Collection<AbstractIdentifiedType> getProperties(final boolean includeSuperTypes) {
         return Collections.emptySet();
     }
 
@@ -129,26 +78,8 @@
      * This feature type is considered independent of all other feature types except itself.
      */
     @Override
-    public boolean isAssignableFrom(FeatureType type) {
-        if (type == this) {
-            return true;
-        }
-        if (type instanceof NamedFeatureType) {
-            type = ((NamedFeatureType) type).resolved;
-        }
-        if (type == null) {
-            return false;
-        }
-        final FeatureType resolved = this.resolved;
-        return (resolved != null) && resolved.isAssignableFrom(type);
-    }
-
-    /**
-     * Unsupported operation, since the feature has not yet been resolved.
-     */
-    @Override
-    public Feature newInstance() throws FeatureInstantiationException {
-        throw new FeatureInstantiationException(Resources.format(Resources.Keys.UnresolvedFeatureName_1, getName()));
+    public boolean isAssignableFrom(final DefaultFeatureType type) {
+        return false;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/OperationResult.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/OperationResult.java
index 04e3ba7..159408c 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/OperationResult.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/OperationResult.java
@@ -18,11 +18,6 @@
 
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Base class of attributes that are the result of a feature operation.
@@ -44,7 +39,7 @@
      * The feature instance to use as a source for computing the result.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected final Feature feature;
+    protected final AbstractFeature feature;
 
     /**
      * Creates a new operation for a result of the given type.
@@ -52,7 +47,7 @@
      * @param type     information about the attribute (base Java class, domain of values, <i>etc.</i>).
      * @param feature  the feature instance to use as a source for computing the result.
      */
-    protected OperationResult(final AttributeType<V> type, final Feature feature) {
+    protected OperationResult(final DefaultAttributeType<V> type, final AbstractFeature feature) {
         super(type);
         this.feature = feature;
     }
@@ -64,6 +59,6 @@
      */
     @Override
     public void setValue(V value) {
-        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class));
+        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, AbstractAttribute.class));
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Property.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Property.java
new file mode 100644
index 0000000..931c1ca
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Property.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.feature;
+
+import org.opengis.util.GenericName;
+
+
+/**
+ * Place-holder for an interface not available in GeoAPI 3.0.
+ * This place-holder will be removed after we upgrade to a later GeoAPI version.
+ *
+ * <p><strong>Do not put this type in public API</strong>. We need to prevent users from using
+ * this type in order to reduce compatibility breaks when we will upgrade the GeoAPI version.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+abstract class Property {
+    public abstract GenericName getName();
+
+    public abstract Object getValue();
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/PropertyView.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/PropertyView.java
index ce5e2fd..c80f925 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/PropertyView.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/PropertyView.java
@@ -26,15 +26,8 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.io.Serializable;
-import org.opengis.feature.Feature;
-import org.opengis.feature.Property;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.MultiValuedPropertyException;
+// Specific to the main branch:
+import org.opengis.util.GenericName;
 
 
 /**
@@ -50,33 +43,11 @@
  *
  * @param <V> the type of property values.
  */
-abstract class PropertyView<V> extends Field<V> implements Property, Serializable {
+final class PropertyView {
     /**
-     * For cross-version compatibility.
+     * Do not allow instantiation of this class.
      */
-    private static final long serialVersionUID = -5605415150581699255L;
-
-    /**
-     * The feature from which to read and where to write the attribute or association value.
-     */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    final Feature feature;
-
-    /**
-     * The string representation of the property name. This is the value to be given in calls to
-     * {@link Feature#getPropertyValue(String)} and {@link Feature#setPropertyValue(String, Object)}.
-     */
-    final String name;
-
-    /**
-     * Creates a new property which will delegate its work to the given feature.
-     *
-     * @param feature  the feature from which to read and where to write the property value.
-     * @param name     the string representation of the property name.
-     */
-    PropertyView(final Feature feature, final String name) {
-        this.feature = feature;
-        this.name = name;
+    private PropertyView() {
     }
 
     /**
@@ -86,32 +57,26 @@
      * @param type     the type of the property. Must be one of the properties listed in the
      *                 {@code feature} (this is not verified by this constructor).
      */
-    static Property create(final Feature feature, final PropertyType type) {
-        if (type instanceof AttributeType<?>) {
-            return AttributeView.create(feature, (AttributeType<?>) type);
-        } else if (type instanceof FeatureAssociationRole) {
-            return AssociationView.create(feature, (FeatureAssociationRole) type);
-        } else if (type instanceof Operation) {
-            return ((Operation) type).apply(feature, null);
+    static Property create(final AbstractFeature feature, final AbstractIdentifiedType type) {
+        if (type instanceof DefaultAttributeType<?>) {
+            return AttributeView.create(feature, (DefaultAttributeType<?>) type);
+        } else if (type instanceof DefaultAssociationRole) {
+            return AssociationView.create(feature, (DefaultAssociationRole) type);
+        } else if (type instanceof AbstractOperation) {
+            return (Property) ((AbstractOperation) type).apply(feature, null);
         } else {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, Classes.getClass(type)));
         }
     }
 
     /**
-     * Returns the class of values.
-     */
-    abstract Class<V> getValueClass();
-
-    /**
      * Returns the singleton value. This default implementation assumes that the property is multi-valued
      * (single-valued properties shall override this method), but we nevertheless provide a fallback for
      * non-{@code Iterable} values as a safety against implementations that are not strictly compliant
-     * to our {@link Feature#getPropertyValue(String)} method contract. Then this method verifies that
+     * to our {@code Feature.getPropertyValue(String)} method contract. Then this method verifies that
      * the value is a collection containing zero or one element and returns that element or {@code null}.
      */
-    @Override
-    public V getValue() throws MultiValuedPropertyException {
+    static Object getValue(final AbstractFeature feature, final String name) {
         Object value = feature.getPropertyValue(name);
         if (value instanceof Iterable<?>) {
             final Iterator<?> it = ((Iterable<?>) value).iterator();
@@ -120,19 +85,18 @@
             }
             value = it.next();
             if (it.hasNext()) {
-                throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, name));
+                throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, name));
             }
         }
-        return getValueClass().cast(value);
+        return value;
     }
 
     /**
      * Sets the values of the given attribute. This default implementation assumes that the property
      * is multi-valued (single-valued properties shall override this method) and that the
-     * {@link Feature#setPropertyValue(String, Object)} implementation will verify the argument type.
+     * {@code Feature.setPropertyValue(String, Object)} implementation will verify the argument type.
      */
-    @Override
-    public void setValue(final V value) {
+    static void setValue(final AbstractFeature feature, final String name, final Object value) {
         feature.setPropertyValue(name, singletonOrEmpty(value));
     }
 
@@ -152,13 +116,11 @@
      * contains elements of the expected type, but this verification is not always possible.
      * Consequently this method may, sometimes, be actually unsafe.
      */
-    @Override
     @SuppressWarnings("unchecked")              // Actually not 100% safe, but we have done our best.
-    public Collection<V> getValues() {
+    static <V> Collection<V> getValues(final AbstractFeature feature, final String name, final Class<V> expected) {
         final Object values = feature.getPropertyValue(name);
         if (values instanceof Collection<?>) {
             if (values instanceof CheckedContainer<?>) {
-                final Class<?> expected = getValueClass();
                 final Class<?> actual = ((CheckedContainer<?>) values).getElementType();
                 if (expected != actual) {       // Really exact match, not Class.isAssignableFrom(Class).
                     throw new ClassCastException(Errors.format(Errors.Keys.UnexpectedTypeForReference_3, name, expected, actual));
@@ -166,49 +128,31 @@
             }
             return (Collection<V>) values;
         } else {
-            return singletonOrEmpty(getValueClass().cast(values));
+            return singletonOrEmpty(expected.cast(values));
         }
     }
 
     /**
      * Sets the values of the given attribute. This method assumes that the
-     * {@link Feature#setPropertyValue(String, Object)} implementation will
+     * {@code Feature.setPropertyValue(String, Object)} implementation will
      * verify the argument type.
      */
-    @Override
-    public final void setValues(final Collection<? extends V> values) {
+    static void setValues(final AbstractFeature feature, final String name, final Collection<?> values) {
         feature.setPropertyValue(name, values);
     }
 
     /**
      * Returns a hash code value for this property.
      */
-    @Override
-    public final int hashCode() {
+    static int hashCode(final AbstractFeature feature, final String name) {
         return Objects.hashCode(name) ^ System.identityHashCode(feature);
     }
 
     /**
-     * Compares this attribute with the given object for equality.
-     */
-    @Override
-    public final boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj != null && obj.getClass() == getClass()) {
-            final PropertyView<?> that = (PropertyView<?>) obj;
-            return feature == that.feature && Objects.equals(name, that.name);
-        }
-        return false;
-    }
-
-    /**
      * Returns a string representation of this property for debugging purposes.
      */
-    @Override
-    public final String toString() {
-        return FieldType.toString(false, getClass().getSimpleName(), getName(),
-                Classes.getShortName(getValueClass()), getValues().iterator()).toString();
+    static String toString(final Class<?> classe, final Class<?> valueClass, final GenericName name, final Collection<?> values) {
+        return FieldType.toString(false, classe.getSimpleName(), name,
+                Classes.getShortName(valueClass), values.iterator()).toString();
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAssociation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAssociation.java
index e97f95c..f384449 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAssociation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAssociation.java
@@ -18,11 +18,6 @@
 
 import java.util.Objects;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole association role} containing at most one value.
@@ -50,15 +45,14 @@
     /**
      * The associated feature.
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private Feature value;
+    private AbstractFeature value;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      */
-    public SingletonAssociation(final FeatureAssociationRole role) {
+    public SingletonAssociation(final DefaultAssociationRole role) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
     }
@@ -69,7 +63,7 @@
      * @param role   information about the association.
      * @param value  the initial value (may be {@code null}).
      */
-    SingletonAssociation(final FeatureAssociationRole role, final Feature value) {
+    SingletonAssociation(final DefaultAssociationRole role, final AbstractFeature value) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
         this.value = value;
@@ -84,7 +78,7 @@
      * @return the associated feature (may be {@code null}).
      */
     @Override
-    public Feature getValue() {
+    public AbstractFeature getValue() {
         return value;
     }
 
@@ -92,10 +86,10 @@
      * Sets the associated feature.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
+     * @throws IllegalArgumentException if the given feature is not valid for this association.
      */
     @Override
-    public void setValue(final Feature value) throws InvalidPropertyValueException {
+    public void setValue(final AbstractFeature value) throws IllegalArgumentException {
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAttribute.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAttribute.java
index 8175cd6..2ef54b2 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAttribute.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SingletonAttribute.java
@@ -19,9 +19,6 @@
 import java.util.Objects;
 import org.apache.sis.util.privy.CloneAccess;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-
 
 /**
  * An instance of an {@linkplain DefaultAttributeType attribute type} containing at most one value.
@@ -63,7 +60,7 @@
      *
      * @param type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public SingletonAttribute(final AttributeType<V> type) {
+    public SingletonAttribute(final DefaultAttributeType<V> type) {
         super(type);
         assert isSingleton(type.getMaximumOccurs());
         value = type.getDefaultValue();
@@ -76,7 +73,7 @@
      * @param type   information about the attribute (base Java class, domain of values, <i>etc.</i>).
      * @param value  the initial value (may be {@code null}).
      */
-    SingletonAttribute(final AttributeType<V> type, final Object value) {
+    SingletonAttribute(final DefaultAttributeType<V> type, final Object value) {
         super(type);
         assert isSingleton(type.getMaximumOccurs());
         this.value = type.getValueClass().cast(value);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
index 8fdb173..21ce42e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
@@ -27,12 +27,6 @@
 import org.apache.sis.util.privy.CloneAccess;
 import org.apache.sis.util.privy.Cloner;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Property;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * A feature in which only a small fraction of properties are expected to be provided. This implementation uses
@@ -119,15 +113,14 @@
      * @param  name  the property name.
      * @return the index for the property of the given name,
      *         or a negative value if the property is a parameterless operation.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
-    private Integer getIndex(final String name) throws PropertyNotFoundException {
-        ArgumentChecks.ensureNonNull("name", name);     // Parameter name in public API.
+    private Integer getIndex(final String name) throws IllegalArgumentException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -172,10 +165,10 @@
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
     @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         // Null value check done by the invoked method.
         requireMapOfProperties();
         return getPropertyInstance(name);
@@ -185,11 +178,11 @@
      * Implementation of {@link #getProperty(String)} invoked when we know that the {@link #properties}
      * map contains {@code Property} instances (as opposed to their value).
      */
-    private Property getPropertyInstance(final String name) throws PropertyNotFoundException {
+    private Property getPropertyInstance(final String name) throws IllegalArgumentException {
         assert valuesKind == PROPERTIES : valuesKind;
         final Integer index = getIndex(name);
         if (index < 0) {
-            return getOperationResult(name);
+            return (Property) getOperationResult(name);
         }
         Property property = (Property) properties.get(index);
         if (property == null) {
@@ -207,9 +200,9 @@
      *         known to this feature, or if the property cannot be set for another reason.
      */
     @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
+    public void setProperty(final Object property) throws IllegalArgumentException {
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
         requireMapOfProperties();
         /*
          * Following index should never be OPERATION_INDEX (a negative value) because the call
@@ -223,13 +216,13 @@
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
+    public Object getPropertyValue(final String name) throws IllegalArgumentException {
         final Object value = getValueOrFallback(name, MISSING);
         if (value != MISSING) return value;
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -253,10 +246,10 @@
         if (element != null) {
             if (valuesKind == VALUES) {
                 return element;                                         // Most common case.
-            } else if (element instanceof Attribute<?>) {
-                return getAttributeValue((Attribute<?>) element);
-            } else if (element instanceof FeatureAssociation) {
-                return getAssociationValue((FeatureAssociation) element);
+            } else if (element instanceof AbstractAttribute<?>) {
+                return getAttributeValue((AbstractAttribute<?>) element);
+            } else if (element instanceof AbstractAssociation) {
+                return getAssociationValue((AbstractAssociation) element);
             } else if (valuesKind == PROPERTIES) {
                 throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
             } else {
@@ -390,10 +383,10 @@
                 for (final Map.Entry<Integer,Object> entry : properties.entrySet()) {
                     final Object p = entry.getValue();
                     final Object value;
-                    if (p instanceof Attribute<?>) {
-                        value = getAttributeValue((Attribute<?>) p);
-                    } else if (p instanceof FeatureAssociation) {
-                        value = getAssociationValue((FeatureAssociation) p);
+                    if (p instanceof AbstractAttribute<?>) {
+                        value = getAttributeValue((AbstractAttribute<?>) p);
+                    } else if (p instanceof AbstractAssociation) {
+                        value = getAssociationValue((AbstractAssociation) p);
                     } else {
                         value = null;
                     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
index 7b07509..77d239c 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
@@ -38,18 +38,6 @@
 import org.apache.sis.feature.internal.Resources;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * An operation concatenating the string representations of the values of multiple properties.
@@ -123,7 +111,7 @@
         @Override public Class<Object>                  getTargetClass() {return Object.class;}
         @Override public Object apply(final Object f) {
             return (f != null) ? format(converter.inverse(),
-                    ((Feature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
+                    ((AbstractFeature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
         }
     }
 
@@ -153,8 +141,7 @@
     /**
      * The type of the result returned by the string concatenation operation.
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private final AttributeType<String> resultType;
+    private final DefaultAttributeType<String> resultType;
 
     /**
      * The characters to use at the beginning of the concatenated string, or an empty string if none.
@@ -176,11 +163,11 @@
      * It is caller's responsibility to ensure that {@code delimiter} and {@code singleAttributes} are not null.
      * This private constructor does not verify that condition on the assumption that the public API did.
      *
-     * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
+     * @see FeatureOperations#compound(Map, String, String, String, AbstractIdentifiedType...)
      */
     @SuppressWarnings({"rawtypes", "unchecked"})                                        // Generic array creation.
     StringJoinOperation(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final PropertyType[] singleAttributes)
+            final String prefix, final String suffix, final AbstractIdentifiedType[] singleAttributes)
             throws UnconvertibleObjectException
     {
         super(identification);
@@ -199,29 +186,29 @@
              * which may in turn produce an AttributeType. We do not accept more complex
              * combinations (e.g. operation producing an association).
              */
-            IdentifiedType propertyType = singleAttributes[i];
+            AbstractIdentifiedType propertyType = singleAttributes[i];
             ArgumentChecks.ensureNonNullElement("singleAttributes", i, propertyType);
             final GenericName name = propertyType.getName();
             int maximumOccurs = 0;                              // May be a bitwise combination; need only to know if > 1.
-            PropertyNotFoundException cause = null;             // In case of failure to find "sis:identifier" property.
-            final boolean isAssociation = (propertyType instanceof FeatureAssociationRole);
+            IllegalArgumentException cause = null;              // In case of failure to find "sis:identifier" property.
+            final boolean isAssociation = (propertyType instanceof DefaultAssociationRole);
             if (isAssociation) {
-                final FeatureAssociationRole role = (FeatureAssociationRole) propertyType;
-                final FeatureType ft = role.getValueType();
+                final DefaultAssociationRole role = (DefaultAssociationRole) propertyType;
+                final DefaultFeatureType ft = role.getValueType();
                 maximumOccurs = role.getMaximumOccurs();
                 try {
                     propertyType = ft.getProperty(AttributeConvention.IDENTIFIER);
-                } catch (PropertyNotFoundException e) {
+                } catch (IllegalArgumentException e) {
                     cause = e;
                 }
             }
-            if (propertyType instanceof Operation) {
-                propertyType = ((Operation) propertyType).getResult();
+            if (propertyType instanceof AbstractOperation) {
+                propertyType = ((AbstractOperation) propertyType).getResult();
             }
-            if (propertyType instanceof AttributeType) {
-                maximumOccurs |= ((AttributeType<?>) propertyType).getMaximumOccurs();
+            if (propertyType instanceof DefaultAttributeType) {
+                maximumOccurs |= ((DefaultAttributeType<?>) propertyType).getMaximumOccurs();
             } else {
-                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), PropertyType.class);
+                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), AbstractIdentifiedType.class);
                 throw new IllegalArgumentException(Resources.forProperties(identification)
                         .getString(Resources.Keys.IllegalPropertyType_2, name, (inf.length != 0) ? inf[0] : null), cause);
             }
@@ -235,7 +222,7 @@
              */
             attributeNames[i] = name.toString();
             ObjectConverter<? super String, ?> converter = ObjectConverters.find(
-                    String.class, ((AttributeType<?>) propertyType).getValueClass());
+                    String.class, ((DefaultAttributeType<?>) propertyType).getValueClass());
             if (isAssociation) {
                 converter = new ForFeature(converter);
             }
@@ -266,7 +253,7 @@
      * @return an {@code AttributeType<String>}.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return resultType;
     }
 
@@ -302,7 +289,7 @@
      * @return the concatenation of feature property values.
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result(Objects.requireNonNull(feature));
     }
 
@@ -322,7 +309,7 @@
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final Feature feature) {
+        Result(final AbstractFeature feature) {
             super(resultType, feature);
         }
 
@@ -381,14 +368,14 @@
          * parsed, then this method does not store any property value ("all or nothing" behavior).
          *
          * @param  value  the concatenated string.
-         * @throws InvalidPropertyValueException if one of the attribute values cannot be parsed to the expected type.
+         * @throws IllegalArgumentException if one of the attribute values cannot be parsed to the expected type.
          */
         @Override
-        public void setValue(final String value) throws InvalidPropertyValueException {
+        public void setValue(final String value) throws IllegalArgumentException {
             final int endAt = value.length() - suffix.length();
             final boolean prefixMatches = value.startsWith(prefix);
             if (!prefixMatches || !value.endsWith(suffix)) {
-                throw new InvalidPropertyValueException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
                         getName(),
                         prefixMatches ? 1 : 0,              // For "{1,choice,0#begin|1#end}" in message format.
                         prefixMatches ? suffix : prefix,
@@ -455,7 +442,7 @@
                     try {
                         values[count] = converter.apply(element);
                     } catch (UnconvertibleObjectException e) {
-                        throw new InvalidPropertyValueException(Errors.format(
+                        throw new IllegalArgumentException(Errors.format(
                                 Errors.Keys.CanNotAssign_2, attributeNames[count], element), e);
                     }
                 }
@@ -469,14 +456,14 @@
              * below do not fail).
              */
             if (values.length != count) {
-                throw new InvalidPropertyValueException(Resources.format(
+                throw new IllegalArgumentException(Resources.format(
                         Resources.Keys.UnexpectedNumberOfComponents_4, getName(), value, values.length, count));
             }
             for (int i=0; i < values.length; i++) {
-                Feature f   = feature;
+                AbstractFeature f = feature;
                 String name = attributeNames[i];
                 if (converters[i] instanceof ForFeature) {
-                    f = (Feature) f.getPropertyValue(name);
+                    f = (AbstractFeature) f.getPropertyValue(name);
                     name = AttributeConvention.IDENTIFIER;
                 }
                 f.setPropertyValue(name, values[i]);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Validator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Validator.java
index 6413f86..d492bb2 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Validator.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Validator.java
@@ -34,16 +34,6 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.apache.sis.metadata.iso.quality.DefaultScope;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-
 
 /**
  * Provides validation methods to be shared by different implementations.
@@ -62,6 +52,7 @@
      * @param scope  {@code FEATURE} if the object to validate is a feature, or
      *               {@code ATTRIBUTE} for an attribute, or {@code null} otherwise.
      */
+    @SuppressWarnings("deprecation")
     Validator(final ScopeCode scope) {
         quality = new DefaultDataQuality();
         if (scope != null) {
@@ -88,7 +79,7 @@
      */
     @SuppressWarnings("deprecation")
     private AbstractElement addViolationReport(AbstractElement report,
-            final PropertyType type, final InternationalString explanation)
+            final AbstractIdentifiedType type, final InternationalString explanation)
     {
         if (report == null) {
             final GenericName name = type.getName();
@@ -119,19 +110,19 @@
      * @param type     the type of the {@code feature} argument, provided explicitly for protecting from user overriding.
      * @param feature  the feature to validate.
      */
-    void validate(final FeatureType type, final Feature feature) {
-        for (final PropertyType pt : type.getProperties(true)) {
-            final Property property = feature.getProperty(pt.getName().toString());
+    void validate(final FeatureType type, final AbstractFeature feature) {
+        for (final AbstractIdentifiedType pt : type.getProperties(true)) {
+            final Object property = feature.getProperty(pt.getName().toString());
             final DataQuality pq;
             if (property instanceof AbstractAttribute<?>) {
                 pq = ((AbstractAttribute<?>) property).quality();
             } else if (property instanceof AbstractAssociation) {
                 pq = ((AbstractAssociation) property).quality();
-            } else if (property instanceof Attribute<?>) {
-                validate(((Attribute<?>) property).getType(), ((Attribute<?>) property).getValues());
+            } else if (property instanceof AbstractAttribute<?>) {
+                validate(((AbstractAttribute<?>) property).getType(), ((AbstractAttribute<?>) property).getValues());
                 continue;
-            } else if (property instanceof FeatureAssociation) {
-                validate(((FeatureAssociation) property).getRole(), ((FeatureAssociation) property).getValues());
+            } else if (property instanceof AbstractAssociation) {
+                validate(((AbstractAssociation) property).getRole(), ((AbstractAssociation) property).getValues());
                 continue;
             } else {
                 continue;
@@ -146,21 +137,21 @@
      * Verifies if the given value is valid for the given attribute type.
      * This method delegates to one of the {@code validate(…)} methods depending of the value type.
      */
-    void validateAny(final PropertyType type, final Object value) {
-        if (type instanceof AttributeType<?>) {
-            validate((AttributeType<?>) type, asList(value,
-                    ((AttributeType<?>) type).getMaximumOccurs()));
+    void validateAny(final AbstractIdentifiedType type, final Object value) {
+        if (type instanceof DefaultAttributeType<?>) {
+            validate((DefaultAttributeType<?>) type, asList(value,
+                    ((DefaultAttributeType<?>) type).getMaximumOccurs()));
         }
-        if (type instanceof FeatureAssociationRole) {
-            validate((FeatureAssociationRole) type, asList(value,
-                    ((FeatureAssociationRole) type).getMaximumOccurs()));
+        if (type instanceof DefaultAssociationRole) {
+            validate((DefaultAssociationRole) type, asList(value,
+                    ((DefaultAssociationRole) type).getMaximumOccurs()));
         }
     }
 
     /**
      * Verifies if the given values are valid for the given attribute type.
      */
-    void validate(final AttributeType<?> type, final Collection<?> values) {
+    void validate(final DefaultAttributeType<?> type, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
             /*
@@ -183,10 +174,10 @@
     /**
      * Verifies if the given value is valid for the given association role.
      */
-    void validate(final FeatureAssociationRole role, final Collection<?> values) {
+    void validate(final DefaultAssociationRole role, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
-            final FeatureType type = ((Feature) value).getType();
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
             final FeatureType valueType = role.getValueType();
             if (!valueType.isAssignableFrom(type)) {
                 report = addViolationReport(report, role, Errors.formatInternational(
@@ -204,7 +195,7 @@
      *
      * @param report  where to add the result, or {@code null} if not yet created.
      */
-    private void verifyCardinality(final AbstractElement report, final PropertyType type,
+    private void verifyCardinality(final AbstractElement report, final AbstractIdentifiedType type,
             final int minimumOccurs, final int maximumOccurs, final int count)
     {
         if (count < minimumOccurs) {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AssociationRoleBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AssociationRoleBuilder.java
index 811848e..a7285c9 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AssociationRoleBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AssociationRoleBuilder.java
@@ -20,23 +20,22 @@
 import org.apache.sis.feature.Features;
 import org.apache.sis.feature.DefaultAssociationRole;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
  * Describes one association from the {@code FeatureType} to be built by an {@code FeatureTypeBuilder} to another
  * {@code FeatureType}. A different instance of {@code AssociationRoleBuilder} exists for each feature association
- * to describe. Those instances are created preferably by {@link FeatureTypeBuilder#addAssociation(FeatureType)},
- * or in case of cyclic reference by {@link FeatureTypeBuilder#addAssociation(GenericName)}.
+ * to describe. Those instances are created preferably by {@code FeatureTypeBuilder.addAssociation(FeatureType)},
+ * or in case of cyclic reference by {@code FeatureTypeBuilder.addAssociation(GenericName)}.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
  * @see org.apache.sis.feature.DefaultAssociationRole
- * @see FeatureTypeBuilder#addAssociation(FeatureType)
+ * @see FeatureTypeBuilder#addAssociation(DefaultFeatureType)
  * @see FeatureTypeBuilder#addAssociation(GenericName)
  *
  * @since 0.8
@@ -45,7 +44,7 @@
     /**
      * The target feature type, or {@code null} if unknown.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * Name of the target feature type (never null).
@@ -56,7 +55,7 @@
      * The association created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient FeatureAssociationRole property;
+    private transient DefaultAssociationRole property;
 
     /**
      * Creates a new {@code AssociationRole} builder for values of the given type.
@@ -64,7 +63,7 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureType type, final GenericName typeName) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultFeatureType type, final GenericName typeName) {
         super(owner);
         this.type     = type;
         this.typeName = typeName;
@@ -75,12 +74,12 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureAssociationRole template) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultAssociationRole template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
-        if (template instanceof DefaultAssociationRole && !((DefaultAssociationRole) template).isResolved()) {
+        if (!template.isResolved()) {
             type     = null;
             typeName = Features.getValueTypeName(template);
         } else {
@@ -248,10 +247,13 @@
      * If a role has already been built and this builder state has not changed since the role creation,
      * then the previously created {@code FeatureAssociationRole} instance is returned.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.FeatureAssociationRole} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the association role.
      */
     @Override
-    public FeatureAssociationRole build() {
+    public DefaultAssociationRole build() {
         if (property == null) {
             if (type != null) {
                 property = new DefaultAssociationRole(identification(), type, minimumOccurs, maximumOccurs);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AttributeTypeBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AttributeTypeBuilder.java
index 7ec54e2..25007cf 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AttributeTypeBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/AttributeTypeBuilder.java
@@ -40,9 +40,6 @@
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Describes one {@code AttributeType} which will be part of the feature type to be built by
@@ -104,7 +101,7 @@
      * The attribute type created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient AttributeType<V> property;
+    private transient DefaultAttributeType<V> property;
 
     /**
      * Creates a new builder initialized to the values of the given builder.
@@ -139,16 +136,16 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add the attribute.
      */
-    AttributeTypeBuilder(final FeatureTypeBuilder owner, final AttributeType<V> template) {
+    AttributeTypeBuilder(final FeatureTypeBuilder owner, final DefaultAttributeType<V> template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
         valueClass    = template.getValueClass();
         defaultValue  = template.getDefaultValue();
-        final Map<String, AttributeType<?>> tc = template.characteristics();
+        final Map<String, DefaultAttributeType<?>> tc = template.characteristics();
         characteristics = new ArrayList<>(tc.size());
-        for (final AttributeType<?> c : tc.values()) {
+        for (final DefaultAttributeType<?> c : tc.values()) {
             characteristics.add(new CharacteristicTypeBuilder<>(this, c));
         }
         initialize(template);
@@ -520,13 +517,17 @@
      * Adds another attribute type that describes this attribute type, using an existing one as a template.
      * See <q>Attribute characterization</q> in {@link DefaultAttributeType} Javadoc for more information.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  <C>       the compile-time type of values in the {@code template} argument.
      * @param  template  an existing attribute type to use as a template.
      * @return a builder for a characteristic of this attribute, initialized with the values of the given template.
      *
      * @see #characteristics()
      */
-    public <C> CharacteristicTypeBuilder<C> addCharacteristic(final AttributeType<C> template) {
+    public <C> CharacteristicTypeBuilder<C> addCharacteristic(final DefaultAttributeType<C> template) {
         ensureNonNull("template", template);
         final CharacteristicTypeBuilder<C> characteristic = new CharacteristicTypeBuilder<>(this, template);
         characteristics.add(characteristic);
@@ -544,7 +545,7 @@
      *
      * @see #getCharacteristic(String)
      * @see #addCharacteristic(Class)
-     * @see #addCharacteristic(AttributeType)
+     * @see #addCharacteristic(DefaultAttributeType)
      * @see #setValidValues(Object...)
      * @see #setCRS(CoordinateReferenceSystem)
      */
@@ -746,12 +747,15 @@
      * i.e. the {@linkplain #getValueClass() value class} is lost at compile-time.
      * By comparison, this {@code build()} method has a more accurate return type.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.AttributeType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the attribute type.
      */
     @Override
-    public AttributeType<V> build() {
+    public DefaultAttributeType<V> build() {
         if (property == null) {
-            final AttributeType<?>[] chrts = new AttributeType<?>[characteristics.size()];
+            final DefaultAttributeType<?>[] chrts = new DefaultAttributeType<?>[characteristics.size()];
             for (int i=0; i<chrts.length; i++) {
                 chrts[i] = characteristics.get(i).build();
             }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
index a8c9011..e7ba8e0 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
@@ -23,9 +23,6 @@
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.UnconvertibleObjectException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Describes one characteristic of the {@code AttributeType} will will be built by a {@code FeatureTypeBuilder}.
@@ -69,7 +66,7 @@
      * The characteristic created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient AttributeType<V> characteristic;
+    private transient DefaultAttributeType<V> characteristic;
 
     /**
      * Creates a new builder initialized to the values of the given builder but a different type.
@@ -104,7 +101,7 @@
      *
      * @param owner  the builder of the {@code AttributeType} for which to add this property.
      */
-    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final AttributeType<V> template) {
+    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final DefaultAttributeType<V> template) {
         super(owner.getLocale());
         this.owner     = owner;
         characteristic = template;
@@ -320,10 +317,13 @@
      * If a type has already been built and this builder state has not changed since the type creation,
      * then the previously created {@code AttributeType} instance is returned.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.AttributeType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the characteristic type.
      */
     @Override
-    public AttributeType<V> build() {
+    public DefaultAttributeType<V> build() {
         if (characteristic == null) {
             characteristic = new DefaultAttributeType<>(identification(), valueClass, 0, 1, defaultValue);
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java
index 78bd67f..e095ccc 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java
@@ -42,17 +42,15 @@
 import org.apache.sis.util.iso.DefaultNameFactory;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
- * Helper class for the creation of {@link FeatureType} instances.
+ * Helper class for the creation of {@code FeatureType} instances.
  * This builder can create the arguments to be given to the
  * {@linkplain DefaultFeatureType#DefaultFeatureType feature type constructor}
  * from simpler parameters given to this builder.
@@ -66,7 +64,7 @@
  *       and whether the feature type is {@linkplain #setAbstract abstract}.</li>
  *   <li>Convenience methods for setting the {@linkplain #setNameSpace name space} and the
  *       {@linkplain #setDefaultMultiplicity default multiplicity} of properties to be added to the feature type.</li>
- *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(FeatureType)
+ *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(DefaultFeatureType)
  *       an association} or {@linkplain #addProperty an operation}.</li>
  *   <li>Method for listing the previously added {@linkplain #properties() properties}.</li>
  *   <li>A {@link #build()} method for creating the {@code FeatureType} instance from all previous information.</li>
@@ -121,7 +119,7 @@
     /**
      * The parent of the feature to create. By default, new features have no parent.
      */
-    private final List<FeatureType> superTypes;
+    private final List<DefaultFeatureType> superTypes;
 
     /**
      * Whether the feature type is abstract. The default value is {@code false}.
@@ -190,7 +188,7 @@
      * The object created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient FeatureType feature;
+    private transient DefaultFeatureType feature;
 
     /**
      * Creates a new builder instance using the default name factory.
@@ -206,11 +204,13 @@
      * to values inferred from the given template. The properties list will contain properties
      * declared explicitly in the given template, not including properties inherited from super types.
      *
-     * @param template  an existing feature type to use as a template, or {@code null} if none.
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
      *
-     * @see #setAll(FeatureType)
+     * @param template  an existing feature type to use as a template, or {@code null} if none.
      */
-    public FeatureTypeBuilder(final FeatureType template) {
+    public FeatureTypeBuilder(final DefaultFeatureType template) {
         this(null, null, null);
         if (template != null) {
             initialize(template);
@@ -266,14 +266,16 @@
     /**
      * Sets all properties of this builder to the values of the given feature type.
      * This builder is {@linkplain #clear() cleared} before the properties of the given type are copied.
-     * The copy is performed as documented in the {@linkplain #FeatureTypeBuilder(FeatureType) constructor}.
+     * The copy is performed as documented in the {@linkplain #FeatureTypeBuilder(DefaultFeatureType) constructor}.
+     *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
      *
      * @param  template  an existing feature type to use as a template, or {@code null} if none.
      * @return {@code this} for allowing method calls chaining.
-     *
-     * @see #FeatureTypeBuilder(FeatureType)
      */
-    public FeatureTypeBuilder setAll(final FeatureType template) {
+    public FeatureTypeBuilder setAll(final DefaultFeatureType template) {
         clear();
         if (template != null) {
             initialize(template);
@@ -284,10 +286,8 @@
     /**
      * Initializes this builder to the value of the given type.
      * The caller is responsible to invoke {@link #clear()} (if needed) before this method.
-     *
-     * @see #setAll(FeatureType)
      */
-    private void initialize(final FeatureType template) {
+    private void initialize(final DefaultFeatureType template) {
         super.initialize(template);
         feature    = template;
         isAbstract = template.isAbstract();
@@ -298,12 +298,12 @@
          * is not one of the operations automatically generated by this builder.
          */
         final Map<String,Set<AttributeRole>> propertyRoles = new HashMap<>();
-        for (final PropertyType property : template.getProperties(false)) {
+        for (final AbstractIdentifiedType property : template.getProperties(false)) {
             PropertyTypeBuilder builder;
-            if (property instanceof AttributeType<?>) {
-                builder = new AttributeTypeBuilder<>(this, (AttributeType<?>) property);
-            } else if (property instanceof FeatureAssociationRole) {
-                builder = new AssociationRoleBuilder(this, (FeatureAssociationRole) property);
+            if (property instanceof DefaultAttributeType<?>) {
+                builder = new AttributeTypeBuilder<>(this, (DefaultAttributeType<?>) property);
+            } else if (property instanceof DefaultAssociationRole) {
+                builder = new AssociationRoleBuilder(this, (DefaultAssociationRole) property);
             } else {
                 builder = null;                             // Do not create OperationWrapper now - see below.
             }
@@ -401,25 +401,33 @@
     /**
      * Returns the direct parents of the feature type to create.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The return type will be changed to {@code FeatureType[]} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @return the parents of the feature type to create, or an empty array if none.
      *
      * @see DefaultFeatureType#getSuperTypes()
      */
-    public FeatureType[] getSuperTypes() {
-        return superTypes.toArray(FeatureType[]::new);
+    public DefaultFeatureType[] getSuperTypes() {
+        return superTypes.toArray(DefaultFeatureType[]::new);
     }
 
     /**
      * Sets the parent types (or super-type) from which to inherit properties.
      * If this method is not invoked, then the default value is no parent.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code parents} argument type will be changed to {@code FeatureType...} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  parents  the parent types from which to inherit properties, or an empty array if none.
      *                  Null elements are ignored.
      * @return {@code this} for allowing method calls chaining.
      */
-    public FeatureTypeBuilder setSuperTypes(final FeatureType... parents) {
+    public FeatureTypeBuilder setSuperTypes(final DefaultFeatureType... parents) {
         ensureNonNull("parents", parents);
-        final List<FeatureType> asList = Arrays.asList(parents);
+        final List<DefaultFeatureType> asList = Arrays.asList(parents);
         if (!superTypes.equals(asList)) {
             superTypes.clear();
             superTypes.addAll(asList);
@@ -592,7 +600,6 @@
      * @return {@code this} for allowing method calls chaining.
      *
      * @see AttributeRole#IDENTIFIER_COMPONENT
-     * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
      */
     public FeatureTypeBuilder setIdentifierDelimiters(final String delimiter, final String prefix, final String suffix) {
         ensureNonEmpty("delimiter", delimiter);
@@ -618,10 +625,10 @@
      *
      * @see #getProperty(String)
      * @see #addAttribute(Class)
-     * @see #addAttribute(AttributeType)
-     * @see #addAssociation(FeatureType)
+     * @see #addAttribute(DefaultAttributeType)
+     * @see #addAssociation(DefaultFeatureType)
      * @see #addAssociation(GenericName)
-     * @see #addAssociation(FeatureAssociationRole)
+     * @see #addAssociation(DefaultAssociationRole)
      */
     public List<PropertyTypeBuilder> properties() {
         return new RemoveOnlyList<>(properties);
@@ -649,8 +656,6 @@
      * @param  name   name of the property to search.
      * @return property of the given name, or {@code null} if none.
      * @throws IllegalArgumentException if the given name is ambiguous.
-     *
-     * @see #addProperty(PropertyType)
      */
     public PropertyTypeBuilder getProperty(final String name) {
         return forName(properties, name, true);
@@ -667,7 +672,7 @@
      *     }
      *
      * The value class cannot be {@code Feature.class} since features shall be handled
-     * as {@linkplain #addAssociation(FeatureType) associations} instead of attributes.
+     * as {@linkplain #addAssociation(DefaultFeatureType) associations} instead of attributes.
      *
      * @param  <V>         the compile-time value of {@code valueClass} argument.
      * @param  valueClass  the class of attribute values (cannot be {@code Feature.class}).
@@ -677,7 +682,7 @@
      */
     public <V> AttributeTypeBuilder<V> addAttribute(final Class<V> valueClass) {
         ensureNonNull("valueClass", valueClass);
-        if (Feature.class.isAssignableFrom(valueClass)) {
+        if (AbstractFeature.class.isAssignableFrom(valueClass)) {
             // We disallow Feature.class because that type shall be handled as association instead of attribute.
             throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, "valueClass", valueClass));
         }
@@ -692,13 +697,17 @@
      * If the new attribute duplicates an existing one (for example if the same template is used many times),
      * caller should use the returned builder for modifying some attributes.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  <V>       the compile-time type of values in the {@code template} argument.
      * @param  template  an existing attribute type to use as a template.
      * @return a builder for an {@code AttributeType}, initialized with the values of the given template.
      *
      * @see #properties()
      */
-    public <V> AttributeTypeBuilder<V> addAttribute(final AttributeType<V> template) {
+    public <V> AttributeTypeBuilder<V> addAttribute(final DefaultAttributeType<V> template) {
         ensureNonNull("template", template);
         final AttributeTypeBuilder<V> property = new AttributeTypeBuilder<>(this, template);
         properties.add(property);
@@ -761,12 +770,16 @@
      * The default association name is the name of the given type, but callers should invoke one
      * of the {@code AssociationRoleBuilder.setName(…)} methods on the returned instance with a better name.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code type} argument type will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  type  the type of feature values.
      * @return a builder for a {@code FeatureAssociationRole}.
      *
      * @see #properties()
      */
-    public AssociationRoleBuilder addAssociation(final FeatureType type) {
+    public AssociationRoleBuilder addAssociation(final DefaultFeatureType type) {
         ensureNonNull("type", type);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, type, type.getName());
         properties.add(property);
@@ -776,7 +789,7 @@
 
     /**
      * Creates a new {@code FeatureAssociationRole} builder for features of a type of the given name.
-     * This method can be invoked as an alternative to {@link #addAssociation(FeatureType)} when the
+     * This method can be invoked as an alternative to {@code addAssociation(FeatureType)} when the
      * {@code FeatureType} instance is not yet available because of cyclic dependency.
      *
      * @param  type  the name of the type of feature values.
@@ -798,12 +811,16 @@
      * same template is used many times), caller should use the returned builder for modifying some
      * associations.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code FeatureAssociationRole} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  template  an existing feature association to use as a template.
      * @return a builder for an {@code FeatureAssociationRole}, initialized with the values of the given template.
      *
      * @see #properties()
      */
-    public AssociationRoleBuilder addAssociation(final FeatureAssociationRole template) {
+    public AssociationRoleBuilder addAssociation(final DefaultAssociationRole template) {
         ensureNonNull("template", template);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, template);
         properties.add(property);
@@ -816,9 +833,9 @@
      * The given property shall be an instance of one of the following types:
      *
      * <ul>
-     *   <li>{@link AttributeType}, in which case this method delegate to {@link #addAttribute(AttributeType)}.</li>
-     *   <li>{@link FeatureAssociationRole}, in which case this method delegate to {@link #addAssociation(FeatureAssociationRole)}.</li>
-     *   <li>{@link Operation}, in which case the given operation object will be added verbatim in the {@code FeatureType};
+     *   <li>{@code AttributeType}, in which case this method delegate to {@code addAttribute(AttributeType)}.</li>
+     *   <li>{@code FeatureAssociationRole}, in which case this method delegate to {@code addAssociation(FeatureAssociationRole)}.</li>
+     *   <li>{@code Operation}, in which case the given operation object will be added verbatim in the {@code FeatureType};
      *       this builder does not create new operations.</li>
      * </ul>
      *
@@ -826,6 +843,9 @@
      * If the same template is used many times, then the caller should use the returned builder
      * for modifying some properties.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument type may be changed to the
+     * {@code org.opengis.feature.PropertyType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @param  template  the property to add to the feature type.
      * @return a builder initialized to the given template.
      *         In the {@code Operation} case, the builder is a read-only accessor on the operation properties.
@@ -833,12 +853,12 @@
      * @see #properties()
      * @see #getProperty(String)
      */
-    public PropertyTypeBuilder addProperty(final PropertyType template) {
+    public PropertyTypeBuilder addProperty(final AbstractIdentifiedType template) {
         ensureNonNull("template", template);
-        if (template instanceof AttributeType<?>) {
-            return addAttribute((AttributeType<?>) template);
-        } else if (template instanceof FeatureAssociationRole) {
-            return addAssociation((FeatureAssociationRole) template);
+        if (template instanceof DefaultAttributeType<?>) {
+            return addAttribute((DefaultAttributeType<?>) template);
+        } else if (template instanceof DefaultAssociationRole) {
+            return addAssociation((DefaultAssociationRole) template);
         } else {
             final PropertyTypeBuilder property = new OperationWrapper(this, template);
             properties.add(property);
@@ -888,6 +908,9 @@
      * One of the {@code setName(…)} methods must have been invoked before this {@code build()} method (mandatory).
      * All other methods are optional, but some calls to a {@code add} method are usually needed.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.FeatureType} interface. This change is pending GeoAPI revision.</div>
+     *
      * <p>If a feature type has already been built and this builder state has not changed since the
      * feature type creation, then the previously created {@code FeatureType} instance is returned.</p>
      *
@@ -897,7 +920,7 @@
      * @see #clear()
      */
     @Override
-    public FeatureType build() throws IllegalStateException {
+    public DefaultFeatureType build() throws IllegalStateException {
         if (feature == null) {
             /*
              * Creates an initial array of property types with up to 3 slots reserved for sis:identifier, sis:geometry
@@ -908,13 +931,13 @@
             int numSynthetic;                               // Number of synthetic properties that may be generated.
             int envelopeIndex = -1;
             int geometryIndex = -1;
-            final PropertyType[] identifierTypes;
+            final AbstractIdentifiedType[] identifierTypes;
             if (identifierCount == 0) {
                 numSynthetic    = 0;
                 identifierTypes = null;
             } else {
                 numSynthetic    = 1;
-                identifierTypes = new PropertyType[identifierCount];
+                identifierTypes = new AbstractIdentifiedType[identifierCount];
             }
             if (defaultGeometry != null) {
                 envelopeIndex = numSynthetic++;
@@ -922,12 +945,12 @@
                     geometryIndex = numSynthetic++;
                 }
             }
-            final PropertyType[] propertyTypes = new PropertyType[numSynthetic + numSpecified];
+            final AbstractIdentifiedType[] propertyTypes = new AbstractIdentifiedType[numSynthetic + numSpecified];
             int propertyCursor = numSynthetic;
             int identifierCursor = 0;
             for (int i=0; i<numSpecified; i++) {
                 final PropertyTypeBuilder builder = properties.get(i);
-                final PropertyType instance = builder.build();
+                final AbstractIdentifiedType instance = builder.build();
                 propertyTypes[propertyCursor] = instance;
                 /*
                  * Collect the attributes to use as identifier components while we loop over all properties.
@@ -989,7 +1012,7 @@
                 }
             }
             feature = new DefaultFeatureType(identification(), isAbstract(),
-                    superTypes.toArray(FeatureType[]::new),
+                    superTypes.toArray(DefaultFeatureType[]::new),
                     ArraysExt.resize(propertyTypes, propertyCursor));
         }
         return feature;
@@ -1033,7 +1056,7 @@
             buffer.insert(buffer.indexOf("[") + 1, "abstract ");
         }
         String separator = " : ";
-        for (final FeatureType parent : superTypes) {
+        for (final DefaultFeatureType parent : superTypes) {
             buffer.append(separator).append('“').append(parent.getName()).append('”');
             separator = ", ";
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java
index aba5d40..9ea8e5c 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java
@@ -19,8 +19,8 @@
 import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.PropertyType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -34,12 +34,12 @@
     /**
      * The wrapped operation.
      */
-    private final PropertyType operation;
+    private final AbstractIdentifiedType operation;
 
     /**
      * Creates a new wrapper for the given operation.
      */
-    OperationWrapper(final FeatureTypeBuilder owner, final PropertyType operation) {
+    OperationWrapper(final FeatureTypeBuilder owner, final AbstractIdentifiedType operation) {
         super(owner);
         this.operation = operation;
         minimumOccurs = 1;
@@ -51,7 +51,7 @@
      * Returns the wrapped operation.
      */
     @Override
-    public PropertyType build() {
+    public AbstractIdentifiedType build() {
         return operation;
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java
index 0b4c088..7ce9c5e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java
@@ -19,11 +19,8 @@
 import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureAssociationRole;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -33,10 +30,10 @@
  *
  * <ul>
  *   <li>{@link FeatureTypeBuilder#addAttribute(Class)}</li>
- *   <li>{@link FeatureTypeBuilder#addAttribute(AttributeType)} for using an existing attribute as a template</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureType)}</li>
+ *   <li>{@link FeatureTypeBuilder#addAttribute(DefaultAttributeType)} for using an existing attribute as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultFeatureType)}</li>
  *   <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureAssociationRole)} for using an existing association as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultAssociationRole)} for using an existing association as a template</li>
  * </ul>
  *
  * @author  Johann Sorel (Geomatys)
@@ -289,11 +286,14 @@
      * then the previously created {@code PropertyType} instance is returned
      * (see {@link AttributeTypeBuilder#build()} for more information).
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.PropertyType}. This change is pending GeoAPI revision.</div>
+     *
      * @return the property type.
      * @throws IllegalStateException if the builder contains inconsistent information.
      */
     @Override
-    public abstract PropertyType build() throws IllegalStateException;
+    public abstract AbstractIdentifiedType build() throws IllegalStateException;
 
     /**
      * Flags this builder as a disposed one. The builder should not be used anymore after this method call.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java
index 3891439..212a42b 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java
@@ -32,10 +32,6 @@
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Information common to all kind of types (feature, association, characteristics).
@@ -118,7 +114,7 @@
      * Initializes this builder to the value of the given type.
      * The caller is responsible to invoke {@link #reset()} (if needed) before this method.
      */
-    final void initialize(final IdentifiedType template) {
+    final void initialize(final AbstractIdentifiedType template) {
         putIfNonNull(AbstractIdentifiedType.NAME_KEY,        template.getName());
         putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY,  template.getDefinition());
         putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation().orElse(null));
@@ -456,7 +452,7 @@
             }
         }
         if (ambiguity != null && nonAmbiguous) {
-            throw new PropertyNotFoundException(errors().getString(
+            throw new IllegalArgumentException(errors().getString(
                     Errors.Keys.AmbiguousName_3, best.getName(), ambiguity.getName(), name));
         }
         return best;
@@ -568,8 +564,11 @@
      * If a type has already been built and this builder state has not changed since the type creation,
      * then the previously created {@code IdentifiedType} instance is returned.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.IdentifiedType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the feature or property type.
      * @throws IllegalStateException if the builder contains inconsistent information.
      */
-    public abstract IdentifiedType build() throws IllegalStateException;
+    public abstract AbstractIdentifiedType build() throws IllegalStateException;
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/AttributeConvention.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/AttributeConvention.java
index 1e2c903..150d5af 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/AttributeConvention.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/AttributeConvention.java
@@ -26,16 +26,12 @@
 import org.apache.sis.feature.Features;
 import org.apache.sis.geometry.wrapper.Geometries;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyNotFoundException;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -104,9 +100,9 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} can be
      * the {@link com.esri.core.geometry.Geometry} class from ESRI's API, or the {@code Geometry} class from
      * <cite>Java Topology Suite</cite> (JTS) library, or any other class defined in future SIS versions.
-     * See {@link #isGeometryAttribute(IdentifiedType)} for testing whether the value is a supported type.</p>
+     * See {@code isGeometryAttribute(IdentifiedType)} for testing whether the value is a supported type.</p>
      *
-     * @see #isGeometryAttribute(IdentifiedType)
+     * @see #isGeometryAttribute(AbstractIdentifiedType)
      */
     public static final ScopedName GEOMETRY_PROPERTY = Names.createScopedName(SCOPE, null, "geometry");
 
@@ -138,7 +134,7 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link org.opengis.referencing.crs.CoordinateReferenceSystem}.</p>
      *
-     * @see #getCRSCharacteristic(Property)
+     * @see #getCRSCharacteristic(Object)
      */
     public static final ScopedName CRS_CHARACTERISTIC = Names.createScopedName(SCOPE, null, "crs");
 
@@ -168,7 +164,7 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link Integer}.</p>
      *
-     * @see #getMaximalLengthCharacteristic(Property)
+     * @see #getMaximalLengthCharacteristic(Object)
      */
     public static final ScopedName MAXIMAL_LENGTH_CHARACTERISTIC = Names.createScopedName(SCOPE, null, "maximalLength");
 
@@ -183,13 +179,13 @@
 
     /**
      * String representation of the {@link #IDENTIFIER_PROPERTY} name.
-     * This can be used in calls to {@link Feature#getPropertyValue(String)}.
+     * This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
      */
     public static final String IDENTIFIER = "sis:identifier";
 
     /**
      * String representation of the {@link #GEOMETRY_PROPERTY} name.
-     * This can be used in calls to {@link Feature#getPropertyValue(String)}.
+     * This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
      */
     public static final String GEOMETRY = "sis:geometry";
 
@@ -249,17 +245,17 @@
      * @param  feature  the feature type to test, or {@code null}.
      * @return whether the given feature type is non-null and has a {@value #IDENTIFIER} property.
      */
-    public static boolean hasIdentifier(final FeatureType feature) {
+    public static boolean hasIdentifier(final DefaultFeatureType feature) {
         if (feature != null) try {
             return feature.getProperty(IDENTIFIER) != null;
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             // Ignore
         }
         return false;
     }
 
     /**
-     * Returns {@code true} if the given type is an {@link AttributeType} or an {@link Operation} computing
+     * Returns {@code true} if the given type is an {@code AttributeType} or an {@code Operation} computing
      * an attribute, and the attribute value is one of the geometry types recognized by SIS.
      * The types currently recognized by SIS are:
      *
@@ -275,8 +271,8 @@
      *
      * @see #GEOMETRY_PROPERTY
      */
-    public static boolean isGeometryAttribute(final IdentifiedType type) {
-        final Optional<AttributeType<?>> at = Features.toAttribute(type);
+    public static boolean isGeometryAttribute(final AbstractIdentifiedType type) {
+        final Optional<DefaultAttributeType<?>> at = Features.toAttribute(type);
         return at.isPresent() && Geometries.isKnownType(at.get().getValueClass());
     }
 
@@ -288,7 +284,7 @@
      * @param  type  the operation or attribute type for which to get the CRS, or {@code null}.
      * @return {@code true} if a characteristic for Coordinate Reference System has been found.
      */
-    public static boolean characterizedByCRS(final IdentifiedType type) {
+    public static boolean characterizedByCRS(final AbstractIdentifiedType type) {
         return hasCharacteristic(type, CRS, CoordinateReferenceSystem.class);
     }
 
@@ -303,7 +299,7 @@
      *
      * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setCRS(CoordinateReferenceSystem)
      */
-    public static CoordinateReferenceSystem getCRSCharacteristic(final Property attribute) {
+    public static CoordinateReferenceSystem getCRSCharacteristic(final Object attribute) {
         return (CoordinateReferenceSystem) getCharacteristic(attribute, CRS);
     }
 
@@ -313,7 +309,7 @@
      * If the given property is a link, then this method follows the link in the given feature type (if non-null).
      *
      * <p>This method should be used only when the actual property instance is unknown.
-     * Otherwise, {@link #getCRSCharacteristic(Property)} should be used because the CRS
+     * Otherwise, {@code getCRSCharacteristic(Property)} should be used because the CRS
      * may vary for each property instance.</p>
      *
      * @param  feature    the feature type in which to follow links, or {@code null} if none.
@@ -322,7 +318,7 @@
      * @throws ClassCastException if {@link #CRS_CHARACTERISTIC} has been found but is associated
      *         to an object which is not a {@link CoordinateReferenceSystem} instance.
      */
-    public static CoordinateReferenceSystem getCRSCharacteristic(final FeatureType feature, final PropertyType attribute) {
+    public static CoordinateReferenceSystem getCRSCharacteristic(final DefaultFeatureType feature, final AbstractIdentifiedType attribute) {
         return (CoordinateReferenceSystem) getCharacteristic(feature, attribute, CRS);
     }
 
@@ -334,7 +330,7 @@
      * @param  type  the operation or attribute type for which to get the maximal length, or {@code null}.
      * @return {@code true} if a characteristic for maximal length has been found.
      */
-    public static boolean characterizedByMaximalLength(final IdentifiedType type) {
+    public static boolean characterizedByMaximalLength(final AbstractIdentifiedType type) {
         return hasCharacteristic(type, MAXIMAL_LENGTH, Integer.class);
     }
 
@@ -349,7 +345,7 @@
      *
      * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setMaximalLength(Integer)
      */
-    public static Integer getMaximalLengthCharacteristic(final Property attribute) {
+    public static Integer getMaximalLengthCharacteristic(final Object attribute) {
         return (Integer) getCharacteristic(attribute, MAXIMAL_LENGTH);
     }
 
@@ -359,7 +355,7 @@
      * If the given property is a link, then this method follows the link in the given feature type (if non-null).
      *
      * <p>This method should be used only when the actual property instance is unknown.
-     * Otherwise, {@link #getMaximalLengthCharacteristic(Property)} should be used because
+     * Otherwise, {@code getMaximalLengthCharacteristic(Property)} should be used because
      * the maximal length may vary for each property instance.</p>
      *
      * @param  feature    the feature type in which to follow links, or {@code null} if none.
@@ -368,7 +364,7 @@
      * @throws ClassCastException if {@link #MAXIMAL_LENGTH_CHARACTERISTIC} has been found but is associated
      *         to an object which is not a {@link CoordinateReferenceSystem} instance.
      */
-    public static Integer getMaximalLengthCharacteristic(final FeatureType feature, final PropertyType attribute) {
+    public static Integer getMaximalLengthCharacteristic(final DefaultFeatureType feature, final AbstractIdentifiedType attribute) {
         return (Integer) getCharacteristic(feature, attribute, MAXIMAL_LENGTH);
     }
 
@@ -381,10 +377,10 @@
      * @param  valueClass  the expected characteristic values.
      * @return {@code true} if a characteristic of the given name exists and has values assignable to the given class.
      */
-    private static boolean hasCharacteristic(IdentifiedType type, final String name, final Class<?> valueClass) {
-        final Optional<AttributeType<?>> at = Features.toAttribute(type);
+    private static boolean hasCharacteristic(AbstractIdentifiedType type, final String name, final Class<?> valueClass) {
+        final Optional<DefaultAttributeType<?>> at = Features.toAttribute(type);
         if (at.isPresent()) {
-            final AttributeType<?> ct = at.get().characteristics().get(name);
+            final DefaultAttributeType<?> ct = at.get().characteristics().get(name);
             if (ct != null) {
                 return valueClass.isAssignableFrom(ct.getValueClass());
             }
@@ -401,16 +397,16 @@
      * @param  name       name of the characteristic to get.
      * @return the value or default value of the given characteristic in the given property, or {@code null} if none.
      */
-    private static Object getCharacteristic(final Property attribute, final String name) {
-        if (attribute instanceof Attribute<?>) {
-            final Attribute<?> at = ((Attribute<?>) attribute).characteristics().get(name);
+    private static Object getCharacteristic(final Object attribute, final String name) {
+        if (attribute instanceof AbstractAttribute<?>) {
+            final AbstractAttribute<?> at = ((AbstractAttribute<?>) attribute).characteristics().get(name);
             if (at != null) {
                 final Object value = at.getValue();
                 if (value != null) {
                     return value;
                 }
             }
-            final AttributeType<?> type = ((Attribute<?>) attribute).getType().characteristics().get(name);
+            final DefaultAttributeType<?> type = ((AbstractAttribute<?>) attribute).getType().characteristics().get(name);
             if (type != null) {
                 return type.getDefaultValue();
             }
@@ -428,13 +424,13 @@
      * @param  characteristic  name of the characteristic from which to get the default value.
      * @return the default value of the named characteristic in the given property, or {@code null} if none.
      */
-    private static Object getCharacteristic(final FeatureType feature, PropertyType property, final String characteristic) {
+    private static Object getCharacteristic(final DefaultFeatureType feature, AbstractIdentifiedType property, final String characteristic) {
         final Optional<String> referent = Features.getLinkTarget(property);
         if (referent.isPresent() && feature != null) {
             property = feature.getProperty(referent.get());
         }
-        if (property instanceof AttributeType<?>) {
-            final AttributeType<?> type = ((AttributeType<?>) property).characteristics().get(characteristic);
+        if (property instanceof DefaultAttributeType<?>) {
+            final DefaultAttributeType<?> type = ((DefaultAttributeType<?>) property).characteristics().get(characteristic);
             if (type != null) {
                 return type.getDefaultValue();
             }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
index b60a74b..9c1b957 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
@@ -25,12 +25,11 @@
 import org.apache.sis.feature.builder.AttributeTypeBuilder;
 import org.apache.sis.filter.internal.Node;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -43,7 +42,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V>  the type of values computed by the expression.
  */
 public interface FeatureExpression<R,V> extends Expression<R,V> {
@@ -74,9 +73,9 @@
 
     /**
      * Provides the expected type of values produced by this expression when a feature of the given
-     * type is evaluated. The resulting type shall describe a "static" property, i.e. it can be an
-     * {@link AttributeType} or a {@link org.opengis.feature.FeatureAssociationRole}
-     * but not an {@link org.opengis.feature.Operation}.
+     * type is evaluated. The resulting type shall describe a "static" property, i.e. it can be a
+     * {@link org.apache.sis.feature.DefaultAttributeType} or a {@link org.apache.sis.feature.DefaultAssociationRole}
+     * but not an {@link org.apache.sis.feature.AbstractOperation}.
      *
      * <p>If this method returns an instance of {@link AttributeTypeBuilder}, then its parameterized
      * type should be the same {@code <V>} than this {@code FeatureExpression}.</p>
@@ -87,7 +86,7 @@
      * @throws IllegalArgumentException if this method can operate only on some feature types
      *         and the given type is not one of them.
      */
-    PropertyTypeBuilder expectedType(FeatureType valueType, FeatureTypeBuilder addTo);
+    PropertyTypeBuilder expectedType(DefaultFeatureType valueType, FeatureTypeBuilder addTo);
 
     /**
      * Tries to cast or convert the given expression to a {@link FeatureExpression}.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureUtilities.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureUtilities.java
index b089754..50aca20 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureUtilities.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureUtilities.java
@@ -29,8 +29,8 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.util.Static;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.PropertyType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -68,14 +68,14 @@
      * @param  properties  the properties for which to get the names, or {@code null}.
      * @return the name of all given properties, or {@code null} if the given list was null.
      */
-    public static String[] getNames(final Collection<? extends PropertyType> properties) {
+    public static String[] getNames(final Collection<? extends AbstractIdentifiedType> properties) {
         if (properties == null) {
             return null;
         }
         final String[] names = new String[properties.size()];
-        final Iterator<? extends PropertyType> it = properties.iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = properties.iterator();
         for (int i=0; i < names.length; i++) {
-            final PropertyType property = it.next();
+            final AbstractIdentifiedType property = it.next();
             if (property != null) {
                 final GenericName name = property.getName();
                 if (name != null) {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/MovingFeatures.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/MovingFeatures.java
index c468b4d..5a78faf 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/MovingFeatures.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/MovingFeatures.java
@@ -25,9 +25,8 @@
 import org.apache.sis.referencing.crs.DefaultTemporalCRS;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractAttribute;
 
 
 /**
@@ -43,14 +42,14 @@
      * Definition of characteristics containing a list of instants, without duplicates.
      * Should be in chronological order, but this is not verified.
      */
-    public static final AttributeType<Instant> TIME_AS_INSTANTS;
+    public static final DefaultAttributeType<Instant> TIME_AS_INSTANTS;
 
     /**
      * An alternative to {@link #TIME_AS_INSTANTS} used when times cannot be mapped to calendar dates.
      * This characteristic uses the same name as {@code TIME_AS_INSTANTS}. Consequently, at most one
      * of {@code TIME_AS_INSTANTS} and {@code TIME_AS_NUMBERS} can be used on the same property.
      */
-    private static final AttributeType<Number> TIME_AS_NUMBERS;
+    private static final DefaultAttributeType<Number> TIME_AS_NUMBERS;
     static {
         final var scope = Names.createLocalName("OGC", null, "MF");
         final var properties = Map.of(DefaultAttributeType.NAME_KEY, Names.createScopedName(scope, null, "datetimes"));
@@ -66,7 +65,7 @@
      * @param  hasCRS  whether a temporal CRS is available.
      * @return the "datetimes" characteristic.
      */
-    public static AttributeType<?> characteristic(final boolean hasCRS) {
+    public static DefaultAttributeType<?> characteristic(final boolean hasCRS) {
         return hasCRS ? TIME_AS_INSTANTS : TIME_AS_NUMBERS;
     }
 
@@ -92,8 +91,8 @@
      * @param  dest    the attribute on which to set time characteristic.
      * @param  millis  times in milliseconds since the epoch.
      */
-    public final void setInstants(final Attribute<?> dest, final long[] millis) {
-        final Attribute<Instant> c = TIME_AS_INSTANTS.newInstance();
+    public final void setInstants(final AbstractAttribute<?> dest, final long[] millis) {
+        final AbstractAttribute<Instant> c = TIME_AS_INSTANTS.newInstance();
         c.setValues(cache.computeIfAbsent(InstantList.vectorize(millis), InstantList::new));
         dest.characteristics().values().add(c);
     }
@@ -110,18 +109,18 @@
      * @param  values     times in arbitrary units since an arbitrary epoch.
      * @param  converter  the CRS to use for converting values to {@link Instant} instances, or {@code null}.
      */
-    public static void setTimes(final Attribute<?> dest, final Vector values, final DefaultTemporalCRS converter) {
-        final Attribute<?> ct;
+    public static void setTimes(final AbstractAttribute<?> dest, final Vector values, final DefaultTemporalCRS converter) {
+        final AbstractAttribute<?> ct;
         if (converter != null) {
             final Instant[] instants = new Instant[values.size()];
             for (int i=0; i<instants.length; i++) {
                 instants[i] = converter.toInstant(values.doubleValue(i));
             }
-            final Attribute<Instant> c = TIME_AS_INSTANTS.newInstance();
+            final AbstractAttribute<Instant> c = TIME_AS_INSTANTS.newInstance();
             c.setValues(UnmodifiableArrayList.wrap(instants));
             ct = c;
         } else {
-            final Attribute<Number> c = TIME_AS_NUMBERS.newInstance();
+            final AbstractAttribute<Number> c = TIME_AS_NUMBERS.newInstance();
             c.setValues(values);
             ct = c;
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
index f5a9c9a..cddc6af 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
@@ -27,10 +27,9 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.math.Fraction;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -40,7 +39,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
         implements FeatureExpression<R,Number>, Optimization.OnExpression<R,Number>
@@ -66,14 +65,14 @@
      * @param  name  name of the attribute to create.
      * @return an attribute of the given name for numbers.
      */
-    static AttributeType<Number> createNumericType(final String name) {
+    static DefaultAttributeType<Number> createNumericType(final String name) {
         return createType(Number.class, name);
     }
 
     /**
      * Returns the type of results computed by this arithmetic function.
      */
-    protected abstract AttributeType<Number> expectedType();
+    protected abstract DefaultAttributeType<Number> expectedType();
 
     /**
      * Returns the type of values computed by this expression.
@@ -88,7 +87,7 @@
      * on the {@code ArithmeticFunction} subclass and is given by {@link #expectedType()}.
      */
     @Override
-    public final PropertyTypeBuilder expectedType(FeatureType ignored, FeatureTypeBuilder addTo) {
+    public final PropertyTypeBuilder expectedType(DefaultFeatureType ignored, FeatureTypeBuilder addTo) {
         return addTo.addProperty(expectedType());
     }
 
@@ -139,8 +138,8 @@
         private static final long serialVersionUID = 5445433312445869201L;
 
         /** Description of results of the {@code "Add"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Add);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Add);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Add"} operation. */
         Add(final Expression<R, ? extends Number> expression1,
@@ -179,8 +178,8 @@
         private static final long serialVersionUID = 3048878022726271508L;
 
         /** Description of results of the {@code "Subtract"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Subtract"} operation. */
         Subtract(final Expression<R, ? extends Number> expression1,
@@ -219,8 +218,8 @@
         private static final long serialVersionUID = -1300022614832645625L;
 
         /** Description of results of the {@code "Multiply"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Multiply"} operation. */
         Multiply(final Expression<R, ? extends Number> expression1,
@@ -259,8 +258,8 @@
         private static final long serialVersionUID = -7709291845568648891L;
 
         /** Description of results of the {@code "Divide"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Divide);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Divide);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Divide"} operation. */
         Divide(final Expression<R, ? extends Number> expression1,
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
index 5597c0d..8e44067 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
@@ -26,14 +26,14 @@
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
 import org.apache.sis.math.FunctionProperty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.opengis.util.ScopedName;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Name;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -47,8 +47,8 @@
  *
  * @see PropertyValue
  */
-final class AssociationValue<V> extends LeafExpression<Feature, V>
-        implements ValueReference<Feature, V>, Optimization.OnExpression<Feature, V>
+final class AssociationValue<V> extends LeafExpression<AbstractFeature, V>
+        implements ValueReference<AbstractFeature, V>, Optimization.OnExpression<AbstractFeature, V>
 {
     /**
      * For cross-version compatibility.
@@ -57,7 +57,7 @@
 
     /**
      * Path to the property from which to retrieve the value.
-     * Each element in the array is an argument to give in a call to {@link Feature#getProperty(String)}.
+     * Each element in the array is an argument to give in a call to {@code Feature.getProperty(String)}.
      * This array should be considered read-only because it may be shared.
      */
     private final String[] path;
@@ -91,12 +91,17 @@
         this.accessor = accessor;
     }
 
+    @Override
+    public final ScopedName getFunctionName() {
+        return Name.VALUE_REFERENCE;
+    }
+
     /**
      * Returns the class of resources expected by this expression.
      */
     @Override
-    public final Class<Feature> getResourceClass() {
-        return Feature.class;
+    public final Class<AbstractFeature> getResourceClass() {
+        return AbstractFeature.class;
     }
 
     /**
@@ -135,12 +140,12 @@
      * @return value for the property identified by the XPath (may be {@code null}).
      */
     @Override
-    public V apply(Feature instance) {
+    public V apply(AbstractFeature instance) {
 walk:   if (instance != null) {
             for (final String p : path) {
                 final Object value = instance.getPropertyValue(p);
-                if (!(value instanceof Feature)) break walk;
-                instance = (Feature) value;
+                if (!(value instanceof AbstractFeature)) break walk;
+                instance = (AbstractFeature) value;
             }
             return accessor.apply(instance);
         }
@@ -152,20 +157,20 @@
      * to the target properties. This is needed for better SQL WHERE clause in database queries.
      */
     @Override
-    public Expression<Feature, V> optimize(final Optimization optimization) {
-        final FeatureType specifiedType = optimization.getFeatureType();
+    public Expression<AbstractFeature, V> optimize(final Optimization optimization) {
+        final DefaultFeatureType specifiedType = optimization.getFeatureType();
 walk:   if (specifiedType != null) try {
-            FeatureType type = specifiedType;
+            DefaultFeatureType type = specifiedType;
             String[] direct = path;                 // To be cloned before any modification.
             for (int i=0; i<path.length; i++) {
-                PropertyType property = type.getProperty(path[i]);
+                AbstractIdentifiedType property = type.getProperty(path[i]);
                 Optional<String> link = Features.getLinkTarget(property);
                 if (link.isPresent()) {
                     if (direct == path) direct = direct.clone();
                     property = type.getProperty(direct[i] = link.get());
                 }
-                if (!(property instanceof FeatureAssociationRole)) break walk;
-                type = ((FeatureAssociationRole) property).getValueType();
+                if (!(property instanceof DefaultAssociationRole)) break walk;
+                type = ((DefaultAssociationRole) property).getValueType();
             }
             /*
              * At this point all links have been resolved, up to the final property to evaluate.
@@ -182,7 +187,7 @@
             if (converted != accessor || direct != path) {
                 return new AssociationValue<>(direct, converted);
             }
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             warning(e, true);
         }
         return this;
@@ -193,7 +198,7 @@
      */
     @Override
     @SuppressWarnings("unchecked")
-    public final <N> Expression<Feature,N> toValueType(final Class<N> target) {
+    public final <N> Expression<AbstractFeature,N> toValueType(final Class<N> target) {
         final PropertyValue<N> converted = accessor.toValueType(target);
         if (converted == accessor) {
             return (AssociationValue<N>) this;
@@ -210,22 +215,22 @@
      * @throws IllegalArgumentException if this method cannot determine the property type for the given feature type.
      */
     @Override
-    public PropertyTypeBuilder expectedType(FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         for (final String p : path) {
-            final PropertyType type;
+            final AbstractIdentifiedType type;
             try {
                 type = valueType.getProperty(p);
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 if (accessor.isVirtual) {
                     // The association does not exist but may be defined on a yet unknown child type.
                     return accessor.expectedType(addTo);
                 }
                 throw e;
             }
-            if (!(type instanceof FeatureAssociationRole)) {
+            if (!(type instanceof DefaultAssociationRole)) {
                 return null;
             }
-            valueType = ((FeatureAssociationRole) type).getValueType();
+            valueType = ((DefaultAssociationRole) type).getValueType();
         }
         return accessor.expectedType(valueType, addTo);
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryFunction.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryFunction.java
index ddafb45..ab7a43c 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryFunction.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryFunction.java
@@ -26,10 +26,6 @@
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.filter.internal.Node;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-
 
 /**
  * Base class for expressions, comparators or filters performing operations on two expressions.
@@ -40,7 +36,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>   the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>   the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V1>  the type of value computed by the first expression.
  * @param  <V2>  the type of value computed by the second expression.
  */
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
index f35237d..42def4b 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -28,16 +28,10 @@
 import org.apache.sis.feature.privy.AttributeConvention;
 import org.apache.sis.filter.internal.Node;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.SpatialOperator;
-import org.opengis.filter.BinarySpatialOperator;
-import org.opengis.filter.InvalidFilterValueException;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyNotFoundException;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -49,9 +43,9 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
-abstract class BinaryGeometryFilter<R> extends Node implements SpatialOperator<R>, Optimization.OnFilter<R> {
+abstract class BinaryGeometryFilter<R> extends Node implements Optimization.OnFilter<R> {
     /**
      * For cross-version compatibility.
      */
@@ -59,16 +53,12 @@
 
     /**
      * The first of the two expressions to be used by this function.
-     *
-     * @see BinarySpatialOperator#getOperand1()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
     protected final Expression<R, GeometryWrapper> expression1;
 
     /**
      * The second of the two expressions to be used by this function.
-     *
-     * @see BinarySpatialOperator#getOperand2()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
     protected final Expression<R, GeometryWrapper> expression2;
@@ -129,7 +119,7 @@
                 }
             }
         } catch (FactoryException | TransformException | IncommensurableException e) {
-            throw new InvalidFilterValueException(e);
+            throw new IllegalArgumentException(e);
         }
         this.expression1 = expression1;
         this.expression2 = expression2;
@@ -146,7 +136,7 @@
     /**
      * Returns the original expression specified by the user.
      *
-     * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  expression  the expression to unwrap.
      * @return the unwrapped expression.
      */
@@ -217,7 +207,7 @@
              * then try to fetch the CRS of the property values. If we can transform the literal to that
              * CRS, do it now in order to avoid doing this transformation for all feature instances.
              */
-            final FeatureType featureType = optimization.getFeatureType();
+            final DefaultFeatureType featureType = optimization.getFeatureType();
             if (featureType != null && other instanceof ValueReference<?,?>) try {
                 final CoordinateReferenceSystem targetCRS = AttributeConvention.getCRSCharacteristic(
                         featureType, featureType.getProperty(((ValueReference<?,?>) other).getXPath()));
@@ -225,12 +215,12 @@
                     final GeometryWrapper geometry    = wrapper.apply(null);
                     final GeometryWrapper transformed = geometry.transform(targetCRS);
                     if (geometry != transformed) {
-                        literal = Optimization.literal(transformed);
+                        literal = (Literal<R,?>) Optimization.literal(transformed);
                         if (literal == effective1) effective1 = literal;
                         else effective2 = literal;
                     }
                 }
-            } catch (PropertyNotFoundException | TransformException e) {
+            } catch (IllegalArgumentException | TransformException e) {
                 warning(e, true);
             }
             /*
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java
index 240cd00..607cd0e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java
@@ -24,10 +24,8 @@
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 import org.apache.sis.geometry.wrapper.Geometries;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.BinarySpatialOperator;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 
 
 /**
@@ -39,9 +37,9 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
-final class BinarySpatialFilter<R> extends BinaryGeometryFilter<R> implements BinarySpatialOperator<R> {
+final class BinarySpatialFilter<R> extends BinaryGeometryFilter<R> {
     /**
      * For cross-version compatibility.
      */
@@ -109,7 +107,6 @@
     /**
      * Returns the first expression to be evaluated.
      */
-    @Override
     public Expression<R,?> getOperand1() {
         return original(expression1);
     }
@@ -117,7 +114,6 @@
     /**
      * Returns the second expression to be evaluated.
      */
-    @Override
     public Expression<R,?> getOperand2() {
         return original(expression2);
     }
@@ -133,7 +129,7 @@
     /**
      * Given an object, determines if the test(s) represented by this filter are passed.
      *
-     * @param  object  the object (often a {@link Feature} instance) to evaluate.
+     * @param  object  the object (often a {@code Feature} instance) to evaluate.
      * @return {@code true} if the test(s) are passed for the provided object.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Capabilities.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Capabilities.java
deleted file mode 100644
index 1150353..0000000
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Capabilities.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.Collection;
-import java.util.Optional;
-import org.opengis.util.LocalName;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.capability.Conformance;
-import org.opengis.filter.capability.IdCapabilities;
-import org.opengis.filter.capability.AvailableFunction;
-import org.opengis.filter.capability.FilterCapabilities;
-import org.opengis.filter.capability.ScalarCapabilities;
-import org.opengis.filter.capability.SpatialCapabilities;
-import org.opengis.filter.capability.TemporalCapabilities;
-import org.apache.sis.util.collection.CodeListSet;
-import org.apache.sis.feature.privy.AttributeConvention;
-
-
-/**
- * Metadata about the specific elements that Apache SIS implementation supports.
- *
- * @todo Missing {@link SpatialCapabilities} and {@link TemporalCapabilities}.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- */
-final class Capabilities implements FilterCapabilities, Conformance, IdCapabilities, ScalarCapabilities {
-    /**
-     * The filter factory which is providing the functions.
-     *
-     * @see #getFunctions()
-     */
-    private final DefaultFilterFactory<?,?,?> factory;
-
-    /**
-     * Creates a new capability document.
-     *
-     * @param  factory  the filter factory which is providing functions.
-     */
-    Capabilities(final DefaultFilterFactory<?,?,?> factory) {
-        this.factory = factory;
-    }
-
-    /**
-     * Declaration of which conformance classes a particular implementation supports.
-     */
-    @Override
-    public Conformance getConformance() {
-        return this;
-    }
-
-    /**
-     * Returns whether the implementation supports the <i>Resource Identification</i> conformance level.
-     */
-    @Override
-    public boolean implementsResourceld() {
-        return true;
-    }
-
-    /**
-     * Provides capabilities used to convey supported identifier operators.
-     */
-    @Override
-    public Optional<IdCapabilities> getIdCapabilities() {
-        return Optional.of(this);
-    }
-
-    /**
-     * Declares the names of the properties used for resource identifiers.
-     */
-    @Override
-    public Collection<LocalName> getResourceIdentifiers() {
-        return Set.of(AttributeConvention.IDENTIFIER_PROPERTY.tip());
-    }
-
-    /**
-     * Returns whether the implementation supports the <i>Standard Filter</i> conformance level.
-     * A value of {@code true} means that all the logical operators ({@code And}, {@code Or}, {@code Not})
-     * are supported, together with all the standard {@link ComparisonOperatorName}. Those operators shall
-     * be listed in the {@linkplain FilterCapabilities#getScalarCapabilities() scalar capabilities}.
-     */
-    @Override
-    public boolean implementsStandardFilter() {
-        return true;
-    }
-
-    /**
-     * Indicates that SIS supports <i>And</i>, <i>Or</i> and <i>Not</i> operators.
-     */
-    @Override
-    public boolean hasLogicalOperators() {
-        return true;
-    }
-
-    /**
-     * Advertises which logical, comparison and arithmetic operators the service supports.
-     */
-    @Override
-    public Optional<ScalarCapabilities> getScalarCapabilities() {
-        return Optional.of(this);
-    }
-
-    /**
-     * Advertises that SIS supports all comparison operators.
-     */
-    @Override
-    public Set<ComparisonOperatorName> getComparisonOperators() {
-        return new CodeListSet<>(ComparisonOperatorName.class, true);
-    }
-
-    /**
-     * Indicates that Apache SIS supports the <i>Spatial Filter</i> conformance level.
-     *
-     * @todo Need to implement {@linkplain FilterCapabilities#getSpatialCapabilities() temporal capabilities}.
-     */
-    @Override
-    public boolean implementsSpatialFilter() {
-        return true;
-    }
-
-    /**
-     * Indicates that Apache SIS supports the <i>Temporal Filter</i> conformance level.
-     *
-     * @todo Need to implement {@linkplain FilterCapabilities#getTemporalCapabilities() temporal capabilities}.
-     */
-    @Override
-    public boolean implementsTemporalFilter() {
-        return true;
-    }
-
-    /**
-     * Indicates that Apache SIS supports the <i>Sorting</i> conformance level.
-     */
-    @Override
-    public boolean implementsSorting() {
-        return true;
-    }
-
-    /**
-     * Enumerates the functions that may be used in filter expressions.
-     *
-     * @return the function that may be used in filter expressions.
-     */
-    @Override
-    public Map<String,AvailableFunction> getFunctions() {
-        return factory.new Functions();
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
index 04b5b9d..d1a0203 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
@@ -38,13 +38,11 @@
 import org.apache.sis.math.Fraction;
 import org.apache.sis.filter.internal.Node;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.MatchAction;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.BetweenComparisonOperator;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.MatchAction;
+import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
+import org.apache.sis.pending.geoapi.filter.BinaryComparisonOperator;
+import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator;
 
 
 /**
@@ -71,7 +69,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
         implements BinaryComparisonOperator<R>, Optimization.OnFilter<R>
@@ -813,6 +811,10 @@
             this.upper = new    LessThanOrEqualTo<>(expression, upper, true, MatchAction.ANY);
         }
 
+        @Override public ComparisonOperatorName getOperatorType() {
+            return ComparisonOperatorName.PROPERTY_IS_BETWEEN;
+        }
+
         /**
          * Creates a new filter of the same type but different parameters.
          */
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
index 3e9d2f3..3275c03 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
@@ -30,9 +30,8 @@
 import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -40,7 +39,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <S>  the type of value computed by the wrapped exception. This is the type to convert.
  * @param  <V>  the type of value computed by this expression. This is the type after conversion.
  *
@@ -170,7 +169,7 @@
      * May return {@code null} if the type cannot be determined.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(expression);
         if (fex == null) {
             return null;
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index c020754..0326488 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
@@ -34,26 +34,30 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.privy.Strings;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Iterator;
-import java.time.Instant;
-import org.opengis.filter.*;
-import org.opengis.feature.Feature;
-import org.opengis.filter.capability.AvailableFunction;
-import org.opengis.filter.capability.FilterCapabilities;
-import org.apache.sis.util.privy.AbstractMap;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.MatchAction;
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
 
 
 /**
  * A factory of default {@link Filter} and {@link Expression} implementations.
  * This base class operates on resources of arbitrary type {@code <R>}.
- * Concrete subclass operates on resources of specific type such as {@link org.opengis.feature.Feature}.
+ * Concrete subclass operates on resources of specific type such as {@link AbstractFeature}.
+ *
+ * <div class="warning"><b>Upcoming API change</b><br>
+ * In a future version, all {@link Filter} and {@link Expression} parameters may be replaced by parameters
+ * of the same names but from the {@code org.opengis.filter} package instead of {@code org.apache.sis.filter}.
+ * This change is pending next GeoAPI release.
+ * In addition, return types may become more specialized types.
+ * </div>
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.5
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) to use as inputs.
+ * @param  <R>  the type of resources (e.g. {@link AbstractFeature}) to use as inputs.
  * @param  <G>  base class of geometry objects. The implementation-neutral type is GeoAPI {@link Geometry},
  *              but this factory allows the use of other implementations such as JTS
  *              {@link org.locationtech.jts.geom.Geometry} or ESRI {@link com.esri.core.geometry.Geometry}.
@@ -61,7 +65,7 @@
  *
  * @since 1.1
  */
-public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implements FilterFactory<R,G,T> {
+public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory {
     /**
      * The geometry library used by this factory.
      */
@@ -128,47 +132,35 @@
     }
 
     /**
-     * Returns a factory operating on {@link Feature} instances.
+     * Returns a factory operating on {@link AbstractFeature} instances.
      * The {@linkplain GeometryLibrary geometry library} will be the system default.
      * The temporal objects can be {@link java.util.Date} or implementations of {@link java.time.temporal.Temporal}.
      *
-     * @return factory operating on {@link Feature} instances.
+     * @return factory operating on {@link AbstractFeature} instances.
      */
-    public static FilterFactory<Feature, Object, Object> forFeatures() {
+    public static DefaultFilterFactory<AbstractFeature, Object, Object> forFeatures() {
         return Features.DEFAULT;
     }
 
     /**
-     * Describes the abilities of this factory. The description includes restrictions on
-     * the available spatial operations, scalar operations, lists of supported functions,
-     * and description of which geometry literals are understood.
-     *
-     * @return description of the abilities of this factory.
-     */
-    @Override
-    public FilterCapabilities getCapabilities() {
-        return new Capabilities(this);              // Cheap to construct, no need to cache.
-    }
-
-    /**
-     * A filter factory operating on {@link Feature} instances.
+     * A filter factory operating on {@link AbstractFeature} instances.
      *
      * @param  <G>  base class of geometry objects. The implementation-neutral type is GeoAPI {@link Geometry},
      *              but this factory allows the use of other implementations such as JTS
      *              {@link org.locationtech.jts.geom.Geometry} or ESRI {@link com.esri.core.geometry.Geometry}.
      * @param  <T>  base class of temporal objects.
      */
-    public static class Features<G,T> extends DefaultFilterFactory<Feature, G, T> {
+    public static class Features<G,T> extends DefaultFilterFactory<AbstractFeature, G, T> {
         /**
          * The instance using system default.
          *
          * @see #forFeatures()
          */
-        static final FilterFactory<Feature,Object,Object> DEFAULT =
+        static final DefaultFilterFactory<AbstractFeature,Object,Object> DEFAULT =
                 new Features<>(Object.class, Object.class, WraparoundMethod.SPLIT);
 
         /**
-         * Creates a new factory operating on {@link Feature} instances.
+         * Creates a new factory operating on {@link AbstractFeature} instances.
          * See the {@linkplain DefaultFilterFactory#DefaultFilterFactory(Class, Class, WraparoundMethod)}
          * super-class constructor} for a list of valid class arguments.
          *
@@ -184,34 +176,12 @@
 
         /**
          * Creates a new predicate to identify an identifiable resource within a filter expression.
-         * The predicate uses no versioning and no time range.
          *
          * @param  identifier  identifier of the resource that shall be selected by the predicate.
          * @return the predicate.
          */
         @Override
-        public ResourceId<Feature> resourceId(final String identifier) {
-            return new IdentifierFilter(identifier);
-        }
-
-        /**
-         * Creates a new predicate to identify an identifiable resource within a filter expression.
-         * If {@code startTime} and {@code endTime} are non-null, the filter will select all versions
-         * of a resource between the specified dates.
-         *
-         * @param  identifier  identifier of the resource that shall be selected by the predicate.
-         * @param  version     version of the resource to select, or {@code null} for any version.
-         * @param  startTime   start time of the resource to select, or {@code null} if none.
-         * @param  endTime     end time of the resource to select, or {@code null} if none.
-         * @return the predicate.
-         *
-         * @todo Current implementation ignores the version, start time and end time.
-         *       This limitation may be resolved in a future version.
-         */
-        @Override
-        public ResourceId<Feature> resourceId(final String identifier, final Version version,
-                                              final Instant startTime, final Instant endTime)
-        {
+        public Filter<AbstractFeature> resourceId(final String identifier) {
             return new IdentifierFilter(identifier);
         }
 
@@ -242,7 +212,7 @@
          * @return an expression evaluating the referenced property value.
          */
         @Override
-        public <V> ValueReference<Feature,V> property(final String xpath, final Class<V> type) {
+        public <V> Expression<AbstractFeature,V> property(final String xpath, final Class<V> type) {
             ArgumentChecks.ensureNonEmpty("xpath", xpath);
             ArgumentChecks.ensureNonNull ("type",  type);
             return PropertyValue.create(xpath, type);
@@ -250,6 +220,41 @@
     }
 
     /**
+     * Creates a predicate to identify an identifiable resource within a filter expression.
+     *
+     * @param  rid  identifier of the resource that shall be selected by the predicate.
+     * @return the predicate.
+     */
+    public abstract Filter<R> resourceId(String rid);
+
+    /**
+     * Creates an expression whose value is computed by retrieving the value indicated by a path in a resource.
+     * If all characters in the path are {@linkplain Character#isUnicodeIdentifierPart(int) Unicode identifier parts},
+     * then the XPath expression is simply a property name.
+     *
+     * @param  xpath  the path to the property whose value will be returned by the {@code apply(R)} method.
+     * @return an expression evaluating the referenced property value.
+     */
+    public Expression<R,?> property(String xpath) {
+        return property(xpath, Object.class);
+    }
+
+    /**
+     * Creates an expression retrieving the value as an instance of the specified class.
+     * The {@code xpath} argument follows the rule described in {@link #property(String)}.
+     *
+     * <p>The desired type of property values can be specified. For example if the property values should be numbers,
+     * then {@code type} can be <code>{@linkplain Number}.class</code>. If property values can be of any type with no
+     * conversion desired, then {@code type} should be {@code Object.class}.</p>
+     *
+     * @param  <V>    the type of the values to be fetched (compile-time value of {@code type}).
+     * @param  xpath  the path to the property whose value will be returned by the {@code apply(R)} method.
+     * @param  type   the type of the values to be fetched (run-time value of {@code <V>}).
+     * @return an expression evaluating the referenced property value.
+     */
+    public abstract <V> Expression<R,V> property(String xpath, Class<V> type);
+
+    /**
      * Creates a constant, literal value that can be used in expressions.
      * The given value should be data objects such as strings, numbers, dates or geometries.
      *
@@ -257,8 +262,7 @@
      * @param  value  the literal value. May be {@code null}.
      * @return a literal for the given value.
      */
-    @Override
-    public <V> Literal<R,V> literal(final V value) {
+    public <V> Expression<R,V> literal(final V value) {
         return new LeafExpression.Literal<>(value);
     }
 
@@ -267,19 +271,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} = {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> equal(final Expression<R,?> expression1,
-                                             final Expression<R,?> expression2,
-                                             boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> equal(final Expression<R,?> expression1,
+                           final Expression<R,?> expression2)
     {
-        return new ComparisonFilter.EqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.EqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -287,19 +284,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} ≠ {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_NOT_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> notEqual(final Expression<R,?> expression1,
-                                                final Expression<R,?> expression2,
-                                                boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> notEqual(final Expression<R,?> expression1,
+                              final Expression<R,?> expression2)
     {
-        return new ComparisonFilter.NotEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.NotEqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -307,19 +297,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} &lt; {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> less(final Expression<R,?> expression1,
-                                            final Expression<R,?> expression2,
-                                            boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> less(final Expression<R,?> expression1,
+                          final Expression<R,?> expression2)
     {
-        return new ComparisonFilter.LessThan<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.LessThan<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -327,19 +310,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} &gt; {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> greater(final Expression<R,?> expression1,
-                                               final Expression<R,?> expression2,
-                                               boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> greater(final Expression<R,?> expression1,
+                             final Expression<R,?> expression2)
     {
-        return new ComparisonFilter.GreaterThan<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.GreaterThan<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -347,19 +323,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} ≤ {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN_OR_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> lessOrEqual(final Expression<R,?> expression1,
-                                                   final Expression<R,?> expression2,
-                                                   boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> lessOrEqual(final Expression<R,?> expression1,
+                                 final Expression<R,?> expression2)
     {
-        return new ComparisonFilter.LessThanOrEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.LessThanOrEqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -367,19 +336,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} ≥ {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> greaterOrEqual(final Expression<R,?> expression1,
-                                                      final Expression<R,?> expression2,
-                                                      boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> greaterOrEqual(final Expression<R,?> expression1,
+                                    final Expression<R,?> expression2)
     {
-        return new ComparisonFilter.GreaterThanOrEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.GreaterThanOrEqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -392,15 +354,27 @@
      * @return a filter evaluating ({@code expression} ≥ {@code lowerBoundary})
      *                       &amp; ({@code expression} ≤ {@code upperBoundary}).
      */
-    @Override
-    public BetweenComparisonOperator<R> between(final Expression<R,?> expression,
-                                                final Expression<R,?> lowerBoundary,
-                                                final Expression<R,?> upperBoundary)
+    public Filter<R> between(final Expression<R,?> expression,
+                             final Expression<R,?> lowerBoundary,
+                             final Expression<R,?> upperBoundary)
     {
         return new ComparisonFilter.Between<>(expression, lowerBoundary, upperBoundary);
     }
 
     /**
+     * Character string comparison operator with pattern matching and default wildcards.
+     * The wildcard character is {@code '%'}, the single character is {@code '_'} and
+     * the escape character is {@code '\\'}. The comparison is case-sensitive.
+     *
+     * @param  expression  source of values to compare against the pattern.
+     * @param  pattern     pattern to match against expression values.
+     * @return a character string comparison operator with pattern matching.
+     */
+    public Filter<R> like(Expression<R,?> expression, String pattern) {
+        return like(expression, pattern, '%', '_', '\\', true);
+    }
+
+    /**
      * Character string comparison operator with pattern matching and specified wildcards.
      *
      * @param  expression      source of values to compare against the pattern.
@@ -411,8 +385,7 @@
      * @param  isMatchingCase  specifies how a filter expression processor should perform string comparisons.
      * @return a character string comparison operator with pattern matching.
      */
-    @Override
-    public LikeOperator<R> like(final Expression<R,?> expression, final String pattern,
+    public Filter<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);
@@ -425,14 +398,13 @@
      * @param  expression  source of values to compare against {@code null}.
      * @return a filter that checks if an expression's value is {@code null}.
      */
-    @Override
-    public NullOperator<R> isNull(final Expression<R,?> expression) {
+    public Filter<R> isNull(final Expression<R,?> expression) {
         return new UnaryFunction.IsNull<>(expression);
     }
 
     /**
      * An operator that tests if an expression's value is nil.
-     * The difference with {@link NullOperator} is that a value should exist
+     * The difference with {@code NullOperator} is that a value should exist
      * but cannot be provided for the reason given by {@code nilReason}.
      * Possible reasons are:
      *
@@ -454,8 +426,7 @@
      * @see org.apache.sis.xml.NilObject
      * @see org.apache.sis.xml.NilReason
      */
-    @Override
-    public NilOperator<R> isNil(final Expression<R,?> expression, final String nilReason) {
+    public Filter<R> isNil(final Expression<R,?> expression, final String nilReason) {
         return new UnaryFunction.IsNil<>(expression, nilReason);
     }
 
@@ -465,11 +436,8 @@
      * @param  operand1  the first operand of the AND operation.
      * @param  operand2  the second operand of the AND operation.
      * @return a filter evaluating {@code operand1 AND operand2}.
-     *
-     * @see LogicalOperatorName#AND
      */
-    @Override
-    public LogicalOperator<R> and(final Filter<R> operand1, final Filter<R> operand2) {
+    public Filter<R> and(final Filter<R> operand1, final Filter<R> operand2) {
         ArgumentChecks.ensureNonNull("operand1", operand1);
         ArgumentChecks.ensureNonNull("operand2", operand2);
         return new LogicalFilter.And<>(operand1, operand2);
@@ -481,11 +449,8 @@
      * @param  operands  a collection of at least 2 operands.
      * @return a filter evaluating {@code operand1 AND operand2 AND operand3}…
      * @throws IllegalArgumentException if the given collection contains less than 2 elements.
-     *
-     * @see LogicalOperatorName#AND
      */
-    @Override
-    public LogicalOperator<R> and(final Collection<? extends Filter<R>> operands) {
+    public Filter<R> and(final Collection<? extends Filter<R>> operands) {
         return new LogicalFilter.And<>(operands);
     }
 
@@ -495,11 +460,8 @@
      * @param  operand1  the first operand of the OR operation.
      * @param  operand2  the second operand of the OR operation.
      * @return a filter evaluating {@code operand1 OR operand2}.
-     *
-     * @see LogicalOperatorName#OR
      */
-    @Override
-    public LogicalOperator<R> or(final Filter<R> operand1, final Filter<R> operand2) {
+    public Filter<R> or(final Filter<R> operand1, final Filter<R> operand2) {
         ArgumentChecks.ensureNonNull("operand1", operand1);
         ArgumentChecks.ensureNonNull("operand2", operand2);
         return new LogicalFilter.Or<>(operand1, operand2);
@@ -511,11 +473,8 @@
      * @param  operands  a collection of at least 2 operands.
      * @return a filter evaluating {@code operand1 OR operand2 OR operand3}…
      * @throws IllegalArgumentException if the given collection contains less than 2 elements.
-     *
-     * @see LogicalOperatorName#OR
      */
-    @Override
-    public LogicalOperator<R> or(final Collection<? extends Filter<R>> operands) {
+    public Filter<R> or(final Collection<? extends Filter<R>> operands) {
         return new LogicalFilter.Or<>(operands);
     }
 
@@ -524,11 +483,8 @@
      *
      * @param  operand  the operand of the NOT operation.
      * @return a filter evaluating {@code NOT operand}.
-     *
-     * @see LogicalOperatorName#NOT
      */
-    @Override
-    public LogicalOperator<R> not(final Filter<R> operand) {
+    public Filter<R> not(final Filter<R> operand) {
         return new LogicalFilter.Not<>(operand);
     }
 
@@ -539,13 +495,8 @@
      * @param  geometry  expression fetching the geometry to check for interaction with bounds.
      * @param  bounds    the bounds to check geometry against.
      * @return a filter checking for any interactions between the bounding boxes.
-     *
-     * @see SpatialOperatorName#BBOX
-     *
-     * @todo Maybe the expression parameterized type should extend {@link Geometry}.
      */
-    @Override
-    public BinarySpatialOperator<R> bbox(final Expression<R, ? extends G> geometry, final Envelope bounds) {
+    public Filter<R> bbox(final Expression<R, ? extends G> geometry, final Envelope bounds) {
         return new BinarySpatialFilter<>(library, geometry, bounds, wraparound);
     }
 
@@ -555,12 +506,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Equals" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#EQUALS
      */
-    @Override
-    public BinarySpatialOperator<R> equals(final Expression<R, ? extends G> geometry1,
-                                           final Expression<R, ? extends G> geometry2)
+    public Filter<R> equals(final Expression<R, ? extends G> geometry1,
+                            final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.EQUALS, library, geometry1, geometry2);
     }
@@ -571,12 +519,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Disjoint" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#DISJOINT
      */
-    @Override
-    public BinarySpatialOperator<R> disjoint(final Expression<R, ? extends G> geometry1,
-                                             final Expression<R, ? extends G> geometry2)
+    public Filter<R> disjoint(final Expression<R, ? extends G> geometry1,
+                              final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.DISJOINT, library, geometry1, geometry2);
     }
@@ -587,12 +532,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Intersects" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#INTERSECTS
      */
-    @Override
-    public BinarySpatialOperator<R> intersects(final Expression<R, ? extends G> geometry1,
-                                               final Expression<R, ? extends G> geometry2)
+    public Filter<R> intersects(final Expression<R, ? extends G> geometry1,
+                                final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.INTERSECTS, library, geometry1, geometry2);
     }
@@ -603,12 +545,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Touches" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#TOUCHES
      */
-    @Override
-    public BinarySpatialOperator<R> touches(final Expression<R, ? extends G> geometry1,
-                                            final Expression<R, ? extends G> geometry2)
+    public Filter<R> touches(final Expression<R, ? extends G> geometry1,
+                             final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.TOUCHES, library, geometry1, geometry2);
     }
@@ -619,12 +558,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Crosses" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#CROSSES
      */
-    @Override
-    public BinarySpatialOperator<R> crosses(final Expression<R, ? extends G> geometry1,
-                                            final Expression<R, ? extends G> geometry2)
+    public Filter<R> crosses(final Expression<R, ? extends G> geometry1,
+                             final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.CROSSES, library, geometry1, geometry2);
     }
@@ -636,12 +572,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Within" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#WITHIN
      */
-    @Override
-    public BinarySpatialOperator<R> within(final Expression<R, ? extends G> geometry1,
-                                           final Expression<R, ? extends G> geometry2)
+    public Filter<R> within(final Expression<R, ? extends G> geometry1,
+                            final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.WITHIN, library, geometry1, geometry2);
     }
@@ -652,12 +585,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Contains" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#CONTAINS
      */
-    @Override
-    public BinarySpatialOperator<R> contains(final Expression<R, ? extends G> geometry1,
-                                             final Expression<R, ? extends G> geometry2)
+    public Filter<R> contains(final Expression<R, ? extends G> geometry1,
+                              final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.CONTAINS, library, geometry1, geometry2);
     }
@@ -669,12 +599,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Overlaps" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#OVERLAPS
      */
-    @Override
-    public BinarySpatialOperator<R> overlaps(final Expression<R, ? extends G> geometry1,
-                                             final Expression<R, ? extends G> geometry2)
+    public Filter<R> overlaps(final Expression<R, ? extends G> geometry1,
+                              final Expression<R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.OVERLAPS, library, geometry1, geometry2);
     }
@@ -688,13 +615,10 @@
      * @param  distance   minimal distance for evaluating the expression as {@code true}.
      * @return operator that evaluates to {@code true} when all of a feature's geometry
      *         is more distant than the given distance from the second geometry.
-     *
-     * @see DistanceOperatorName#BEYOND
      */
-    @Override
-    public DistanceOperator<R> beyond(final Expression<R, ? extends G> geometry1,
-                                      final Expression<R, ? extends G> geometry2,
-                                      final Quantity<Length> distance)
+    public Filter<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);
     }
@@ -708,13 +632,10 @@
      * @param  distance   maximal distance for evaluating the expression as {@code true}.
      * @return operator that evaluates to {@code true} when any part of the feature's geometry
      *         lies within the given distance of the second geometry.
-     *
-     * @see DistanceOperatorName#WITHIN
      */
-    @Override
-    public DistanceOperator<R> within(final Expression<R, ? extends G> geometry1,
-                                      final Expression<R, ? extends G> geometry2,
-                                      final Quantity<Length> distance)
+    public Filter<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);
     }
@@ -725,12 +646,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "After" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#AFTER
      */
-    @Override
-    public TemporalOperator<R> after(final Expression<R, ? extends T> time1,
-                                     final Expression<R, ? extends T> time2)
+    public Filter<R> after(final Expression<R, ? extends T> time1,
+                           final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.After::new, time1, time2);
     }
@@ -741,12 +659,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Before" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#BEFORE
      */
-    @Override
-    public TemporalOperator<R> before(final Expression<R, ? extends T> time1,
-                                      final Expression<R, ? extends T> time2)
+    public Filter<R> before(final Expression<R, ? extends T> time1,
+                            final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.Before::new, time1, time2);
     }
@@ -757,12 +672,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Begins" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#BEGINS
      */
-    @Override
-    public TemporalOperator<R> begins(final Expression<R, ? extends T> time1,
-                                      final Expression<R, ? extends T> time2)
+    public Filter<R> begins(final Expression<R, ? extends T> time1,
+                            final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.Begins::new, time1, time2);
     }
@@ -773,12 +685,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "BegunBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#BEGUN_BY
      */
-    @Override
-    public TemporalOperator<R> begunBy(final Expression<R, ? extends T> time1,
-                                       final Expression<R, ? extends T> time2)
+    public Filter<R> begunBy(final Expression<R, ? extends T> time1,
+                             final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.BegunBy::new, time1, time2);
     }
@@ -789,12 +698,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "TContains" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#CONTAINS
      */
-    @Override
-    public TemporalOperator<R> tcontains(final Expression<R, ? extends T> time1,
-                                         final Expression<R, ? extends T> time2)
+    public Filter<R> tcontains(final Expression<R, ? extends T> time1,
+                               final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.Contains::new, time1, time2);
     }
@@ -805,12 +711,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "During" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#DURING
      */
-    @Override
-    public TemporalOperator<R> during(final Expression<R, ? extends T> time1,
-                                      final Expression<R, ? extends T> time2)
+    public Filter<R> during(final Expression<R, ? extends T> time1,
+                            final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.During::new, time1, time2);
     }
@@ -821,12 +724,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "TEquals" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#EQUALS
      */
-    @Override
-    public TemporalOperator<R> tequals(final Expression<R, ? extends T> time1,
-                                       final Expression<R, ? extends T> time2)
+    public Filter<R> tequals(final Expression<R, ? extends T> time1,
+                             final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.Equals::new, time1, time2);
     }
@@ -837,12 +737,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "TOverlaps" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#OVERLAPS
      */
-    @Override
-    public TemporalOperator<R> toverlaps(final Expression<R, ? extends T> time1,
-                                         final Expression<R, ? extends T> time2)
+    public Filter<R> toverlaps(final Expression<R, ? extends T> time1,
+                               final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.Overlaps::new, time1, time2);
     }
@@ -853,12 +750,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Meets" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#MEETS
      */
-    @Override
-    public TemporalOperator<R> meets(final Expression<R, ? extends T> time1,
-                                     final Expression<R, ? extends T> time2)
+    public Filter<R> meets(final Expression<R, ? extends T> time1,
+                           final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.Meets::new, time1, time2);
     }
@@ -869,12 +763,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Ends" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#ENDS
      */
-    @Override
-    public TemporalOperator<R> ends(final Expression<R, ? extends T> time1,
-                                    final Expression<R, ? extends T> time2)
+    public Filter<R> ends(final Expression<R, ? extends T> time1,
+                          final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.Ends::new, time1, time2);
     }
@@ -885,12 +776,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "OverlappedBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#OVERLAPPED_BY
      */
-    @Override
-    public TemporalOperator<R> overlappedBy(final Expression<R, ? extends T> time1,
-                                            final Expression<R, ? extends T> time2)
+    public Filter<R> overlappedBy(final Expression<R, ? extends T> time1,
+                                  final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.OverlappedBy::new, time1, time2);
     }
@@ -901,12 +789,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "MetBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#MET_BY
      */
-    @Override
-    public TemporalOperator<R> metBy(final Expression<R, ? extends T> time1,
-                                     final Expression<R, ? extends T> time2)
+    public Filter<R> metBy(final Expression<R, ? extends T> time1,
+                           final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.MetBy::new, time1, time2);
     }
@@ -917,12 +802,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "EndedBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#ENDED_BY
      */
-    @Override
-    public TemporalOperator<R> endedBy(final Expression<R, ? extends T> time1,
-                                       final Expression<R, ? extends T> time2)
+    public Filter<R> endedBy(final Expression<R, ? extends T> time1,
+                             final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.EndedBy::new, time1, time2);
     }
@@ -934,12 +816,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "AnyInteracts" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#ANY_INTERACTS
      */
-    @Override
-    public TemporalOperator<R> anyInteracts(final Expression<R, ? extends T> time1,
-                                            final Expression<R, ? extends T> time2)
+    public Filter<R> anyInteracts(final Expression<R, ? extends T> time1,
+                                  final Expression<R, ? extends T> time2)
     {
         return TemporalFilter.create(temporal, TemporalOperation.AnyInteracts::new, time1, time2);
     }
@@ -950,10 +829,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Add" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> add(final Expression<R, ? extends Number> operand1,
                                     final Expression<R, ? extends Number> operand2)
     {
@@ -966,10 +842,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Subtract" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> subtract(final Expression<R, ? extends Number> operand1,
                                          final Expression<R, ? extends Number> operand2)
     {
@@ -982,10 +855,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Multiply" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> multiply(final Expression<R, ? extends Number> operand1,
                                          final Expression<R, ? extends Number> operand2)
     {
@@ -998,10 +868,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Divide" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> divide(final Expression<R, ? extends Number> operand1,
                                        final Expression<R, ? extends Number> operand2)
     {
@@ -1009,6 +876,35 @@
     }
 
     /**
+     * Creates an implementation-specific function with a single parameter.
+     *
+     * @param  name       name of the function to call.
+     * @param  parameter  expression providing values for the function argument.
+     * @return an expression which will call the specified function.
+     * @throws IllegalArgumentException if the given name is not recognized,
+     *         or if the argument is illegal for the specified function.
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public Expression<R,?> function(String name, Expression<? super R, ?> parameter) {
+        return function(name, new Expression[] {parameter});
+    }
+
+    /**
+     * Creates an implementation-specific function with two parameters.
+     *
+     * @param  name    name of the function to call.
+     * @param  param1  expression providing values for the first function argument.
+     * @param  param2  expression providing values for the second function argument.
+     * @return an expression which will call the specified function.
+     * @throws IllegalArgumentException if the given name is not recognized,
+     *         or if the arguments are illegal for the specified function.
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public Expression<R,?> function(String name, Expression<? super R, ?> param1, Expression<? super R, ?> param2) {
+        return function(name, new Expression[] {param1, param2});
+    }
+
+    /**
      * Returns the provider for the function of the given name.
      * If the given name is {@code null}, then this method only
      * ensures that {@link #availableFunctions} is initialized.
@@ -1043,7 +939,6 @@
 
     /**
      * Creates an implementation-specific function.
-     * The names of available functions is given by {@link #getCapabilities()}.
      *
      * @param  name        name of the function to call.
      * @param  parameters  expressions providing values for the function arguments.
@@ -1051,7 +946,6 @@
      * @throws IllegalArgumentException if the given name is not recognized,
      *         or if the arguments are illegal for the specified function.
      */
-    @Override
     public Expression<R,?> function(final String name, Expression<R,?>[] parameters) {
         ArgumentChecks.ensureNonNull("name", name);
         ArgumentChecks.ensureNonNull("parameters", parameters);
@@ -1065,107 +959,4 @@
         }
         return register.create(name, parameters);
     }
-
-    /**
-     * Map of all functions supported by this factory, together with their providers.
-     * This is a view over {@link #availableFunctions} which delegates descriptions
-     * to {@link FunctionRegister#describe(String)}. No values is stored in this map.
-     *
-     * @see Capabilities#getFunctions()
-     */
-    final class Functions extends AbstractMap<String, AvailableFunction> {
-        /**
-         * Creates a new map.
-         */
-        Functions() {
-        }
-
-        /**
-         * {@return the number of functions}.
-         */
-        @Override
-        public int size() {
-            synchronized (availableFunctions) {
-                register(null);     // Ensure that `availableFunctions` is initialized.
-                return availableFunctions.size();
-            }
-        }
-
-        /**
-         * Returns the description of the function of the given name.
-         * This method delegates to {@link FunctionRegister#describe(String)}.
-         *
-         * @param  key  name of the function to describe.
-         * @return description of the requested function, or {@code null} if none.
-         */
-        @Override
-        public AvailableFunction get(final Object key) {
-            if (key instanceof String) {
-                final String name = (String) key;
-                final FunctionRegister register = register(name);
-                if (register != null) {
-                    return register.describe(name);
-                }
-            }
-            return null;
-        }
-
-        /**
-         * {@return an iterator over the entries in this map}.
-         */
-        @Override
-        protected EntryIterator<String, AvailableFunction> entryIterator() {
-            final Iterator<Entry<String, FunctionRegister>> it;
-            synchronized (availableFunctions) {
-                register(null);     // Ensure that `availableFunctions` is initialized.
-                it = availableFunctions.entrySet().iterator();
-            }
-            /*
-             * Following is theoretically not thread-safe, but it is okay in our case
-             * because the `availableFunctions` map is not changed after construction.
-             */
-            return new EntryIterator<>() {
-                private Entry<String, FunctionRegister> entry;
-
-                @Override protected boolean next() {
-                    return (entry = it.hasNext() ? it.next() : null) != null;
-                }
-
-                @Override protected String getKey() {
-                    return entry.getKey();
-                }
-
-                @Override protected AvailableFunction getValue() {
-                    return entry.getValue().describe(getKey());
-                }
-            };
-        }
-    }
-
-    /**
-     * Indicates a property by which contents should be sorted, along with intended order.
-     * The given expression should evaluate to {@link Comparable} objects,
-     * but {@link Iterable} objects are accepted as well.
-     *
-     * @param  property  the property to sort by.
-     * @param  order     the sorting order, ascending or descending.
-     * @return definition of sort order of a property.
-     */
-    @Override
-    public SortProperty<R> sort(final ValueReference<R,?> property, final SortOrder order) {
-        return new DefaultSortProperty<>(property, order);
-    }
-
-    /**
-     * Returns a string representation of this factory for debugging purposes.
-     * The string returned by this method may change in any future version.
-     *
-     * @return a string representation for debugging purposes.
-     *
-     * @since 1.5
-     */
-    @Override
-    public String toString() {
-        return Strings.toString(getClass(), "spatial", library.library, "temporal", temporal.getSimpleName());
-    }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultSortProperty.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultSortProperty.java
index 6c67e14..9b4d59c 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultSortProperty.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultSortProperty.java
@@ -23,10 +23,10 @@
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SortOrder;
+import org.apache.sis.pending.geoapi.filter.SortProperty;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java
index 72d5a39..aec9b2e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java
@@ -26,11 +26,9 @@
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 import org.apache.sis.geometry.wrapper.SpatialOperationContext;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.DistanceOperator;
-import org.opengis.filter.DistanceOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -44,9 +42,9 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
-final class DistanceFilter<R> extends BinaryGeometryFilter<R> implements DistanceOperator<R> {
+final class DistanceFilter<R> extends BinaryGeometryFilter<R> {
     /**
      * For cross-version compatibility.
      */
@@ -124,7 +122,6 @@
     /**
      * Returns the buffer distance around the geometry that will be used when comparing features geometries.
      */
-    @Override
     public Quantity<Length> getDistance() {
         return distance;
     }
@@ -134,7 +131,6 @@
      *
      * @throws IllegalStateException if the geometry is not a literal.
      */
-    @Override
     public Geometry getGeometry() {
         final Literal<R, ? extends GeometryWrapper> literal;
         if (expression2 instanceof Literal<?,?>) {
@@ -150,7 +146,7 @@
     /**
      * Given an object, determines if the test(s) represented by this filter are passed.
      *
-     * @param  object  the object (often a {@link Feature} instance) to evaluate.
+     * @param  object  the object (often a {@code Feature} instance) to evaluate.
      * @return {@code true} if the test(s) are passed for the provided object.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Expression.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Expression.java
new file mode 100644
index 0000000..82f2c09
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Expression.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.List;
+import java.util.function.Function;
+import org.opengis.util.ScopedName;
+
+
+/**
+ * A literal or a named procedure that performs a distinct computation.
+ *
+ * <div class="warning"><b>Upcoming API change</b><br>
+ * This is a placeholder for a GeoAPI 3.1 interface not yet released.
+ * In a future version, all usages of this interface may be replaced
+ * by an interface of the same name but in the {@code org.opengis.filter} package
+ * instead of {@code org.apache.sis.filter}.
+ * </div>
+ *
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
+ * @param  <V>  the type of values computed by the expression.
+ */
+public interface Expression<R,V> extends Function<R,V> {
+    /**
+     * Returns the name of the function to be called.
+     *
+     * @return name of the function to be called.
+     */
+    ScopedName getFunctionName();
+
+    /**
+     * Returns the class of resources expected by this expression.
+     *
+     * @return type of resources accepted by this expression.
+     */
+    Class<? super R> getResourceClass();
+
+    /**
+     * Returns the list sub-expressions that will be evaluated to provide the parameters to the function.
+     *
+     * @return the sub-expressions to be evaluated, or an empty list if none.
+     */
+    List<Expression<R,?>> getParameters();
+
+    /**
+     * Evaluates the expression value based on the content of the given object.
+     *
+     * @param  input  the object to be evaluated by the expression.
+     *         Can be {@code null} if this expression allows null values.
+     * @return value computed by the expression.
+     * @throws NullPointerException if {@code input} is null and this expression requires non-null values.
+     * @throws IllegalArgumentException if the expression can not be applied on the given object.
+     */
+    @Override
+    V apply(R input);
+
+    /**
+     * Returns an expression doing the same evaluation than this method, but returning results
+     * as values of the specified type.
+     *
+     * @param  <N>   compile-time value of {@code type}.
+     * @param  type  desired type of expression results.
+     * @return expression doing the same operation this this expression but with results of the specified type.
+     * @throws ClassCastException if the specified type is not a target type supported by implementation.
+     */
+    <N> Expression<R,N> toValueType(Class<N> type);
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Filter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Filter.java
new file mode 100644
index 0000000..abada86
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Filter.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+
+/**
+ * Identification of a subset of resources from a collection of resources
+ * whose property values satisfy a set of logically connected predicates.
+ *
+ * <div class="warning"><b>Upcoming API change</b><br>
+ * This is a placeholder for a GeoAPI 3.1 interface not yet released.
+ * In a future version, all usages of this interface may be replaced
+ * by an interface of the same name but in the {@code org.opengis.filter} package
+ * instead of {@code org.apache.sis.filter}.
+ * </div>
+ *
+ * @param  <R>  the type of resources (e.g. {@link org.apache.sis.feature.AbstractFeature}) to filter.
+ *
+ * @since 1.1
+ */
+public interface Filter<R> extends Predicate<R> {
+    /**
+     * A filter that always evaluates to {@code true}.
+     *
+     * @param  <R>  the type of resources to filter.
+     * @return the "no filtering" filter.
+     */
+    @SuppressWarnings("unchecked")
+    static <R> Filter<R> include() {
+        return FilterLiteral.INCLUDE;
+    }
+
+    /**
+     * A filter that always evaluates to {@code false}.
+     *
+     * @param  <R>  the type of resources to filter.
+     * @return the "exclude all" filter.
+     */
+    @SuppressWarnings("unchecked")
+    static <R> Filter<R> exclude() {
+        return FilterLiteral.EXCLUDE;
+    }
+
+    /**
+     * Returns the nature of the operator.
+     *
+     * @return the nature of this operator.
+     */
+    Enum<?> getOperatorType();
+
+    /**
+     * Returns the class of resources expected by this filter.
+     *
+     * @return type of resources accepted by this filter.
+     *
+     * @since 1.4
+     */
+    Class<? super R> getResourceClass();
+
+    /**
+     * Returns the expressions used as arguments for this filter.
+     *
+     * @return the expressions used as inputs, or an empty list if none.
+     */
+    List<Expression<R,?>> getExpressions();
+
+    /**
+     * Given an object, determines if the test(s) represented by this filter are passed.
+     *
+     * @param  object  the object (often a {@code Feature} instance) to evaluate.
+     * @return {@code true} if the test(s) are passed for the provided object.
+     * @throws NullPointerException if {@code object} is null.
+     * @throws IllegalArgumentException if the filter can not be applied on the given object.
+     */
+    @Override
+    boolean test(R object);
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FilterLiteral.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FilterLiteral.java
new file mode 100644
index 0000000..d39cef5
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FilterLiteral.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import java.util.List;
+import java.util.Collections;
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+final class FilterLiteral implements Filter<Object>, Serializable {
+    @SuppressWarnings("rawtypes")
+    public static final Filter INCLUDE = new FilterLiteral(true);
+
+    @SuppressWarnings("rawtypes")
+    public static final Filter EXCLUDE = new FilterLiteral(false);
+
+    private final boolean value;
+
+    private FilterLiteral(final boolean value) {
+        this.value = value;
+    }
+
+    @Override
+    public Enum<?> getOperatorType() {
+        return value ? FilterName.INCLUDE : FilterName.EXCLUDE;
+    }
+
+    @Override
+    public Class<Object> getResourceClass() {
+        return Object.class;
+    }
+
+    @Override
+    public List<Expression<? super Object, ?>> getExpressions() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public boolean test(Object object) {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return "Filter." + (value ? "INCLUDE" : "EXCLUDE");
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return value ? INCLUDE : EXCLUDE;
+    }
+}
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FilterName.java
similarity index 76%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FilterName.java
index 4307e9c..7f23319 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FilterName.java
@@ -14,14 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.filter;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+enum FilterName {
+     RESOURCE_ID, INCLUDE, EXCLUDE;
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
index 1789b46..810ede2 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
@@ -18,10 +18,6 @@
 
 import java.util.Collection;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.capability.AvailableFunction;
-
 
 /**
  * A factory of {@code org.opengis.filter} functions identified by their names.
@@ -36,8 +32,6 @@
  * @author  Johann Sorel (Geomatys)
  * @version 1.5
  * @since   1.5
- *
- * @see org.opengis.filter.FilterFactory#function(String, Expression...)
  */
 public interface FunctionRegister {
     /**
@@ -57,18 +51,9 @@
     Collection<String> getNames();
 
     /**
-     * Describes the parameters of a function.
-     *
-     * @param  name  name of the function to describe (not null).
-     * @return description of the function parameters.
-     * @throws IllegalArgumentException if function name is unknown..
-     */
-    AvailableFunction describe(String name) throws IllegalArgumentException;
-
-    /**
      * Creates a new function of the given name with given parameters.
      *
-     * @param  <R>         the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>         the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  name        name of the function to create (not null).
      * @param  parameters  function parameters.
      * @return function for the given name and parameters.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
index 54bf782..4d3919e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
@@ -22,11 +22,8 @@
 import org.apache.sis.filter.internal.Node;
 import org.apache.sis.feature.privy.AttributeConvention;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ResourceId;
-import org.opengis.filter.Filter;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -36,7 +33,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class IdentifierFilter extends Node implements ResourceId<Feature>, Optimization.OnFilter<Feature> {
+final class IdentifierFilter extends Node implements Optimization.OnFilter<AbstractFeature> {
     /**
      * For cross-version compatibility.
      */
@@ -55,12 +52,17 @@
         this.identifier = identifier;
     }
 
+    @Override
+    public Enum<?> getOperatorType() {
+        return FilterName.RESOURCE_ID;
+    }
+
     /**
      * Nothing to optimize here. The {@code Optimization.OnFilter} interface
      * is implemented for inheriting the AND, OR and NOT methods overriding.
      */
     @Override
-    public Filter<Feature> optimize(Optimization optimization) {
+    public Filter<AbstractFeature> optimize(Optimization optimization) {
         return this;
     }
 
@@ -68,14 +70,13 @@
      * Returns the class of resources expected by this expression.
      */
     @Override
-    public Class<Feature> getResourceClass() {
-        return Feature.class;
+    public Class<AbstractFeature> getResourceClass() {
+        return AbstractFeature.class;
     }
 
     /**
      * Returns the identifiers of feature instances to accept.
      */
-    @Override
     public String getIdentifier() {
         return identifier;
     }
@@ -84,7 +85,7 @@
      * Returns the parameters of this filter.
      */
     @Override
-    public List<Expression<Feature,?>> getExpressions() {
+    public List<Expression<AbstractFeature,?>> getExpressions() {
         return List.of(new LeafExpression.Literal<>(identifier));
     }
 
@@ -98,11 +99,11 @@
     }
 
     /**
-     * Returns {@code true} if the given object is a {@link Feature} instance and its identifier
+     * Returns {@code true} if the given object is a {@link AbstractFeature} instance and its identifier
      * is one of the identifier specified at {@code IdentifierFilter} construction time.
      */
     @Override
-    public boolean test(final Feature object) {
+    public boolean test(final AbstractFeature object) {
         if (object == null) {
             return false;
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
index 6350cc0..4605000 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
@@ -32,10 +32,9 @@
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
 import org.apache.sis.math.FunctionProperty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -45,7 +44,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V>  the type of value computed by the expression.
  */
 abstract class LeafExpression<R,V> extends Node implements FeatureExpression<R,V> {
@@ -89,7 +88,7 @@
      * @param  <R>  the type of resources used as inputs.
      * @param  <V>  the type of value computed by the expression.
      */
-    static class Literal<R,V> extends LeafExpression<R,V> implements org.opengis.filter.Literal<R,V> {
+    static class Literal<R,V> extends LeafExpression<R,V> implements org.apache.sis.pending.geoapi.filter.Literal<R,V> {
         /** The properties of this function, which returns constants. */
         private static final Set<FunctionProperty> CONSTANT =
                 Set.of(FunctionProperty.ORDER_PRESERVING, FunctionProperty.ORDER_REVERSING);
@@ -106,6 +105,10 @@
             this.value = value;             // Null is accepted.
         }
 
+        @Override public Class<? super R> getResourceClass() {
+            return Object.class;
+        }
+
         /** For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations. */
         @Override protected Collection<?> getChildren() {
             // Not `List.of(…)` because value may be null.
@@ -152,15 +155,15 @@
 
         /**
          * Provides the type of values returned by {@link #apply(Object)}
-         * wrapped in an {@link AttributeType} named "Literal".
+         * wrapped in an {@link DefaultAttributeType} named "Literal".
          *
          * @param  addTo  where to add the type of properties evaluated by the given expression.
          * @return builder of the added property.
          */
         @Override
-        public PropertyTypeBuilder expectedType(FeatureType ignored, final FeatureTypeBuilder addTo) {
+        public PropertyTypeBuilder expectedType(DefaultFeatureType ignored, final FeatureTypeBuilder addTo) {
             final Class<?> valueType = getValueClass();
-            AttributeType<?> propertyType;
+            DefaultAttributeType<?> propertyType;
             synchronized (TYPES) {
                 propertyType = TYPES.get(valueType);
                 if (propertyType == null) {
@@ -175,17 +178,17 @@
         }
 
         /**
-         * A cache of {@link AttributeType} instances for literal classes. Used for avoiding to create
+         * A cache of {@link DefaultAttributeType} instances for literal classes. Used for avoiding to create
          * duplicated instances when the literal is a common type like {@link String} or {@link Integer}.
          */
         @SuppressWarnings("unchecked")
-        private static final WeakValueHashMap<Class<?>, AttributeType<?>> TYPES = new WeakValueHashMap<>((Class) Class.class);
+        private static final WeakValueHashMap<Class<?>, DefaultAttributeType<?>> TYPES = new WeakValueHashMap<>((Class) Class.class);
 
         /**
          * Invoked when a new attribute type need to be created for the given standard type.
          * The given standard type should be a GeoAPI interface, not the implementation class.
          */
-        private static <R> AttributeType<R> newType(final Class<R> standardType) {
+        private static <R> DefaultAttributeType<R> newType(final Class<R> standardType) {
             return createType(standardType, Names.createLocalName(null, null, "Literal"));
         }
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java
index 25a6870..f4e0df0 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LikeFilter.java
@@ -22,10 +22,8 @@
 import java.util.regex.Pattern;
 import org.apache.sis.filter.internal.Node;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.LikeOperator;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
 
 
 /**
@@ -34,9 +32,9 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
-final class LikeFilter<R> extends Node implements LikeOperator<R>, Optimization.OnFilter<R> {
+final class LikeFilter<R> extends Node implements Optimization.OnFilter<R> {
     /**
      * For cross-version compatibility.
      */
@@ -150,6 +148,11 @@
         this.regex          = original.regex;
     }
 
+    @Override
+    public ComparisonOperatorName getOperatorType() {
+        return ComparisonOperatorName.PROPERTY_IS_LIKE;
+    }
+
     /**
      * Creates a new filter of the same type but different parameters.
      */
@@ -187,7 +190,6 @@
      * Returns the pattern character for matching any sequence of characters.
      * For the SQL "{@code LIKE}" operator, this property is the {@code %} character.
      */
-    @Override
     public char getWildCard() {
         return wildcard;
     }
@@ -196,7 +198,6 @@
      * Returns the pattern character for matching exactly one character.
      * For the SQL "{@code LIKE}" operator, this property is the {@code _} character.
      */
-    @Override
     public char getSingleChar() {
         return singleChar;
     }
@@ -205,7 +206,6 @@
      * Returns the pattern character for indicating that the next character should be matched literally.
      * For the SQL "{@code LIKE}" operator, this property is the {@code '} character.
      */
-    @Override
     public char getEscapeChar() {
         return escape;
     }
@@ -213,7 +213,6 @@
     /**
      * Specifies how a filter expression processor should perform string comparisons.
      */
-    @Override
     public boolean isMatchingCase() {
         return isMatchingCase;
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java
index 42ef29a..6127180 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LogicalFilter.java
@@ -25,10 +25,9 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.LogicalOperator;
+import org.apache.sis.pending.geoapi.filter.LogicalOperatorName;
 
 
 /**
@@ -38,7 +37,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 abstract class LogicalFilter<R> extends Node implements LogicalOperator<R>, Optimization.OnFilter<R> {
     /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
index 5a396dc..40c624b 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
@@ -29,13 +29,11 @@
 import org.apache.sis.filter.internal.Node;
 import org.apache.sis.util.privy.CollectionsExt;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.LogicalOperator;
+import org.apache.sis.pending.geoapi.filter.LogicalOperatorName;
 
 
 /**
@@ -90,7 +88,7 @@
     /**
      * The type of feature instances to be filtered, or {@code null} if unknown.
      */
-    private FeatureType featureType;
+    private DefaultFeatureType featureType;
 
     /**
      * Filters and expressions already optimized. Also used for avoiding never-ending loops.
@@ -113,12 +111,12 @@
 
     /**
      * Returns the type of feature instances to be filtered, or {@code null} if unknown.
-     * This is the last value specified by a call to {@link #setFeatureType(FeatureType)}.
+     * This is the last value specified by a call to {@link #setFeatureType(DefaultFeatureType)}.
      * The default value is {@code null}.
      *
      * @return the type of feature instances to be filtered, or {@code null} if unknown.
      */
-    public FeatureType getFeatureType() {
+    public DefaultFeatureType getFeatureType() {
         return featureType;
     }
 
@@ -130,7 +128,7 @@
      *
      * @param  type  the type of feature instances to be filtered, or {@code null} if unknown.
      */
-    public void setFeatureType(final FeatureType type) {
+    public void setFeatureType(final DefaultFeatureType type) {
         featureType = type;
     }
 
@@ -448,7 +446,7 @@
         if (filter == null) {
             return List.of();
         }
-        final CodeList<?> type = filter.getOperatorType();
+        final Enum<?> type = filter.getOperatorType();
         if (type == LogicalOperatorName.AND) {
             return ((LogicalOperator<R>) filter).getOperands();
         }
@@ -538,7 +536,7 @@
      *
      * @see DefaultFilterFactory#literal(Object)
      */
-    public static <R,V> Literal<R,V> literal(final V value) {
+    public static <R,V> Expression<R,V> literal(final V value) {
         return new LeafExpression.Literal<>(value);
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index bb62d75..9fc8e48 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
@@ -29,15 +29,15 @@
 import org.apache.sis.filter.privy.XPath;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-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.feature.PropertyNotFoundException;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.opengis.util.ScopedName;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Name;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -52,8 +52,8 @@
  *
  * @see AssociationValue
  */
-abstract class PropertyValue<V> extends LeafExpression<Feature,V>
-        implements ValueReference<Feature,V>, Optimization.OnExpression<Feature,V>
+abstract class PropertyValue<V> extends LeafExpression<AbstractFeature,V>
+        implements ValueReference<AbstractFeature,V>, Optimization.OnExpression<AbstractFeature,V>
 {
     /**
      * For cross-version compatibility.
@@ -86,6 +86,11 @@
         this.isVirtual = isVirtual;
     }
 
+    @Override
+    public final ScopedName getFunctionName() {
+        return Name.VALUE_REFERENCE;
+    }
+
     /**
      * Creates a new expression retrieving values from a property of the given path.
      * Simple path expressions of the form "a/b/c" can be used.
@@ -97,7 +102,7 @@
      * @throws IllegalArgumentException if the given XPath is not supported.
      */
     @SuppressWarnings("unchecked")
-    static <V> ValueReference<Feature,V> create(final String xpath, final Class<V> type) {
+    static <V> ValueReference<AbstractFeature,V> create(final String xpath, final Class<V> type) {
         final var parsed = new XPath(xpath);
         List<String> path = parsed.path;
         boolean isVirtual = false;
@@ -129,8 +134,8 @@
      * Returns the class of resources expected by this expression.
      */
     @Override
-    public final Class<Feature> getResourceClass() {
-        return Feature.class;
+    public final Class<AbstractFeature> getResourceClass() {
+        return AbstractFeature.class;
     }
 
     /**
@@ -162,7 +167,7 @@
     }
 
     /**
-     * Returns the type of values fetched from {@link Feature} instance.
+     * Returns the type of values fetched from {@link AbstractFeature} instance.
      * This is the type before conversion to the {@linkplain #getValueClass() target type}.
      * The type is always {@link Object} on newly created expression because the type of feature property
      * values is unknown, but may become a specialized type after {@link Optimization} has been applied.
@@ -229,7 +234,7 @@
          * If no value is found for the given feature, then this method returns {@code null}.
          */
         @Override
-        public Object apply(final Feature instance) {
+        public Object apply(final AbstractFeature instance) {
             return (instance != null) ? instance.getValueOrFallback(name, null) : null;
         }
 
@@ -241,11 +246,11 @@
          */
         @Override
         public PropertyValue<Object> optimize(final Optimization optimization) {
-            final FeatureType type = optimization.getFeatureType();
+            final DefaultFeatureType type = optimization.getFeatureType();
             if (type != null) try {
                 return Features.getLinkTarget(type.getProperty(name))
                         .map((rename) -> new AsObject(rename, isVirtual)).orElse(this);
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 warning(e, true);
             }
             return this;
@@ -292,11 +297,14 @@
          * If no value is found for the given feature, then this method returns {@code null}.
          */
         @Override
-        public V apply(final Feature instance) {
+        public V apply(final AbstractFeature instance) {
             if (instance != null) try {
                 return ObjectConverters.convert(instance.getValueOrFallback(name, null), type);
             } catch (UnconvertibleObjectException e) {
                 warning(e, false);
+            } catch (IllegalArgumentException e) {
+                warning(e, true);
+                // Null will be returned below.
             }
             return null;
         }
@@ -308,7 +316,7 @@
          */
         @Override
         public final PropertyValue<V> optimize(final Optimization optimization) {
-            final FeatureType featureType = optimization.getFeatureType();
+            final DefaultFeatureType featureType = optimization.getFeatureType();
             if (featureType != null) try {
                 /*
                  * Resolve link (e.g. "sis:identifier" as a reference to the real identifier property).
@@ -316,12 +324,12 @@
                  * If there is no renaming to apply (which is the usual case), then `rename` is null.
                  */
                 String rename = name;
-                PropertyType property = featureType.getProperty(rename);
+                AbstractIdentifiedType property = featureType.getProperty(rename);
                 Optional<String> target = Features.getLinkTarget(property);
                 if (target.isPresent()) try {
                     rename = target.get();
                     property = featureType.getProperty(rename);
-                } catch (PropertyNotFoundException e) {
+                } catch (IllegalArgumentException e) {
                     warning(e, true);
                     rename = name;
                 }
@@ -332,8 +340,8 @@
                  */
                 Class<?> source = getSourceClass();
                 final Class<?> original = source;
-                if (property instanceof AttributeType<?>) {
-                    source = ((AttributeType<?>) property).getValueClass();
+                if (property instanceof DefaultAttributeType<?>) {
+                    source = ((DefaultAttributeType<?>) property).getValueClass();
                 }
                 if (!(rename.equals(name) && source.equals(original))) {
                     if (source == Object.class) {
@@ -342,7 +350,7 @@
                         return new CastedAndConverted<>(source, type, rename, isVirtual);
                     }
                 }
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 warning(e, true);
             }
             return this;
@@ -353,7 +361,7 @@
          * when a feature of the given type is evaluated.
          */
         @Override
-        public final PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+        public final PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
             final PropertyTypeBuilder p = super.expectedType(valueType, addTo);
             if (p instanceof AttributeTypeBuilder<?>) {
                 final AttributeTypeBuilder<?> a = (AttributeTypeBuilder<?>) p;
@@ -374,23 +382,23 @@
      * @throws IllegalArgumentException if this method cannot determine the property type for the given feature type.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        PropertyType type;
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
+        AbstractIdentifiedType type;
         try {
             type = valueType.getProperty(name);
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             if (isVirtual) {
                 // The property does not exist but may be defined on a yet unknown child type.
                 return expectedType(addTo);
             }
             throw e;
         }
-        while (type instanceof Operation) {
-            final IdentifiedType result = ((Operation) type).getResult();
-            if (result != type && result instanceof PropertyType) {
-                type = (PropertyType) result;
-            } else if (result instanceof FeatureType) {
-                return addTo.addAssociation((FeatureType) result).setName(name);
+        while (type instanceof AbstractOperation) {
+            final AbstractIdentifiedType result = ((AbstractOperation) type).getResult();
+            if (result != type && result != null) {
+                type = result;
+            } else if (result instanceof DefaultFeatureType) {
+                return addTo.addAssociation((DefaultFeatureType) result).setName(name);
             } else {
                 return null;
             }
@@ -428,7 +436,7 @@
         }
 
         /**
-         * Returns the type of values fetched from {@link Feature} instance.
+         * Returns the type of values fetched from {@link AbstractFeature} instance.
          */
         @Override
         protected Class<S> getSourceClass() {
@@ -440,11 +448,13 @@
          * If no value is found for the given feature, then this method returns {@code null}.
          */
         @Override
-        public V apply(final Feature instance) {
+        public V apply(final AbstractFeature instance) {
             if (instance != null) try {
                 return converter.apply(source.cast(instance.getValueOrFallback(name, null)));
             } catch (ClassCastException | UnconvertibleObjectException e) {
                 warning(e, false);
+            } catch (IllegalArgumentException e) {
+                warning(e, true);
             }
             return null;
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
index 88ebd77..21629a0 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
@@ -19,12 +19,9 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.feature.privy.FeatureExpression;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.TemporalOperator;
-import org.opengis.filter.TemporalOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.temporal.Period;
+import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
 
 
 /**
@@ -35,11 +32,11 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <T>  the base type of temporal objects, or {@code Object.class} for any type.
  */
 class TemporalFilter<R,T> extends BinaryFunction<R,T,T>
-        implements TemporalOperator<R>, Optimization.OnFilter<R>
+        implements Filter<R>, Optimization.OnFilter<R>
 {
     /**
      * For cross-version compatibility.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
index d826f84..535990f 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
@@ -26,8 +26,8 @@
 import static org.apache.sis.filter.TimeMethods.EQUAL;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
-import org.opengis.filter.TemporalOperatorName;
+import org.apache.sis.pending.geoapi.temporal.Period;
+import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
 
 
 /**
@@ -171,7 +171,7 @@
      * @param  self   the object on which to invoke the method identified by {@code test}.
      * @param  other  the argument to give to the test method call, or {@code null} if none.
      * @return the result of performing the comparison identified by {@code test}.
-     * @throws InvalidFilterValueException if the two objects cannot be compared.
+     * @throws IllegalArgumentException if the two objects cannot be compared.
      */
     final boolean compare(final int test, final T self, final Temporal other) {
         return (other != null) && comparators.compare(test, self, other);
@@ -185,7 +185,7 @@
      * @param  self   the object on which to invoke the method identified by {@code test}, or {@code null} if none.
      * @param  other  the argument to give to the test method call, or {@code null} if none.
      * @return the result of performing the comparison identified by {@code test}.
-     * @throws InvalidFilterValueException if the two objects cannot be compared.
+     * @throws IllegalArgumentException if the two objects cannot be compared.
      */
     @SuppressWarnings("unchecked")
     static boolean compare(final int test, final Temporal self, final Temporal other) {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TimeMethods.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TimeMethods.java
index 11cc0ca..18d60de 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TimeMethods.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TimeMethods.java
@@ -39,9 +39,6 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.InvalidFilterValueException;
-
 
 /**
  * Provides the <i>is before</i> and <i>is after</i> operations for various {@code java.time} objects.
@@ -138,7 +135,7 @@
      * @param  self   the object on which to invoke the method identified by {@code test}.
      * @param  other  the argument to give to the test method call.
      * @return the result of performing the comparison identified by {@code test}.
-     * @throws InvalidFilterValueException if the two objects cannot be compared.
+     * @throws IllegalArgumentException if the two objects cannot be compared.
      */
     @SuppressWarnings("unchecked")
     final boolean compare(final int test, final T self, final TemporalAccessor other) {
@@ -159,7 +156,7 @@
      * @param  self   the object on which to invoke the method identified by {@code test}.
      * @param  other  the argument to give to the test method call.
      * @return the result of performing the comparison identified by {@code test}.
-     * @throws InvalidFilterValueException if the two objects cannot be compared.
+     * @throws IllegalArgumentException if the two objects cannot be compared.
      */
     static <T> boolean compare(final int test, final Class<T> type, final T self, final T other) {
         /*
@@ -218,7 +215,7 @@
         } else if (value instanceof Date) {
             return ((Date) value).toInstant();      // Overridden in `Date` subclasses.
         } else {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.CannotCompareInstanceOf_2, value.getClass(), TemporalAccessor.class));
         }
     }
@@ -231,7 +228,7 @@
      * @param  self   the object on which to invoke the method identified by {@code test}.
      * @param  other  the argument to give to the test method call.
      * @return the result of performing the comparison identified by {@code test}.
-     * @throws InvalidFilterValueException if the two objects cannot be compared.
+     * @throws IllegalArgumentException if the two objects cannot be compared.
      */
     private static boolean compareAsInstants(final int test, final TemporalAccessor self, final TemporalAccessor other) {
         try {
@@ -246,7 +243,7 @@
             }
             return test == ((t1 < t2) ? BEFORE : AFTER);
         } catch (DateTimeException | ArithmeticException e) {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.CannotCompareInstanceOf_2, self.getClass(), other.getClass()), e);
         }
     }
@@ -297,7 +294,7 @@
                         (self, other) -> ((Comparable) self).compareTo(other) > 0,
                         (self, other) -> ((Comparable) self).compareTo(other) == 0);
             } else {
-                throw new InvalidFilterValueException(Errors.format(Errors.Keys.CannotCompareInstanceOf_2, type, type));
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.CannotCompareInstanceOf_2, type, type));
             }
         } else {
             return fallback(type);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/UnaryFunction.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/UnaryFunction.java
index ee78c0e..eefd5a1 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/UnaryFunction.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/UnaryFunction.java
@@ -23,11 +23,8 @@
 import org.apache.sis.xml.NilReason;
 import org.apache.sis.filter.internal.Node;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.NilOperator;
-import org.opengis.filter.NullOperator;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
 
 
 /**
@@ -37,7 +34,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V>  the type of value computed by the expression.
  */
 class UnaryFunction<R,V> extends Node {
@@ -107,8 +104,8 @@
      * @param  <R>  the type of resources used as inputs.
      */
     static final class IsNull<R> extends UnaryFunction<R,Object>
-            implements NullOperator<R>, Optimization.OnFilter<R>
-    {
+            implements Filter<R>, Optimization.OnFilter<R>
+ {
         /** For cross-version compatibility. */
         private static final long serialVersionUID = 2960285515924533419L;
 
@@ -117,6 +114,10 @@
             super(expression);
         }
 
+        @Override public ComparisonOperatorName getOperatorType() {
+            return ComparisonOperatorName.PROPERTY_IS_NULL;
+        }
+
         /** Creates a new filter of the same type but different parameters. */
         @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new IsNull<>(effective[0]);
@@ -142,7 +143,7 @@
      * @param  <R>  the type of resources used as inputs.
      */
     static final class IsNil<R> extends UnaryFunction<R,Object>
-            implements NilOperator<R>, Optimization.OnFilter<R>
+            implements Filter<R>, Optimization.OnFilter<R>
     {
         /** For cross-version compatibility. */
         private static final long serialVersionUID = -7540765433296725888L;
@@ -156,13 +157,17 @@
             this.nilReason = nilReason;
         }
 
+        @Override public ComparisonOperatorName getOperatorType() {
+            return ComparisonOperatorName.PROPERTY_IS_NIL;
+        }
+
         /** Creates a new filter of the same type but different parameters. */
         @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
             return new IsNil<>(effective[0], nilReason);
         }
 
         /** Returns the reason why the value is nil. */
-        @Override public Optional<String> getNilReason() {
+        public Optional<String> getNilReason() {
             return Optional.ofNullable(nilReason);
         }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java
index fbdff72..9ef6b01 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java
@@ -31,10 +31,9 @@
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 import org.apache.sis.filter.Optimization;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -44,7 +43,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the geometry implementation type.
  *
  * @see org.apache.sis.filter.ConvertFunction
@@ -143,7 +142,7 @@
      *
      * @param  input  the geometry to evaluate with this expression.
      * @return the geometry wrapper, or {@code null} if the evaluated value is null.
-     * @throws InvalidFilterValueException if the expression result is not an instance of a supported type.
+     * @throws IllegalArgumentException if the expression result is not an instance of a supported type.
      */
     @Override
     public GeometryWrapper apply(final R input) {
@@ -160,7 +159,7 @@
                 return library.castOrWrap(value);
             }
         } catch (ClassCastException | MismatchedDimensionException e) {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalClass_2, library.rootClass, Classes.getClass(value)), e);
         }
         return library.toGeometry2D(envelope, WraparoundMethod.NONE);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
index 9602325..caecc6e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
@@ -41,11 +41,9 @@
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.system.Loggers;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -91,10 +89,10 @@
      *
      * @see Expression#getFunctionName()
      */
-    protected static <T> AttributeType<T> createType(final Class<T> type, final Object name) {
+    protected static <T> DefaultAttributeType<T> createType(final Class<T> type, final Object name) {
         // We do not use `Map.of(…)` for letting the attribute type constructor do the null check.
         return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),
-                                          type, 1, 1, null, (AttributeType<?>[]) null);
+                                          type, 1, 1, null, (DefaultAttributeType<?>[]) null);
     }
 
     /**
@@ -168,12 +166,12 @@
     /**
      * Returns an expression whose results is a geometry wrapper.
      *
-     * @param  <R>         the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>         the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  <G>         the geometry implementation type.
      * @param  library     the geometry library to use.
      * @param  expression  the expression providing source values.
      * @return an expression whose results is a geometry wrapper.
-     * @throws InvalidFilterValueException if the given expression is already a wrapper
+     * @throws IllegalArgumentException if the given expression is already a wrapper
      *         but for another geometry implementation.
      */
     @SuppressWarnings("unchecked")
@@ -184,7 +182,7 @@
             if (library.equals(((GeometryConverter<?,?>) expression).library)) {
                 return (GeometryConverter<R,G>) expression;
             } else {
-                throw new InvalidFilterValueException();        // TODO: provide a message.
+                throw new IllegalArgumentException();        // TODO: provide a message.
             }
         }
         return new GeometryConverter<>(library, expression);
@@ -194,7 +192,7 @@
      * If the given exception was wrapped by {@link #toGeometryWrapper(Geometries, Expression)},
      * returns the original expression. Otherwise returns the given expression.
      *
-     * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  expression  the expression to unwrap.
      * @return the unwrapped expression.
      */
@@ -218,7 +216,7 @@
         if (expression instanceof GeometryConverter<?,?>) {
             return ((GeometryConverter<?,?>) expression).library;
         }
-        throw new InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
+        throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java
index d944af3..1100972 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java
@@ -34,7 +34,7 @@
  *       <li>For other operations, the common CRS is chosen by
  *         {@linkplain org.apache.sis.referencing.CRS#suggestCommonTarget referencing utility method}.
  *         If that method cannot provide a common space,
- *         then an {@link org.opengis.filter.InvalidFilterValueException} is thrown.</li>
+ *         then an {@link java.lang.IllegalArgumentException} is thrown.</li>
  *     </ul>
  *   </li>
  * </ul>
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/CopyVisitor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/CopyVisitor.java
deleted file mode 100644
index e274fcd..0000000
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/CopyVisitor.java
+++ /dev/null
@@ -1,707 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter.privy;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Objects;
-import javax.measure.Quantity;
-import javax.measure.quantity.Length;
-import org.opengis.util.CodeList;
-import org.opengis.geometry.Envelope;
-import org.opengis.filter.*;
-import org.apache.sis.util.privy.CollectionsExt;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.feature.internal.Resources;
-
-
-/**
- * Visitor used to copy expressions and filters with potentially a change of parameterized types.
- * This class can be used when filters need to be recreated using a different {@link FilterFactory},
- * for example because the type of resources changed. For example different filter implementations
- * may be needed if the filters need to operate on {@link org.apache.sis.coverage.grid.GridCoverage}
- * resources instead of {@link org.opengis.feature.Feature} instances.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- *
- * @param  <SR>  the type of resources expected by the filters to copy (source resource).
- * @param  <TR>  the type of resources expected by the copied filters (target resource).
- * @param  <G>   base class of geometry objects.
- * @param  <T>   base class of temporal objects.
- */
-public class CopyVisitor<SR,TR,G,T> extends Visitor<SR, List<Object>> {
-    /**
-     * The factory to use for creating the new filters and expressions.
-     * Note that some methods in this factory may return {@code null}.
-     * See <cite>Partially implemented factory</cite> in {@link EditVisitor} Javadoc.
-     */
-    protected final FilterFactory<TR,G,T> factory;
-
-    /**
-     * Whether to force creation of new filters or expressions even when the operands did not changed.
-     * If {@code false}, new filters and expressions are created only when at least one operand changed.
-     * This flag should be {@code true} if the {@code <SR>} and {@code <TR>} types are not the same,
-     * or if the user knows that the {@linkplain #factory} may create a different kind of object even
-     * when the operands are the same.
-     */
-    private final boolean forceNew;
-
-    /**
-     * Whether to force the use of newly created filters or expressions even when they are equal to the original ones.
-     * If {@code false}, then the previously existing filters or expressions will be reused when the newly created
-     * instances are equal according to {@link Object#equals(Object)}. Note this sharing requires that the filter
-     * or expression to reuse expects a base resource type {@code <R>} which is common to both {@code <? super SR>}
-     * and {@code <? super TR>}. We have no way to verify that.
-     *
-     * <p>This flag should be {@code true} if the filters or expressions are mutable.
-     * Otherwise it may be {@code false} as a way to share existing instances.</p>
-     */
-    private final boolean forceUse;
-
-    /**
-     * The factory method to invoke for creating a binary comparison operator.
-     * This is used for invoking one of the {@link FilterFactory} methods for
-     * a given {@link ComparisonOperatorName}. Example:
-     *
-     * {@snippet lang="java" :
-     *     copyVisitor.setCopyHandler(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO, FilterFactory::equal);
-     * }
-     *
-     * @param  <R>  the same type of resources specified by the enclosing {@link CopyVisitor} class.
-     *
-     * @see CopyVisitor#setCopyHandler(ComparisonOperatorName, BinaryComparisonFactory)
-     */
-    @FunctionalInterface
-    protected interface BinaryComparisonFactory<R> {
-        /**
-         * Creates a new binary comparison operator from the given operands.
-         *
-         * @param  factory         the factory to use for creating the filter
-         * @param  expression1     the first of the two expressions to be used by this comparator.
-         * @param  expression2     the second of the two expressions to be used by this comparator.
-         * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-         * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
-         * @return the new filter, or {@code null} if this method cannot create a filter from the given arguments.
-         */
-        BinaryComparisonOperator<R> create(
-                FilterFactory<R,?,?> factory,
-                Expression<R,?> expression1,
-                Expression<R,?> expression2,
-                boolean isMatchingCase, MatchAction matchAction);
-    }
-
-    /**
-     * The factory method to invoke for creating a temporal operator.
-     * This is used for invoking one of the {@link FilterFactory} methods
-     * for a given {@link TemporalOperatorName}. Example:
-     *
-     * {@snippet lang="java" :
-     *     copyVisitor.setCopyHandler(TemporalOperatorName.AFTER, FilterFactory::after);
-     * }
-     *
-     * @param  <R>  the same type of resources specified by the enclosing {@link CopyVisitor} class.
-     * @param  <T>  base class of temporal objects specified by the enclosing class.
-     *
-     * @see CopyVisitor#setCopyHandler(TemporalOperatorName, TemporalComparisonFactory)
-     */
-    @FunctionalInterface
-    protected interface TemporalFactory<R,T> {
-        /**
-         * Creates a new temporal operator from the given operands.
-         *
-         * @param  factory  the factory to use for creating the filter.
-         * @param  time1    expression fetching the first temporal value.
-         * @param  time2    expression fetching the second temporal value.
-         * @return the new filter, or {@code null} if this method cannot create a filter from the given arguments.
-         */
-        TemporalOperator<R> create(
-                FilterFactory<R,?,T> factory,
-                Expression<R, ? extends T> time1,
-                Expression<R, ? extends T> time2);
-    }
-
-    /**
-     * The factory method to invoke for creating a spatial operator.
-     * This is used for invoking one of the {@link FilterFactory} methods
-     * for a given {@link SpatialOperatorName}. Example:
-     *
-     * {@snippet lang="java" :
-     *     copyVisitor.setCopyHandler(SpatialOperatorName.EQUALS, FilterFactory::equals);
-     * }
-     *
-     * @param  <R>  the same type of resources specified by the enclosing {@link CopyVisitor} class.
-     * @param  <G>  base class of geometry objects specified by the enclosing class.
-     *
-     * @see CopyVisitor#setCopyHandler(SpatialOperatorName, SpatialFactory)
-     */
-    @FunctionalInterface
-    protected interface SpatialFactory<R,G> {
-        /**
-         * Creates a new spatial operator from the given operands.
-         *
-         * @param  factory    the factory to use for creating the filter.
-         * @param  geometry1  expression fetching the first geometry of the binary operator.
-         * @param  geometry2  expression fetching the second geometry of the binary operator.
-         * @return the new filter, or {@code null} if this method cannot create a filter from the given arguments.
-         */
-        BinarySpatialOperator<R> create(
-                FilterFactory<R,G,?> factory,
-                Expression<R, ? extends G> geometry1,
-                Expression<R, ? extends G> geometry2);
-
-        /**
-         * Bridge for the "bbox" operation, because it uses a different method signature.
-         * Usage example:
-         *
-         * {@snippet lang="java" :
-         *     copyVisitor.setCopyHandler(SpatialOperatorName.BBOX, SpatialFactory::bbox);
-         * }
-         *
-         * @param  <R>        the {@link CopyVisitor} type of resources.
-         * @param  <G>        base class of geometry objects specified by {@code CopyVisitor}.
-         * @param  factory    the factory to use for creating the filter.
-         * @param  geometry1  expression fetching the geometry to check for interaction with bounds.
-         * @param  geometry2  the bounds to check geometry against.
-         * @return the new filter, or {@code null} if this method cannot create a filter from the given arguments.
-         *
-         * @see SpatialOperatorName#BBOX
-         */
-        static <R,G> BinarySpatialOperator<R> bbox(
-                final FilterFactory<R,G,?> factory,
-                final Expression<R, ? extends G> geometry1,
-                final Expression<R, ? extends G> geometry2)
-        {
-            if (geometry2 instanceof Literal<?,?>) {
-                final Object bounds = ((Literal<?,?>) geometry2).getValue();
-                if (bounds instanceof Envelope) {
-                    return factory.bbox(geometry1, (Envelope) bounds);
-                }
-            }
-            return null;
-        }
-    }
-
-    /**
-     * The factory method to invoke for creating a distance operator.
-     * This is used for invoking one of the {@link FilterFactory} methods
-     * for a given {@link DistanceOperatorName}. Example:
-     *
-     * {@snippet lang="java" :
-     *     copyVisitor.setCopyHandler(DistanceOperatorName.BEYOND, FilterFactory::beyond);
-     * }
-     *
-     * @param  <R>  the same type of resources specified by the enclosing {@link CopyVisitor} class.
-     * @param  <G>  base class of geometry objects specified by the enclosing class.
-     *
-     * @see CopyVisitor#setCopyHandler(DistanceOperatorName, DistanceFactory)
-     */
-    @FunctionalInterface
-    protected interface DistanceFactory<R,G> {
-        /**
-         * Creates a new spatial operator from the given operands.
-         *
-         * @param  factory    the factory to use for creating the filter.
-         * @param  geometry1  expression fetching the first geometry of the binary operator.
-         * @param  geometry2  expression fetching the second geometry of the binary operator.
-         * @param  distance   distance for evaluating the expression as {@code true}.
-         * @return the new filter, or {@code null} if this method cannot create a filter from the given arguments.
-         */
-        DistanceOperator<R> create(
-                FilterFactory<R,G,?> factory,
-                Expression<R, ? extends G> geometry1,
-                Expression<R, ? extends G> geometry2,
-                Quantity<Length> distance);
-    }
-
-    /**
-     * The factory method to invoke for creating a logical operator.
-     * This is used for invoking one of the {@link FilterFactory}
-     * methods for a given {@link LogicalOperatorName}. Example:
-     *
-     * {@snippet lang="java" :
-     *     copyVisitor.setCopyHandler(LogicalOperatorName.AND, FilterFactory::and);
-     * }
-     *
-     * @param  <R>  the same type of resources specified by the enclosing {@link CopyVisitor} class.
-     *
-     * @see CopyVisitor#setCopyHandler(LogicalOperatorName, LogicalFactory)
-     */
-    @FunctionalInterface
-    protected interface LogicalFactory<R> {
-        /**
-         * Creates a new binary comparison operator from the given operands.
-         *
-         * @param  factory   the factory to use for creating the filter.
-         * @param  operands  a collection of operands.
-         * @return the new filter, or {@code null} if this method cannot create a filter from the given arguments.
-         */
-        LogicalOperator<R> create(
-                FilterFactory<R,?,?> factory,
-                Collection<? extends Filter<R>> operands);
-
-        /**
-         * Bridge for the "not" operation, because it uses a different method signature.
-         * This method signature shall be identical to {@code create(…)} method signature.
-         * Usage example:
-         *
-         * {@snippet lang="java" :
-         *     copyVisitor.setCopyHandler(LogicalOperatorName.NOT, LogicalFactory::not);
-         * }
-         *
-         * @param  <R>       the {@link CopyVisitor} type of resources.
-         * @param  factory   the factory to use for creating the filter.
-         * @param  operands  the operand of the NOT operation.
-         * @return a filter evaluating {@code NOT operand}.
-         */
-        static <R> LogicalOperator<R> not(
-                final FilterFactory<R,?,?> factory,
-                final Collection<? extends Filter<R>> operands)
-        {
-            Filter<R> op = CollectionsExt.singletonOrNull(operands);
-            return (op != null) ? factory.not(op) : null;
-        }
-    }
-
-    /**
-     * The factory method to invoke for creating a binary function.
-     * This is used for invoking one of the {@link FilterFactory}
-     * methods for a given arithmetic function name. Example:
-     *
-     * {@snippet lang="java" :
-     *     copyVisitor.setCopyHandler("Multiply", FilterFactory::multiply);
-     * }
-     *
-     * @param  <R>  the same type of resources specified by the enclosing {@link CopyVisitor} class.
-     *
-     * @see CopyVisitor#setCopyHandler(String, BinaryFunctionFactory)
-     */
-    @FunctionalInterface
-    protected interface BinaryFunctionFactory<R> {
-        /**
-         * Creates a new binary function from the given operands.
-         *
-         * @param  factory   the factory to use for creating the filter.
-         * @param  operand1  the first of the two expressions to be used by this function.
-         * @param  operand2  the second of the two expressions to be used by this function.
-         * @return the new expression, or {@code null} if this method cannot create an expression from the given arguments.
-         */
-        Expression<R,Number> create(
-                FilterFactory<R,?,?> factory,
-                Expression<R, ? extends Number> operand1,
-                Expression<R, ? extends Number> operand2);
-    }
-
-    /**
-     * Creates a new copy visitor with the given factory.
-     *
-     * @param  factory  the factory to use for creating the new filters and expressions.
-     * @param  force    whether to force new filters or expressions even when existing instances could be reused.
-     */
-    public CopyVisitor(final FilterFactory<TR,G,T> factory, final boolean force) {
-        this(factory, true, force);
-    }
-
-    /**
-     * Creates a new copy visitor with the given factory.
-     * The {@code forceNew} argument can be {@code false}
-     * only if {@code <SR>} and {@code <TR>} are the same type.
-     *
-     * @param  newFactory  the factory to use for creating the new filters and expressions.
-     * @param  forceNew    whether to force creation of new filters or expressions even when the operands did not changed.
-     * @param  forceUse    whether to force the use of newly created filters or expressions even when they are equal to the original ones.
-     */
-    @SuppressWarnings("this-escape")
-    CopyVisitor(final FilterFactory<TR,G,T> newFactory, final boolean forceNew, final boolean forceUse) {
-        this.factory  = Objects.requireNonNull(newFactory);
-        this.forceNew = forceNew;
-        this.forceUse = forceUse;
-        setCopyHandler(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO,                 FilterFactory::equal);
-        setCopyHandler(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO,             FilterFactory::notEqual);
-        setCopyHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,                FilterFactory::less);
-        setCopyHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN,             FilterFactory::greater);
-        setCopyHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    FilterFactory::lessOrEqual);
-        setCopyHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, FilterFactory::greaterOrEqual);
-        setCopyHandler(  TemporalOperatorName.AFTER,                                FilterFactory::after);
-        setCopyHandler(  TemporalOperatorName.BEFORE,                               FilterFactory::before);
-        setCopyHandler(  TemporalOperatorName.BEGINS,                               FilterFactory::begins);
-        setCopyHandler(  TemporalOperatorName.BEGUN_BY,                             FilterFactory::begunBy);
-        setCopyHandler(  TemporalOperatorName.CONTAINS,                             FilterFactory::tcontains);
-        setCopyHandler(  TemporalOperatorName.DURING,                               FilterFactory::during);
-        setCopyHandler(  TemporalOperatorName.EQUALS,                               FilterFactory::tequals);
-        setCopyHandler(  TemporalOperatorName.OVERLAPS,                             FilterFactory::toverlaps);
-        setCopyHandler(  TemporalOperatorName.MEETS,                                FilterFactory::meets);
-        setCopyHandler(  TemporalOperatorName.ENDS,                                 FilterFactory::ends);
-        setCopyHandler(  TemporalOperatorName.OVERLAPPED_BY,                        FilterFactory::overlappedBy);
-        setCopyHandler(  TemporalOperatorName.MET_BY,                               FilterFactory::metBy);
-        setCopyHandler(  TemporalOperatorName.ENDED_BY,                             FilterFactory::endedBy);
-        setCopyHandler(  TemporalOperatorName.ANY_INTERACTS,                        FilterFactory::anyInteracts);
-        setCopyHandler(   SpatialOperatorName.BBOX,                                SpatialFactory::bbox);
-        setCopyHandler(   SpatialOperatorName.EQUALS,                               FilterFactory::equals);
-        setCopyHandler(   SpatialOperatorName.DISJOINT,                             FilterFactory::disjoint);
-        setCopyHandler(   SpatialOperatorName.INTERSECTS,                           FilterFactory::intersects);
-        setCopyHandler(   SpatialOperatorName.TOUCHES,                              FilterFactory::touches);
-        setCopyHandler(   SpatialOperatorName.CROSSES,                              FilterFactory::crosses);
-        setCopyHandler(   SpatialOperatorName.WITHIN,                               FilterFactory::within);
-        setCopyHandler(   SpatialOperatorName.CONTAINS,                             FilterFactory::contains);
-        setCopyHandler(   SpatialOperatorName.OVERLAPS,                             FilterFactory::overlaps);
-        setCopyHandler(  DistanceOperatorName.WITHIN,                               FilterFactory::within);
-        setCopyHandler(  DistanceOperatorName.BEYOND,                               FilterFactory::beyond);
-        setCopyHandler(   LogicalOperatorName.AND,                                  FilterFactory::and);
-        setCopyHandler(   LogicalOperatorName.OR,                                   FilterFactory::or);
-        setCopyHandler(   LogicalOperatorName.NOT,                                 LogicalFactory::not);
-        setCopyHandler(         FunctionNames.Add,                                  FilterFactory::add);
-        setCopyHandler(         FunctionNames.Subtract,                             FilterFactory::subtract);
-        setCopyHandler(         FunctionNames.Multiply,                             FilterFactory::multiply);
-        setCopyHandler(         FunctionNames.Divide,                               FilterFactory::divide);
-        /*
-         * Following are factory methods with different signatures, but where each signature appears only once.
-         * It is not worth to create e.g. a `TrinaryComparisonFactory` functional interface for only one method.
-         */
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN), (filter, accumulator) -> {
-            BetweenComparisonOperator<TR> target = null;
-            BetweenComparisonOperator<SR> source = (BetweenComparisonOperator<SR>) filter;
-            var exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 2) {
-                target = factory.between(exps.get(0), exps.get(1), exps.get(2));
-            }
-            accept(accumulator, source, target);
-        });
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE), (filter, accumulator) -> {
-            LikeOperator<TR> target = null;
-            LikeOperator<SR> source = (LikeOperator<SR>) filter;
-            var exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 2) {
-                final Expression<?,?> p2 = exps.get(1);
-                if (p2 instanceof Literal<?,?>) {
-                    final Object literal = ((Literal<?,?>) p2).getValue();
-                    if (literal instanceof String) {
-                        target = factory.like(exps.get(0), (String) literal, source.getWildCard(),
-                                source.getSingleChar(), source.getEscapeChar(), source.isMatchingCase());
-                    }
-                }
-            }
-            accept(accumulator, source, target);
-        });
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NIL), (filter, accumulator) -> {
-            NilOperator<TR> target = null;
-            NilOperator<SR> source = (NilOperator<SR>) filter;
-            var exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 1) {
-                target = factory.isNil(exps.get(0), source.getNilReason().orElse(null));
-            }
-            accept(accumulator, source, target);
-        });
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NULL), (filter, accumulator) -> {
-            NullOperator<TR> target = null;
-            NullOperator<SR> source = (NullOperator<SR>) filter;
-            var exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 1) {
-                target = factory.isNull(exps.get(0));
-            }
-            accept(accumulator, source, target);
-        });
-        setExpressionHandler(FunctionNames.Literal, (expression, accumulator) -> {
-            Literal<SR,?> source = (Literal<SR,?>) expression;
-            Literal<TR,?> target = factory.literal(source.getValue());
-            accept(accumulator, source, target);
-        });
-        setExpressionHandler(FunctionNames.ValueReference, (expression, accumulator) -> {
-            ValueReference<SR,?> source = (ValueReference<SR,?>) expression;
-            ValueReference<TR,?> target = factory.property(source.getXPath());
-            accept(accumulator, source, target);
-        });
-    }
-
-    /**
-     * Sets the action to execute for the given type of binary comparison operator.
-     * Example:
-     *
-     * {@snippet lang="java" :
-     *     setCopyHandler(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO, FilterFactory::equal);
-     * }
-     *
-     * @param  type    identification of the filter type.
-     * @param  action  the action to execute when the identified filter is found.
-     */
-    protected final void setCopyHandler(final ComparisonOperatorName type, final BinaryComparisonFactory<TR> action) {
-        setFilterHandler(type, (filter, accumulator) -> {
-            BinaryComparisonOperator<TR> target = null;
-            BinaryComparisonOperator<SR> source = (BinaryComparisonOperator<SR>) filter;
-            var exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 2) {
-                target = action.create(factory, exps.get(0), exps.get(1), source.isMatchingCase(), source.getMatchAction());
-            }
-            accept(accumulator, source, target);
-        });
-    }
-
-    /**
-     * Sets the action to execute for the given type of temporal operator.
-     * Example:
-     *
-     * {@snippet lang="java" :
-     *     setCopyHandler(TemporalOperatorName.AFTER, FilterFactory::after);
-     * }
-     *
-     * @param  type    identification of the filter type.
-     * @param  action  the action to execute when the identified filter is found.
-     */
-    protected final void setCopyHandler(final TemporalOperatorName type, final TemporalFactory<TR,T> action) {
-        setFilterHandler(type, (filter, accumulator) -> {
-            TemporalOperator<TR> target = null;
-            TemporalOperator<SR> source = (TemporalOperator<SR>) filter;
-            List<Expression<TR,T>> exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 2) {
-                target = action.create(factory, exps.get(0), exps.get(1));
-            }
-            accept(accumulator, source, target);
-        });
-    }
-
-    /**
-     * Sets the action to execute for the given type of spatial operator.
-     * Example:
-     *
-     * {@snippet lang="java" :
-     *     setCopyHandler(SpatialOperatorName.EQUALS, FilterFactory::equals);
-     * }
-     *
-     * @param  type    identification of the filter type.
-     * @param  action  the action to execute when the identified filter is found.
-     */
-    protected final void setCopyHandler(final SpatialOperatorName type, final SpatialFactory<TR,G> action) {
-        setFilterHandler(type, (filter, accumulator) -> {
-            BinarySpatialOperator<TR> target = null;
-            BinarySpatialOperator<SR> source = (BinarySpatialOperator<SR>) filter;
-            List<Expression<TR,G>> exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 2) {
-                target = action.create(factory, exps.get(0), exps.get(1));
-            }
-            accept(accumulator, source, target);
-        });
-    }
-
-    /**
-     * Sets the action to execute for the given type of distance operator.
-     * Example:
-     *
-     * {@snippet lang="java" :
-     *     setCopyHandler(DistanceOperatorName.BEYOND, FilterFactory::beyond);
-     * }
-     *
-     * @param  type    identification of the filter type.
-     * @param  action  the action to execute when the identified filter is found.
-     */
-    protected final void setCopyHandler(final DistanceOperatorName type, final DistanceFactory<TR,G> action) {
-        setFilterHandler(type, (filter, accumulator) -> {
-            DistanceOperator<TR> target = null;
-            DistanceOperator<SR> source = (DistanceOperator<SR>) filter;
-            List<Expression<TR,G>> exps = copyExpressions(source.getExpressions());
-            if (exps != null && exps.size() == 3) {
-                target = action.create(factory, exps.get(0), exps.get(1), source.getDistance());
-            }
-            accept(accumulator, source, target);
-        });
-    }
-
-    /**
-     * Sets the action to execute for the given type of logical operator.
-     * Example:
-     *
-     * {@snippet lang="java" :
-     *     setCopyHandler(LogicalOperatorName.AND, FilterFactory::and);
-     * }
-     *
-     * @param  type    identification of the filter type.
-     * @param  action  the action to execute when the identified filter is found.
-     */
-    protected final void setCopyHandler(final LogicalOperatorName type, final LogicalFactory<TR> action) {
-        setFilterHandler(type, (filter, accumulator) -> {
-            LogicalOperator<TR> target = null;
-            LogicalOperator<SR> source = (LogicalOperator<SR>) filter;
-            final var exps = copyFilters(source.getOperands());
-            if (exps != null) {
-                target = action.create(factory, exps);
-            }
-            accept(accumulator, source, target);
-        });
-    }
-
-    /**
-     * Sets the action to execute for the given function.
-     * Example:
-     *
-     * {@snippet lang="java" :
-     *     setCopyHandler("Multiply", FilterFactory::multiply);
-     * }
-     *
-     * @param  name    identification of the function.
-     * @param  action  the action to execute when the identified function is found.
-     */
-    protected final void setCopyHandler(final String name, final BinaryFunctionFactory<TR> action) {
-        setExpressionHandler(name, (source, accumulator) -> {
-            Expression<TR,?> target = null;
-            List<Expression<TR,Number>> exps = copyExpressions(source.getParameters());
-            if (exps != null) {
-                target = action.create(factory, exps.get(0), exps.get(1));
-            }
-            accept(accumulator, source, target);
-        });
-    }
-
-    /**
-     * Adds the target filter or expression in the list of copied elements.
-     *
-     * @param  accumulator  the list of copied elements where to add the {@code target} element.
-     * @param  source       the original filter or expression which has been copied.
-     * @param  target       the copied filter or expression, or {@code null} if the copy could not be done.
-     * @throws IllegalArgumentException if {@code source} copy was mandated and couldn't be done.
-     */
-    private void accept(final List<Object> accumulator, final Object source, Object target) {
-        if (target == null) {
-            if (forceNew) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.CanNotCopy_1, source));
-            }
-            target = source;
-        } else if (!forceUse && target.equals(source)) {
-            target = source;
-        }
-        accumulator.add(target);
-    }
-
-    /**
-     * Copies all expressions that are in the given list.
-     * The returned list has the same length as the given list.
-     * If all copied expressions are equal to the original expressions and {@link #forceNew} is {@code false},
-     * then this method returns {@code null} for telling that filter or expression does not need to be created.
-     *
-     * <h4>Note on parameterized types</h4>
-     * This method cannot guarantee that the elements in the returned list have really the parameterized types
-     * declared in this method signature. They <em>should be</em> if the {@link FilterFactory}, {@link Filter}
-     * and {@link Expression} methods invoked by the caller fulfill the API contract. For example the operands
-     * of an arithmetic function should have {@link Number} value. But we have no way to verify that.
-     *
-     * @param  <V>  expected type of values returned by the expressions. This type <em>is not verified</em>,
-     *         so this method is not really type-safe. This parameterized type is nevertheless used for more
-     *         convenient casts by the callers, but should not be used outside private methods.
-     * @param  source  the list of expressions to copy.
-     * @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<SR,?>> source) {
-        final List<Object> results = new ArrayList<>(source.size());
-        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;
-    }
-
-    /**
-     * Copies all filters that are in the given list.
-     * The returned list has the same length as the given list.
-     * If all copied filters are equal to the original filters and {@link #forceNew} is {@code false},
-     * then this method returns {@code null} for telling that filter does not need to be created.
-     *
-     * @param  source  the list of filters to copy.
-     * @return the copies filters, or {@code null} if no copy is needed.
-     */
-    @SuppressWarnings({"unchecked","rawtypes"})
-    private List<Filter<TR>> copyFilters(final List<Filter<SR>> source) {
-        final var results = new ArrayList<Object>(source.size());
-        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;
-    }
-
-    /**
-     * Copies the given filter using the factory specified at construction time.
-     *
-     * @param  source  the filter to copy.
-     * @return the copied filter.
-     * @throws IllegalArgumentException if the filter cannot be copied.
-     */
-    @SuppressWarnings("unchecked")
-    public Filter<TR> copy(final Filter<SR> source) {
-        final var accumulator = new ArrayList<Object>(1);
-        visit(source, accumulator);
-        switch (accumulator.size()) {
-            case 0:  return null;
-            case 1:  return (Filter<TR>) accumulator.get(0);
-            default: throw new AssertionError(accumulator);     // Should never happen.
-        }
-    }
-
-    /**
-     * Copies the given expression using the factory specified at construction time.
-     *
-     * @param  <V>     the type of values computed by the expression.
-     * @param  source  the expression to copy.
-     * @return the copied expression.
-     * @throws IllegalArgumentException if the expression cannot be copied.
-     */
-    @SuppressWarnings("unchecked")
-    public <V> Expression<TR,V> copy(final Expression<SR,V> source) {
-        final var accumulator = new ArrayList<Object>(1);
-        visit(source, accumulator);
-        switch (accumulator.size()) {
-            case 0:  return null;
-            case 1:  return (Expression<TR,V>) accumulator.get(0);
-            default: throw new AssertionError(accumulator);     // Should never happen.
-        }
-    }
-
-    /**
-     * Invoked when no copy operation is registered for the given filter.
-     * The default implementation throws an {@link IllegalArgumentException}.
-     *
-     * @param  type         the filter type which has not been found.
-     * @param  filter       the filter to copy.
-     * @param  accumulator  where to add filters.
-     * @throws IllegalArgumentException if a copy of the given filter was required by cannot be performed.
-     */
-    @Override
-    protected void typeNotFound(final CodeList<?> type, final Filter<SR> filter, final List<Object> accumulator) {
-        if (forceNew) {
-            throw new IllegalArgumentException(Resources.format(Resources.Keys.CanNotVisit_2, 0, type));
-        }
-        accumulator.add(filter);
-    }
-
-    /**
-     * Invoked when no copy operation is registered for the given expression.
-     * The default implementation creates a new function of the same name using the generic API.
-     *
-     * @param  name         the expression type which has not been found.
-     * @param  expression   the expression.
-     * @param  accumulator  where to add expressions.
-     * @throws IllegalArgumentException if an expression cannot be copied.
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    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));
-        }
-        accumulator.add(expression);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/EditVisitor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/EditVisitor.java
deleted file mode 100644
index 517cf55..0000000
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/EditVisitor.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter.privy;
-
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-
-
-/**
- * Visitor used to copy expressions and filters with same parameterized types.
- * This class can be used when some filters need to be recreated with the same types
- * but potentially different values. For example a change of {@link Literal} value
- * requires to recreate all parents in the filter graph.
- *
- * <h2>Partially implemented factory</h2>
- * {@code EditVisitor} relaxes the usual factory API contract by allowing unsupported factory
- * methods to return {@code null} instead of throwing an {@link UnsupportedOperationException}.
- * A null value is interpreted as an instruction to continue to use the old filter or expression,
- * without replacing it by a new instance created by the {@linkplain #factory}.
- * By contrast, an {@link UnsupportedOperationException} causes the copy operation to fail.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- *
- * @param  <R>  the type of resources expected by the filters and expressions.
- * @param  <G>  base class of geometry objects.
- * @param  <T>  base class of temporal objects.
- */
-public class EditVisitor<R,G,T> extends CopyVisitor<R,R,G,T> {
-    /**
-     * Creates a new edit visitor with given factory.
-     * If the {@code force} argument is {@code false}, then the factory is used for
-     * creating new filters and expressions only when at least one operand changed.
-     *
-     * @param  factory  the factory to use for creating the new filters and expressions.
-     * @param  force    whether to force new filters or expressions even when existing instances could be reused.
-     */
-    public EditVisitor(final FilterFactory<R,G,T> factory, final boolean force) {
-        super(factory, force, force);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/FunctionNames.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/FunctionNames.java
index 06be9b3..ad709c9 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/FunctionNames.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/FunctionNames.java
@@ -27,22 +27,22 @@
  * @author  Martin Desruisseaux (Geomatys)
  */
 public final class FunctionNames extends Static {
-    /** Value of {@link org.opengis.filter.NullOperator#getOperatorType()}. */
+    /** Value of {@code NullOperator.getOperatorType()}. */
     public static final String PROPERTY_IS_NULL = "PROPERTY_IS_NULL";
 
-    /** Value of {@link org.opengis.filter.NilOperator#getOperatorType()}. */
+    /** Value of {@code NilOperator.getOperatorType()}. */
     public static final String PROPERTY_IS_NIL = "PROPERTY_IS_NIL";
 
-    /** Value of {@link org.opengis.filter.LikeOperator#getOperatorType()}. */
+    /** Value of {@code LikeOperator.getOperatorType()}. */
     public static final String PROPERTY_IS_LIKE = "PROPERTY_IS_LIKE";
 
     /** Value of {@link org.opengis.filter.BetweenComparisonOperator#getOperatorType()}. */
     public static final String PROPERTY_IS_BETWEEN = "PROPERTY_IS_BETWEEN";
 
-    /** Value of {@link org.opengis.filter.Literal#getFunctionName()}. */
+    /** Value of {@code Literal.getFunctionName()}. */
     public static final String Literal = "Literal";
 
-    /** Value of {@link org.opengis.filter.ValueReference#getFunctionName()}. */
+    /** Value of {@code ValueReference.getFunctionName()}. */
     public static final String ValueReference = "ValueReference";
 
     /** The "Add" (+) arithmetic expression. */
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/SortByComparator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/SortByComparator.java
index 624e60d..9c1d3a5 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/SortByComparator.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/SortByComparator.java
@@ -25,10 +25,10 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SortBy;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SortBy;
+import org.apache.sis.pending.geoapi.filter.SortProperty;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java
index b314efe..f6b47d6 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java
@@ -23,15 +23,14 @@
 import java.util.function.BiConsumer;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.CodeList;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
-import org.opengis.filter.TemporalOperatorName;
-import org.opengis.filter.ComparisonOperatorName;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.LogicalOperatorName;
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
+import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
+import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
 
 
 /**
@@ -50,7 +49,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <A>  type of the accumulator object where actions will write their results.
  */
 public abstract class Visitor<R,A> {
@@ -58,9 +57,9 @@
      * All filters known to this visitor.
      * May contain an entry associated to the {@code null} key.
      *
-     * @see #setFilterHandler(CodeList, BiConsumer)
+     * @see #setFilterHandler(Enum, BiConsumer)
      */
-    private final Map<CodeList<?>, BiConsumer<Filter<R>,A>> filters;
+    private final Map<Enum<?>, BiConsumer<Filter<R>,A>> filters;
 
     /**
      * All expressions known to this visitor.
@@ -114,7 +113,7 @@
      * @param  type  identification of the filter type (can be {@code null}).
      * @return the action to execute when the identified filter is found, or {@code null} if none.
      */
-    protected final BiConsumer<Filter<R>,A> getFilterHandler(final CodeList<?> type) {
+    protected final BiConsumer<Filter<R>,A> getFilterHandler(final Enum<?> type) {
         return filters.get(type);
     }
 
@@ -138,7 +137,7 @@
      * @param  type    identification of the filter type (can be {@code null}).
      * @param  action  the action to execute when the identified filter is found.
      */
-    protected final void setFilterHandler(final CodeList<?> type, final BiConsumer<Filter<R>,A> action) {
+    protected final void setFilterHandler(final Enum<?> type, final BiConsumer<Filter<R>,A> action) {
         filters.put(type, action);
     }
 
@@ -149,8 +148,8 @@
      * @param  lastType  identification of the last filter type (inclusive).
      * @param  action    the action to execute when an identified filter is found.
      */
-    private void setFamilyHandlers(final CodeList<?> lastType, final BiConsumer<Filter<R>,A> action) {
-        for (final CodeList<?> type : lastType.family()) {
+    private void setFamilyHandlers(final Enum<?> lastType, final BiConsumer<Filter<R>,A> action) {
+        for (final Enum<?> type : lastType.getClass().getEnumConstants()) {
             filters.put(type, action);
             if (type == lastType) break;
         }
@@ -195,8 +194,8 @@
      * @param  action  the action to execute when one of the enumerated filters is found.
      */
     protected final void setNullAndNilHandlers(final BiConsumer<Filter<R>,A> action) {
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NULL), action);
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NIL),  action);
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NULL, action);
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NIL,  action);
     }
 
     /**
@@ -247,7 +246,7 @@
      *
      * @param  types  types of filters to remove.
      */
-    protected final void removeFilterHandlers(final Collection<? extends CodeList<?>> types) {
+    protected final void removeFilterHandlers(final Collection<? extends Enum<?>> types) {
         filters.keySet().removeAll(types);
     }
 
@@ -259,7 +258,7 @@
      * 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.
+     * {@link #setFilterHandler(Enum, 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 (Filter<R>) filter} cast is actually safe if the handlers do not invoke any
@@ -274,7 +273,7 @@
      * @throws UnsupportedOperationException if there is no action registered for the given filter.
      */
     public void visit(final Filter<R> filter, final A accumulator) {
-        final CodeList<?> type = (filter != null) ? filter.getOperatorType() : null;
+        final Enum<?> type = (filter != null) ? filter.getOperatorType() : null;
         final BiConsumer<Filter<R>, A> f = filters.get(type);
         if (f != null) {
             f.accept(filter, accumulator);
@@ -310,7 +309,7 @@
      * @param  accumulator  where to write the result of all actions.
      * @throws UnsupportedOperationException if there is no default action.
      */
-    protected void typeNotFound(final CodeList<?> type, final Filter<R> filter, final A accumulator) {
+    protected void typeNotFound(final Enum<?> type, final Filter<R> filter, final A accumulator) {
         throw new UnsupportedOperationException(Resources.format(Resources.Keys.CanNotVisit_2, 0, type));
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
deleted file mode 100644
index 263646d..0000000
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter.sqlmm;
-
-import java.util.List;
-import java.io.Serializable;
-import java.lang.reflect.Type;
-import org.opengis.util.TypeName;
-import org.opengis.util.LocalName;
-import org.opengis.parameter.ParameterValue;
-import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.filter.capability.AvailableFunction;
-import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.Utilities;
-import org.apache.sis.util.iso.Names;
-import org.apache.sis.geometry.wrapper.Geometries;
-import org.apache.sis.geometry.wrapper.GeometryType;
-import org.apache.sis.metadata.simple.SimpleIdentifiedObject;
-import org.apache.sis.parameter.DefaultParameterValue;
-import org.apache.sis.referencing.NamedIdentifier;
-
-
-/**
- * Description of a SQLMM function with its parameters.
- *
- * @todo Argument descriptions are incomplete. They have no good names,
- *       and the types are missing (they are {@code null}) except for geometry types.
- *
- * @author  Martin Desruisseaux (Geomatys)
- *
- * @see SQLMM#description(Geometries)
- */
-final class FunctionDescription implements AvailableFunction, Serializable {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = -264656649425360058L;
-
-    /**
-     * The name space for function descriptions.
-     */
-    private static final String NAMESPACE = "SQLMM";
-
-    /**
-     * The function name in SQLMM namespace.
-     *
-     * @see #getName()
-     */
-    @SuppressWarnings("serial")     // The implementation used here is serializable.
-    private final LocalName name;
-
-    /**
-     * The name and type of each argument expected by this function.
-     * This list is unmodifiable.
-     *
-     * @see #getArguments()
-     */
-    @SuppressWarnings("serial")     // The implementation used here is serializable.
-    private final List<ParameterDescriptor<?>> arguments;
-
-    /**
-     * The type of return value.
-     *
-     * @see #getReturnType()
-     */
-    @SuppressWarnings("serial")     // The implementation used here is serializable.
-    private final TypeName result;
-
-    /**
-     * Creates a new description for the given SQLMM function.
-     *
-     * @param  function  the SQLMM function for which to create a description.
-     * @param  library   the geometry library implementation in use.
-     */
-    FunctionDescription(final SQLMM function, final Geometries<?> library) {
-        name = Names.createLocalName(NAMESPACE, null, function.name());
-        final var args = new Argument<?>[function.maxParamCount];
-        for (int i=0; i<args.length; i++) {
-            final GeometryType gt;
-            switch (i) {
-                case 0:  gt = function.geometryType1; break;
-                case 1:  gt = function.geometryType2; break;
-                default: gt = null; break;
-            }
-            final TypeName type = (gt != null) ? gt.getTypeName(library) : null;
-            args[i] = new Argument<>("arg" + (i+1), type, Argument.getValueClass(type), true);
-        }
-        arguments = List.of(args);
-        result = function.getGeometryType().map((t) -> t.getTypeName(library))
-                         .orElseGet(() -> Names.createTypeName(function.getReturnType(library)));
-    }
-
-    /**
-     * Returns the function name in SQLMM namespace. This is the {@linkplain SQLMM#name() name}
-     * of the enumeration value, but wrapped in a {@link LocalName} with the "SQLMM" namespace.
-     *
-     * @return the function name.
-     */
-    @Override
-    public LocalName getName() {
-        return name;
-    }
-
-    /**
-     * Returns the name and type of each argument expected by this function.
-     *
-     * @return arguments that the function accepts.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public List<ParameterDescriptor<?>> getArguments() {
-        return arguments;
-    }
-
-    /**
-     * {@return the type of return value}.
-     */
-    @Override
-    public TypeName getReturnType() {
-        return result;
-    }
-
-    /**
-     * Description of an argument of a SQLMM function.
-     *
-     * @todo Argument names are not informative.
-     * @todo Argument types are unknown if not geometric.
-     *
-     * @param  <T>  the type of the argument value.
-     */
-    private static final class Argument<T> extends SimpleIdentifiedObject implements ParameterDescriptor<T> {
-        /**
-         * For cross-version compatibility.
-         */
-        private static final long serialVersionUID = 1607271450895713628L;
-
-        /**
-         * Java type of the argument value, or {@code Object.class} if unknown.
-         */
-        private final Class<T> valueClass;
-
-        /**
-         * The value type in OGC namespace, or {@code null} if unknown.
-         */
-        @SuppressWarnings("serial")     // Most Apache SIS implementations are serializable.
-        private final TypeName type;
-
-        /**
-         * Whether this argument is mandatory.
-         */
-        private final boolean mandatory;
-
-        /**
-         * Creates a new argument description.
-         *
-         * @param  name        name of the argument being created.
-         * @param  type        the {@code valueClass} name in OGC namespace, or {@code null} if unknown.
-         * @param  valueClass  Java type of the argument value, or {@code Object.class} if unknown.
-         * @param  mandatory   whether this argument is mandatory.
-         */
-        Argument(final String name, final TypeName type, final Class<T> valueClass, final boolean mandatory) {
-            this.name       = new NamedIdentifier(null, NAMESPACE, name, null, null);
-            this.type       = type;
-            this.valueClass = valueClass;
-            this.mandatory  = mandatory;
-        }
-
-        /**
-         * {@return the name of the type of the argument}.
-         */
-        @Override
-        public TypeName getValueType() {
-            return type;
-        }
-
-        /**
-         * Returns the Java type for the specified type name, or {@code Object.class} if none.
-         * This method is for computing the value returned by {@link #getValueClass()}.
-         * It cannot be inlined because of parameterized type.
-         *
-         * @param  type  type for which to get the Java class.
-         * @return the Java class for the specified type name.
-         */
-        static Class<?> getValueClass(final TypeName type) {
-            if (type != null) {
-                final Type t = type.toJavaType().orElse(null);
-                if (t instanceof Class<?>) {
-                    return (Class<?>) t;
-                }
-            }
-            return Object.class;
-        }
-
-        /**
-         * Returns the Java type of the argument value, or {@code Object.class} if none.
-         * This is the Java equivalent of {@link #getValueType()}.
-         *
-         * @return the Java type of the argument value.
-         */
-        @Override
-        public Class<T> getValueClass() {
-            return valueClass;
-        }
-
-        /**
-         * Creates a new instance of parameter value initialized with the default value.
-         *
-         * @return a new parameter value initialized to the default value.
-         */
-        @Override
-        public ParameterValue<T> createValue() {
-            return new DefaultParameterValue<>(this);
-        }
-
-        /**
-         * Returns the maximum number of times that values for this argument can be provided.
-         * This is 0 for optional argument and 1 for mandatory argument.
-         *
-         * @return the minimum occurrence.
-         */
-        @Override
-        public int getMinimumOccurs() {
-            return mandatory ? 1 : 0;
-        }
-
-        /**
-         * Returns the maximum number of times that values for this argument can be provided.
-         *
-         * @return the maximum occurrence.
-         */
-        @Override
-        public int getMaximumOccurs() {
-            return 1;
-        }
-
-        /**
-         * Tests whether the given object is equal to this argument description.
-         *
-         * @param  obj   the object to test for equality.
-         * @param  mode  the strictness level of the comparison.
-         * @return whether the given object describes the same argument as this.
-         */
-        @Override
-        public boolean equals(final Object obj, final ComparisonMode mode) {
-            if (obj == this) {
-                return true;
-            }
-            if (obj instanceof Argument) {
-                final var other = (Argument) obj;
-                return Utilities.deepEquals(name, other.name, mode)
-                    && Utilities.deepEquals(type, other.type, mode);
-            }
-            return false;
-        }
-
-        /**
-         * {@return a hash-code value for this argument description}.
-         */
-        @Override
-        public int hashCode() {
-            return name.hashCode() + type.hashCode();
-        }
-
-        /**
-         * {@return a string representation of this argument}.
-         * Current version includes the name and the type.
-         * Should be used only for debugging purposes.
-         */
-        @Override
-        public String toString() {
-            final var sb = new StringBuilder(20);
-            addType(sb.append(name.getCode()), type);
-            return sb.toString();
-        }
-    }
-
-    /**
-     * Appends the given type name if non-null.
-     *
-     * @param sb    where to append the type name.
-     * @param type  the type name to add, or {@code null} if none.
-     */
-    private static void addType(final StringBuilder sb, final TypeName type) {
-        if (type != null) {
-            sb.append(" : ").append(type);
-        }
-    }
-
-    /**
-     * Tests whether the given object is equal to this function description.
-     *
-     * @param  obj  the object to test for equality.
-     * @return whether the given object describes the same function as this.
-     */
-    @Override
-    public boolean equals(final Object obj) {
-        if (obj instanceof FunctionDescription) {
-            final var other = (FunctionDescription) obj;
-            return name.equals(other.name)
-                && result.equals(other.result)
-                && arguments.equals(other.arguments);
-        }
-        return false;
-    }
-
-    /**
-     * {@return a hash-code value for this function description}.
-     */
-    @Override
-    public int hashCode() {
-        return name.hashCode() + arguments.hashCode() + result.hashCode();
-    }
-
-    /**
-     * {@return a string representation of this function with its argument}.
-     * Should be used only for debugging purposes.
-     */
-    @Override
-    public String toString() {
-        final var sb = new StringBuilder(40).append(name).append('(');
-        boolean isMore = false;
-        for (final ParameterDescriptor<?> arg : getArguments()) {
-            if (isMore) sb.append(", ");
-            addType(sb.append(arg.getName().getCode()), arg.getValueType());
-            isMore = true;
-        }
-        addType(sb.append(')'), getReturnType());
-        return sb.toString();
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java
index 3e56cef..40728be 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java
@@ -30,11 +30,10 @@
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.Literal;
 
 
 /**
@@ -45,7 +44,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 abstract class FunctionWithSRID<R> extends SpatialFunction<R> {
     /**
@@ -115,7 +114,7 @@
                 if (literalCRS) try {
                     setTargetCRS(value);
                 } catch (FactoryException e) {
-                    throw new InvalidFilterValueException(e);
+                    throw new IllegalArgumentException(e);
                 }
             }
         } else {
@@ -221,7 +220,7 @@
      * @throws IllegalArgumentException if the given feature type does not contain the expected properties.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         final PropertyTypeBuilder pt = super.expectedType(valueType, addTo);
         if (pt instanceof AttributeTypeBuilder<?>) {
             // We must unconditionally override the CRS set by parent class.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
index c7f892b..0528807 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
@@ -24,9 +24,8 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -37,7 +36,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  */
 class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
@@ -124,7 +123,7 @@
             final Object   geometry = library.getGeometry(result);
             final Class<?> expected = operation.getReturnType(library);
             if (!expected.isInstance(geometry)) {
-                throw new InvalidFilterValueException(Errors.format(
+                throw new IllegalArgumentException(Errors.format(
                         Errors.Keys.IllegalArgumentClass_3, "geom", expected, Classes.getClass(geometry)));
             }
             if (srid != null) {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
index af5ac75..a5f3580 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
@@ -22,9 +22,8 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -35,7 +34,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  */
 abstract class GeometryParser<R,G> extends GeometryConstructor<R,G> {
@@ -98,7 +97,7 @@
                     case ST_BdPolyFromText:
                     case ST_BdMPolyFromWKB:
                     case ST_BdMPolyFromText: break;
-                    default: warning(new InvalidFilterValueException(Errors.format(
+                    default: warning(new IllegalArgumentException(Errors.format(
                                             Errors.Keys.IllegalArgumentClass_3, inputName(),
                                             getValueClass(),
                                             Classes.getClass(library.getGeometry(result)))), true);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
index 2ca9c78..e823758 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
@@ -20,8 +20,8 @@
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -32,7 +32,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 class OneGeometry<R> extends SpatialFunction<R> {
     /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
index 632b473..c2c749e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
@@ -22,9 +22,8 @@
 import org.apache.sis.filter.FunctionRegister;
 import org.apache.sis.pending.jdk.JDK16;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.capability.AvailableFunction;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -66,18 +65,6 @@
     }
 
     /**
-     * Describes the parameters of a function.
-     *
-     * @param  name  name of the function to describe.
-     * @return description of the function parameters.
-     * @throws IllegalArgumentException if function name is unknown..
-     */
-    @Override
-    public AvailableFunction describe(final String name) {
-        return SQLMM.valueOf(name).description(library);
-    }
-
-    /**
      * Create a new function of the given name with given parameters.
      * It is caller's responsibility to ensure that the given array is non-null,
      * has been cloned and does not contain null elements.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SQLMM.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SQLMM.java
index 748d06f..2283eb0 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SQLMM.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SQLMM.java
@@ -23,11 +23,8 @@
 import org.apache.sis.geometry.wrapper.GeometryType;
 import static org.apache.sis.geometry.wrapper.GeometryType.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.EnumMap;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.capability.AvailableFunction;
-import org.apache.sis.setup.GeometryLibrary;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 
 
 /**
@@ -787,14 +784,6 @@
     private final Object returnType;
 
     /**
-     * Description of this SQLMM function, created when first needed.
-     * The associated Java type depends on the geometry library.
-     *
-     * @see #description(Geometries)
-     */
-    private transient EnumMap<GeometryLibrary,AvailableFunction> descriptions;
-
-    /**
      * Creates a new enumeration value for an operation expecting exactly one geometry object
      * and no other argument.
      */
@@ -835,20 +824,6 @@
     }
 
     /**
-     * Returns a description of this SQLMM function.
-     * The Java types associated to arguments and return value depend on which geometry library is used.
-     *
-     * @param  library  the geometry library implementation to use.
-     * @return description of this SQLMM function.
-     */
-    public final synchronized AvailableFunction description(final Geometries<?> library) {
-        if (descriptions == null) {
-            descriptions = new EnumMap<>(GeometryLibrary.class);
-        }
-        return descriptions.computeIfAbsent(library.library, (key) -> new FunctionDescription(this, library));
-    }
-
-    /**
      * Returns the number of parameters that are geometry objects. Those parameters shall be first.
      * This value shall be between {@link #minParamCount} and {@link #maxParamCount}.
      *
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromBinary.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromBinary.java
index f8e6b47..b9bc122 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromBinary.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromBinary.java
@@ -20,8 +20,8 @@
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -30,7 +30,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  */
 final class ST_FromBinary<R,G> extends GeometryParser<R,G> {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromText.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromText.java
index ffb4df3..f409618 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromText.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_FromText.java
@@ -19,8 +19,8 @@
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -29,7 +29,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  */
 final class ST_FromText<R,G> extends GeometryParser<R,G> {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
index 9aec4e3..efe88a1 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
@@ -25,9 +25,8 @@
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -45,7 +44,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 final class ST_Point<R> extends FunctionWithSRID<R> {
     /**
@@ -116,13 +115,13 @@
     /**
      * Returns the numerical value evaluated by the expression at the given index.
      * If the value is {@code null}, then {@link Double#NaN} is returned.
-     * If the value is not a number, then an {@link InvalidFilterValueException} is thrown.
+     * If the value is not a number, then an {@link IllegalArgumentException} is thrown.
      *
      * @param  input  the object to be evaluated by the expression. Can be {@code null}.
      * @param  index  the parameter index.
      * @param  name   parameter name to report in exception message if the value is not a number.
      * @return the numerical value, or {@link Double#NaN} if the value was null.
-     * @throws InvalidFilterValueException if the value is not a number.
+     * @throws IllegalArgumentException if the value is not a number.
      */
     private double value(final R input, final int index, final String name) {
         final Object value = parameters[index].apply(input);
@@ -131,7 +130,7 @@
         } else if (value instanceof Number) {
             return ((Number) value).doubleValue();
         } else {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, name, Number.class, value.getClass()));
         }
     }
@@ -143,7 +142,7 @@
      *
      * @param  value  the WKB or WKT value to parse. Can be {@code null}.
      * @return the parsed point, or {@code null} if the given value is null.
-     * @throws InvalidFilterValueException if the value is not a string or byte array.
+     * @throws IllegalArgumentException if the value is not a string or byte array.
      * @throws Exception if parsing failed for another reason.
      */
     private GeometryWrapper parse(final Object value) throws Exception {
@@ -157,7 +156,7 @@
         } else if (value instanceof String) {
             point = library.parseWKT((String) value);
         } else {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, "wkt|wkb", String.class, value.getClass()));
         }
         final Object geometry = library.getGeometry(point);
@@ -165,7 +164,7 @@
             return point;
         } else {
             final String type = (value instanceof String) ? "wkt" : "wkb";
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, type, library.pointClass, point.getClass()));
         }
     }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
index 3d7b299..e3d151a 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
@@ -24,10 +24,9 @@
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.util.collection.BackingStoreException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.Literal;
 
 
 /**
@@ -77,7 +76,7 @@
      * Creates a new function with the given parameters. It is caller's responsibility to ensure
      * that the given array is non-null and does not contain null elements.
      *
-     * @throws InvalidFilterValueException if CRS cannot be constructed from the second expression.
+     * @throws IllegalArgumentException if CRS cannot be constructed from the second expression.
      */
     ST_Transform(final Expression<R,?>[] parameters, final Geometries<?> library) {
         super(SQLMM.ST_Transform, parameters, PRESENT);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
index c127135..f4f27cc 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
@@ -32,10 +32,9 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.Names;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -44,7 +43,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 abstract class SpatialFunction<R> extends Node implements FeatureExpression<R,Object>, Optimization.OnExpression<R,Object> {
     /**
@@ -184,12 +183,12 @@
      * @param  valueType  the type of features on which to apply this expression.
      * @param  addTo      where to add the type of properties evaluated by this expression.
      * @return builder of type resulting from expression evaluation (never null).
-     * @throws InvalidFilterValueException if the given feature type does not contain the expected properties,
+     * @throws IllegalArgumentException if the given feature type does not contain the expected properties,
      *         or if this method cannot determine the result type of the expression.
      *         It may be because that expression is backed by an unsupported implementation.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         AttributeTypeBuilder<?> att;
 cases:  if (operation.isGeometryInOut()) {
             final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(getParameters().get(0));
@@ -204,7 +203,7 @@
                     }
                 }
             }
-            throw new InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
+            throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
         } else {
             att = addTo.addAttribute(getValueClass());
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
index dcdf386..baa77fa 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
@@ -24,12 +24,11 @@
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -38,7 +37,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  */
 class TwoGeometries<R> extends SpatialFunction<R> {
     /**
@@ -78,7 +77,7 @@
      */
     @Override
     public Expression<R,?> optimize(final Optimization optimization) {
-        final FeatureType featureType = optimization.getFeatureType();
+        final DefaultFeatureType featureType = optimization.getFeatureType();
         if (featureType != null) {
             final Expression<R,?> p1 = unwrap(geometry1);
             if (p1 instanceof ValueReference<?,?> && unwrap(geometry2) instanceof Literal<?,?>) try {
@@ -96,7 +95,7 @@
                         }
                     }
                 }
-            } catch (PropertyNotFoundException | TransformException e) {
+            } catch (IllegalArgumentException | TransformException e) {
                 warning(e, true);
             }
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java
index a2289c7..dde8b5f 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java
@@ -328,8 +328,8 @@
         final Object geometry;
         final int n = point.getDimension();
         switch (n) {
-            case 2: geometry = createPoint(point.getCoordinate(0), point.getCoordinate(1)); break;
-            case 3: geometry = createPoint(point.getCoordinate(0), point.getCoordinate(1), point.getCoordinate(2)); break;
+            case 2: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1)); break;
+            case 3: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1), point.getOrdinate(2)); break;
             default: throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "point", (n <= 2) ? 2 : 3, n));
         }
         final GeometryWrapper wrapper = castOrWrap(geometry);
@@ -474,10 +474,10 @@
         } else {
             final DirectPosition lc = envelope.getLowerCorner();
             final DirectPosition uc = envelope.getUpperCorner();
-            xmin = lc.getCoordinate(xd);
-            ymin = lc.getCoordinate(yd);
-            xmax = uc.getCoordinate(xd);
-            ymax = uc.getCoordinate(yd);
+            xmin = lc.getOrdinate(xd);
+            ymin = lc.getOrdinate(yd);
+            xmax = uc.getOrdinate(xd);
+            ymax = uc.getOrdinate(yd);
         }
         final double[] coordinates;
         /*
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
index 39f0671..ac52762 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
@@ -28,8 +28,8 @@
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
 import org.opengis.util.FactoryException;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.filter.sqlmm.SQLMM;
@@ -40,14 +40,9 @@
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Set;
-import org.opengis.geometry.Boundary;
-import org.opengis.geometry.TransfiniteSet;
-import org.opengis.geometry.complex.Complex;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -115,7 +110,6 @@
      * @return the geometry CRS, or {@code null} if unknown.
      * @throws BackingStoreException if the CRS is defined by a SRID code and that code cannot be used.
      */
-    @Override
     public abstract CoordinateReferenceSystem getCoordinateReferenceSystem();
 
     /**
@@ -135,7 +129,6 @@
      *
      * @return the geometry envelope. Should never be {@code null}.
      */
-    @Override
     public abstract GeneralEnvelope getEnvelope();
 
     /**
@@ -145,7 +138,6 @@
      *
      * @todo Consider a {@code getCentroid2D()} method avoiding the cost of fetching the CRS.
      */
-    @Override
     public abstract DirectPosition getCentroid();
 
     /**
@@ -198,7 +190,7 @@
      * @param  context   the preferred CRS and other context to use if geometry transformations are needed.
      * @return result of applying the specified predicate.
      * @throws UnsupportedOperationException if the operation cannot be performed with current implementation.
-     * @throws InvalidFilterValueException if an error occurred while executing the operation on given geometries.
+     * @throws IllegalArgumentException if an error occurred while executing the operation on given geometries.
      */
     public final boolean predicate(final DistanceOperatorName type, final GeometryWrapper other,
                                    final Quantity<Length> distance, final SpatialOperationContext context)
@@ -214,7 +206,7 @@
                 return geometries[0].predicateSameCRS(type, geometries[1], dv);
             }
         } catch (FactoryException | TransformException | IncommensurableException e) {
-            throw new InvalidFilterValueException(e);
+            throw new IllegalArgumentException(e);
         }
         /*
          * No common CRS. Consider that we have no intersection, no overlap, etc.
@@ -235,7 +227,7 @@
      * @param  context  the preferred CRS and other context to use if geometry transformations are needed.
      * @return result of applying the specified predicate.
      * @throws UnsupportedOperationException if the operation cannot be performed with current implementation.
-     * @throws InvalidFilterValueException if an error occurred while executing the operation on given geometries.
+     * @throws IllegalArgumentException if an error occurred while executing the operation on given geometries.
      */
     public final boolean predicate(final SpatialOperatorName type, final GeometryWrapper other,
                                    final SpatialOperationContext context)
@@ -246,7 +238,7 @@
                 return geometries[0].predicateSameCRS(type, geometries[1]);
             }
         } catch (FactoryException | TransformException | IncommensurableException e) {
-            throw new InvalidFilterValueException(e);
+            throw new IllegalArgumentException(e);
         }
         /*
          * No common CRS. Consider that we have no intersection, no overlap, etc.
@@ -505,7 +497,6 @@
      *
      * @see #getCoordinateReferenceSystem()
      */
-    @Override
     public GeometryWrapper transform(final CoordinateReferenceSystem targetCRS) throws TransformException {
         if (targetCRS == null) {
             return this;
@@ -555,33 +546,6 @@
     }
 
     /**
-     * Methods from the {@link Geometry} interface. The {@link Override} annotation is intentionally omitted
-     * for reducing the risk of compilation failures during the upcoming revision of GeoAPI interfaces since
-     * some of those methods will be removed.
-     */
-    @Deprecated public final Geometry       getMbRegion()                             {throw new UnsupportedOperationException();}
-    @Deprecated public final DirectPosition getRepresentativePoint()                  {throw new UnsupportedOperationException();}
-    @Deprecated public final Boundary       getBoundary()                             {throw new UnsupportedOperationException();}
-    @Deprecated public final Complex        getClosure()                              {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        isSimple()                                {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        isCycle()                                 {throw new UnsupportedOperationException();}
-    @Deprecated public final double         distance(Geometry geometry)               {throw new UnsupportedOperationException();}
-    @Deprecated public final int            getDimension(DirectPosition point)        {throw new UnsupportedOperationException();}
-    @Deprecated public final int            getCoordinateDimension()                  {throw new UnsupportedOperationException();}
-    @Deprecated public final Set<Complex>   getMaximalComplex()                       {throw new UnsupportedOperationException();}
-    @Deprecated public final Geometry       getConvexHull()                           {throw new UnsupportedOperationException();}
-    @Deprecated public final Geometry       getBuffer(double distance)                {throw new UnsupportedOperationException();}
-    @Deprecated public final Geometry       clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();}
-    @Deprecated public final boolean        contains(TransfiniteSet pointSet)         {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        contains(DirectPosition point)            {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        intersects(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        equals(TransfiniteSet pointSet)           {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet union(TransfiniteSet pointSet)            {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet intersection(TransfiniteSet pointSet)     {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet difference(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet symmetricDifference(TransfiniteSet ps)    {throw new UnsupportedOperationException();}
-
-    /**
      * Returns {@code true} if the given object is a wrapper of the same class
      * and the wrapped geometry implementations are equal.
      *
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
index 0ef3b23..2f47a1c 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
@@ -49,9 +49,9 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -306,7 +306,6 @@
      * @throws TransformException if a coordinate conversion was required but failed.
      * @throws IncommensurableException if a coordinate system does not use the expected units.
      */
-    @SuppressWarnings("deprecation")
     private static CoordinateReferenceSystem usingSystemUnit(final GeometryWrapper           geometry,
                                                              final CoordinateReferenceSystem geometryCRS,
                                                                    CoordinateReferenceSystem targetCRS,
@@ -375,7 +374,6 @@
          * @throws TransformException if a coordinate conversion was required but failed.
          * @throws IncommensurableException if a coordinate system does not use the expected units.
          */
-        @SuppressWarnings("deprecation")
         ProjectedCRS create(final GeographicCRS baseCRS, DirectPosition centroid, CoordinateReferenceSystem geometryCRS)
                 throws FactoryException, TransformException, IncommensurableException
         {
@@ -403,7 +401,7 @@
             double latitude = Double.NaN, longitude = Double.NaN;
             for (int i=0; i<BIDIMENSIONAL; i++) {
                 final CoordinateSystemAxis axis = cs.getAxis(i);
-                double coordinate = centroid.getCoordinate(i);
+                double coordinate = centroid.getOrdinate(i);
                 coordinate = axis.getUnit().getConverterToAny(Units.DEGREE).convert(coordinate);
                 final AxisDirection direction = axis.getDirection();
                      if (direction == AxisDirection.NORTH) latitude  =  coordinate;
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java
index f04df85..1f01f6e 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java
@@ -44,8 +44,8 @@
 import org.apache.sis.filter.sqlmm.SQLMM;
 import org.apache.sis.util.Debug;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SpatialOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java
index 43fe93c..b0a7809 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java
@@ -32,8 +32,8 @@
 import org.apache.sis.filter.sqlmm.SQLMM;
 import org.apache.sis.util.Debug;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SpatialOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java
index 08bcc9e..facec27 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java
@@ -39,8 +39,8 @@
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Debug;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SpatialOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
index c2650f4..f6a92ba 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
@@ -45,8 +45,8 @@
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.GeneralDirectPosition;
@@ -62,9 +62,9 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.filter.sqlmm.SQLMM;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -722,7 +722,7 @@
              * We wrap that exception because `Geometry.transform(…)` does not declare `FactoryException`.
              * We may revisit in a future version if `Geometry.transform(…)` method declaration is updated.
              */
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java
index 7946d0c..7bca9db 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java
@@ -29,9 +29,6 @@
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
-
 
 /**
  * A pixel iterator reading values directly from a {@link DataBuffer} instead of using {@link Raster} API.
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Interpolation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Interpolation.java
index 196c1c2..bb109ee 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Interpolation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Interpolation.java
@@ -73,7 +73,7 @@
      *
      * Values in {@code source} buffer are always given with band index varying fastest, then column index,
      * then row index. Columns are traversed from left to right and rows are traversed from top to bottom
-     * ({@link org.opengis.coverage.grid.SequenceType#LINEAR} iteration order).
+     * ({@link org.apache.sis.image.SequenceType#LINEAR} iteration order).
      *
      * <p>The interpolation point is in the middle. For example if the {@linkplain #getSupportSize() support size}
      * is 4×4 pixels, then the interpolation point is the dot below and the fractional coordinates are relative to
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java
index 4c04121..f9c8ba8 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java
@@ -49,9 +49,6 @@
 import org.apache.sis.coverage.privy.ImageUtilities;
 import static org.apache.sis.pending.jdk.JDK18.ceilDiv;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
-
 
 /**
  * An iterator over sample values in a raster or an image.  This iterator makes easier to read and write efficiently
@@ -1094,7 +1091,7 @@
      * <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
      * Values are always stored with band index varying fastest, then column index, then row index.
      * Columns are traversed from left to right and rows are traversed from top to bottom
-     * ({@link SequenceType#LINEAR} iteration order).
+     * (linear iteration order).
      * That order is the same regardless the {@linkplain #getIterationOrder() iteration order} of this iterator.
      *
      * <div class="note"><b>Example:</b>
@@ -1115,7 +1112,7 @@
      * <h4>Usage example</h4>
      * following code creates an iterator over the full area of given image, then a window of 5×5 pixels.
      * The window is moved over all the image area in iteration order. Inside the window, data are copied
-     * in {@linkplain SequenceType#LINEAR linear order} regardless the iteration order.
+     * in linear order regardless the iteration order.
      *
      * {@snippet lang="java" :
      *     PixelIterator it = create(image, null, new Dimension(5, 5), null);     // Windows size will be 5×5 pixels.
@@ -1192,8 +1189,8 @@
          * capacity is <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
          * Values are always stored with band index varying fastest, then column index, then row index.
          * Columns are traversed from left to right and rows are traversed from top to bottom
-         * ({@link SequenceType#LINEAR} iteration order).
-         * That order is the same regardless the {@linkplain PixelIterator#getIterationOrder() iteration order}
+         * (linear iteration order).
+         * That order is the same regardless the iteration order
          * of enclosing iterator.
          *
          * <p>Every time that {@link #update()} is invoked, the buffer content is replaced by sample values
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/SequenceType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/SequenceType.java
new file mode 100644
index 0000000..eca9e32
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/SequenceType.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image;
+
+
+/**
+ * Specifies the order in which attribute value records are assigned to grid points.
+ * Placeholder for {@code org.opengis.coverage.grid.SequenceType}.
+ *
+ * <div class="note"><b>Upcoming API change:</b>
+ * this class may move to GeoAPI in a future version. If that move happens,
+ * the {@code org.apache.sis.image} package name would become {@code org.opengis.coverage}.</div>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ */
+public enum SequenceType {
+    /**
+     * Iterate consecutive grid points along complete grid lines.
+     */
+    LINEAR
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java
index 3c15235..1632d62 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java
@@ -25,9 +25,6 @@
 import java.awt.image.WritableRenderedImage;
 import org.apache.sis.feature.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
-
 
 /**
  * A pixel iterator capable to write sample values. This iterator can edit pixel values in place,
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/isoline/Isolines.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/isoline/Isolines.java
index cc85ca3..9822b13 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/isoline/Isolines.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/isoline/Isolines.java
@@ -37,8 +37,8 @@
 import static org.apache.sis.image.processing.isoline.Tracer.UPPER_RIGHT;
 import static org.apache.sis.image.processing.isoline.Tracer.LOWER_RIGHT;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
+// Specific to the main branch:
+import org.apache.sis.image.SequenceType;
 
 
 /**
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/BetweenComparisonOperator.java
similarity index 61%
copy from incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/BetweenComparisonOperator.java
index f35ee94..e59a407 100644
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/BetweenComparisonOperator.java
@@ -14,25 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.map.service;
+package org.apache.sis.pending.geoapi.filter;
+
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.Filter;
 
 
 /**
- * Exception that may be thrown by a portraying operation.
- *
- * @author Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public class RenderingException extends Exception {
-
-    public RenderingException(String message) {
-        super(message);
-    }
-
-    public RenderingException(Throwable cause) {
-        super(cause);
-    }
-
-    public RenderingException(String message, Throwable cause) {
-        super(message, cause);
-    }
+@SuppressWarnings("doclint:missing")
+public interface BetweenComparisonOperator<R> extends Filter<R> {
+    Expression<R,?> getExpression();
+    Expression<R,?> getLowerBoundary();
+    Expression<R,?> getUpperBoundary();
 }
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/BinaryComparisonOperator.java
similarity index 61%
copy from incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/BinaryComparisonOperator.java
index f35ee94..85df309 100644
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/BinaryComparisonOperator.java
@@ -14,25 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.map.service;
+package org.apache.sis.pending.geoapi.filter;
+
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.Filter;
 
 
 /**
- * Exception that may be thrown by a portraying operation.
- *
- * @author Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public class RenderingException extends Exception {
-
-    public RenderingException(String message) {
-        super(message);
-    }
-
-    public RenderingException(Throwable cause) {
-        super(cause);
-    }
-
-    public RenderingException(String message, Throwable cause) {
-        super(message, cause);
-    }
+@SuppressWarnings("doclint:missing")
+public interface BinaryComparisonOperator<R> extends Filter<R> {
+    Expression<R,?> getOperand1();
+    Expression<R,?> getOperand2();
+    boolean isMatchingCase();
+    MatchAction getMatchAction();
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/ComparisonOperatorName.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/ComparisonOperatorName.java
new file mode 100644
index 0000000..5936152
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/ComparisonOperatorName.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.filter;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+@SuppressWarnings("doclint:missing")
+public enum ComparisonOperatorName {
+    PROPERTY_IS_EQUAL_TO,
+    PROPERTY_IS_NOT_EQUAL_TO,
+    PROPERTY_IS_LESS_THAN,
+    PROPERTY_IS_GREATER_THAN,
+    PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,
+    PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO,
+    PROPERTY_IS_BETWEEN,
+    PROPERTY_IS_LIKE,
+    PROPERTY_IS_NULL,
+    PROPERTY_IS_NIL;
+}
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/DistanceOperatorName.java
similarity index 73%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/DistanceOperatorName.java
index 4307e9c..843efbd 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/DistanceOperatorName.java
@@ -14,14 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.pending.geoapi.filter;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+@SuppressWarnings("doclint:missing")
+public enum DistanceOperatorName {
+    BEYOND, WITHIN;
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/FilterExpressions.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/FilterExpressions.java
new file mode 100644
index 0000000..4cbbb41
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/FilterExpressions.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.filter;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.AbstractList;
+import java.util.Locale;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.util.iso.Names;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+@SuppressWarnings("doclint:missing")
+final class FilterExpressions<R> extends AbstractList<Expression<R,?>> {
+    private final List<Filter<R>> filters;
+
+    FilterExpressions(final List<Filter<R>> filters) {
+        this.filters = Objects.requireNonNull(filters);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return filters.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return filters.size();
+    }
+
+    @Override
+    public Expression<R,?> get(final int index) {
+        return new Element<>(filters.get(index));
+    }
+
+    private static final class Element<R> implements Expression<R,Boolean> {
+        private final Filter<R> filter;
+
+        Element(final Filter<R> filter) {
+            this.filter = Objects.requireNonNull(filter);
+        }
+
+        @Override
+        public ScopedName getFunctionName() {
+            final Enum<?> type = filter.getOperatorType();
+            final String identifier = type.name().toLowerCase(Locale.US);
+            if (identifier != null) {
+                return Names.createScopedName(Name.STANDARD, null, identifier);
+            } else {
+                return Names.createScopedName(Name.EXTENSION, null, type.name());
+            }
+        }
+
+        @Override
+        public Class<? super R> getResourceClass() {
+            return filter.getResourceClass();
+        }
+
+        @Override
+        public List<Expression<R,?>> getParameters() {
+            return filter.getExpressions();
+        }
+
+        @Override
+        public Boolean apply(final R input) {
+            return filter.test(input);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <N> Expression<R,N> toValueType(final Class<N> type) {
+            if (type.isAssignableFrom(Boolean.class)) return (Expression<R,N>) this;
+            else throw new ClassCastException();
+        }
+
+        @Override
+        public int hashCode() {
+            return ~filter.hashCode();
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            return (obj instanceof Element) && filter.equals(((Element) obj).filter);
+        }
+
+        @Override
+        public String toString() {
+            return "Expression[" + filter.toString() + ']';
+        }
+    }
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/Literal.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/Literal.java
new file mode 100644
index 0000000..de447db
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/Literal.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.filter;
+
+import java.util.List;
+import java.util.Collections;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Expression;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+@SuppressWarnings("doclint:missing")
+public interface Literal<R,V> extends Expression<R,V> {
+    @Override
+    default ScopedName getFunctionName() {
+        return Name.LITERAL;
+    }
+
+    @Override
+    default List<Expression<R,?>> getParameters() {
+        return Collections.emptyList();
+    }
+
+    V getValue();
+}
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/LogicalOperator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/LogicalOperator.java
new file mode 100644
index 0000000..9ecd8f7
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/LogicalOperator.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.filter;
+
+import java.util.List;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+@SuppressWarnings("doclint:missing")
+public interface LogicalOperator<R> extends Filter<R> {
+    @Override
+    LogicalOperatorName getOperatorType();
+
+    @Override
+    default List<Expression<R,?>> getExpressions() {
+        return new FilterExpressions<>(getOperands());
+    }
+
+    List<Filter<R>> getOperands();
+}
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/LogicalOperatorName.java
similarity index 73%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/LogicalOperatorName.java
index 4307e9c..8cda37d 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/LogicalOperatorName.java
@@ -14,14 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.pending.geoapi.filter;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+@SuppressWarnings("doclint:missing")
+public enum LogicalOperatorName {
+    AND, OR, NOT;
 }
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/MatchAction.java
similarity index 73%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/MatchAction.java
index 4307e9c..bb48f8e 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/MatchAction.java
@@ -14,14 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.pending.geoapi.filter;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+@SuppressWarnings("doclint:missing")
+public enum MatchAction {
+    ANY, ALL, ONE;
 }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/Name.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/Name.java
new file mode 100644
index 0000000..a2d6c7c
--- /dev/null
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/Name.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.filter;
+
+import org.opengis.util.LocalName;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.privy.FunctionNames;
+import org.apache.sis.util.iso.Names;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+@SuppressWarnings("doclint:missing")
+public final class Name {
+    static final LocalName STANDARD = Names.createLocalName(null, null, "fes");
+
+    static final LocalName EXTENSION = Names.createLocalName(null, null, "extension");
+
+    public static final ScopedName LITERAL = Names.createScopedName(STANDARD, null, FunctionNames.Literal);
+
+    public static final ScopedName VALUE_REFERENCE = Names.createScopedName(STANDARD, null, FunctionNames.ValueReference);
+
+    private Name() {
+    }
+}
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortBy.java
similarity index 67%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortBy.java
index 4307e9c..acda9ca 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortBy.java
@@ -14,14 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.pending.geoapi.filter;
+
+import java.util.List;
+import java.util.Comparator;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+@SuppressWarnings("doclint:missing")
+public interface SortBy<R> extends Comparator<R> {
+    List<SortProperty<R>> getSortProperties();
 }
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortOrder.java
similarity index 64%
rename from incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
rename to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortOrder.java
index f35ee94..6ed09b0 100644
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortOrder.java
@@ -14,25 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.map.service;
+package org.apache.sis.pending.geoapi.filter;
 
 
 /**
- * Exception that may be thrown by a portraying operation.
- *
- * @author Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public class RenderingException extends Exception {
+@SuppressWarnings("doclint:missing")
+public enum SortOrder {
+    ASCENDING("ASC"), DESCENDING("DESC");
 
-    public RenderingException(String message) {
-        super(message);
+    private final String sql;
+
+    private SortOrder(final String sql) {
+        this.sql = sql;
     }
 
-    public RenderingException(Throwable cause) {
-        super(cause);
-    }
-
-    public RenderingException(String message, Throwable cause) {
-        super(message, cause);
+    public String toSQL() {
+        return sql;
     }
 }
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortProperty.java
similarity index 66%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortProperty.java
index 4307e9c..6927421 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SortProperty.java
@@ -14,14 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.pending.geoapi.filter;
+
+import java.util.Comparator;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+@SuppressWarnings("doclint:missing")
+public interface SortProperty<R> extends Comparator<R> {
+    ValueReference<R,?> getValueReference();
+
+    SortOrder getSortOrder();
 }
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SpatialOperatorName.java
similarity index 69%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SpatialOperatorName.java
index 4307e9c..a9fbf5c 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/SpatialOperatorName.java
@@ -14,14 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.pending.geoapi.filter;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+@SuppressWarnings("doclint:missing")
+public enum SpatialOperatorName {
+    BBOX, EQUALS, DISJOINT, INTERSECTS, TOUCHES, CROSSES, WITHIN, CONTAINS, OVERLAPS;
 }
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/TemporalOperatorName.java
similarity index 62%
copy from incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/TemporalOperatorName.java
index f35ee94..a301355 100644
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/TemporalOperatorName.java
@@ -14,25 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.map.service;
+package org.apache.sis.pending.geoapi.filter;
 
 
 /**
- * Exception that may be thrown by a portraying operation.
- *
- * @author Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public class RenderingException extends Exception {
+@SuppressWarnings("doclint:missing")
+public enum TemporalOperatorName {
+    AFTER, BEFORE, BEGINS, BEGUN_BY, CONTAINS, DURING, EQUALS, OVERLAPS, MEETS, ENDS,
+    OVERLAPPED_BY, MET_BY, ENDED_BY, ANY_INTERACTS;
 
-    public RenderingException(String message) {
-        super(message);
-    }
-
-    public RenderingException(Throwable cause) {
-        super(cause);
-    }
-
-    public RenderingException(String message, Throwable cause) {
-        super(message, cause);
+    public String identifier() {
+        return name().toLowerCase();
     }
 }
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/ValueReference.java
similarity index 68%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/ValueReference.java
index 4307e9c..868ac52 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/ValueReference.java
@@ -14,14 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.pending.geoapi.filter;
+
+import org.apache.sis.filter.Expression;
+
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+@SuppressWarnings("doclint:missing")
+public interface ValueReference<R,V> extends Expression<R,V> {
+    String getXPath();
 }
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/package-info.java
similarity index 79%
rename from incubator/src/org.apache.sis.cql/main/module-info.java
rename to endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/package-info.java
index 4307e9c..4b7b314 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter/package-info.java
@@ -16,12 +16,7 @@
  */
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
-}
+package org.apache.sis.pending.geoapi.filter;
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionAppenderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionAppenderTest.java
index 9c24413..ca5f3c2 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionAppenderTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionAppenderTest.java
@@ -34,8 +34,8 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java
index 4376261..e2c41ac 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java
@@ -34,8 +34,8 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -126,7 +126,7 @@
      * @param target     expected reduced coordinates.
      */
     private static void testPosition(final DimensionalityReduction reduction, double[] source, double[] target) {
-        assertArrayEquals(target, reduction.apply(new DirectPositionView.Double(source)).getCoordinates());
+        assertArrayEquals(target, reduction.apply(new DirectPositionView.Double(source)).getCoordinate());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverage2DTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index 08f8dcf..6354999 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverage2DTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -43,10 +43,9 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.feature.Assertions.assertPixelsEqual;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.PointOutsideCoverageException;
-import static org.opengis.test.Assertions.assertSampleValuesEqual;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -292,7 +291,7 @@
         assertEquals(2,         result.getMinY());
         assertEquals(GRID_SIZE, result.getWidth());
         assertEquals(GRID_SIZE, result.getHeight());
-        assertSampleValuesEqual(coverage.render(null), result, STRICT, null);
+        assertPixelsEqual(coverage.render(null), null, result, null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
index 968e266..8fa3634 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -48,9 +48,9 @@
 import org.apache.sis.referencing.operation.HardCodedConversions;
 import static org.apache.sis.referencing.Assertions.assertEnvelopeEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertBetween;
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertBetween;
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -615,7 +615,7 @@
                 .build();
 
         // Build expected grid point focused after slicing. We expect it to be upper corner.
-        expectedGridPoint = DoubleStream.of(grid3d.getUpperCorner().getCoordinates())
+        expectedGridPoint = DoubleStream.of(grid3d.getUpperCorner().getCoordinate())
                 .mapToLong(value -> (long) value)
                 .map(exclusiveValue -> exclusiveValue - 1)        // Exclusive to inclusive.
                 .toArray();
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java
index 31e4661..00e1187 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -45,10 +45,10 @@
 import static org.apache.sis.test.Assertions.assertMultilinesEquals;
 import static org.apache.sis.referencing.Assertions.assertEnvelopeEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.PointOutsideCoverageException;
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import org.apache.sis.coverage.PointOutsideCoverageException;
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
index d59e9a0..603e2dd 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -42,8 +42,8 @@
 import org.apache.sis.referencing.operation.HardCodedConversions;
 import static org.apache.sis.referencing.Assertions.assertEnvelopeEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/PixelTranslationTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/PixelTranslationTest.java
index 28c184f..3235bc1 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/PixelTranslationTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/PixelTranslationTest.java
@@ -28,8 +28,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index 76e85b9..9e0f328 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -59,9 +59,8 @@
 import static org.apache.sis.feature.Assertions.assertValuesEqual;
 import static org.apache.sis.feature.Assertions.assertPixelsEqual;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertSampleValuesEqual;
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -404,7 +403,7 @@
                 new AxisDirection[] {AxisDirection.FUTURE, AxisDirection.EAST, AxisDirection.NORTH, AxisDirection.UP},
                 "Expected (t,λ,φ,H) axes.");
 
-        assertSampleValuesEqual(source.render(null), result.render(null), STRICT, null);
+        assertPixelsEqual(source.render(null), null, result.render(null), null);
     }
 
     /**
@@ -544,7 +543,7 @@
          * Target image should be 6×6 pixels, like source image.
          */
         final GridCoverage result = resample(source, targetGeom);
-        assertSampleValuesEqual(source.render(null), result.render(null), STRICT, null);
+        assertPixelsEqual(source.render(null), null, result.render(null), null);
     }
 
     /**
@@ -560,7 +559,7 @@
         final GridGeometry target4D = createGridGeometryND(HardCodedCRS.WGS84_4D, 0, 1, 2, 3, false);
         final GridCoverage result   = resample(source3D, target4D);
         assertEquals(target4D, result.getGridGeometry());
-        assertSampleValuesEqual(source3D.render(null), result.render(null), STRICT, null);
+        assertPixelsEqual(source3D.render(null), null, result.render(null), null);
     }
 
     /**
@@ -574,7 +573,7 @@
         final GridCoverage source4D = createCoverageND(true);
         final GridCoverage result   = resample(source4D, target3D);
         assertEquals(target3D, result.getGridGeometry());
-        assertSampleValuesEqual(source4D.render(null), result.render(null), STRICT, null);
+        assertPixelsEqual(source4D.render(null), null, result.render(null), null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java
index 637b6a1..c1caf4d 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java
@@ -23,12 +23,6 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests some default method implementations provided in {@link AbstractFeature}.
@@ -67,8 +61,8 @@
          */
         CustomFeature(final DefaultFeatureType type) {
             super(type);
-            for (final PropertyType property : type.getProperties(true)) {
-                if (property instanceof AttributeType<?> attribute) {
+            for (final AbstractIdentifiedType property : type.getProperties(true)) {
+                if (property instanceof DefaultAttributeType<?> attribute) {
                     Object value = attribute.getDefaultValue();
                     if (isMultiValued(property)) {
                         value = new ArrayList<>(PropertyView.singletonOrEmpty(value));
@@ -83,8 +77,8 @@
         /**
          * Returns {@code true} if the given property can contains more than one value.
          */
-        private static boolean isMultiValued(final PropertyType pt) {
-            return (pt instanceof AttributeType<?>) && (((AttributeType<?>) pt).getMaximumOccurs() > 1);
+        private static boolean isMultiValued(final AbstractIdentifiedType pt) {
+            return (pt instanceof DefaultAttributeType<?>) && (((DefaultAttributeType<?>) pt).getMaximumOccurs() > 1);
         }
 
         /**
@@ -122,7 +116,7 @@
             }
             if (value != null) {
                 final Class<?> base;
-                if (property instanceof AttributeType<?> attribute) {
+                if (property instanceof DefaultAttributeType<?> attribute) {
                     base = attribute.getValueClass();
                 } else {
                     base = FeatureType.class;
@@ -158,10 +152,7 @@
      */
     @Override
     boolean assertSameProperty(final String name, final Property expected, final boolean modified) {
-        final var actual = feature.getProperty(name);
-        if ((expected instanceof PropertyView) == (actual instanceof PropertyView)) {
-            assertEquals(expected, actual, name);
-        }
+        final var actual = (Property) feature.getProperty(name);
         assertSame(expected.getName(), actual.getName(), "name");
         if (!modified) {
             assertSame(expected.getValue(), actual.getValue(), "value");
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/Assertions.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/Assertions.java
index 20545ef..12a634a 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/Assertions.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/Assertions.java
@@ -26,8 +26,9 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
+// Specific to the main branch:
+import java.awt.geom.PathIterator;
+import org.apache.sis.image.SequenceType;
 
 
 /**
@@ -169,4 +170,38 @@
                 + "— matrix indices (" + i + ", " + j + ") band " + band
                 + ": expected " + expected + " but found " + actual;
     }
+
+    /**
+     * Asserts that the path is equal to given reference.
+     *
+     * @param  expected   expected geometry outline.
+     * @param  actual     actual geometry outline.
+     * @param  tolerance  tolerance threshold for floating point value comparisons.
+     */
+    @SuppressWarnings("fallthrough")
+    public static void assertPathEquals(final PathIterator expected, final PathIterator actual, final double tolerance) {
+        assertEquals(expected.getWindingRule(), actual.getWindingRule(), "windingRule");
+        final double[] buffer = new double[6];
+        final double[] values = new double[6];
+        while (!expected.isDone()) {
+            assertFalse(actual.isDone(), "isDone");
+            final int type = expected.currentSegment(buffer);
+            assertEquals(type, actual.currentSegment(values), "currentSegment");
+            switch (type) {
+                case PathIterator.SEG_CUBICTO: assertEquals(buffer[4], values[4], tolerance, "x₃");
+                                               assertEquals(buffer[5], values[5], tolerance, "y₃");
+                case PathIterator.SEG_QUADTO:  assertEquals(buffer[2], values[2], tolerance, "x₂");
+                                               assertEquals(buffer[3], values[3], tolerance, "y₂");
+                case PathIterator.SEG_LINETO:  assertEquals(buffer[0], values[0], tolerance, "x₁");
+                                               assertEquals(buffer[1], values[1], tolerance, "y₁"); break;
+                case PathIterator.SEG_MOVETO:  assertEquals(buffer[0], values[0], tolerance, "x₀");
+                                               assertEquals(buffer[1], values[1], tolerance, "y₀");
+                case PathIterator.SEG_CLOSE:   break;
+                default: fail("Unexpected type: " + type);
+            }
+            expected.next();
+            actual.next();
+        }
+        assertTrue(actual.isDone(), "isDone");
+    }
 }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicMapTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicMapTest.java
index 2ff66c6..b663148 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicMapTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicMapTest.java
@@ -27,9 +27,6 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-
 
 /**
  * Tests {@link CharacteristicMap} indirectly, through {@link AbstractAttribute} construction.
@@ -64,7 +61,7 @@
     }
 
     /**
-     * Tests adding explicitly a characteristic with {@link CharacteristicMap#put(String, Attribute)}.
+     * Tests adding explicitly a characteristic with {@code CharacteristicMap.put(String, AbstractAttribute)}.
      */
     @Test
     public void testPut() {
@@ -140,7 +137,7 @@
     }
 
     /**
-     * Tests adding a characteristic indirectly with {@link CharacteristicMap#addValue(Attribute)}.
+     * Tests adding a characteristic indirectly with {@code CharacteristicMap.addValue(AbstractAttribute)}.
      */
     @Test
     public void testAddValue() {
@@ -196,7 +193,7 @@
      */
     @Test
     public void testAddKey() {
-        final Attribute<?> units, accuracy;
+        final AbstractAttribute<?> units, accuracy;
         final AbstractAttribute<?> temperature = temperature();
         final var characteristics = temperature.characteristics();
         final Collection<String> keys = characteristics.keySet();
@@ -251,8 +248,8 @@
      * @param  accuracy         the second expected value in iteration order.
      * @param  characteristics  the map to verify.
      */
-    private static void assertEntriesEqual(final Attribute<?> units, final Attribute<?> accuracy,
-            final Map<String,Attribute<?>> characteristics)
+    private static void assertEntriesEqual(final AbstractAttribute<?> units, final AbstractAttribute<?> accuracy,
+            final Map<String,AbstractAttribute<?>> characteristics)
     {
         assertArrayEquals(new String[] {"accuracy", "units"}, characteristics.keySet().toArray());
         assertArrayEquals(new Object[] { accuracy ,  units }, characteristics.values().toArray());
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicTypeMapTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicTypeMapTest.java
index 9fb94bd..7e8300f 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicTypeMapTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CharacteristicTypeMapTest.java
@@ -27,9 +27,6 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Tests {@link CharacteristicTypeMap} indirectly, through {@link DefaultAttributeType} construction.
@@ -77,7 +74,7 @@
      */
     @Test
     public void testMapMethods() {
-        final AttributeType<?> units, accuracy;
+        final DefaultAttributeType<?> units, accuracy;
         final DefaultAttributeType<Float> temperature = temperature();
         final var characteristics = temperature.characteristics();
 
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CustomAttribute.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CustomAttribute.java
index 9cbb0d6..2ebf0f1 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CustomAttribute.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/CustomAttribute.java
@@ -24,9 +24,6 @@
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.referencing.NamedIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-
 
 /**
  * For testing {@link AbstractAttribute} customization.
@@ -49,7 +46,7 @@
     /**
      * Creates a new attribute.
      */
-    public CustomAttribute(final AttributeType<V> type) {
+    public CustomAttribute(final DefaultAttributeType<V> type) {
         super(type);
         value = type.getDefaultValue();
     }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAssociationRoleTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAssociationRoleTest.java
index 3f63365..5c024fb 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAssociationRoleTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAssociationRoleTest.java
@@ -29,10 +29,6 @@
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Tests {@link DefaultAssociationRole}.
@@ -88,10 +84,10 @@
      * @return the feature type to use for testing purpose.
      */
     private static DefaultFeatureType createType(final Object name,
-            final FeatureType parent, final FeatureAssociationRole... property)
+            final DefaultFeatureType parent, final DefaultAssociationRole... property)
     {
         return new DefaultFeatureType(Map.of(NAME_KEY, name),
-                false, new FeatureType[] {parent}, property);
+                false, new DefaultFeatureType[] {parent}, property);
     }
 
     /**
@@ -104,7 +100,7 @@
     }
 
     /**
-     * Tests {@link DefaultAssociationRole#getTitleProperty(FeatureAssociationRole)}.
+     * Tests {@code DefaultAssociationRole.getTitleProperty(FeatureAssociationRole)}.
      */
     @Test
     public void testGetTitleProperty() {
@@ -127,7 +123,7 @@
     @Test
     public void testBidirectionalAssociation() {
         final DefaultFeatureType twinTown = twinTownCity(true);
-        final var association = assertInstanceOf(FeatureAssociationRole.class, twinTown.getProperty("twin town"));
+        final var association = (DefaultAssociationRole) twinTown.getProperty("twin town");
         assertSame(twinTown, association.getValueType());
         /*
          * Creates a FeatureType copy containing the same properties. Used for verifying
@@ -142,7 +138,7 @@
     }
 
     /**
-     * Tests {@link DefaultFeatureType#isAssignableFrom(FeatureType)} and {@link DefaultFeatureType#equals(Object)}
+     * Tests {@code DefaultFeatureType.isAssignableFrom(FeatureType)} and {@code DefaultFeatureType.equals(Object)}
      * on a feature type having a bidirectional association to another feature. This test will fall in an infinite
      * loop if the implementation does not have proper guard against infinite recursivity.
      */
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultFeatureTypeTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultFeatureTypeTest.java
index a27dc36..1d79073 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultFeatureTypeTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultFeatureTypeTest.java
@@ -30,10 +30,6 @@
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Tests {@link DefaultFeatureType}.
@@ -203,7 +199,7 @@
             final String... expected)
     {
         int index = 0;
-        for (final PropertyType property : feature.getProperties(includeSuperTypes)) {
+        for (final AbstractIdentifiedType property : feature.getProperties(includeSuperTypes)) {
             assertTrue(index < expected.length, "Found more properties than expected.");
             final String name = expected[index++];
             assertNotNull(property, name);
@@ -432,7 +428,7 @@
         assertPropertiesEquals(metroCapital, false, "country");
         assertPropertiesEquals(metroCapital, true, "city", "population", "region", "isGlobal", "parliament", "country");
         assertEquals(CharSequence.class,
-                assertInstanceOf(AttributeType.class, metroCapital.getProperty("region")).getValueClass());
+                assertInstanceOf(DefaultAttributeType.class, metroCapital.getProperty("region")).getValueClass());
 
         // Check based only on name.
         assertTrue (DefaultFeatureType.maybeAssignableFrom(capital, metroCapital));
@@ -473,7 +469,7 @@
         assertPropertiesEquals(worldMetropolis, false, "region", "temperature");
         assertPropertiesEquals(worldMetropolis, true, "city", "population", "region", "isGlobal", "universities", "temperature");
         assertEquals(InternationalString.class,
-                assertInstanceOf(AttributeType.class, worldMetropolis.getProperty("region")).getValueClass());
+                assertInstanceOf(DefaultAttributeType.class, worldMetropolis.getProperty("region")).getValueClass());
 
         // Check based only on name.
         assertTrue (DefaultFeatureType.maybeAssignableFrom(metropolis, worldMetropolis));
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/EnvelopeOperationTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/EnvelopeOperationTest.java
index 8e17de1..ed6ecb5 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/EnvelopeOperationTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/EnvelopeOperationTest.java
@@ -37,11 +37,6 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.referencing.Assertions.assertEnvelopeEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Tests {@link EnvelopeOperation}.
@@ -62,12 +57,12 @@
      * @see #initialize()
      * @see #initialize(CoordinateReferenceSystem, boolean, CoordinateReferenceSystem, boolean)
      */
-    private FeatureType type;
+    private DefaultFeatureType type;
 
     /**
      * The feature created by a test method. Saved for allowing additional checks or operations.
      */
-    private Feature feature;
+    private AbstractFeature feature;
 
     /**
      * Creates a new test case.
@@ -163,7 +158,7 @@
 
         if (asCharacteristic) {
             @SuppressWarnings("unchecked")
-            final var property = (Attribute<GeometryWrapper>) feature.getProperty(propertyName);
+            final var property = (AbstractAttribute<GeometryWrapper>) feature.getProperty(propertyName);
             final var crsCharacteristic = Features.cast(
                     property.getType().characteristics().get(AttributeConvention.CRS),
                     CoordinateReferenceSystem.class).newInstance();
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureOperationsTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureOperationsTest.java
index c4029ca..d5a9b86 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureOperationsTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureOperationsTest.java
@@ -34,9 +34,6 @@
 import static org.apache.sis.test.Assertions.assertSetEquals;
 import static org.apache.sis.referencing.Assertions.assertEnvelopeEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests a feature combining various {@link FeatureOperations} applied of either sparse or dense features.
@@ -76,7 +73,7 @@
         final var normalizedCRS = new DefaultAttributeType<>(
                 name(AttributeConvention.CRS_CHARACTERISTIC), CoordinateReferenceSystem.class, 1, 1, HardCodedCRS.WGS84);
 
-        final PropertyType[] attributes = {
+        final AbstractIdentifiedType[] attributes = {
             new DefaultAttributeType<>(name("name"),          String.class,  1, 1, null),
             new DefaultAttributeType<>(name("classes"),       Polygon.class, 1, 1, null, standardCRS),
             new DefaultAttributeType<>(name("climbing wall"), Point.class,   1, 1, null, standardCRS),
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java
index 9cba286..081fee0 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java
@@ -35,11 +35,6 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Property;
-
 
 /**
  * Tests common to {@link DenseFeatureTest} and {@link SparseFeatureTest}.
@@ -77,7 +72,7 @@
 
     /**
      * Asserts that {@link AbstractFeature#getProperty(String)} returns the given instance.
-     * This assertion is verified after a call to {@link AbstractFeature#setProperty(Property)}
+     * This assertion is verified after a call to {@code AbstractFeature.setProperty(Property)}
      * and should be true for all Apache SIS concrete implementations. But it is not guaranteed
      * to be true for non-SIS implementations, for example built on top of {@code AbstractFeature}
      * without overriding {@code setProperty(Property)}.
@@ -106,7 +101,7 @@
              *   - Attribute value shall be the same as the one we got at the beginning of this method.
              *   - Attribute values (as a collection) is either empty or contains the same value.
              */
-            final var property = assertInstanceOf(Attribute.class, feature.getProperty(name));
+            final var property = assertInstanceOf(AbstractAttribute.class, feature.getProperty(name));
             assertSame(feature.getType().getProperty(name), property.getType(), name);
             assertSame(value, property.getValue(), name);
             final Collection<?> values = property.getValues();
@@ -163,9 +158,9 @@
         feature.setPropertyValue("REF_INSEE",   "92007");
         feature.setPropertyValue("CODE_POSTAL", "92220");
 
-        assertEquals("92220",   feature.getProperty("CODE_POSTAL").getValue());
-        assertEquals("92007",   feature.getProperty("REF_INSEE")  .getValue());
-        assertEquals("Bagneux", feature.getProperty("COMMUNE")    .getValue());
+        assertEquals("92220",   ((AbstractAttribute) feature.getProperty("CODE_POSTAL")).getValue());
+        assertEquals("92007",   ((AbstractAttribute) feature.getProperty("REF_INSEE"))  .getValue());
+        assertEquals("Bagneux", ((AbstractAttribute) feature.getProperty("COMMUNE"))    .getValue());
 
         assertEquals("92220",   feature.getPropertyValue("CODE_POSTAL"));
         assertEquals("92007",   feature.getPropertyValue("REF_INSEE"));
@@ -287,13 +282,13 @@
     }
 
     /**
-     * Tests the possibility to plugin custom attributes via {@link AbstractFeature#setProperty(Property)}.
+     * Tests the possibility to plugin custom attributes via {@code AbstractFeature.setProperty(Property)}.
      */
     @Test
     public void testCustomAttribute() {
         feature = createFeature(DefaultFeatureTypeTest.city());
         final var wrong  = SingletonAttributeTest.parliament();
-        final var city   = assertInstanceOf(AttributeType.class, feature.getType().getProperty("city"));
+        final var city   = assertInstanceOf(DefaultAttributeType.class, feature.getType().getProperty("city"));
         final var casted = new CustomAttribute<>(Features.cast(city, String.class));
 
         feature.setProperty(casted);
@@ -419,13 +414,13 @@
          * Force the conversion of a property value into a full Property object on one and only one of
          * the Features to be compared. The implementation shall be able to wrap or unwrap the values.
          */
-        assertEquals("Tokyo", clone.getProperty("city").getValue());
+        assertEquals("Tokyo", ((AbstractAttribute) clone.getProperty("city")).getValue());
         assertEquals(feature.hashCode(), clone.hashCode());
         assertEquals(feature, clone);
         /*
          * For the other Feature instance to contain full Property object and test again.
          */
-        assertEquals("Tokyo", feature.getProperty("city").getValue());
+        assertEquals("Tokyo", ((AbstractAttribute) feature.getProperty("city")).getValue());
         assertEquals(feature.hashCode(), clone.hashCode());
         assertEquals(feature, clone);
     }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeaturesTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeaturesTest.java
index 7398ae8..6b044c4 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeaturesTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeaturesTest.java
@@ -22,9 +22,6 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Tests {@link Features}.
@@ -40,7 +37,7 @@
     }
 
     /**
-     * Tests {@link Features#cast(AttributeType, Class)}.
+     * Tests {@code Features.cast(AttributeType, Class)}.
      */
     @Test
     public void testCastAttributeType() {
@@ -73,7 +70,7 @@
          * Feature is invalid because of missing property “population”.
          * Validation should raise an exception.
          */
-        var e = assertThrows(InvalidPropertyValueException.class, () -> Features.validate(feature));
+        var e = assertThrows(IllegalArgumentException.class, () -> Features.validate(feature));
         String message = e.getMessage();
         assertTrue(message.contains("city") || message.contains("population"), message);
 
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/GroupAsPolylineOperationTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/GroupAsPolylineOperationTest.java
index 1d92c7d..aced83f 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/GroupAsPolylineOperationTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/GroupAsPolylineOperationTest.java
@@ -28,9 +28,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-
 
 /**
  * Tests {@link GroupAsPolylineOperation}.
@@ -62,7 +59,7 @@
                           GeometryLibrary.ESRI, feature.getType().getProperty("points"));
 
         final var result = group.apply(feature, null);
-        final var value  = assertInstanceOf(Attribute.class, result).getValue();
+        final var value  = assertInstanceOf(AbstractAttribute.class, result).getValue();
         final var poly   = assertInstanceOf(Polyline.class, value);
         assertEquals(-6, poly.getPoint(0).getX());
         assertEquals( 7, poly.getPoint(1).getY());
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/NoOperation.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/NoOperation.java
index b34a465..bb62097 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/NoOperation.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/NoOperation.java
@@ -20,11 +20,6 @@
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-
 
 /**
  * An operation that does nothing.
@@ -42,7 +37,7 @@
     /**
      * The type of the result, or {@code null} if none.
      */
-    private final IdentifiedType result;
+    private final AbstractIdentifiedType result;
 
     /**
      * Constructs an operation from the given properties. The identification map is given unchanged to
@@ -53,7 +48,7 @@
      * @param  result          the type of the result, or {@code null} if none.
      */
     NoOperation(final Map<String,?> identification,
-            final ParameterDescriptorGroup parameters, final IdentifiedType result)
+            final ParameterDescriptorGroup parameters, final AbstractIdentifiedType result)
     {
         super(identification);
         this.parameters = parameters;
@@ -76,7 +71,7 @@
      * @return the type of the result, or {@code null} if none.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return result;
     }
 
@@ -86,7 +81,7 @@
      * @return {@code null}
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Object apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/StringJoinOperationTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/StringJoinOperationTest.java
index 8225648..55640af 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/StringJoinOperationTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/StringJoinOperationTest.java
@@ -25,9 +25,6 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Tests {@link StringJoinOperation}.
@@ -139,24 +136,24 @@
     @Test
     public void testIllegalArgument() {
         final var feature = new DenseFeature(person());
-        InvalidPropertyValueException e;
+        IllegalArgumentException e;
 
-        e = assertThrows(InvalidPropertyValueException.class,
+        e = assertThrows(IllegalArgumentException.class,
                 () -> feature.setPropertyValue("concat", "((:marc/21:>>"),
                 "Should fail because of mismatched prefix.");
         assertMessageContains(e, "<<:", "((");
 
-        e = assertThrows(InvalidPropertyValueException.class,
+        e = assertThrows(IllegalArgumentException.class,
                 () -> feature.setPropertyValue("concat", "<<:marc/21:))"),
                 "Should fail because of mismatched suffix.");
         assertMessageContains(e, ":>>", "))");
 
-        e = assertThrows(InvalidPropertyValueException.class,
+        e = assertThrows(IllegalArgumentException.class,
                 () -> feature.setPropertyValue("concat", "<<:marc/21/julie:>>"),
                 "Should fail because of too many components.");
         assertMessageContains(e, "<<:marc/21/julie:>>");
 
-        e = assertThrows(InvalidPropertyValueException.class,
+        e = assertThrows(IllegalArgumentException.class,
                 () -> feature.setPropertyValue("concat", "<<:marc/julie:>>"),
                 "Should fail because of unparsable number.");
         assertMessageContains(e, "julie", "age");
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
index 4ddd00c..a660023 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
@@ -31,9 +31,9 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -241,11 +241,11 @@
         assertEquals(Integer.class, boxBuilder.getValueClass(), "Attribute value type should have been boxed");
 
         final var featureType  = ftb.build();
-        final var propertyType = assertInstanceOf(AttributeType.class, featureType.getProperty("boxed"));
+        final var propertyType = assertInstanceOf(DefaultAttributeType.class, featureType.getProperty("boxed"));
         assertEquals(Integer.class, propertyType.getValueClass(), "Attribute value type should have been boxed");
 
         final var feature  = featureType.newInstance();
-        final var property = assertInstanceOf(Attribute.class, feature.getProperty("boxed"));
+        final var property = assertInstanceOf(AbstractAttribute.class, feature.getProperty("boxed"));
         assertEquals(Integer.class, property.getType().getValueClass(), "Attribute value type should have been boxed");
 
         int value = 3;
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
index 3e4f9bb..ce8572b 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
@@ -33,11 +33,10 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -55,13 +54,13 @@
     }
 
     /**
-     * Verifies that {@link FeatureTypeBuilder#setSuperTypes(FeatureType...)} ignores null parents.
+     * Verifies that {@code FeatureTypeBuilder.setSuperTypes(FeatureType...)} ignores null parents.
      * This method tests only the builder state without creating feature type.
      */
     @Test
     public void testNullParents() {
         var builder = new FeatureTypeBuilder(null);
-        assertSame(builder, builder.setSuperTypes(new FeatureType[6]));
+        assertSame(builder, builder.setSuperTypes(new DefaultFeatureType[6]));
         assertEquals(0, builder.getSuperTypes().length);
     }
 
@@ -376,9 +375,9 @@
     /**
      * Verifies that the given property is an attribute with the given name and value class.
      */
-    private static void assertPropertyEquals(final String name, final Class<?> valueClass, IdentifiedType property) {
+    private static void assertPropertyEquals(final String name, final Class<?> valueClass, AbstractIdentifiedType property) {
         assertEquals(name, property.getName().toString());
-        if (property instanceof Operation op) {
+        if (property instanceof AbstractOperation op) {
             property = op.getResult();
         }
         assertEquals(valueClass, attributeType(property).getValueClass());
@@ -387,8 +386,8 @@
     /**
      * Casts a property to an attribute.
      */
-    private static AttributeType<?> attributeType(final IdentifiedType property) {
-        return assertInstanceOf(AttributeType.class, property);
+    private static DefaultAttributeType<?> attributeType(final AbstractIdentifiedType property) {
+        return assertInstanceOf(DefaultAttributeType.class, property);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/test/FeatureComparator.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/test/FeatureComparator.java
index 8466701..2c30b0b 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/test/FeatureComparator.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/test/FeatureComparator.java
@@ -33,15 +33,13 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.apache.sis.util.Deprecable;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
 
 
 /**
@@ -54,22 +52,22 @@
     /**
      * The expected feature, or {@code null} if comparing only feature type.
      */
-    private final Feature expectedInstance;
+    private final AbstractFeature expectedInstance;
 
     /**
      * The expected feature type.
      */
-    private final FeatureType expectedType;
+    private final DefaultFeatureType expectedType;
 
     /**
      * The actual feature, or {@code null} if comparing only feature type.
      */
-    private final Feature actualInstance;
+    private final AbstractFeature actualInstance;
 
     /**
      * The actual feature type.
      */
-    private final FeatureType actualType;
+    private final DefaultFeatureType actualType;
 
     /**
      * The fully-qualified name of properties to ignore in comparisons.
@@ -100,7 +98,7 @@
      * to feature types and property types. The default value is {@code false}.
      *
      * @see #compare()
-     * @see IdentifiedType#getDefinition()
+     * @see AbstractIdentifiedType#getDefinition()
      */
     public boolean ignoreDefinition;
 
@@ -109,7 +107,7 @@
      * to feature types and property types. The default value is {@code false}.
      *
      * @see #compare()
-     * @see IdentifiedType#getDesignation()
+     * @see AbstractIdentifiedType#getDesignation()
      */
     public boolean ignoreDesignation;
 
@@ -118,7 +116,7 @@
      * to feature types and property types. The default value is {@code false}.
      *
      * @see #compare()
-     * @see IdentifiedType#getDescription()
+     * @see AbstractIdentifiedType#getDescription()
      */
     public boolean ignoreDescription;
 
@@ -133,7 +131,7 @@
      * @param  expected  the expected feature instance.
      * @param  actual    the actual feature instance.
      */
-    public FeatureComparator(final Feature expected, final Feature actual) {
+    public FeatureComparator(final AbstractFeature expected, final AbstractFeature actual) {
         expectedInstance = expected;
         expectedType     = expected.getType();
         actualInstance   = actual;
@@ -146,7 +144,7 @@
      * @param  expected  the expected feature type.
      * @param  actual    the actual feature type.
      */
-    public FeatureComparator(final FeatureType expected, final FeatureType actual) {
+    public FeatureComparator(final DefaultFeatureType expected, final DefaultFeatureType actual) {
         expectedInstance = null;
         expectedType     = Objects.requireNonNull(expected);
         actualInstance   = null;
@@ -176,16 +174,16 @@
      * @param  actual    the actual type.
      * @throws AssertionError if the actual type is not equal to the expected type.
      */
-    private void compareType(final IdentifiedType expected, final IdentifiedType actual) {
+    private void compareType(final AbstractIdentifiedType expected, final AbstractIdentifiedType actual) {
         boolean recognized = false;
-        if (expected instanceof FeatureType type) {
-            var c = assertInstanceOf(FeatureType.class, actual, this::path);
+        if (expected instanceof DefaultFeatureType type) {
+            var c = assertInstanceOf(DefaultFeatureType.class, actual, this::path);
             compareFeatureType(type, c);
             recognized = true;
         }
-        if (expected instanceof PropertyType type) {
-            var c = assertInstanceOf(PropertyType.class, actual, this::path);
-            comparePropertyType(type, c);
+        /* Condition in "geoapi-3.1" branch removed in this branch. */ {
+            var c = assertInstanceOf(AbstractIdentifiedType.class, actual, this::path);
+            comparePropertyType(expected, c);
             recognized = true;
         }
         if (!recognized) {
@@ -200,7 +198,7 @@
      * @param  actual    the actual type.
      * @throws AssertionError if the actual type is not equal to the expected type.
      */
-    private void compareFeatureType(final FeatureType expected, final FeatureType actual) {
+    private void compareFeatureType(final DefaultFeatureType expected, final DefaultFeatureType actual) {
         compareIdentifiedType(expected, actual);
 
         assertEquals(expected.isAbstract(),    actual.isAbstract(),    () -> path() + "Abstract state differ.");
@@ -209,12 +207,12 @@
          * Compare all properties that are not ignored.
          * Properties are removed from the `actualProperties` list as we found them.
          */
-        final List<PropertyType> actualProperties = new ArrayList<>(actual.getProperties(false));
+        final List<AbstractIdentifiedType> actualProperties = new ArrayList<>(actual.getProperties(false));
         actualProperties.removeIf(this::isIgnored);
-        for (final PropertyType pte : expected.getProperties(false)) {
+        for (final AbstractIdentifiedType pte : expected.getProperties(false)) {
             if (!isIgnored(pte)) {
                 final String tip = push(pte.getName().toString());
-                PropertyType pta = findAndRemove(actualProperties, pte.getName());
+                AbstractIdentifiedType pta = findAndRemove(actualProperties, pte.getName());
                 comparePropertyType(pte, pta);
                 pull(tip);
             }
@@ -226,7 +224,7 @@
             final StringBuilder b = new StringBuilder(path())
                     .append("Actual type contains a property not declared in expected type:")
                     .append(System.lineSeparator());
-            for (final PropertyType pta : actualProperties) {
+            for (final AbstractIdentifiedType pta : actualProperties) {
                 b.append("  ").append(pta.getName()).append(System.lineSeparator());
             }
             fail(b.toString());
@@ -241,9 +239,9 @@
      * @param  actual    the actual instance.
      * @throws AssertionError if the actual instance is not equal to the expected instance.
      */
-    private void compareFeature(final Feature expected, final Feature actual) {
+    private void compareFeature(final AbstractFeature expected, final AbstractFeature actual) {
         compareFeatureType(expected.getType(), actual.getType());
-        for (final PropertyType p : expected.getType().getProperties(true)) {
+        for (final AbstractIdentifiedType p : expected.getType().getProperties(true)) {
             if (isIgnored(p)) {
                 continue;
             }
@@ -256,8 +254,8 @@
             while (expectedIter.hasNext()) {
                 final Object expectedElement = expectedIter.next();
                 final Object actualElement = actualIter.next();
-                if (expectedElement instanceof Feature instance) {
-                    compareFeature(instance, (Feature) actualElement);
+                if (expectedElement instanceof AbstractFeature instance) {
+                    compareFeature(instance, (AbstractFeature) actualElement);
                 } else {
                     assertEquals(expectedElement, actualElement);
                 }
@@ -273,17 +271,17 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void comparePropertyType(final PropertyType expected, final PropertyType actual) {
-        if (expected instanceof AttributeType<?> type) {
-            var c = assertInstanceOf(AttributeType.class, actual, this::path);
+    private void comparePropertyType(final AbstractIdentifiedType expected, final AbstractIdentifiedType actual) {
+        if (expected instanceof DefaultAttributeType<?> type) {
+            var c = assertInstanceOf(DefaultAttributeType.class, actual, this::path);
             compareAttribute(type, c);
         }
-        if (expected instanceof FeatureAssociationRole role) {
-            var c = assertInstanceOf(FeatureAssociationRole.class, actual, this::path);
+        if (expected instanceof DefaultAssociationRole role) {
+            var c = assertInstanceOf(DefaultAssociationRole.class, actual, this::path);
             compareFeatureAssociationRole(role, c);
         }
-        if (expected instanceof Operation op) {
-            var c = assertInstanceOf(Operation.class, actual, this::path);
+        if (expected instanceof AbstractOperation op) {
+            var c = assertInstanceOf(AbstractOperation.class, actual, this::path);
             compareOperation(op, c);
         }
     }
@@ -295,21 +293,21 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void compareAttribute(final AttributeType<?> expected, final AttributeType<?> actual) {
+    private void compareAttribute(final DefaultAttributeType<?> expected, final DefaultAttributeType<?> actual) {
         compareIdentifiedType(expected, actual);
         assertEquals(expected.getValueClass(),   expected.getValueClass(),   () -> path() + "Value classe differ.");
         assertEquals(expected.getDefaultValue(), expected.getDefaultValue(), () -> path() + "Default value differ.");
 
-        final Map<String, AttributeType<?>> expectedChrs = expected.characteristics();
-        final Map<String, AttributeType<?>> actualChrs = actual.characteristics();
+        final Map<String, DefaultAttributeType<?>> expectedChrs = expected.characteristics();
+        final Map<String, DefaultAttributeType<?>> actualChrs = actual.characteristics();
         final List<String> actualChrNames = new ArrayList<>(actualChrs.keySet());
         actualChrNames.removeIf((p) -> ignoredCharacteristics.contains(p));
 
-        for (final Map.Entry<String, AttributeType<?>> entry : expectedChrs.entrySet()) {
+        for (final Map.Entry<String, DefaultAttributeType<?>> entry : expectedChrs.entrySet()) {
             final String p = entry.getKey();
             if (!ignoredCharacteristics.contains(p)) {
-                final AttributeType<?> expectedChr = entry.getValue();
-                final AttributeType<?> actualChr = actualChrs.get(p);
+                final DefaultAttributeType<?> expectedChr = entry.getValue();
+                final DefaultAttributeType<?> actualChr = actualChrs.get(p);
                 final String tip = push("characteristic(" + p + ')');
                 assertNotNull(actualChr, this::path);
                 assertTrue(actualChrNames.remove(p));
@@ -338,7 +336,7 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void compareFeatureAssociationRole(final FeatureAssociationRole expected, final FeatureAssociationRole actual) {
+    private void compareFeatureAssociationRole(final DefaultAssociationRole expected, final DefaultAssociationRole actual) {
         compareIdentifiedType(expected, actual);
         assertEquals(expected.getMinimumOccurs(), actual.getMinimumOccurs(), () -> path() + "Minimum occurences differ.");
         assertEquals(expected.getMaximumOccurs(), actual.getMaximumOccurs(), () -> path() + "Maximum occurences differ.");
@@ -354,7 +352,7 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void compareOperation(final Operation expected, final Operation actual) {
+    private void compareOperation(final AbstractOperation expected, final AbstractOperation actual) {
         compareIdentifiedType(expected, actual);
         assertEquals(expected.getParameters(), actual.getParameters());
         final String tip = push("operation-actual(" + expected.getResult().getName() + ')');
@@ -369,7 +367,7 @@
      * @param  actual    the actual type.
      * @throws AssertionError if the actual type is not equal to the expected type.
      */
-    private void compareIdentifiedType(final IdentifiedType expected, final IdentifiedType actual) {
+    private void compareIdentifiedType(final AbstractIdentifiedType expected, final AbstractIdentifiedType actual) {
         assertEquals(expected.getName(), actual.getName(), () -> path() + "Name differ.");
         if (!ignoreDefinition) {
             assertEquals(expected.getDefinition(), actual.getDefinition(), () -> path() + "Definition differ.");
@@ -380,8 +378,8 @@
         if (!ignoreDescription) {
             assertEquals(expected.getDescription(), actual.getDescription(), () -> path() + "Description differ.");
         }
-        if (expected instanceof Deprecable de && actual instanceof Deprecable da) {
-            assertEquals(de.isDeprecated(), da.isDeprecated(), () -> path() + "Deprecated state differ.");
+        /* Condition in "geoapi-3.1" branch removed in this branch. */ {
+            assertEquals(expected.isDeprecated(), actual.isDeprecated(), () -> path() + "Deprecated state differ.");
         }
     }
 
@@ -413,7 +411,7 @@
     /**
      * Returns {@code true} if the given property should be ignored in feature comparisons.
      */
-    private boolean isIgnored(final PropertyType property) {
+    private boolean isIgnored(final AbstractIdentifiedType property) {
         return ignoredProperties.contains(property.getName().toString());
     }
 
@@ -421,10 +419,10 @@
      * Searches for a property of the given name in the given list and remove the property from that list.
      * If the property is not found, then this method fails.
      */
-    private PropertyType findAndRemove(final Collection<PropertyType> properties, final GenericName name) {
-        final Iterator<PropertyType> it = properties.iterator();
+    private AbstractIdentifiedType findAndRemove(final Collection<AbstractIdentifiedType> properties, final GenericName name) {
+        final Iterator<AbstractIdentifiedType> it = properties.iterator();
         while (it.hasNext()) {
-            final PropertyType pt = it.next();
+            final AbstractIdentifiedType pt = it.next();
             if (pt.getName().equals(name)) {
                 it.remove();
                 return pt;
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java
index 71ad083..a7889ca 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java
@@ -22,9 +22,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.FilterFactory;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -36,7 +35,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/BinarySpatialFilterTestCase.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/BinarySpatialFilterTestCase.java
index 277f233..8cf9ae7 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/BinarySpatialFilterTestCase.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/BinarySpatialFilterTestCase.java
@@ -37,11 +37,10 @@
 import org.apache.sis.referencing.operation.HardCodedConversions;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.DistanceOperatorName;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -58,7 +57,7 @@
     /**
      * The factory to use for testing purpose.
      */
-    private final FilterFactory<Feature, G, Object> factory;
+    private final DefaultFilterFactory<AbstractFeature, G, Object> factory;
 
     /**
      * The geometry library used by this factory.
@@ -87,7 +86,7 @@
     /**
      * Creates the polygon identified by the given enumeration value.
      */
-    private Literal<Feature,G> literal(final Polygon p) {
+    private Literal<AbstractFeature,G> literal(final Polygon p) {
         final byte[] coordinates;
         boolean polygon = true;
         switch (p) {
@@ -100,7 +99,8 @@
             case TOUCHES:    coordinates = new byte[] {4,  2,    7,  5,    9,  3};  polygon = false; break;
             default: throw new AssertionError(p);
         }
-        return factory.literal(library.createPolyline(polygon, 2, Vector.create(coordinates, false)));
+        return (Literal<AbstractFeature,G>)
+                factory.literal(library.createPolyline(polygon, 2, Vector.create(coordinates, false)));
     }
 
     /**
@@ -126,7 +126,7 @@
     public void testExpressionType() {
         var geometry = literal(Polygon.RIGHT);
         var filter   = factory.bbox(geometry, new Envelope2D(null, 0, 0, 1, 1));
-        var arg2     = assertInstanceOf(Literal.class, filter.getOperand2());
+        var arg2     = assertInstanceOf(Literal.class, filter.getExpressions().get(1));
         assertInstanceOf(Envelope.class, arg2.getValue(), "Second argument value should be an envelope.");
         assertSame(arg2, filter.getExpressions().get(1), "The two ways to acquire the second argument return different values.");
     }
@@ -320,7 +320,7 @@
         // Ensure no error is raised, even if a reprojection is involved.
         envelope = new Envelope2D(HardCodedCRS.WGS84, -1.36308465, -5.98385631, 6.56E-5, 6.57E-5);
         filter = factory.bbox(geometry, envelope);
-        assertTrue(filter.test(null));
+        filter.test(null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/CapabilitiesTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/CapabilitiesTest.java
deleted file mode 100644
index e4b152b..0000000
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/CapabilitiesTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter;
-
-import java.util.Set;
-import org.opengis.util.LocalName;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.capability.IdCapabilities;
-import org.opengis.filter.capability.ScalarCapabilities;
-import org.opengis.filter.capability.AvailableFunction;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import org.apache.sis.test.TestCase;
-import org.apache.sis.test.TestUtilities;
-
-
-/**
- * Tests {@link Capabilities} implementations.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class CapabilitiesTest extends TestCase {
-    /**
-     * Creates a new test case.
-     */
-    public CapabilitiesTest() {
-    }
-
-    /**
-     * Tests {@link Capabilities#getResourceIdentifiers()}.
-     */
-    @Test
-    public void testResourceIdentifiers() {
-        final var capabilities = DefaultFilterFactory.forFeatures().getCapabilities();
-        assertTrue(capabilities.getConformance().implementsResourceld());
-        final IdCapabilities idc = capabilities.getIdCapabilities().get();
-        final LocalName id = TestUtilities.getSingleton(idc.getResourceIdentifiers());
-        assertEquals("identifier", id.toString());
-    }
-
-    /**
-     * Tests {@link Capabilities#getComparisonOperators()}.
-     */
-    @Test
-    public void testComparisonOperators() {
-        final var capabilities = DefaultFilterFactory.forFeatures().getCapabilities();
-        final ScalarCapabilities c = capabilities.getScalarCapabilities().get();
-        final Set<ComparisonOperatorName> op = c.getComparisonOperators();
-        assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO));
-        assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_LESS_THAN));
-        assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN));
-    }
-
-    /**
-     * Tests {@link Capabilities#getFunctions()}.
-     */
-    @Test
-    public void testFunctions() {
-        final var capabilities = DefaultFilterFactory.forFeatures().getCapabilities();
-        AvailableFunction desc = capabilities.getFunctions().get("ST_Transform");
-        assertEquals("SQLMM:ST_Transform", desc.getName().toFullyQualifiedName().toString());
-        assertEquals("OGC:Geometry", desc.getReturnType().toFullyQualifiedName().toString());
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java
index e82bc64..612c38e 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java
@@ -22,15 +22,10 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.ComparisonOperator;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.BetweenComparisonOperator;
-import org.apache.sis.filter.privy.FunctionNames;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
+import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator;
 
 
 /**
@@ -43,12 +38,12 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Expressions used as constant for the tests.
      */
-    private final Literal<Feature,Integer> c05, c10, c20;
+    private final Expression<AbstractFeature,Integer> c05, c10, c20;
 
     /**
      * Expected name of the filter to be evaluated. The {@code evaluate(…)} methods
@@ -59,7 +54,7 @@
     /**
      * The filter tested by last call to {@code evaluate(…)} methods.
      */
-    private ComparisonOperator<Feature> filter;
+    private Filter<AbstractFeature> filter;
 
     /**
      * Creates a new test case.
@@ -75,7 +70,7 @@
      * Evaluates the given filter. The {@link #expectedName} field must be set before this method is invoked.
      * This method assumes that the first expression of all filters is {@link #c10}.
      */
-    private boolean evaluate(final BinaryComparisonOperator<Feature> filter) {
+    private boolean evaluate(final Filter<AbstractFeature> filter) {
         this.filter = filter;
         assertInstanceOf(ComparisonFilter.class, filter);
         assertEquals(expectedName, filter.getOperatorType());
@@ -86,7 +81,7 @@
     /**
      * Evaluates the given "Property is between" filter.
      */
-    private boolean evaluate(final BetweenComparisonOperator<Feature> filter) {
+    private boolean evaluate(final BetweenComparisonOperator<AbstractFeature> filter) {
         this.filter = filter;
         assertInstanceOf(ComparisonFilter.Between.class, filter);
         assertEquals(expectedName, filter.getOperatorType());
@@ -170,10 +165,10 @@
      */
     @Test
     public void testBetween() {
-        expectedName = ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN);
-        assertTrue (evaluate(factory.between(c10, c05, c20)));
-        assertFalse(evaluate(factory.between(c20, c05, c10)));
-        assertFalse(evaluate(factory.between(c05, c10, c20)));
+        expectedName = ComparisonOperatorName.PROPERTY_IS_BETWEEN;
+        assertTrue (evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c10, c05, c20)));
+        assertFalse(evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c20, c05, c10)));
+        assertFalse(evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c05, c10, c20)));
         assertSerializedEquals(filter);
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java
index 361e3cd..cea0ce8 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java
@@ -25,11 +25,9 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -42,7 +40,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -71,17 +69,17 @@
     public void testEvaluate() {
         final var builder = new FeatureTypeBuilder();
         builder.addAttribute(String.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final Feature f1 = builder.setName("Test 1").build().newInstance();
+        final AbstractFeature f1 = builder.setName("Test 1").build().newInstance();
         f1.setPropertyValue("att", "123");
 
         builder.clear().addAttribute(Integer.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final Feature f2 = builder.setName("Test 2").build().newInstance();
+        final AbstractFeature f2 = builder.setName("Test 2").build().newInstance();
         f2.setPropertyValue("att", 123);
 
-        final Feature f3 = builder.clear().setName("Test 3").build().newInstance();
+        final AbstractFeature f3 = builder.clear().setName("Test 3").build().newInstance();
 
-        final Filter<Feature> id = factory.resourceId("123");
-        assertEquals(Feature.class, id.getResourceClass());
+        final Filter<AbstractFeature> id = factory.resourceId("123");
+        assertEquals(AbstractFeature.class, id.getResourceClass());
         assertTrue (id.test(f1));
         assertTrue (id.test(f2));
         assertFalse(id.test(f3));
@@ -94,17 +92,17 @@
     public void testEvaluateCombined() {
         final var builder = new FeatureTypeBuilder();
         builder.addAttribute(String.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final FeatureType type = builder.setName("Test").build();
+        final DefaultFeatureType type = builder.setName("Test").build();
 
-        final Feature f1 = type.newInstance(); f1.setPropertyValue("att", "123");
-        final Feature f2 = type.newInstance(); f2.setPropertyValue("att", "abc");
-        final Feature f3 = type.newInstance(); f3.setPropertyValue("att", "abc123");
+        final AbstractFeature f1 = type.newInstance(); f1.setPropertyValue("att", "123");
+        final AbstractFeature f2 = type.newInstance(); f2.setPropertyValue("att", "abc");
+        final AbstractFeature f3 = type.newInstance(); f3.setPropertyValue("att", "abc123");
 
-        final Filter<Feature> id = factory.or(
+        final Filter<AbstractFeature> id = factory.or(
                 factory.resourceId("abc"),
                 factory.resourceId("123"));
 
-        assertEquals(Feature.class, id.getResourceClass());
+        assertEquals(AbstractFeature.class, id.getResourceClass());
         assertTrue (id.test(f1));
         assertTrue (id.test(f2));
         assertFalse(id.test(f3));
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java
index cd285db..16619ed 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java
@@ -24,9 +24,10 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.FilterFactory;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -38,7 +39,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -53,7 +54,7 @@
     @Test
     public void testReferenceSerialization() {
         var filter = factory.property("some_property", String.class);
-        assertEquals("some_property", filter.getXPath());
+        assertEquals("some_property", ((ValueReference<?,?>) filter).getXPath());
         assertSerializedEquals(filter);
     }
 
@@ -62,11 +63,11 @@
      */
     @Test
     public void testLiteralSerialization() {
-        final var e1 = factory.literal(true);
-        final var e2 = factory.literal("a text string");
-        final var e3 = factory.literal('x');
-        final var e4 = factory.literal(122);
-        final var e5 = factory.literal(45.56);
+        final var e1 = (Literal<?,?>) factory.literal(true);
+        final var e2 = (Literal<?,?>) factory.literal("a text string");
+        final var e3 = (Literal<?,?>) factory.literal('x');
+        final var e4 = (Literal<?,?>) factory.literal(122);
+        final var e5 = (Literal<?,?>) factory.literal(45.56);
 
         assertEquals(Boolean.TRUE,    e1.getValue());
         assertEquals("a text string", e2.getValue());
@@ -91,7 +92,7 @@
         final var instance = builder.setName("Test").build().newInstance();
 
         var reference = factory.property("some_property");
-        assertEquals(Feature.class, reference.getResourceClass());
+        assertEquals(AbstractFeature.class, reference.getResourceClass());
         assertNull(reference.apply(instance));
         assertNull(reference.apply(null));
 
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
index 2dcdb65..7a1520c 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
@@ -30,11 +30,10 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.LogicalOperator;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.LogicalOperator;
 
 
 /**
@@ -47,7 +46,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -82,7 +81,7 @@
     public void testNot() {
         final var literal = factory.literal("text");
         final var operand = factory.isNull(literal);
-        final var filter  = factory.not(operand);
+        final var filter  = (LogicalOperator<AbstractFeature>) factory.not(operand);
         assertArrayEquals(new Filter<?>[] {operand}, filter.getOperands().toArray());
         assertTrue(filter.test(null));
         assertSerializedEquals(filter);
@@ -104,7 +103,7 @@
      */
     @Test
     public void testVolatile() {
-        final var literal = new LeafExpression.Literal<Feature,Object>("test") {
+        final var literal = new LeafExpression.Literal<AbstractFeature,Object>("test") {
             @Override public Set<FunctionProperty> properties() {
                 return Set.of(FunctionProperty.VOLATILE);
             }
@@ -121,8 +120,8 @@
      * @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<Feature>, Filter<Feature>, LogicalOperator<Feature>> binary,
-                        final Function<Collection<Filter<Feature>>, LogicalOperator<Feature>> anyArity,
+    private void create(final BiFunction<Filter<AbstractFeature>, Filter<AbstractFeature>, Filter<AbstractFeature>> binary,
+                        final Function<Collection<Filter<AbstractFeature>>, Filter<AbstractFeature>> anyArity,
                         final boolean expected)
     {
         final var f1 = factory.isNull(factory.literal("text"));
@@ -147,7 +146,7 @@
         /*
          * Test construction, evaluation and serialization.
          */
-        var filter = binary.apply(f1, f2);
+        var filter = (LogicalOperator<AbstractFeature>) binary.apply(f1, f2);
         assertArrayEquals(new Filter<?>[] {f1, f2}, filter.getOperands().toArray());
         assertEquals(expected, filter.test(null));
         assertSerializedEquals(filter);
@@ -155,7 +154,7 @@
         /*
          * Same test, using the constructor accepting any number of operands.
          */
-        filter = anyArity.apply(List.of(f1, f2, f1));
+        filter = (LogicalOperator<AbstractFeature>) anyArity.apply(List.of(f1, f2, f1));
         assertArrayEquals(new Filter<?>[] {f1, f2, f1}, filter.getOperands().toArray());
         assertEquals(expected, filter.test(null));
         assertSerializedEquals(filter);
@@ -223,7 +222,7 @@
     /**
      * Verifies an optimization which is expected to evaluate immediately.
      */
-    private static void optimize(final Filter<Feature> original, final Filter<Feature> expected) {
+    private static void optimize(final Filter<AbstractFeature> original, final Filter<AbstractFeature> expected) {
         final Filter<?> optimized = new Optimization().apply(original);
         assertNotSame(original, optimized, "Expected a new optimized filter.");
         assertSame(optimized, new Optimization().apply(optimized), "Second optimization should have no effect.");
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
index b52d849..faa3eb8 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
@@ -19,18 +19,10 @@
 import java.time.Instant;
 import java.io.Serializable;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.time.temporal.TemporalAmount;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalPrimitive;
-import org.opengis.temporal.TemporalGeometricPrimitive;
-
-// Specific to the geoapi-3.1 branch:
-import org.opengis.referencing.ReferenceIdentifier;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.temporal.Period;
 
 
 /**
@@ -40,7 +32,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  */
 @SuppressWarnings("serial")
-final class PeriodLiteral implements Period, Literal<Feature,Period>, Serializable {
+final class PeriodLiteral implements Period, Literal<AbstractFeature,Period>, Serializable {
     /**
      * Period beginning and ending, in milliseconds since Java epoch.
      */
@@ -56,17 +48,15 @@
      * Returns the constant value held by this object.
      */
     @Override public Period getValue() {return this;}
+    @Override public Period apply(AbstractFeature input) {return this;}
 
     /** Returns a bound of this period. */
     @Override public Instant getBeginning() {return Instant.ofEpochMilli(begin);}
     @Override public Instant getEnding()    {return Instant.ofEpochMilli(end);}
 
     /** Not needed for the tests. */
-    @Override public ReferenceIdentifier getName()                           {throw new UnsupportedOperationException();}
-    @Override public RelativePosition relativePosition(TemporalPrimitive o)  {throw new UnsupportedOperationException();}
-    @Override public TemporalAmount   distance(TemporalGeometricPrimitive o) {throw new UnsupportedOperationException();}
-    @Override public TemporalAmount   length()                               {throw new UnsupportedOperationException();}
-    @Override public <N> Expression<Feature,N> toValueType(Class<N> target)  {throw new UnsupportedOperationException();}
+    @Override public <N> Expression<AbstractFeature,N> toValueType(Class<N> target) {throw new UnsupportedOperationException();}
+    @Override public Class<AbstractFeature> getResourceClass() {return AbstractFeature.class;}
 
     /**
      * Hash code value. Used by the tests for checking the results of deserialization.
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
index b349e62..a07fd7b 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
@@ -26,12 +26,10 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
-import org.opengis.feature.Feature;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.TemporalOperator;
-import org.opengis.filter.TemporalOperatorName;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.temporal.Period;
+import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
 
 
 /**
@@ -44,13 +42,13 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private FilterFactory<Feature, Object, ? super Period> factory;
+    private DefaultFilterFactory<AbstractFeature, Object, ? super Period> factory;
 
     /**
      * The filter to test. This field shall be assigned by each {@code testFoo()} method by invoking
      * a {@link #factory} method with {@link #expression1} and {@link #expression2} in arguments.
      */
-    private TemporalOperator<Feature> filter;
+    private Filter<AbstractFeature> filter;
 
     /**
      * The expression to test. They are the arguments to be given to {@link #factory} method.
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/CopyVisitorTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/CopyVisitorTest.java
deleted file mode 100644
index 61afa6c..0000000
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/CopyVisitorTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter.privy;
-
-import java.util.Map;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.FilterFactory;
-import org.apache.sis.filter.DefaultFilterFactory;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import org.apache.sis.test.TestCase;
-
-
-/**
- * Tests copies using {@link CopyVisitor}.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class CopyVisitorTest extends TestCase {
-    /**
-     * Creates a new test case.
-     */
-    public CopyVisitorTest() {
-    }
-
-    /**
-     * Tests copy a value reference.
-     */
-    @Test
-    public void copyValueReference() {
-        final FilterFactory<Feature, ?, ?>                source  = DefaultFilterFactory.forFeatures();
-        final FilterFactory<Map<String,?>, ?, ?>          target  = new FilterFactoryMock();
-        final CopyVisitor  <Feature, Map<String,?>, ?, ?> visitor = new CopyVisitor<>(target, true);
-
-        final String xpath = "someProperty";
-        final Expression<Feature, ?> exp = source.property(xpath);
-        final Expression<Map<String,?>, ?> result = visitor.copy(exp);
-
-        assertTrue(result instanceof ValueReferenceMock);
-        assertEquals(xpath, ((ValueReferenceMock) result).getXPath());
-    }
-
-    /**
-     * Tests copy of a function.
-     */
-    @Test
-    public void copyFunction() {
-        final FilterFactory<Feature, ?, ?>                source  = DefaultFilterFactory.forFeatures();
-        final FilterFactory<Map<String,?>, ?, ?>          target  = new FilterFactoryMock();
-        final CopyVisitor  <Feature, Map<String,?>, ?, ?> visitor = new CopyVisitor<>(target, true);
-
-        final Expression<Feature, ?> exp1 = source.property("someProperty");
-        final Expression<Feature, ?> exp2 = source.property("crs");
-        final Expression<Feature, ?> fct  = source.function("ST_GeomFromText", exp1, exp2);
-        final Expression<Map<String,?>, ?> result = visitor.copy(fct);
-
-        assertTrue(result instanceof FunctionMock);
-        var resultfct = ((FunctionMock) result).getParameters();
-        assertEquals(2, resultfct.size());
-        assertTrue(resultfct.get(0) instanceof ValueReferenceMock);
-        assertTrue(resultfct.get(1) instanceof ValueReferenceMock);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FilterFactoryMock.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FilterFactoryMock.java
deleted file mode 100644
index 39455f6..0000000
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FilterFactoryMock.java
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter.privy;
-
-import java.time.Instant;
-import java.util.Map;
-import java.util.Arrays;
-import java.util.Collection;
-import javax.measure.Quantity;
-import javax.measure.quantity.Length;
-import org.opengis.geometry.Envelope;
-import org.opengis.metadata.citation.Citation;
-import org.opengis.filter.*;
-import org.opengis.filter.capability.FilterCapabilities;
-import org.apache.sis.metadata.simple.SimpleCitation;
-
-
-/**
- * Dummy implementation of filter factory.
- * The features handled by this implementation are property-value maps.
- *
- * @author  Johann Sorel (Geomatys)
- */
-final class FilterFactoryMock implements FilterFactory<Map<String,?>, Object, Object> {
-    /**
-     * Creates a new dummy factory.
-     */
-    FilterFactoryMock() {
-    }
-
-    /**
-     * Returns the "vendor" responsible for creating this factory implementation.
-     */
-    @Override
-    public Citation getVendor() {
-        return new SimpleCitation("SIS-tests");
-    }
-
-    /**
-     * Creates an expression retrieving the value as an instance of the specified class.
-     */
-    @Override
-    public <V> ValueReference<Map<String,?>, V> property(String xpath, Class<V> type) {
-        return new ValueReferenceMock<>(xpath, type);
-    }
-
-    /**
-     * Creates a dummy function with an arbitrary number of parameters.
-     */
-    @Override
-    public Expression<Map<String,?>, ?> function(String name, Expression<Map<String,?>, ?>[] parameters) {
-        return new FunctionMock(name, Arrays.asList(parameters));
-    }
-
-    // ======== All operations below this point are unsupported ===================================================
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public FilterCapabilities getCapabilities() {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public ResourceId<Map<String,?>> resourceId(String rid, Version version, Instant startTime, Instant endTime) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public <V> Literal<Map<String,?>, V> literal(V value) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinaryComparisonOperator<Map<String,?>> equal(
-            Expression<Map<String,?>, ?> expression1,
-            Expression<Map<String,?>, ?> expression2,
-            boolean isMatchingCase, MatchAction matchAction)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinaryComparisonOperator<Map<String,?>> notEqual(
-            Expression<Map<String,?>, ?> expression1,
-            Expression<Map<String,?>, ?> expression2,
-            boolean isMatchingCase, MatchAction matchAction)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinaryComparisonOperator<Map<String,?>> less(
-            Expression<Map<String,?>, ?> expression1,
-            Expression<Map<String,?>, ?> expression2,
-            boolean isMatchingCase, MatchAction matchAction)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinaryComparisonOperator<Map<String,?>> greater(
-            Expression<Map<String,?>, ?> expression1,
-            Expression<Map<String,?>, ?> expression2,
-            boolean isMatchingCase, MatchAction matchAction)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinaryComparisonOperator<Map<String,?>> lessOrEqual(
-            Expression<Map<String,?>, ?> expression1,
-            Expression<Map<String,?>, ?> expression2,
-            boolean isMatchingCase, MatchAction matchAction)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinaryComparisonOperator<Map<String,?>> greaterOrEqual(
-            Expression<Map<String,?>, ?> expression1,
-            Expression<Map<String,?>, ?> expression2,
-            boolean isMatchingCase, MatchAction matchAction)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BetweenComparisonOperator<Map<String,?>> between(
-            Expression<Map<String,?>, ?> expression,
-            Expression<Map<String,?>, ?> lowerBoundary,
-            Expression<Map<String,?>, ?> upperBoundary)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public LikeOperator<Map<String,?>> like(
-            Expression<Map<String,?>, ?> expression,
-            String pattern, char wildcard, char singleChar, char escape, boolean isMatchingCase)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public NullOperator<Map<String,?>> isNull(Expression<Map<String,?>, ?> expression) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public NilOperator<Map<String,?>> isNil(Expression<Map<String,?>, ?> expression, String nilReason) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public LogicalOperator<Map<String,?>> and(Collection<? extends Filter<Map<String,?>>> operands) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public LogicalOperator<Map<String,?>> or(Collection<? extends Filter<Map<String,?>>> operands) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public LogicalOperator<Map<String,?>> not(Filter<Map<String,?>> operand) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> bbox(
-            Expression<Map<String,?>, ? extends Object> geometry,
-            Envelope bounds)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> equals(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> disjoint(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> intersects(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> touches(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> crosses(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> within(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> contains(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public BinarySpatialOperator<Map<String,?>> overlaps(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public DistanceOperator<Map<String,?>> beyond(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2,
-            Quantity<Length> distance)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public DistanceOperator<Map<String,?>> within(
-            Expression<Map<String,?>, ? extends Object> geometry1,
-            Expression<Map<String,?>, ? extends Object> geometry2,
-            Quantity<Length> distance)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> after(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> before(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> begins(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> begunBy(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> tcontains(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> during(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> tequals(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> toverlaps(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> meets(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> ends(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> overlappedBy(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> metBy(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> endedBy(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public TemporalOperator<Map<String,?>> anyInteracts(
-            Expression<Map<String,?>, ? extends Object> time1,
-            Expression<Map<String,?>, ? extends Object> time2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public Expression<Map<String,?>, Number> add(
-            Expression<Map<String,?>, ? extends Number> operand1,
-            Expression<Map<String,?>, ? extends Number> operand2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public Expression<Map<String,?>, Number> subtract(
-            Expression<Map<String,?>, ? extends Number> operand1,
-            Expression<Map<String,?>, ? extends Number> operand2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public Expression<Map<String,?>, Number> multiply(
-            Expression<Map<String,?>, ? extends Number> operand1,
-            Expression<Map<String,?>, ? extends Number> operand2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public Expression<Map<String,?>, Number> divide(
-            Expression<Map<String,?>, ? extends Number> operand1,
-            Expression<Map<String,?>, ? extends Number> operand2)
-    {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public SortProperty<Map<String,?>> sort(ValueReference<Map<String,?>, ?> property, SortOrder order) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FunctionMock.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FunctionMock.java
deleted file mode 100644
index 1ad445e..0000000
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FunctionMock.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter.privy;
-
-import java.util.Map;
-import java.util.List;
-import org.opengis.util.ScopedName;
-import org.opengis.filter.Expression;
-import org.apache.sis.util.iso.Names;
-
-
-/**
- * Dummy implementation of filter function.
- * The features handled by this implementation are property-value maps.
- * This class stores a function name and parameters but cannot do any operation.
- *
- * @author  Johann Sorel (Geomatys)
- */
-final class FunctionMock implements Expression<Map<String,?>, Object> {
-    /**
-     * The local part of the function name.
-     */
-    private final String name;
-
-    /**
-     * The function parameters.
-     */
-    private final List<Expression<Map<String,?>, ?>> parameters;
-
-    /**
-     * Creates a new dummy function.
-     *
-     * @param  name        the local part of the function name.
-     * @param  parameters  the function parameters.
-     */
-    FunctionMock(final String name, final List<Expression<Map<String,?>, ?>> parameters) {
-        this.name = name;
-        this.parameters = parameters;
-    }
-
-    /**
-     * Returns the function name.
-     */
-    @Override
-    public ScopedName getFunctionName() {
-        return Names.createScopedName(null, null, name);
-    }
-
-    /**
-     * Returns the type of resources accepted by this mock.
-     */
-    @Override
-    public Class<Map> getResourceClass() {
-        return Map.class;
-    }
-
-    /**
-     * Returns the function parameters.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public List<Expression<Map<String,?>, ?>> getParameters() {
-        return parameters;
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public Object apply(Map<String,?> feature) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Unsupported operation.
-     */
-    @Override
-    public <N> Expression<Map<String,?>, N> toValueType(Class<N> target) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FunctionNamesTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FunctionNamesTest.java
index f3e8064..27072d9 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FunctionNamesTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/FunctionNamesTest.java
@@ -24,18 +24,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.List;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.ComparisonOperator;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BetweenComparisonOperator;
-import org.opengis.filter.NullOperator;
-import org.opengis.filter.NilOperator;
-import org.opengis.filter.LikeOperator;
-import org.opengis.filter.Expression;
-
 
 /**
  * Verifies the values declared in {@link FunctionNames}.
@@ -62,81 +50,6 @@
     }
 
     /**
-     * Base class for dummy implementation of filter.
-     */
-    private static abstract class FilterBase implements ComparisonOperator<Object> {
-        @Override public List<Expression<Object,?>> getExpressions() {return List.of();}
-        @Override public Class<Object> getResourceClass() {return Object.class;}
-        @Override public boolean test(Object resource) {return false;}
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_NULL} name.
-     */
-    @Test
-    public void verifyPropertyIsNull() {
-        final class Instanciable extends FilterBase implements NullOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NULL));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_NIL} name.
-     */
-    @Test
-    public void verifyPropertyIsNil() {
-        final class Instanciable extends FilterBase implements NilOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NIL));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_LIKE} name.
-     */
-    @Test
-    public void verifyPropertyIsLike() {
-        final class Instanciable extends FilterBase implements LikeOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_BETWEEN} name.
-     */
-    @Test
-    public void verifyPropertyIsBetween() {
-        final class Instanciable extends FilterBase implements BetweenComparisonOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#Literal} name.
-     */
-    @Test
-    public void verifyLiteral() {
-        final var expression = new Literal<Object,Object>() {
-            @Override public Object getValue() {return null;}
-            @Override public <N> Expression<Object, N> toValueType(Class<N> target) {
-                throw new UnsupportedOperationException();
-            }
-        };
-        assertEquals(expression.getFunctionName().tip().toString(), FunctionNames.Literal);
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#ValueReference} name.
-     */
-    @Test
-    public void verifyValueReference() {
-        final var expression = new ValueReference<Object,Object>() {
-            @Override public String getXPath()      {return null;}
-            @Override public Object apply(Object o) {return null;}
-            @Override public Class<Object> getResourceClass() {return Object.class;}
-            @Override public <N> Expression<Object,N> toValueType(Class<N> target) {
-                throw new UnsupportedOperationException();
-            }
-        };
-        assertEquals(expression.getFunctionName().tip().toString(), FunctionNames.ValueReference);
-    }
-
-    /**
      * Verifies SQLMM names.
      */
     @Test
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/ValueReferenceMock.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/ValueReferenceMock.java
deleted file mode 100644
index 0f3fe71..0000000
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/privy/ValueReferenceMock.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.filter.privy;
-
-import java.util.Map;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
-
-
-/**
- * Dummy implementation of property value reference.
- * The features handled by this implementation are property-value maps.
- *
- * @author  Johann Sorel (Geomatys)
- *
- * @param  <V>  type of values returned by this expression.
- */
-final class ValueReferenceMock<V> implements ValueReference<Map<String,?>, V> {
-    /**
-     * Name of the property for which to get values.
-     */
-    private final String xpath;
-
-    /**
-     * Expected type of values.
-     */
-    private final Class<V> type;
-
-    /**
-     * Creates a new dummy reference.
-     *
-     * @param xpath  name of the property for which to get values.
-     * @param type   type of values returned by this expression.
-     */
-    ValueReferenceMock(final String xpath, final Class<V> type) {
-        this.xpath = xpath;
-        this.type  = type;
-    }
-
-    /**
-     * Returns the type of resources accepted by this mock.
-     */
-    @Override
-    public Class<Map> getResourceClass() {
-        return Map.class;
-    }
-
-    /**
-     * Returns the name of the property for which to get values.
-     */
-    @Override
-    public String getXPath() {
-        return xpath;
-    }
-
-    /**
-     * Returns the value of the referenced property in the given feature.
-     */
-    @Override
-    public V apply(final Map<String,?> feature) {
-        return type.cast(feature.get(xpath));
-    }
-
-    /**
-     * Returns a reference to the same value but casted to a different type.
-     */
-    @Override
-    public <N> Expression<Map<String,?>, N> toValueType(final Class<N> target) {
-        return new ValueReferenceMock<>(xpath, target);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java
index faf21cf..368d68f 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/RegistryTestCase.java
@@ -23,8 +23,8 @@
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.filter.Optimization;
+import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.WraparoundMethod;
 import org.apache.sis.filter.internal.Node;
@@ -39,12 +39,10 @@
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.test.TestCaseWithLogs;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.capability.AvailableFunction;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.Literal;
 
 
 /**
@@ -65,7 +63,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature, G, Object> factory;
+    private final DefaultFilterFactory<AbstractFeature, G, Object> factory;
 
     /**
      * The geometry library used by this factory.
@@ -90,7 +88,7 @@
      *
      * @see #evaluate(String, Object...)
      */
-    private Expression<Feature,?> function;
+    private Expression<AbstractFeature,?> function;
 
     /**
      * Tolerance threshold for assertions. Default value is 0.
@@ -113,26 +111,6 @@
     }
 
     /**
-     * Tests {@link Registry#describe(String)}.
-     */
-    @Test
-    public void testDescribe() {
-        final var r = new Registry(library);
-        AvailableFunction desc = r.describe("ST_Transform");
-        assertEquals("SQLMM:ST_Transform", desc.getName().toFullyQualifiedName().toString());
-        assertEquals("OGC:Geometry", desc.getReturnType().toFullyQualifiedName().toString());
-        assertEquals(library.rootClass, desc.getReturnType().toJavaType().get());
-
-        desc = r.describe("ST_PointFromText");
-        assertEquals("ST_PointFromText", desc.getName().toString());
-        assertEquals("OGC:Point", desc.getReturnType().toFullyQualifiedName().toString());
-        assertEquals(library.pointClass, desc.getReturnType().toJavaType().get());
-
-        // Tested methods should not log.
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
      * Verifies that attempts to create a function of the given name fail if no argument is provided.
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
@@ -167,7 +145,7 @@
      * @param  type  the type of value in the property.
      * @return a feature with a property of the given type.
      */
-    private Feature createFeatureWithGeometry(final Class<?> type) {
+    private AbstractFeature createFeatureWithGeometry(final Class<?> type) {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(type).setName(P_NAME);
         final var mockType = ftb.setName("Test").build();
@@ -186,7 +164,7 @@
      */
     private Object evaluate(final String name, final Object... values) {
         @SuppressWarnings({"unchecked","rawtypes"})
-        final Literal<Feature,?>[] parameters = new Literal[values.length];
+        final Expression<AbstractFeature,?>[] parameters = new Literal[values.length];
         for (int i=0; i<values.length; i++) {
             parameters[i] = factory.literal(values[i]);
         }
@@ -346,7 +324,7 @@
         assertEnvelopeEquals(result, false, null, 12, 3.3, 13.1, 5.7);
 
         // After testing literal data, try to extract data from a feature.
-        Feature instance = createFeatureWithGeometry(library.polylineClass);
+        AbstractFeature instance = createFeatureWithGeometry(library.polylineClass);
         function = factory.function("ST_Envelope", factory.property(P_NAME, library.polylineClass));
         result = function.apply(instance);
         assertEnvelopeEquals(result, false, null, 12, 3.3, 13.1, 5.7);
@@ -367,7 +345,7 @@
         assertEquals(Boolean.FALSE, evaluate("ST_Intersects", point, geometry));
 
         // Border should intersect. Also use Feature instead of Literal as a source.
-        final Feature instance = createFeatureWithGeometry(library.polygonClass);
+        final AbstractFeature instance = createFeatureWithGeometry(library.polygonClass);
         final var reference = factory.property(P_NAME, library.polygonClass);
         point = library.createPoint(0.2, 0.3);
         function = factory.function("ST_Intersects", reference, factory.literal(point));
@@ -529,7 +507,7 @@
          */
         final Object literal = optimized.getParameters().get(1).apply(null);
         final DirectPosition point = library.castOrWrap(literal).getCentroid();
-        assertArrayEquals(new double[] {30, 10}, point.getCoordinates());
+        assertArrayEquals(new double[] {30, 10}, point.getCoordinate());
 
         // Tested methods should not log.
         loggings.assertNoUnexpectedLog();
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java
index 7bd32b6..b1d8d40 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java
@@ -31,10 +31,9 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.FilterFactory;
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -48,7 +47,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -118,9 +117,9 @@
      * @throws FactoryException if an error occurred while fetching the CRS from a JTS geometry.
      */
     private void verifyPoint(final CoordinateReferenceSystem expectedCRS,
-                             final BiFunction<Expression<Feature, Double>,
-                                              Expression<Feature, Double>,
-                                              Expression<Feature, ?>[]> argumentBundler)
+                             final BiFunction<Expression<AbstractFeature, Double>,
+                                              Expression<AbstractFeature, Double>,
+                                              Expression<AbstractFeature, ?>[]> argumentBundler)
             throws FactoryException
     {
         final var x = factory.literal(1.0);
@@ -130,7 +129,7 @@
         Point point = assertInstanceOf(Point.class, rawPoint);
         CoordinateReferenceSystem pointCRS = JTS.getCoordinateReferenceSystem(point);
         assertEquals(expectedCRS, pointCRS);
-        assertEquals(point.getX(), x.getValue());
-        assertEquals(point.getY(), y.getValue());
+        assertEquals(point.getX(), x.apply(null));
+        assertEquals(point.getY(), y.apply(null));
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/GeometryTypeTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/GeometryTypeTest.java
index 94d06fa..5cd1608 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/GeometryTypeTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/GeometryTypeTest.java
@@ -54,11 +54,9 @@
     public void testTypeName() {
         TypeName name = GeometryType.LINESTRING.getTypeName(org.apache.sis.geometry.wrapper.jts.Factory.INSTANCE);
         assertEquals("OGC:LineString", name.toFullyQualifiedName().toString());
-        assertEquals(org.locationtech.jts.geom.LineString.class, name.toJavaType().get());
 
         name = GeometryType.LINESTRING.getTypeName(org.apache.sis.geometry.wrapper.esri.Factory.INSTANCE);
         assertEquals("OGC:LineString", name.toFullyQualifiedName().toString());
-        assertEquals(com.esri.core.geometry.Polyline.class, name.toJavaType().get());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/j2d/FlatShapeTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/j2d/FlatShapeTest.java
index 4eb9c4e..568f008 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/j2d/FlatShapeTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/j2d/FlatShapeTest.java
@@ -23,8 +23,8 @@
 import org.junit.jupiter.api.Test;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertPathEquals;
+// Specific to the main branch:
+import static org.apache.sis.feature.Assertions.assertPathEquals;
 
 
 /**
@@ -67,7 +67,7 @@
 
         final Path2D.Double r = new Path2D.Double(Path2D.WIND_NON_ZERO);
         createReferenceShape(r, coordinates, closed);
-        assertPathEquals(r.getPathIterator(null), p.getPathIterator(null), STRICT, STRICT, null);
+        assertPathEquals(r.getPathIterator(null), p.getPathIterator(null), STRICT);
     }
 
     /**
@@ -107,7 +107,7 @@
             createReferenceShape(r, coordinates[i], false);
         }
         final MultiPolylines p = new MultiPolylines(polylines);
-        assertPathEquals(r.getPathIterator(null), p.getPathIterator(null), STRICT, STRICT, null);
+        assertPathEquals(r.getPathIterator(null), p.getPathIterator(null), STRICT);
     }
 
     /**
@@ -130,6 +130,6 @@
         createReferenceShape(r, new double[]{4,5, 6,3, 8,5, 3,8, 7,5, 9,3, -2,5}, true);
         createReferenceShape(r, new double[]{3,5, 6,1, -2,7}, false);
         createReferenceShape(r, new double[]{3,8, 10,4, 6,4}, true);
-        assertPathEquals(r.getPathIterator(null), b.build().getPathIterator(null), STRICT, STRICT, null);
+        assertPathEquals(r.getPathIterator(null), b.build().getPathIterator(null), STRICT);
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java
index 6c488a1..c9bce93 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java
@@ -36,8 +36,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -154,8 +154,8 @@
             wrapper.setCoordinateReferenceSystem(crs);
             final GeneralEnvelope envelope = wrapper.getEnvelope();
             assertEquals(crs, envelope.getCoordinateReferenceSystem());
-            assertArrayEquals(new double[] {5, 6}, envelope.getLowerCorner().getCoordinates());
-            assertArrayEquals(new double[] {5, 6}, envelope.getUpperCorner().getCoordinates());
+            assertArrayEquals(new double[] {5, 6}, envelope.getLowerCorner().getCoordinate());
+            assertArrayEquals(new double[] {5, 6}, envelope.getUpperCorner().getCoordinate());
         }
 
         {   /*
@@ -170,8 +170,8 @@
             wrapper.setCoordinateReferenceSystem(crs);
             final GeneralEnvelope envelope = wrapper.getEnvelope();
             assertEquals(crs, envelope.getCoordinateReferenceSystem());
-            assertArrayEquals(new double[] {5, 6, Double.NaN}, envelope.getLowerCorner().getCoordinates());
-            assertArrayEquals(new double[] {5, 6, Double.NaN}, envelope.getUpperCorner().getCoordinates());
+            assertArrayEquals(new double[] {5, 6, Double.NaN}, envelope.getLowerCorner().getCoordinate());
+            assertArrayEquals(new double[] {5, 6, Double.NaN}, envelope.getUpperCorner().getCoordinate());
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedIteratorTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedIteratorTest.java
index 21907b6..bf51189 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedIteratorTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/BandedIteratorTest.java
@@ -25,9 +25,6 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
-
 
 /**
  * Tests {@link BandedIterator} on floating point values.
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/LinearIteratorTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/LinearIteratorTest.java
index 7f845cc..1afacb3 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/LinearIteratorTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/LinearIteratorTest.java
@@ -22,9 +22,6 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
-
 
 /**
  * Tests the linear read-write iterator on signed short integer values.
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/MaskedImageTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/MaskedImageTest.java
index 6a085e4..0999045 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/MaskedImageTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/MaskedImageTest.java
@@ -39,9 +39,6 @@
 import static org.apache.sis.feature.Assertions.assertPixelsEqual;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertSampleValuesEqual;
-
 
 /**
  * Tests {@link MaskedImage}.
@@ -91,7 +88,7 @@
     public void noErrorOnEmptyMasks() {
         final BufferedImage source = monoTile();
         final RenderedImage masked = new MaskedImage(source, new Polygon(), true, new Number[] {127});
-        assertSampleValuesEqual(source, masked, STRICT, null);
+        assertPixelsEqual(source, null, masked, null);
         assertSame(source.getRaster(), masked.getTile(0,0));    // Optimization applied by MaskedImage.
     }
 
@@ -205,7 +202,7 @@
                 3, 3, 3, 3, 2, 2, 2, 2,
                 3, 3, 3, 3, 2, 2, 2, 2
         });
-        assertSampleValuesEqual(expected, masked, STRICT, null);
+        assertPixelsEqual(expected, null, masked, null);
     }
 
     /**
@@ -224,7 +221,7 @@
                 4, 4, 4, 4, 4, 4, 4, 4,
                 4, 4, 4, 4, 4, 4, 4, 4
         });
-        assertSampleValuesEqual(expected, masked, STRICT, null);
+        assertPixelsEqual(expected, null, masked, null);
     }
 
     /**
@@ -243,7 +240,7 @@
                 3, 3, 3, 3, 2, 2, 2, 2,
                 3, 3, 3, 3, 2, 2, 2, 2
         });
-        assertSampleValuesEqual(expected, masked, STRICT, null);
+        assertPixelsEqual(expected, null, masked, null);
     }
 
     /**
@@ -262,7 +259,7 @@
                 4, 4, 4, 4, 4, 4, 4, 4,
                 4, 4, 4, 4, 4, 4, 4, 4
         });
-        assertSampleValuesEqual(expected, masked, STRICT, null);
+        assertPixelsEqual(expected, null, masked, null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/PixelIteratorTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/PixelIteratorTest.java
index b461006..2f7a6f8 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/PixelIteratorTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/PixelIteratorTest.java
@@ -42,9 +42,6 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
-
 
 /**
  * Base class of {@link PixelIterator} tests. This base class tests the default read-write iterator
diff --git a/endorsed/src/org.apache.sis.metadata/main/module-info.java b/endorsed/src/org.apache.sis.metadata/main/module-info.java
index 4aacf66..d4ac51c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/module-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/module-info.java
@@ -164,6 +164,13 @@
             org.apache.sis.storage.xml,
             org.apache.sis.gui;                 // In the "optional" sub-project.
 
+    exports org.apache.sis.pending.geoapi.evolution to
+            org.apache.sis.referencing,
+            org.opengis.geoapi;
+
+    // For instantiation of new CodeList values by reflection.
+    opens org.apache.sis.pending.geoapi.evolution to org.opengis.geoapi;
+
     /*
      * Allow JAXB to use reflection for marshalling and
      * unmarshalling Apache SIS objects in XML documents.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
index a0efb63..9ed116f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
@@ -45,6 +45,9 @@
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Enumeration of some metadata standards. A standard is defined by a set of Java interfaces
@@ -114,7 +117,7 @@
      * after construction should be the same.</p>
      */
     @Configuration
-    static final boolean IMPLEMENTATION_CAN_ALTER_API = false;
+    static final boolean IMPLEMENTATION_CAN_ALTER_API = true;
 
     /**
      * Metadata instances defined in this class. Standards will be tested in the order they appear in this array.
@@ -148,7 +151,7 @@
 
     /**
      * An instance working on ISO 19123 standard as defined by GeoAPI interfaces
-     * in the {@link org.opengis.coverage} package and sub-packages.
+     * in the {@code org.opengis.coverage} package and sub-packages.
      */
     public static final MetadataStandard ISO_19123;
     static {
@@ -765,10 +768,10 @@
      * <p>In the particular case of Apache SIS implementation, all values in the information map
      * additionally implement the following interfaces:</p>
      * <ul>
-     *   <li>{@link Identifier} with the following properties:
+     *   <li>{@link ReferenceIdentifier} with the following properties:
      *     <ul>
      *       <li>The {@linkplain Identifier#getAuthority() authority} is this metadata standard {@linkplain #getCitation() citation}.</li>
-     *       <li>The {@linkplain Identifier#getCodeSpace() codespace} is the standard name of the interface that contain the property.</li>
+     *       <li>The {@linkplain ReferenceIdentifier#getCodeSpace() codespace} is the standard name of the interface that contain the property.</li>
      *       <li>The {@linkplain Identifier#getCode() code} is the standard name of the property.</li>
      *     </ul>
      *   </li>
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyComparator.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyComparator.java
index a253377..5f48dee 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyComparator.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyComparator.java
@@ -24,6 +24,9 @@
 import org.opengis.annotation.UML;
 import org.opengis.annotation.Obligation;
 
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
+
 
 /**
  * The comparator for sorting the properties in a metadata object.
@@ -124,8 +127,14 @@
                      * the parent class, and we want the properties in the parent class to be sorted first.
                      * If duplicated properties are found, keep the first occurence (i.e. sort the property
                      * with the most specialized child that declared it).
+                     *
+                     * We make an exception for ResponsibleParty.role, which should be replaced by Party.role
+                     * but this replacement is not yet effective in GeoAPI 3.0.
                      */
-                    order.putIfAbsent(propOrder[i], order.size());
+                    final String prop = propOrder[i];
+                    if (!"role".equals(prop) || !ResponsibleParty.class.isAssignableFrom(implementation)) {
+                        order.putIfAbsent(prop, order.size());
+                    }
                 }
             }
             implementation = implementation.getSuperclass();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyInformation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyInformation.java
index a28bc00..f570d26 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyInformation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyInformation.java
@@ -181,6 +181,30 @@
     }
 
     /**
+     * Unconditionally returns {@code null}.
+     *
+     * @deprecated This property was defined in the 2003 edition of ISO 19115,
+     *             but has been removed in the 2014 edition.
+     */
+    @Override
+    @Deprecated
+    public String getShortName() {
+        return null;
+    }
+
+    /**
+     * Unconditionally returns {@code null}.
+     *
+     * @deprecated This property was defined in the 2003 edition of ISO 19115,
+     *             but has been removed in the 2014 edition.
+     */
+    @Override
+    @Deprecated
+    public Integer getDomainCode() {
+        return null;
+    }
+
+    /**
      * Returns the definition of this property, or {@code null} if none.
      */
     @Override
@@ -302,6 +326,22 @@
     }
 
     /**
+     * Unconditionally returns {@code null}.
+     */
+    public InternationalString getRationale() {
+        return null;
+    }
+
+    /**
+     * Unconditionally returns an empty list.
+     */
+    @Override
+    @Deprecated
+    public Collection<InternationalString> getRationales() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Returns the name of the person or organization creating the element.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java
index 11c2ec4..5b791fc 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java
@@ -22,8 +22,8 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import static org.apache.sis.metadata.ValueExistencePolicy.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -133,7 +133,7 @@
                 } else if (!prune && element instanceof Emptiable) {
                     isEmptyElement = ((Emptiable) element).isEmpty();
                     // If 'prune' is true, we will rather test for Emptiable after our pruning attempt.
-                } else if (!(element instanceof ControlledVocabulary)) {
+                } else if (!(element instanceof Enum<?>) && !(element instanceof CodeList<?>)) {
                     final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
                     if (standard != null) {
                         /*
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java
index f60fd51..84db6e3 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java
@@ -26,10 +26,6 @@
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.system.Modules;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.annotation.Classifier;
-import org.opengis.annotation.Stereotype;
-
 
 /**
  * Information about an Apache SIS metadata standard implementation.
@@ -54,6 +50,12 @@
     private final String implementationPackage;
 
     /**
+     * The prefixes that implementation classes may have.
+     * The most common prefixes should be first, because the prefixes will be tried in that order.
+     */
+    private static final String[] IMPL_PREFIXES = {"Default", "Abstract"};
+
+    /**
      * The acronyms that implementation classes may have, or {@code null} if none. If non-null,
      * then this array shall contain (<var>full text</var>, <var>acronym</var>) pairs. The full
      * text shall appear to the end of the class name, otherwise it is not replaced. This is
@@ -95,17 +97,6 @@
     }
 
     /**
-     * Returns {@code true} if the given type is conceptually abstract.
-     * The given type is usually an interface, so here "abstract" cannot be in the Java sense.
-     * If this method cannot find information about whether the given type is abstract,
-     * then this method conservatively returns {@code false}.
-     */
-    private static boolean isAbstract(final Class<?> type) {
-        final Classifier c = type.getAnnotation(Classifier.class);
-        return (c != null) && c.value() == Stereotype.ABSTRACT;
-    }
-
-    /**
      * Accepts Apache SIS implementation classes as "pseudo-interfaces" if they are annotated with {@link UML}.
      * We use this feature for example in the transition from ISO 19115:2003 to ISO 19115:2014, when new API is
      * defined in Apache SIS but not yet available in GeoAPI interfaces.
@@ -155,17 +146,21 @@
                         }
                     }
                     /*
-                     * Try to instantiate the implementation class.
+                     * Try to insert a prefix in front of the class name, until a match is found.
                      */
                     final int prefixPosition = buffer.lastIndexOf(".") + 1;
-                    buffer.insert(prefixPosition, isAbstract(type) ? "Abstract" : "Default");
-                    classname = buffer.toString();
-                    try {
-                        candidate = Class.forName(classname);
+                    int length = 0;
+                    for (final String p : IMPL_PREFIXES) {
+                        classname = buffer.replace(prefixPosition, prefixPosition + length, p).toString();
+                        try {
+                            candidate = Class.forName(classname);
+                        } catch (ClassNotFoundException e) {
+                            Logging.recoverableException(LOGGER, MetadataStandard.class, "getImplementation", e);
+                            length = p.length();
+                            continue;
+                        }
                         implementations.put(type, candidate);
                         return candidate.asSubclass(type);
-                    } catch (ClassNotFoundException e) {
-                        Logging.recoverableException(LOGGER, MetadataStandard.class, "getImplementation", e);
                     }
                     implementations.put(type, Void.TYPE);                       // Marker for "class not found".
                 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/CitationConstant.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/CitationConstant.java
index 4be6930..7e8e848 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/CitationConstant.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/CitationConstant.java
@@ -38,10 +38,6 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.BrowseGraphic;
-
 
 /**
  * Base class for the {@code public static final Citation} constants defined in some SIS classes.
@@ -221,8 +217,6 @@
     @Override public Collection<PresentationForm>               getPresentationForms()       {return delegate().getPresentationForms();}
     @Override public Series                                     getSeries()                  {return delegate().getSeries();}
     @Override public InternationalString                        getOtherCitationDetails()    {return delegate().getOtherCitationDetails();}
-    @Override public Collection<? extends OnlineResource>       getOnlineResources()         {return delegate().getOnlineResources();}
-    @Override public Collection<? extends BrowseGraphic>        getGraphics()                {return delegate().getGraphics();}
     @Override public String                                     getISBN()                    {return delegate().getISBN();}
     @Override public String                                     getISSN()                    {return delegate().getISSN();}
 
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ServicesForUtility.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ServicesForUtility.java
index 5b67ae9..e049709 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ServicesForUtility.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ServicesForUtility.java
@@ -19,9 +19,9 @@
 import java.text.Format;
 import java.util.Locale;
 import java.util.TimeZone;
-import java.util.function.Supplier;
 import javax.sql.DataSource;
 import java.sql.SQLException;
+import java.util.function.Supplier;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Exceptions;
@@ -35,8 +35,8 @@
 import org.apache.sis.metadata.privy.ReferencingServices;
 import org.apache.sis.xml.bind.Context;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -69,10 +69,10 @@
      * @param  locale  desired locale for the title.
      * @return the title.
      *
-     * @see org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)
+     * @see org.apache.sis.util.iso.Types#getCodeTitle(CodeList)
      */
     @Override
-    public String getCodeTitle(final ControlledVocabulary code, final Locale locale) {
+    public String getCodeTitle(final CodeList<?> code, final Locale locale) {
         return Types.getCodeTitle(code).toString(locale);
     }
 
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists.properties b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists.properties
new file mode 100644
index 0000000..cb51675
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists.properties
@@ -0,0 +1,18 @@
+# Copy from GeoAPI 3.0 (BSD-like license)
+CI_DateTypeCode.creation=Creation
+CI_DateTypeCode.publication=Publication
+CI_DateTypeCode.revision=Revision
+CI_OnLineFunctionCode.download=Download
+CI_OnLineFunctionCode.information=Information
+CI_OnLineFunctionCode.offlineAccess=Offline access
+CI_OnLineFunctionCode.order=Order
+CI_OnLineFunctionCode.search=Search
+MD_ScopeCode.attribute=Attribute
+MD_ScopeCode.attributeType=Attribute type
+MD_ScopeCode.dataset=Dataset
+MD_ScopeCode.series=Series
+MD_ScopeCode.nonGeographicDataset=Non geographic dataset
+MD_ScopeCode.feature=Feature
+MD_ScopeCode.featureType=Feature type
+MD_ScopeCode.propertyType=Property type
+MD_ScopeCode.tile=Tile
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists_en.properties b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists_en.properties
new file mode 100644
index 0000000..d70945d
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists_en.properties
@@ -0,0 +1 @@
+# Inherit all resources from CodeLists.properties
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists_fr.properties b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists_fr.properties
new file mode 100644
index 0000000..f8afb6f
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/CodeLists_fr.properties
@@ -0,0 +1,18 @@
+# Copy from GeoAPI 3.0 (BSD-like license)
+CI_DateTypeCode.creation=Création
+CI_DateTypeCode.publication=Publication
+CI_DateTypeCode.revision=Révision
+CI_OnLineFunctionCode.download=Téléchargement
+CI_OnLineFunctionCode.information=Information
+CI_OnLineFunctionCode.offlineAccess=Accès hors ligne
+CI_OnLineFunctionCode.order=Commande en ligne
+CI_OnLineFunctionCode.search=Moteur de recherche
+MD_ScopeCode.attribute=Attribut
+MD_ScopeCode.attributeType=Type d\u2019attribut
+MD_ScopeCode.dataset=Jeu de données
+MD_ScopeCode.series=Série
+MD_ScopeCode.nonGeographicDataset=Jeu de données non géographiques
+MD_ScopeCode.feature=Élément
+MD_ScopeCode.featureType=Type d\u2019élément
+MD_ScopeCode.propertyType=Type de propriété
+MD_ScopeCode.tile=Tuile
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
index 1ed8ffc..3038d78 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
@@ -40,9 +40,10 @@
 import org.opengis.metadata.Obligation;
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.AbstractSet;
-import java.util.Iterator;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -551,8 +552,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "rationale")
+    @UML(identifier="rationale", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getRationale() {
         return LegacyPropertyAdapter.getSingleton(rationales, InternationalString.class, null,
                 DefaultExtendedElementInformation.class, "getRationale");
@@ -578,29 +579,7 @@
     @Deprecated(since="1.0")
     @Dependencies("getRationale")
     public Collection<InternationalString> getRationales() {
-        return new AbstractSet<InternationalString>() {
-            /** Returns 0 if empty, or 1 if a density has been specified. */
-            @Override public int size() {
-                return getRationale() != null ? 1 : 0;
-            }
-
-            /** Returns an iterator over 0 or 1 element. Current iterator implementation is unmodifiable. */
-            @Override public Iterator<InternationalString> iterator() {
-                return CollectionsExt.singletonOrEmpty(getRationale()).iterator();
-            }
-
-            /** Adds an element only if the set is empty. This method is invoked by JAXB at unmarshalling time. */
-            @Override public boolean add(final InternationalString newValue) {
-                if (isEmpty()) {
-                    setRationale(newValue);
-                    return true;
-                } else {
-                    LegacyPropertyAdapter.warnIgnoredExtraneous(InternationalString.class,
-                            DefaultExtendedElementInformation.class, "setRationales");
-                    return false;
-                }
-            }
-        };
+        return rationales = nonNullCollection(rationales, InternationalString.class);
     }
 
     /**
@@ -610,8 +589,7 @@
      */
     @Deprecated(since="1.0")
     public void setRationales(final Collection<? extends InternationalString> newValues) {
-        setRationale(LegacyPropertyAdapter.getSingleton(newValues, InternationalString.class,
-                null, DefaultExtendedElementInformation.class, "setRationales"));
+        rationales = writeCollection(newValues, rationales, InternationalString.class);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultIdentifier.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultIdentifier.java
index 53fdf82..bc7e45d 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultIdentifier.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultIdentifier.java
@@ -27,6 +27,12 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.xml.Namespaces;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Value uniquely identifying an object within a namespace.
@@ -218,10 +224,17 @@
         super(object);
         if (object != null) {
             code        = object.getCode();
-            codeSpace   = object.getCodeSpace();
-            version     = object.getVersion();
-            description = object.getDescription();
             authority   = object.getAuthority();
+            if (object instanceof DefaultIdentifier) {
+                final DefaultIdentifier c = (DefaultIdentifier) object;
+                codeSpace   = c.getCodeSpace();
+                version     = c.getVersion();
+                description = c.getDescription();
+            } else if (object instanceof ReferenceIdentifier) {
+                final ReferenceIdentifier c = (ReferenceIdentifier) object;
+                codeSpace = c.getCodeSpace();
+                version   = c.getVersion();
+            }
         }
     }
 
@@ -313,8 +326,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "codeSpace")
+    @UML(identifier="codeSpace", obligation=OPTIONAL, specification=ISO_19115)
     public String getCodeSpace() {
         return codeSpace;
     }
@@ -341,8 +354,8 @@
      *
      * @return the version identifier for the namespace, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "version")
+    @UML(identifier="version", obligation=OPTIONAL, specification=ISO_19115)
     public String getVersion() {
         return version;
     }
@@ -367,8 +380,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java
index 3a0b9aa..b7340eb 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java
@@ -86,8 +86,14 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.MetadataScope;
+// Specific to the main branch:
+import java.util.LinkedHashMap;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.xml.bind.metadata.code.MD_CharacterSetCode;
 
 
 /**
@@ -223,7 +229,7 @@
      * Scope to which the metadata applies.
      */
     @SuppressWarnings("serial")
-    private Collection<MetadataScope> metadataScopes;
+    private Collection<DefaultMetadataScope> metadataScopes;
 
     /**
      * Parties responsible for the metadata information.
@@ -354,8 +360,8 @@
      * @param identificationInfo  basic information about the resource to which the metadata applies.
      */
     public DefaultMetadata(final ResponsibleParty contact,
-                           final Date           dateStamp,
-                           final Identification identificationInfo)
+                           final Date             dateStamp,
+                           final Identification   identificationInfo)
     {
         this.contacts  = singleton(contact, ResponsibleParty.class);
         this.identificationInfo = singleton(identificationInfo, Identification.class);
@@ -376,16 +382,7 @@
     public DefaultMetadata(final Metadata object) {
         super(object);
         if (object != null) {
-            identifiers                   = singleton(object.getMetadataIdentifier(), Identifier.class);
-            parentMetadata                = object.getParentMetadata();
-            locales                       = copyMap       (object.getLocalesAndCharsets(),            Locale.class);
-            metadataScopes                = copyCollection(object.getMetadataScopes(),                MetadataScope.class);
             contacts                      = copyCollection(object.getContacts(),                      ResponsibleParty.class);
-            dateInfo                      = copyCollection(object.getDateInfo(),                      CitationDate.class);
-            metadataStandards             = copyCollection(object.getMetadataStandards(),             Citation.class);
-            metadataProfiles              = copyCollection(object.getMetadataProfiles(),              Citation.class);
-            alternativeMetadataReferences = copyCollection(object.getAlternativeMetadataReferences(), Citation.class);
-            metadataLinkages              = copyCollection(object.getMetadataLinkages(),              OnlineResource.class);
             spatialRepresentationInfo     = copyCollection(object.getSpatialRepresentationInfo(),     SpatialRepresentation.class);
             referenceSystemInfo           = copyCollection(object.getReferenceSystemInfo(),           ReferenceSystem.class);
             metadataExtensionInfo         = copyCollection(object.getMetadataExtensionInfo(),         MetadataExtensionInformation.class);
@@ -398,7 +395,35 @@
             applicationSchemaInfo         = copyCollection(object.getApplicationSchemaInfo(),         ApplicationSchemaInformation.class);
             metadataMaintenance           = object.getMetadataMaintenance();
             acquisitionInformation        = copyCollection(object.getAcquisitionInformation(),        AcquisitionInformation.class);
-            resourceLineages              = copyCollection(object.getResourceLineages(),              Lineage.class);
+            if (object instanceof DefaultMetadata) {
+                final DefaultMetadata c = (DefaultMetadata) object;
+                identifiers                   = singleton(c.getMetadataIdentifier(), Identifier.class);
+                parentMetadata                = c.getParentMetadata();
+                locales                       = copyMap       (c.getLocalesAndCharsets(),            Locale.class);
+                metadataScopes                = copyCollection(c.getMetadataScopes(),                DefaultMetadataScope.class);
+                dateInfo                      = copyCollection(c.getDateInfo(),                      CitationDate.class);
+                metadataStandards             = copyCollection(c.getMetadataStandards(),             Citation.class);
+                metadataProfiles              = copyCollection(c.getMetadataProfiles(),              Citation.class);
+                alternativeMetadataReferences = copyCollection(c.getAlternativeMetadataReferences(), Citation.class);
+                metadataLinkages              = copyCollection(c.getMetadataLinkages(),              OnlineResource.class);
+                resourceLineages              = copyCollection(c.getResourceLineages(),              Lineage.class);
+            } else {
+                setFileIdentifier         (object.getFileIdentifier());
+                setParentIdentifier       (object.getParentIdentifier());
+                setLanguage               (object.getLanguage());
+                setLocales                (object.getLocales());
+                setCharacterSet           (object.getCharacterSet());
+                setHierarchyLevels        (object.getHierarchyLevels());
+                setHierarchyLevelNames    (object.getHierarchyLevelNames());
+                setDateStamp              (object.getDateStamp());
+                setMetadataStandardName   (object.getMetadataStandardName());
+                setMetadataStandardVersion(object.getMetadataStandardVersion());
+                try {
+                    setDataSetUri(object.getDataSetUri());
+                } catch (URISyntaxException e) {
+                    throw new IllegalArgumentException(e);
+                }
+            }
         }
     }
 
@@ -479,9 +504,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "metadataIdentifier")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="metadataIdentifier", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getMetadataIdentifier() {
         return super.getIdentifier();
     }
@@ -558,8 +583,8 @@
      *
      * @since 1.0
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="defaultLocale+otherLocale", obligation=CONDITIONAL, specification=ISO_19115)
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
     }
@@ -644,6 +669,27 @@
     }
 
     /**
+     * Sets information about an alternatively used localized character string for a linguistic extension.
+     *
+     * @param  newValues  the new locales.
+     *
+     * @deprecated Replaced by putting keys in {@link #getLocalesAndCharsets()}.
+     */
+    @Deprecated
+    public void setLocales(final Collection<? extends Locale> newValues) {
+        final Collection<Locale> legacy = getLocales();
+        if (legacy != null) {
+            legacy.addAll(newValues);
+        } else if (!newValues.isEmpty()) {
+            final Map<Locale,Charset> locales = new LinkedHashMap<>();
+            for (final Locale locale : newValues) {
+                locales.put(locale, null);
+            }
+            setLocalesAndCharsets(locales);
+        }
+    }
+
+    /**
      * Converter from {@link PT_Locale} and {@link Locale}.
      */
     private static final class ToLocale extends SurjectiveConverter<PT_Locale,Locale> {
@@ -691,7 +737,7 @@
     @Dependencies("getLocalesAndCharsets")
     // @XmlElement at the end of this class.
     public CharacterSet getCharacterSet() {
-        return CharacterSet.fromCharset(getCharacterSets());
+        return MD_CharacterSetCode.fromCharset(getCharacterSets());
     }
 
     /**
@@ -714,9 +760,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "parentMetadata")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="parentMetadata", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getParentMetadata() {
         return parentMetadata;
     }
@@ -783,25 +829,35 @@
     /**
      * Returns the scope or type of resource for which metadata is provided.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code MetadataScope} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return scope or type of resource for which metadata is provided.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<MetadataScope> getMetadataScopes() {
-        return metadataScopes = nonNullCollection(metadataScopes, MetadataScope.class);
+    @UML(identifier="metadataScope", obligation=CONDITIONAL, specification=ISO_19115)
+    public Collection<DefaultMetadataScope> getMetadataScopes() {
+        return metadataScopes = nonNullCollection(metadataScopes, DefaultMetadataScope.class);
     }
 
     /**
      * Sets the scope or type of resource for which metadata is provided.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code MetadataScope} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new scope or type of resource.
      *
      * @since 0.5
      */
-    public void setMetadataScopes(final Collection<? extends MetadataScope> newValues) {
-        metadataScopes = writeCollection(newValues, metadataScopes, MetadataScope.class);
+    public void setMetadataScopes(final Collection<? extends DefaultMetadataScope> newValues) {
+        metadataScopes = writeCollection(newValues, metadataScopes, DefaultMetadataScope.class);
     }
 
     /**
@@ -820,22 +876,19 @@
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
         return new MetadataScopeAdapter<ScopeCode>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected MetadataScope wrap(final ScopeCode value) {
+            @Override protected DefaultMetadataScope wrap(final ScopeCode value) {
                 return new DefaultMetadataScope(value, null);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected ScopeCode unwrap(final MetadataScope container) {
+            @Override protected ScopeCode unwrap(final DefaultMetadataScope container) {
                 return container.getResourceScope();
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final MetadataScope container, final ScopeCode value) {
-                if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setResourceScope(value);
-                    return true;
-                }
-                return false;
+            @Override protected boolean update(final DefaultMetadataScope container, final ScopeCode value) {
+                container.setResourceScope(value);
+                return true;
             }
         }.validOrNull();
     }
@@ -870,23 +923,20 @@
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
         return new MetadataScopeAdapter<String>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected MetadataScope wrap(final String value) {
+            @Override protected DefaultMetadataScope wrap(final String value) {
                 return new DefaultMetadataScope(null, value);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected String unwrap(final MetadataScope container) {
+            @Override protected String unwrap(final DefaultMetadataScope container) {
                 final InternationalString name = container.getName();
                 return (name != null) ? name.toString() : null;
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final MetadataScope container, final String value) {
-                if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setName(value != null ? new SimpleInternationalString(value) : null);
-                    return true;
-                }
-                return false;
+            @Override protected boolean update(final DefaultMetadataScope container, final String value) {
+                container.setName(value != null ? new SimpleInternationalString(value) : null);
+                return true;
             }
         }.validOrNull();
     }
@@ -939,8 +989,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="dateInfo", obligation=MANDATORY, specification=ISO_19115)
     public Collection<CitationDate> getDateInfo() {
         return dateInfo = nonNullCollection(dateInfo, CitationDate.class);
     }
@@ -1031,8 +1081,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataStandard", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getMetadataStandards() {
         return metadataStandards = nonNullCollection(metadataStandards, Citation.class);
     }
@@ -1059,8 +1109,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataProfile", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getMetadataProfiles() {
         return metadataProfiles = nonNullCollection(metadataProfiles, Citation.class);
     }
@@ -1084,8 +1134,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="alternativeMetadataReference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAlternativeMetadataReferences() {
         return alternativeMetadataReferences = nonNullCollection(alternativeMetadataReferences, Citation.class);
     }
@@ -1211,8 +1261,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataLinkage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getMetadataLinkages() {
         return metadataLinkages = nonNullCollection(metadataLinkages, OnlineResource.class);
     }
@@ -1246,8 +1296,8 @@
         if (FilterByVersion.LEGACY_METADATA.accept() && (info = getIdentificationInfo()) != null) {
             for (final Identification identification : info) {
                 final Citation citation = identification.getCitation();
-                if (citation != null) {
-                    final Collection<? extends OnlineResource> onlineResources = citation.getOnlineResources();
+                if (citation instanceof DefaultCitation) {
+                    final Collection<? extends OnlineResource> onlineResources = ((DefaultCitation) citation).getOnlineResources();
                     if (onlineResources != null) {
                         for (final OnlineResource link : onlineResources) {
                             final URI uri = link.getLinkage();
@@ -1577,8 +1627,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resourceLineage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Lineage> getResourceLineages() {
         return resourceLineages = nonNullCollection(resourceLineages, Lineage.class);
     }
@@ -1705,7 +1755,7 @@
     }
 
     @XmlElement(name = "metadataScope")
-    private Collection<MetadataScope> getMetadataScope() {
+    private Collection<DefaultMetadataScope> getMetadataScope() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getMetadataScopes() : null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadataScope.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadataScope.java
index c1fe82d..121d6a7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadataScope.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadataScope.java
@@ -23,8 +23,11 @@
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.MetadataScope;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -34,6 +37,14 @@
  * <div class="preformat">{@code MD_MetadataScope}
  * {@code   └─resourceScope……} Resource scope</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code MetadataScope} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -53,7 +64,8 @@
     "name"
 })
 @XmlRootElement(name = "MD_MetadataScope")
-public class DefaultMetadataScope extends ISOMetadata implements MetadataScope {
+@UML(identifier="MD_MetadataScope", specification=ISO_19115)
+public class DefaultMetadataScope extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -93,10 +105,8 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(MetadataScope)
      */
-    public DefaultMetadataScope(final MetadataScope object) {
+    public DefaultMetadataScope(final DefaultMetadataScope object) {
         super(object);
         if (object != null) {
             resourceScope = object.getResourceScope();
@@ -105,37 +115,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultMetadataScope}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultMetadataScope} instance is created using the
-     *       {@linkplain #DefaultMetadataScope(MetadataScope) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultMetadataScope castOrCopy(final MetadataScope object) {
-        if (object == null || object instanceof DefaultMetadataScope) {
-            return (DefaultMetadataScope) object;
-        }
-        return new DefaultMetadataScope(object);
-    }
-
-    /**
      * Returns the code for the scope.
      *
      * @return the code for the scope.
      */
-    @Override
     @XmlElement(name = "resourceScope", required = true)
+    @UML(identifier="resourceScope", obligation=MANDATORY, specification=ISO_19115)
     public ScopeCode getResourceScope() {
         return resourceScope;
     }
@@ -155,8 +140,8 @@
      *
      * @return description of the scope, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "name")
+    @UML(identifier="name", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/MetadataScopeAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
index c0bf7c2..cfdb969 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
@@ -23,9 +23,6 @@
 import java.util.ConcurrentModificationException;
 import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.MetadataScope;
-
 
 /**
  * A specialization of {@link LegacyPropertyAdapter} which will try to merge the
@@ -34,25 +31,25 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,MetadataScope> {
+abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,DefaultMetadataScope> {
     /**
      * @param scopes Value of {@link DefaultMetadata#getMetadataScopes()}.
      */
-    MetadataScopeAdapter(final Collection<MetadataScope> scopes) {
+    MetadataScopeAdapter(final Collection<DefaultMetadataScope> scopes) {
         super(scopes);
     }
 
     /**
      * Invoked (indirectly) by JAXB when adding a new scope code or scope name. This implementation searches
-     * for an existing {@link MetadataScope} instance with a free slot for the new value before to create a
+     * for an existing {@code MetadataScope} instance with a free slot for the new value before to create a
      * new {@link DefaultMetadataScope} instance.
      */
     @Override
     public boolean add(final L newValue) {
         int n = 0;
-        final Iterator<MetadataScope> it = elements.iterator();
+        final Iterator<DefaultMetadataScope> it = elements.iterator();
         while (it.hasNext()) {
-            MetadataScope scope = it.next();
+            DefaultMetadataScope scope = it.next();
             if (unwrap(scope) != null) {
                 n++;
                 continue;
@@ -63,18 +60,16 @@
              * But if the metadata is not modifiable, then we will need to clone it and replaces the element in
              * the collection.
              */
-            if (!(scope instanceof DefaultMetadataScope) ||
-                    ((DefaultMetadataScope) scope).state() == DefaultMetadataScope.State.FINAL)
-            {
+            if (scope.state() == DefaultMetadataScope.State.FINAL) {
                 scope = new DefaultMetadataScope(scope);
                 if (elements instanceof List<?>) {
-                    ((List<MetadataScope>) elements).set(n, scope);
+                    ((List<DefaultMetadataScope>) elements).set(n, scope);
                 } else {
                     /*
                      * Not a list. Delete all the remaining parts, substitute the value
                      * and reinsert everything in the same order.
                      */
-                    final MetadataScope[] remaining = new MetadataScope[elements.size() - n];
+                    final DefaultMetadataScope[] remaining = new DefaultMetadataScope[elements.size() - n];
                     remaining[0] = scope;
                     n = 1;
                     it.remove();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
index 355f11c..7da3c4d 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
@@ -68,7 +68,7 @@
     "types",
     "functions",
     "extents",
-    "objectiveOccurrences",
+    "objectiveOccurences",
     "pass",
     "sensingInstruments"
 })
@@ -77,7 +77,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 3312985886806161441L;
+    private static final long serialVersionUID = 8273806197892815938L;
 
     /**
      * Priority applied to the target.
@@ -108,7 +108,7 @@
      * Event or events associated with objective completion.
      */
     @SuppressWarnings("serial")
-    private Collection<Event> objectiveOccurrences;
+    private Collection<Event> objectiveOccurences;
 
     /**
      * Pass of the platform over the objective.
@@ -145,7 +145,7 @@
             types                = copyCollection(object.getTypes(), ObjectiveType.class);
             functions            = copyCollection(object.getFunctions(), InternationalString.class);
             extents              = copyCollection(object.getExtents(), Extent.class);
-            objectiveOccurrences = copyCollection(object.getObjectiveOccurences(), Event.class);
+            objectiveOccurences  = copyCollection(object.getObjectiveOccurences(), Event.class);
             pass                 = copyCollection(object.getPass(), PlatformPass.class);
             sensingInstruments   = copyCollection(object.getSensingInstruments(), Instrument.class);
         }
@@ -295,45 +295,27 @@
     /**
      * Returns the event or events associated with objective completion.
      *
-     * @return events associated with objective completion.
-     *
-     * @since 1.0
-     */
-    @XmlElement(name = "objectiveOccurence", required = true)
-    public Collection<Event> getObjectiveOccurrences() {
-        return objectiveOccurrences = nonNullCollection(objectiveOccurrences, Event.class);
-    }
-
-    /**
-     * @deprecated Renamed {@link #getObjectiveOccurrences()}.
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is misspelled (missing "r"). Its name may be fixed in GeoAPI 4.0.</div>
      *
      * @return events associated with objective completion.
      */
     @Override
-    @Deprecated
+    @XmlElement(name = "objectiveOccurence", required = true)
     public Collection<Event> getObjectiveOccurences() {
-        return getObjectiveOccurrences();
+        return objectiveOccurences = nonNullCollection(objectiveOccurences, Event.class);
     }
 
     /**
      * Sets the event or events associated with objective completion.
      *
-     * @param  newValues  the new objective occurrences values.
-     *
-     * @since 1.0
-     */
-    public void setObjectiveOccurrences(final Collection<? extends Event> newValues) {
-        objectiveOccurrences = writeCollection(newValues, objectiveOccurrences, Event.class);
-    }
-
-    /**
-     * @deprecated Renamed {@link #setObjectiveOccurrences(Collection)}.
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is misspelled (missing "r"). Its name may be fixed in GeoAPI 4.0.</div>
      *
      * @param  newValues  the new objective occurrences values.
      */
-    @Deprecated
     public void setObjectiveOccurences(final Collection<? extends Event> newValues) {
-        setObjectiveOccurrences(newValues);
+        objectiveOccurences = writeCollection(newValues, objectiveOccurences, Event.class);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/AbstractParty.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/AbstractParty.java
index 143fe50..7d413cb 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/AbstractParty.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/AbstractParty.java
@@ -30,10 +30,11 @@
 import org.apache.sis.xml.IdentifierSpace;
 import org.apache.sis.xml.bind.NonMarshalledAuthority;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
-import org.opengis.metadata.citation.Party;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -44,6 +45,14 @@
  * <div class="preformat">{@code CI_Party}
  * {@code   └─name……} Name of the party.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Party} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -69,7 +78,8 @@
     DefaultIndividual.class,
     DefaultOrganisation.class
 })
-public class AbstractParty extends ISOMetadata implements Party {
+@UML(identifier="CI_Party", specification=ISO_19115)
+public class AbstractParty extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -110,10 +120,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Party)
      */
-    public AbstractParty(final Party object) {
+    public AbstractParty(final AbstractParty object) {
         super(object);
         if (object != null) {
             name        = object.getName();
@@ -122,47 +130,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link Individual} or {@link Organisation},
-     *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
-     *       Note that if the given object implements more than one of the above-cited interfaces,
-     *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code AbstractParty}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code AbstractParty} instance is created using the
-     *       {@linkplain #AbstractParty(Party) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static AbstractParty castOrCopy(final Party object) {
-        if (object instanceof Individual) {
-            return DefaultIndividual.castOrCopy((Individual) object);
-        }
-        if (object instanceof Organisation) {
-            return DefaultOrganisation.castOrCopy((Organisation) object);
-        }
-        if (object == null || object instanceof AbstractParty) {
-            return (AbstractParty) object;
-        }
-        return new AbstractParty(object);
-    }
-
-    /**
      * Return the name of the party.
      *
      * @return name of the party.
      */
-    @Override
     @XmlElement(name = "name")
+    @UML(identifier="name", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
@@ -219,8 +192,8 @@
      *
      * @return contact information for the party.
      */
-    @Override
     @XmlElement(name = "contactInfo")
+    @UML(identifier="contactInfo", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Contact> getContactInfo() {
         return contactInfo = nonNullCollection(contactInfo, Contact.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java
index 46dfd1a..cf77c20 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java
@@ -39,6 +39,9 @@
 import org.apache.sis.system.SystemListener;
 import org.apache.sis.metadata.iso.DefaultIdentifier;           // For javadoc
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * A set of predefined constants and static methods working on {@linkplain Citation citations}.
@@ -732,10 +735,10 @@
                      * Found a possible match. We will take the code space in account only if it is defined
                      * by both identifiers. If a code space is undefined, we consider that we have a match.
                      */
-                    if (identifier != null) {
-                        final String codeSpace = identifier.getCodeSpace();
-                        if (codeSpace != null) {
-                            final String cs = citId.getCodeSpace();
+                    if (identifier instanceof ReferenceIdentifier) {
+                        final String codeSpace = ((ReferenceIdentifier) identifier).getCodeSpace();
+                        if (codeSpace != null && citId instanceof ReferenceIdentifier) {
+                            final String cs = ((ReferenceIdentifier) citId).getCodeSpace();
                             if (cs != null && !equalsFiltered(codeSpace, cs)) {
                                 continue;       // Check other identifiers.
                             }
@@ -757,7 +760,8 @@
                     final CharSequence localPart = code.subSequence(++s, length);
                     for (it = citIds.iterator(); it.hasNext();) {
                         final Identifier id = it.next();
-                        if (equalsFiltered(codeSpace, id.getCodeSpace()) && equalsFiltered(localPart, id.getCode())) {
+                        final String cs = (id instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) id).getCodeSpace() : null;
+                        if (equalsFiltered(codeSpace, cs) && equalsFiltered(localPart, id.getCode())) {
                             return true;
                         }
                     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultAddress.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultAddress.java
index 76f50f1..d658155 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultAddress.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultAddress.java
@@ -56,7 +56,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -1709738216789373888L;
+    private static final long serialVersionUID = 1357443146723845129L;
 
     /**
      * State, province of the location.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java
index 7f06cf9..26e877a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java
@@ -44,6 +44,11 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Standardized resource reference.
@@ -206,7 +211,7 @@
      *
      * @see #castOrCopy(Citation)
      */
-    @SuppressWarnings({"this-escape", "deprecation"})
+    @SuppressWarnings("this-escape")
     public DefaultCitation(final Citation object) {
         super(object);
         if (object != null) {
@@ -221,8 +226,11 @@
             series                  = object.getSeries();
             otherCitationDetails    = object.getOtherCitationDetails();
             collectiveTitle         = object.getCollectiveTitle();
-            onlineResources         = copyCollection(object.getOnlineResources(), OnlineResource.class);
-            graphics                = copyCollection(object.getGraphics(), BrowseGraphic.class);
+            if (object instanceof DefaultCitation) {
+                final DefaultCitation c = (DefaultCitation) object;
+                onlineResources = copyCollection(c.getOnlineResources(), OnlineResource.class);
+                graphics        = copyCollection(c.getGraphics(), BrowseGraphic.class);
+            }
             final String id1        = object.getISBN();
             final String id2        = object.getISSN();
             if (id1 != null || id2 != null) {
@@ -637,8 +645,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="onlineResource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getOnlineResources() {
         return onlineResources = nonNullCollection(onlineResources, OnlineResource.class);
     }
@@ -661,8 +669,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="graphic", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getGraphics() {
         return graphics = nonNullCollection(graphics, BrowseGraphic.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultContact.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultContact.java
index 99f6a6b..f63fedc 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultContact.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultContact.java
@@ -38,8 +38,12 @@
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.util.privy.CollectionsExt;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.TelephoneType;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList;
 
 
 /**
@@ -78,7 +82,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2972124992825717056L;
+    private static final long serialVersionUID = -969735574940462381L;
 
     /**
      * Telephone numbers at which the organization or individual may be contacted.
@@ -144,12 +148,19 @@
     public DefaultContact(final Contact object) {
         super(object);
         if (object != null) {
-            phones              = copyCollection(object.getPhones(), Telephone.class);
-            addresses           = copyCollection(object.getAddresses(), Address.class);
-            onlineResources     = copyCollection(object.getOnlineResources(), OnlineResource.class);
             hoursOfService      = object.getHoursOfService();
             contactInstructions = object.getContactInstructions();
-            contactType         = object.getContactType();
+            if (object instanceof DefaultContact) {
+                final DefaultContact c = (DefaultContact) object;
+                phones          = copyCollection(c.getPhones(), Telephone.class);
+                addresses       = copyCollection(c.getAddresses(), Address.class);
+                onlineResources = copyCollection(c.getOnlineResources(), OnlineResource.class);
+                contactType     = c.getContactType();
+            } else {
+                phones          = singleton(object.getPhone(), Telephone.class);
+                addresses       = singleton(object.getAddress(), Address.class);
+                onlineResources = singleton(object.getOnlineResource(), OnlineResource.class);
+            }
         }
     }
 
@@ -185,8 +196,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="phone", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Telephone> getPhones() {
         return phones = nonNullCollection(phones, Telephone.class);
     }
@@ -225,8 +236,8 @@
 
     /**
      * Returns telephone numbers at which the organization or individual may be contacted.
-     * This method returns the first telephone number associated to {@link TelephoneType#VOICE}
-     * or {@link TelephoneType#FACSIMILE FACSIMILE}.
+     * This method returns the first telephone number associated to {@code TelephoneType.VOICE}
+     * or {@code TelephoneType.FACSIMILE FACSIMILE}.
      *
      * @return telephone numbers at which the organization or individual may be contacted, or {@code null}.
      *
@@ -240,16 +251,19 @@
         Telephone phone = null;
         if (FilterByVersion.LEGACY_METADATA.accept()) {
             final Collection<Telephone> phones = getPhones();
-            if (phones != null) {                                   // May be null on marshalling.
-                TelephoneType ignored = null;
+            if (phones != null) { // May be null on marshalling.
+                CodeList<?> ignored = null;
                 for (final Telephone c : phones) {
-                    final TelephoneType type = c.getNumberType();
-                    if (type == TelephoneType.VOICE || type == TelephoneType.FACSIMILE) {
-                        if (phone == null) {
-                            phone = c;
+                    if (c instanceof DefaultTelephone) {
+                        String name;
+                        final CodeList<?> type = ((DefaultTelephone) c).numberType;
+                        if (type != null && ("VOICE".equals(name = type.name()) || "FACSIMILE".equals(name))) {
+                            if (phone == null) {
+                                phone = c;
+                            }
+                        } else if (ignored == null) {
+                            ignored = type;
                         }
-                    } else if (ignored == null) {
-                        ignored = type;
                     }
                 }
                 if (ignored != null) {
@@ -258,7 +272,7 @@
                      * because we want the property to appear as "TelephoneType[FOO]" instead of "FOO".
                      */
                     Context.warningOccured(Context.current(), DefaultContact.class, "getPhone",
-                            Messages.class, Messages.Keys.IgnoredPropertyAssociatedTo_1, ignored.toString());
+                            Messages.class, Messages.Keys.IgnoredPropertyAssociatedTo_1, ignored);
                 }
             }
         }
@@ -282,10 +296,10 @@
             } else {
                 newValues = new ArrayList<>(4);
                 for (String number : newValue.getVoices()) {
-                    newValues.add(new DefaultTelephone(number, TelephoneType.VOICE));
+                    newValues.add(new DefaultTelephone(number, UnsupportedCodeList.VOICE));
                 }
                 for (String number : newValue.getFacsimiles()) {
-                    newValues.add(new DefaultTelephone(number, TelephoneType.FACSIMILE));
+                    newValues.add(new DefaultTelephone(number, UnsupportedCodeList.FACSIMILE));
                 }
             }
         }
@@ -299,8 +313,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="address", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Address> getAddresses() {
         return addresses = nonNullCollection(addresses, Address.class);
     }
@@ -355,8 +369,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="onlineResource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getOnlineResources() {
         return onlineResources = nonNullCollection(onlineResources, OnlineResource.class);
     }
@@ -464,9 +478,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "contactType")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="contactType", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getContactType() {
         return contactType;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultIndividual.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
index e4da2f8..2184ddd 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
@@ -23,13 +23,23 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Individual;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
  * Information about the party if the party is an individual.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Individual} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -48,7 +58,8 @@
     "positionName"
 })
 @XmlRootElement(name = "CI_Individual")
-public class DefaultIndividual extends AbstractParty implements Individual {
+@UML(identifier="CI_Individual", specification=ISO_19115)
+public class DefaultIndividual extends AbstractParty {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -87,10 +98,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Individual)
      */
-    public DefaultIndividual(final Individual object) {
+    public DefaultIndividual(final DefaultIndividual object) {
         super(object);
         if (object != null) {
             positionName = object.getPositionName();
@@ -98,37 +107,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultIndividual}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultIndividual} instance is created using the
-     *       {@linkplain #DefaultIndividual(Individual) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultIndividual castOrCopy(final Individual object) {
-        if (object == null || object instanceof DefaultIndividual) {
-            return (DefaultIndividual) object;
-        }
-        return new DefaultIndividual(object);
-    }
-
-    /**
      * Returns position of the individual in an organization, or {@code null} if none.
      *
      * @return position of the individual in an organization, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "positionName")
+    @UML(identifier="positionName", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getPositionName() {
         return positionName;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
index c190272..c19fd68 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
@@ -28,6 +28,11 @@
 import org.apache.sis.xml.bind.gco.URIAdapter;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about on-line sources from which the dataset, specification, or
@@ -67,7 +72,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2627991335953178610L;
+    private static final long serialVersionUID = 1413613911128890864L;
 
     /**
      * Location (address) for on-line access using a Uniform Resource Locator address or
@@ -142,7 +147,9 @@
             name               = object.getName();
             description        = object.getDescription();
             function           = object.getFunction();
-            protocolRequest    = object.getProtocolRequest();
+            if (object instanceof DefaultOnlineResource) {
+                protocolRequest = ((DefaultOnlineResource) object).getProtocolRequest();
+            }
         }
     }
 
@@ -329,9 +336,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "protocolRequest")
     @XmlJavaTypeAdapter(StringAdapter.Since2014.class)
+    @UML(identifier="protocolRequest", obligation=OPTIONAL, specification=ISO_19115)
     public String getProtocolRequest() {
         return protocolRequest;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
index bda3d51..2483bd4 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
@@ -23,14 +23,24 @@
 import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.identification.BrowseGraphic;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
  * Information about the party if the party is an organization.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Organisation} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -50,7 +60,8 @@
     "individual"
 })
 @XmlRootElement(name = "CI_Organisation")
-public class DefaultOrganisation extends AbstractParty implements Organisation {
+@UML(identifier="CI_Organisation", specification=ISO_19115)
+public class DefaultOrganisation extends AbstractParty {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -66,7 +77,7 @@
      * Individuals in the named organization.
      */
     @SuppressWarnings("serial")
-    private Collection<Individual> individual;
+    private Collection<DefaultIndividual> individual;
 
     /**
      * Constructs an initially empty organization.
@@ -84,12 +95,12 @@
      */
     public DefaultOrganisation(final CharSequence name,
                                final BrowseGraphic logo,
-                               final Individual individual,
+                               final DefaultIndividual individual,
                                final Contact contactInfo)
     {
         super(name, contactInfo);
         this.logo       = singleton(logo, BrowseGraphic.class);
-        this.individual = singleton(individual, Individual.class);
+        this.individual = singleton(individual, DefaultIndividual.class);
     }
 
     /**
@@ -98,49 +109,22 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Organisation)
      */
-    public DefaultOrganisation(final Organisation object) {
+    public DefaultOrganisation(final DefaultOrganisation object) {
         super(object);
         if (object != null) {
             logo       = copyCollection(object.getLogo(), BrowseGraphic.class);
-            individual = copyCollection(object.getIndividual(), Individual.class);
+            individual = copyCollection(object.getIndividual(), DefaultIndividual.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOrganisation}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOrganisation} instance is created using the
-     *       {@linkplain #DefaultOrganisation(Organisation) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOrganisation castOrCopy(final Organisation object) {
-        if (object == null || object instanceof DefaultOrganisation) {
-            return (DefaultOrganisation) object;
-        }
-        return new DefaultOrganisation(object);
-    }
-
-    /**
      * Returns the graphics identifying organization.
      *
      * @return graphics identifying organization, or an empty collection if there is none.
      */
-    @Override
     @XmlElement(name = "logo")
+    @UML(identifier="logo", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getLogo() {
         return logo = nonNullCollection(logo, BrowseGraphic.class);
     }
@@ -157,20 +141,30 @@
     /**
      * Returns the individuals in the named organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Individual} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return individuals in the named organization, or an empty collection.
      */
-    @Override
     @XmlElement(name = "individual")
-    public Collection<Individual> getIndividual() {
-        return individual = nonNullCollection(individual, Individual.class);
+    @UML(identifier="individual", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultIndividual> getIndividual() {
+        return individual = nonNullCollection(individual, DefaultIndividual.class);
     }
 
     /**
      * Sets the individuals in the named organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Individual} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new individuals in the named organization.
      */
-    public void setIndividual(final Collection<? extends Individual> newValues) {
-        individual = writeCollection(newValues, individual, Individual.class);
+    public void setIndividual(final Collection<? extends DefaultIndividual> newValues) {
+        individual = writeCollection(newValues, individual, DefaultIndividual.class);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
index c8e0224..d6ae634 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
@@ -28,9 +28,12 @@
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.metadata.code.CI_RoleCode;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -43,6 +46,14 @@
  * {@code   │   └─name……………} Name of the party.
  * {@code   └─role………………………} Function performed by the responsible party.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Responsibility} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -68,7 +79,8 @@
 @XmlSeeAlso({
     DefaultResponsibleParty.class
 })
-public class DefaultResponsibility extends ISOMetadata implements Responsibility {
+@UML(identifier="CI_Responsibility", specification=ISO_19115)
+public class DefaultResponsibility extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -89,7 +101,7 @@
      * Information about the parties.
      */
     @SuppressWarnings("serial")
-    private Collection<Party> parties;
+    private Collection<AbstractParty> parties;
 
     /**
      * Constructs an initially empty responsible party.
@@ -104,10 +116,10 @@
      * @param extent  spatial or temporal extent of the role, or {@code null}.
      * @param party   information about the party, or {@code null}.
      */
-    public DefaultResponsibility(final Role role, final Extent extent, final Party party) {
+    public DefaultResponsibility(final Role role, final Extent extent, final AbstractParty party) {
         this.role    = role;
         this.extents = singleton(extent, Extent.class);
-        this.parties = singleton(party, Party.class);
+        this.parties = singleton(party, AbstractParty.class);
     }
 
     /**
@@ -116,41 +128,29 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Responsibility)
      */
-    public DefaultResponsibility(final Responsibility object) {
+    public DefaultResponsibility(final DefaultResponsibility object) {
         super(object);
         if (object != null) {
             this.role    = object.getRole();
             this.extents = copyCollection(object.getExtents(), Extent.class);
-            this.parties = copyCollection(object.getParties(), Party.class);
+            this.parties = copyCollection(object.getParties(), AbstractParty.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultResponsibility}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultResponsibility} instance is created using the
-     *       {@linkplain #DefaultResponsibility(Responsibility) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       Responsibility contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
+     * Bridge constructor for {@link DefaultResponsibleParty#DefaultResponsibleParty(ResponsibleParty)}.
      */
-    public static DefaultResponsibility castOrCopy(final Responsibility object) {
-        if (object == null || object instanceof DefaultResponsibility) {
-            return (DefaultResponsibility) object;
+    DefaultResponsibility(final ResponsibleParty object) {
+        super(object);
+        if (object != null) {
+            this.role = object.getRole();
+            if (object instanceof DefaultResponsibility) {
+                final DefaultResponsibility c = (DefaultResponsibility) object;
+                this.extents = copyCollection(c.getExtents(), Extent.class);
+                this.parties = copyCollection(c.getParties(), AbstractParty.class);
+            }
         }
-        return new DefaultResponsibility(object);
     }
 
     /**
@@ -158,9 +158,9 @@
      *
      * @return function performed by the responsible party.
      */
-    @Override
     @XmlElement(name = "role", required = true)
     @XmlJavaTypeAdapter(CI_RoleCode.Since2014.class)
+    @UML(identifier="role", obligation=MANDATORY, specification=ISO_19115)
     public Role getRole() {
         return role;
     }
@@ -180,8 +180,8 @@
      *
      * @return the spatial or temporal extents of the role.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="extent", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -198,21 +198,31 @@
     /**
      * Returns information about the parties.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Party} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the parties.
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<Party> getParties() {
-        return parties = nonNullCollection(parties, Party.class);
+    @UML(identifier="party", obligation=MANDATORY, specification=ISO_19115)
+    public Collection<AbstractParty> getParties() {
+        return parties = nonNullCollection(parties, AbstractParty.class);
     }
 
     /**
      * Sets information about the parties.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Party} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  new information about the parties.
      */
-    public void setParties(final Collection<? extends Party> newValues) {
-        parties = writeCollection(newValues, parties, Party.class);
+    public void setParties(final Collection<? extends AbstractParty> newValues) {
+        parties = writeCollection(newValues, parties, AbstractParty.class);
     }
 
 
@@ -242,7 +252,7 @@
     }
 
     @XmlElement(name = "party", required = true)
-    private Collection<Party> getParty() {
+    private Collection<AbstractParty> getParty() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getParties() : null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
index 4917733..45cece3 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
@@ -33,12 +33,6 @@
 import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
 import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * Identification of, and means of communication with, person(s) and
@@ -51,8 +45,11 @@
  * {@code   └─party…………………………} Information about the parties.
  * {@code       └─name…………………} Name of the party.</div>
  *
- * @deprecated As of ISO 19115:2014, the {@code ResponsibleParty} type has been replaced by {@code Responsibility}
- *             to allow more flexible associations of individuals, organizations, and roles.
+ * <div class="warning"><b>Upcoming API change — deprecation</b><br>
+ * As of ISO 19115:2014, the {@code ResponsibleParty} type has been replaced by {@code Responsibility}
+ * to allow more flexible associations of individuals, organisations, and roles.
+ * This {@code ResponsibleParty} interface may be deprecated in GeoAPI 4.0.
+ * </div>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Touraïvane (IRD)
@@ -60,7 +57,6 @@
  * @version 1.4
  * @since   0.3
  */
-@Deprecated(since="1.0")
 @XmlType(name = "CI_ResponsibleParty_Type", namespace = LegacyNamespaces.GMD, propOrder = {
     "individualName",
     "organisationName",
@@ -96,14 +92,29 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Responsibility)
      */
-    public DefaultResponsibleParty(final Responsibility object) {
+    public DefaultResponsibleParty(final DefaultResponsibility object) {
         super(object);
     }
 
     /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
+     * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
+     * given object are not recursively copied.
+     *
+     * @param object The metadata to copy values from, or {@code null} if none.
+     *
+     * @see #castOrCopy(ResponsibleParty)
+     */
+    public DefaultResponsibleParty(final ResponsibleParty object) {
+        super(object);
+        if (object != null && !(object instanceof DefaultResponsibility)) {
+            setIndividualName(object.getIndividualName());
+            setOrganisationName(object.getOrganisationName());
+        }
+    }
+
+    /**
      * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
      * This method performs the first applicable action in the following choices:
      *
@@ -112,7 +123,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultResponsibleParty}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code DefaultResponsibleParty} instance is created using the
-     *       {@linkplain #DefaultResponsibleParty(Responsibility) copy constructor} and returned.
+     *       {@linkplain #DefaultResponsibleParty(ResponsibleParty) copy constructor} and returned.
      *       Note that this is a <em>shallow</em> copy operation, because the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -121,7 +132,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultResponsibleParty castOrCopy(final Responsibility object) {
+    public static DefaultResponsibleParty castOrCopy(final ResponsibleParty object) {
         if (object == null || object instanceof DefaultResponsibleParty) {
             return (DefaultResponsibleParty) object;
         }
@@ -140,12 +151,12 @@
      * @see #getPositionName()
      */
     private InternationalString getIndividual(final boolean position) {
-        final Collection<Party> parties = getParties();
-        InternationalString name = getName(parties, Individual.class, position);
+        final Collection<AbstractParty> parties = getParties();
+        InternationalString name = getName(parties, DefaultIndividual.class, position);
         if (name == null && parties != null) {
-            for (final Party party : parties) {
-                if (party instanceof Organisation) {
-                    name = getName(((Organisation) party).getIndividual(), Individual.class, position);
+            for (final AbstractParty party : parties) {
+                if (party instanceof DefaultOrganisation) {
+                    name = getName(((DefaultOrganisation) party).getIndividual(), DefaultIndividual.class, position);
                     if (name != null) {
                         break;
                     }
@@ -165,20 +176,20 @@
      * @see #getIndividualName()
      * @see #getPositionName()
      */
-    private static InternationalString getName(final Collection<? extends Party> parties,
-            final Class<? extends Party> type, final boolean position)
+    private static InternationalString getName(final Collection<? extends AbstractParty> parties,
+            final Class<? extends AbstractParty> type, final boolean position)
     {
         InternationalString name = null;
         if (parties != null) {                              // May be null on marshalling.
-            for (final Party party : parties) {
+            for (final AbstractParty party : parties) {
                 if (type.isInstance(party)) {
                     if (name != null) {
                         LegacyPropertyAdapter.warnIgnoredExtraneous(type, DefaultResponsibleParty.class,
-                                position ? "getPositionName" : (type == Individual.class)
+                                position ? "getPositionName" : (type == DefaultIndividual.class)
                                          ? "getIndividualName" : "getOrganisationName");
                         break;
                     }
-                    name = position ? ((Individual) party).getPositionName() : party.getName();
+                    name = position ? ((DefaultIndividual) party).getPositionName() : party.getName();
                 }
             }
         }
@@ -189,22 +200,22 @@
      * Sets the name of the first party of the given type.
      * If no existing party is found, generate a new party using the given creator.
      */
-    private void setName(final Class<? extends Party> type, final boolean position, final InternationalString name,
-                         final Function<InternationalString,Party> creator)
+    private void setName(final Class<? extends AbstractParty> type, final boolean position, final InternationalString name,
+                         final Function<InternationalString,AbstractParty> creator)
     {
-        final Collection<Party> parties = getParties();
+        final Collection<AbstractParty> parties = getParties();
         checkWritePermission(valueIfDefined(parties));
         if (parties != null) {                                  // May be null on unmarshalling.
-            final Iterator<Party> it = parties.iterator();
+            final Iterator<AbstractParty> it = parties.iterator();
             while (it.hasNext()) {
-                final Party party = it.next();
-                if (party instanceof AbstractParty && type.isInstance(party)) {
+                final AbstractParty party = it.next();
+                if (type.isInstance(party)) {
                     if (position) {
                         ((DefaultIndividual) party).setPositionName(name);
                     } else {
-                        ((AbstractParty) party).setName(name);
+                        party.setName(name);
                     }
-                    if (((AbstractParty) party).isEmpty()) {
+                    if (party.isEmpty()) {
                         it.remove();
                     }
                     return;
@@ -212,7 +223,7 @@
             }
         }
         if (name != null) {                             // If no party and name is null, there is nothing to set.
-            final Party party = creator.apply(name);
+            final AbstractParty party = creator.apply(name);
             if (parties != null) {                      // May be null on unmarshalling.
                 parties.add(party);
             } else {
@@ -226,9 +237,9 @@
      * Only one of {@code individualName}, {@link #getOrganisationName() organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the name of the first {@link Individual} found in the collection of
+     * <p>This implementation returns the name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
-     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
+     * on the first organisation member.</p>
      *
      * @return name, surname, given name and title of the responsible person, or {@code null}.
      *
@@ -248,7 +259,7 @@
      * Only one of {@code individualName}, {@link #getOrganisationName() organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation sets the name of the first {@link Individual} found in the collection of
+     * <p>This implementation sets the name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}, or create a new individual if no existing instance was found.</p>
      *
      * @param  newValue  the new individual name, or {@code null} if none.
@@ -257,13 +268,13 @@
      */
     @Deprecated(since="1.0")
     public void setIndividualName(final String newValue) {
-        setName(Individual.class, false, Types.toInternationalString(newValue), DefaultResponsibleParty::individual);
+        setName(DefaultIndividual.class, false, Types.toInternationalString(newValue), DefaultResponsibleParty::individual);
     }
 
     /**
      * Generates a new individual from the given name.
      */
-    private static Party individual(final InternationalString name) {
+    private static AbstractParty individual(final InternationalString name) {
         return new DefaultIndividual(name, null, null);
     }
 
@@ -272,7 +283,7 @@
      * {@link #getIndividualName() individualName}, {@code organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the name of the first {@link Organisation}
+     * <p>This implementation returns the name of the first {@code Organisation}
      * found in the collection of {@linkplain #getParties() parties}.</p>
      *
      * @return name of the responsible organization, or {@code null}.
@@ -284,7 +295,7 @@
     @Dependencies("getParties")
     @XmlElement(name = "organisationName")
     public InternationalString getOrganisationName() {
-        return getName(getParties(), Organisation.class, false);
+        return getName(getParties(), DefaultOrganisation.class, false);
     }
 
     /**
@@ -292,7 +303,7 @@
      * {@link #getIndividualName() individualName}, {@code organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation sets the name of the first {@link Organisation} found in the collection of
+     * <p>This implementation sets the name of the first {@code Organisation} found in the collection of
      * {@linkplain #getParties() parties}, or create a new organization if no existing instance was found.</p>
      *
      * @param  newValue  the new organization name, or {@code null} if none.
@@ -301,13 +312,13 @@
      */
     @Deprecated(since="1.0")
     public void setOrganisationName(final InternationalString newValue) {
-        setName(Organisation.class, false, newValue, DefaultResponsibleParty::organisation);
+        setName(DefaultOrganisation.class, false, newValue, DefaultResponsibleParty::organisation);
     }
 
     /**
      * Generates a new organization from the given name.
      */
-    private static Party organisation(final InternationalString name) {
+    private static AbstractParty organisation(final InternationalString name) {
         return new DefaultOrganisation(name, null, null, null);
     }
 
@@ -316,9 +327,9 @@
      * {@link #getIndividualName() individualName}, {@link #getOrganisationName() organisationName}
      * and {@code positionName} shall be provided.
      *
-     * <p>This implementation returns the position of the first {@link Individual} found in the collection of
+     * <p>This implementation returns the position of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
-     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
+     * on the first organisation member.</p>
      *
      * @return role or position of the responsible person, or {@code null}
      *
@@ -337,7 +348,7 @@
      * {@link #getIndividualName() individualName}, {@link #getOrganisationName() organisationName}
      * and {@code positionName} shall be provided.
      *
-     * <p>This implementation sets the position name of the first {@link Individual} found in the collection of
+     * <p>This implementation sets the position name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}, or create a new individual if no existing instance was found.</p>
      *
      * @param  newValue  the new position name, or {@code null} if none.
@@ -352,7 +363,7 @@
     /**
      * Generates a new position from the given name.
      */
-    private static Party position(final InternationalString name) {
+    private static AbstractParty position(final InternationalString name) {
         return new DefaultIndividual(null, name, null);
     }
 
@@ -371,9 +382,9 @@
     @Dependencies("getParties")
     @XmlElement(name = "contactInfo")
     public Contact getContactInfo() {
-        final Collection<Party> parties = getParties();
+        final Collection<AbstractParty> parties = getParties();
         if (parties != null) {                                          // May be null on marshalling.
-            for (final Party party : parties) {
+            for (final AbstractParty party : parties) {
                 final Collection<? extends Contact> contacts = party.getContactInfo();
                 if (contacts != null) {                                 // May be null on marshalling.
                     for (final Contact contact : contacts) {
@@ -399,19 +410,17 @@
      */
     @Deprecated(since="1.0")
     public void setContactInfo(final Contact newValue) {
-        final Collection<Party> parties = getParties();
+        final Collection<AbstractParty> parties = getParties();
         checkWritePermission(valueIfDefined(parties));
         if (parties != null) {                                  // May be null on unmarshalling.
-            final Iterator<Party> it = parties.iterator();
+            final Iterator<AbstractParty> it = parties.iterator();
             while (it.hasNext()) {
-                final Party party = it.next();
-                if (party instanceof AbstractParty) {
-                    ((AbstractParty) party).setContactInfo(newValue != null ? Collections.singleton(newValue) : null);
-                    if (((AbstractParty) party).isEmpty()) {
-                        it.remove();
-                    }
-                    return;
+                final AbstractParty party = it.next();
+                party.setContactInfo(newValue != null ? Collections.singleton(newValue) : null);
+                if (party.isEmpty()) {
+                    it.remove();
                 }
+                return;
             }
         }
         /*
@@ -419,7 +428,7 @@
          * it should be an individual or an organization. Arbitrarily choose an individual for now.
          */
         if (newValue != null) {
-            final Party party = new DefaultIndividual(null, null, newValue);
+            final AbstractParty party = new DefaultIndividual(null, null, newValue);
             if (parties != null) {
                 parties.add(party);
             } else {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultTelephone.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
index c560ce5..6c0e433 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
@@ -32,8 +32,14 @@
 import org.apache.sis.xml.bind.metadata.code.CI_TelephoneTypeCode;
 import org.apache.sis.metadata.internal.Dependencies;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.TelephoneType;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.pending.geoapi.evolution.InterimType;
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList;
 
 
 /**
@@ -96,7 +102,7 @@
     /**
      * Type of telephone number.
      */
-    private TelephoneType numberType;
+    CodeList<?> numberType;
 
     /**
      * Constructs a default telephone.
@@ -109,10 +115,8 @@
      *
      * @param number      the telephone number, or {@code null}.
      * @param numberType  the type of telephone number, or {@code null}.
-     *
-     * @since 0.5
      */
-    public DefaultTelephone(final String number, final TelephoneType numberType) {
+    DefaultTelephone(final String number, final CodeList<?> numberType) {
         this.number     = number;
         this.numberType = numberType;
     }
@@ -129,8 +133,13 @@
     public DefaultTelephone(final Telephone object) {
         super(object);
         if (object != null) {
-            number     = object.getNumber();
-            numberType = object.getNumberType();
+            if (object instanceof DefaultTelephone) {
+                number     = ((DefaultTelephone) object).getNumber();
+                numberType = ((DefaultTelephone) object).numberType;
+            } else {
+                setVoices(object.getVoices());
+                setFacsimiles(object.getFacsimiles());
+            }
         }
     }
 
@@ -166,9 +175,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "number", required = true)
     @XmlJavaTypeAdapter(StringAdapter.Since2014.class)
+    @UML(identifier="number", obligation=MANDATORY, specification=ISO_19115)
     public String getNumber() {
         return number;
     }
@@ -187,26 +196,63 @@
 
     /**
      * Returns the type of telephone number, or {@code null} if none.
+     * If non-null, the type can be {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The return type will be changed to the {@code TelephoneType} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
      *
      * @return type of telephone number, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
+    @InterimType(UnsupportedCodeList.class)
     @XmlElement(name = "numberType")
     @XmlJavaTypeAdapter(CI_TelephoneTypeCode.Since2014.class)
-    public TelephoneType getNumberType() {
+    @UML(identifier="numberType", obligation=OPTIONAL, specification=ISO_19115)
+    public CodeList<?> getNumberType() {
         return numberType;
     }
 
     /**
      * Sets the type of telephone number.
+     * If non-null, the type can only be {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code TelephoneType} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@snippet lang="java" :
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       @Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     *   }
+     * </div>
      *
      * @param  newValue  the new type of telephone number.
      *
      * @since 0.5
      */
-    public void setNumberType(final TelephoneType newValue) {
+    public void setNumberType(final CodeList<?> newValue) {
         checkWritePermission(numberType);
         numberType = newValue;
     }
@@ -275,7 +321,7 @@
      * @return telephone numbers by which individuals can speak to the responsible organization or individual.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #getNumber() number}
-     *             with {@link TelephoneType#VOICE}.
+     *             with {@code TelephoneType.VOICE}.
      */
     @Override
     @Deprecated(since="1.0")
@@ -283,9 +329,9 @@
     @XmlElement(name = "voice", namespace = LegacyNamespaces.GMD)
     public final Collection<String> getVoices() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return new LegacyTelephones(getOwner(), TelephoneType.VOICE);
+            return new LegacyTelephones(getOwner(), UnsupportedCodeList.VOICE);
         }
-        return null;                    // Marshalling newer ISO 19115-3 document.
+        return null;
     }
 
     /**
@@ -296,7 +342,7 @@
      * @param  newValues  the new telephone numbers, or {@code null} if none.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #setNumber(String) number}
-     *             with {@link TelephoneType#VOICE}.
+     *             code {@code TelephoneType.VOICE}.
      */
     @Deprecated(since="1.0")
     public void setVoices(final Collection<? extends String> newValues) {
@@ -311,7 +357,7 @@
      * @return telephone numbers of a facsimile machine for the responsible organization or individual.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #getNumber() number}
-     *             with {@link TelephoneType#FACSIMILE}.
+     *             code {@code TelephoneType.FACSIMILE}.
      */
     @Override
     @Deprecated(since="1.0")
@@ -319,7 +365,7 @@
     @XmlElement(name = "facsimile", namespace = LegacyNamespaces.GMD)
     public final Collection<String> getFacsimiles() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return new LegacyTelephones(getOwner(), TelephoneType.FACSIMILE);
+            return new LegacyTelephones(getOwner(), UnsupportedCodeList.FACSIMILE);
         }
         return null;                    // Marshalling newer ISO 19115-3 document.
     }
@@ -332,7 +378,7 @@
      * @param  newValues  the new telephone number, or {@code null} if none.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #setNumber(String) number}
-     *             with {@link TelephoneType#FACSIMILE}.
+     *             with {@code TelephoneType.FACSIMILE}.
      */
     @Deprecated(since="1.0")
     public void setFacsimiles(final Collection<? extends String> newValues) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/LegacyTelephones.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
index 2c8e409..bbdd961 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
@@ -21,8 +21,8 @@
 import org.opengis.metadata.citation.Telephone;
 import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.TelephoneType;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -35,14 +35,14 @@
 final class LegacyTelephones extends LegacyPropertyAdapter<String,Telephone> {
     /**
      * The type of telephone number.
-     * Either {@link TelephoneType#VOICE} or {@link TelephoneType#FACSIMILE}.
+     * Either {@code UnsupportedCodeList.VOICE} or {@code UnsupportedCodeList.FACSIMILE}.
      */
-    private final TelephoneType type;
+    private final CodeList<?> type;
 
     /**
      * Wraps the given telephone list for the given type.
      */
-    LegacyTelephones(final Collection<Telephone> telephones, final TelephoneType type) {
+    LegacyTelephones(final Collection<Telephone> telephones, final CodeList<?> type) {
         super(telephones);
         this.type = type;
     }
@@ -60,8 +60,13 @@
      */
     @Override
     protected String unwrap(final Telephone container) {
-        if (container != null && type.equals(container.getNumberType())) {
-            return container.getNumber();
+        if (container instanceof DefaultTelephone) {
+            final CodeList<?> ct = ((DefaultTelephone) container).numberType;
+            if (ct != null) {
+                if (type.name().equals(ct.name())) {
+                    return ((DefaultTelephone) container).getNumber();
+                }
+            }
         }
         return null;
     }
@@ -72,10 +77,10 @@
     @Override
     protected boolean update(final Telephone container, final String value) {
         if (container instanceof DefaultTelephone) {
-            final TelephoneType ct = container.getNumberType();
-            if (ct == null || ct.equals(type)) {
+            final CodeList<?> ct = ((DefaultTelephone) container).numberType;
+            if (ct == null || type.name().equals(ct.name())) {
                 if (ct == null) {
-                    ((DefaultTelephone) container).setNumberType(type);
+                    ((DefaultTelephone) container).numberType = type;
                 }
                 ((DefaultTelephone) container).setNumber(value);
                 return true;
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
index 546a873..18e38c7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
@@ -28,16 +28,18 @@
 import org.opengis.metadata.constraint.Constraints;
 import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.SecurityConstraints;
+import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.metadata.MD_Releasability;
 import org.apache.sis.xml.bind.metadata.MD_Scope;
-import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Responsibility;
-import org.opengis.metadata.constraint.Releasability;
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 
 
 /**
@@ -108,13 +110,13 @@
      * Information concerning the parties to whom the resource can or cannot be released.
      */
     @SuppressWarnings("serial")
-    private Releasability releasability;
+    private DefaultReleasability releasability;
 
     /**
      * Party responsible for the resource constraints.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> responsibleParties;
+    private Collection<DefaultResponsibility> responsibleParties;
 
     /**
      * Constructs an initially empty constraints.
@@ -144,11 +146,14 @@
         super(object);
         if (object != null) {
             useLimitations             = copyCollection(object.getUseLimitations(), InternationalString.class);
-            constraintApplicationScope = object.getConstraintApplicationScope();
-            graphics                   = copyCollection(object.getGraphics(), BrowseGraphic.class);
-            references                 = copyCollection(object.getReferences(), Citation.class);
-            releasability              = object.getReleasability();
-            responsibleParties         = copyCollection(object.getResponsibleParties(), Responsibility.class);
+            if (object instanceof DefaultConstraints) {
+                final DefaultConstraints c = (DefaultConstraints) object;
+                constraintApplicationScope = c.getConstraintApplicationScope();
+                graphics                   = copyCollection(c.getGraphics(), BrowseGraphic.class);
+                references                 = copyCollection(c.getReferences(), Citation.class);
+                releasability              = c.getReleasability();
+                responsibleParties         = copyCollection(c.getResponsibleParties(), DefaultResponsibility.class);
+            }
         }
     }
 
@@ -219,9 +224,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "constraintApplicationScope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="constraintApplicationScope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getConstraintApplicationScope() {
         return constraintApplicationScope;
     }
@@ -245,8 +250,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="graphic", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getGraphics() {
         return graphics = nonNullCollection(graphics, BrowseGraphic.class);
     }
@@ -270,8 +275,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="reference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getReferences() {
         return references = nonNullCollection(references, Citation.class);
     }
@@ -290,25 +295,35 @@
     /**
      * Returns information concerning the parties to whom the resource can or cannot be released.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The return type will be changed to the {@code Releasability} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information concerning the parties to whom the resource can or cannot be released, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "releasability")
     @XmlJavaTypeAdapter(MD_Releasability.Since2014.class)
-    public Releasability getReleasability() {
+    @UML(identifier="releasability", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultReleasability getReleasability() {
         return releasability;
     }
 
     /**
      * Sets the information concerning the parties to whom the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code Releasability} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValue  the new information concerning the parties to whom the resource can or cannot be released.
      *
      * @since 0.5
      */
-    public void setReleasability(final Releasability newValue) {
+    public void setReleasability(final DefaultReleasability newValue) {
         checkWritePermission(releasability);
         releasability = newValue;
     }
@@ -316,25 +331,35 @@
     /**
      * Returns the parties responsible for the resource constraints.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return parties responsible for the resource constraints.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<Responsibility> getResponsibleParties() {
-        return responsibleParties = nonNullCollection(responsibleParties, Responsibility.class);
+    @UML(identifier="responsibleParty", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultResponsibility> getResponsibleParties() {
+        return responsibleParties = nonNullCollection(responsibleParties, DefaultResponsibility.class);
     }
 
     /**
      * Sets the parties responsible for the resource constraints.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new parties responsible for the resource constraints.
      *
      * @since 0.5
      */
-    public void setResponsibleParties(final Collection<? extends Responsibility> newValues) {
-        responsibleParties = writeCollection(newValues, responsibleParties, Responsibility.class);
+    public void setResponsibleParties(final Collection<? extends DefaultResponsibility> newValues) {
+        responsibleParties = writeCollection(newValues, responsibleParties, DefaultResponsibility.class);
     }
 
 
@@ -369,7 +394,7 @@
     }
 
     @XmlElement(name = "responsibleParty")
-    private Collection<Responsibility> getResponsibleParty() {
+    private Collection<DefaultResponsibility> getResponsibleParty() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getResponsibleParties() : null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
index 0575585..5c55f90 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
@@ -24,14 +24,24 @@
 import org.opengis.metadata.constraint.Restriction;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.constraint.Releasability;
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 
 
 /**
  * Information about resource release constraints.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Releasability} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -51,7 +61,8 @@
     "disseminationConstraints"
 })
 @XmlRootElement(name = "MD_Releasability")
-public class DefaultReleasability extends ISOMetadata implements Releasability {
+@UML(identifier="MD_Releasability", specification=ISO_19115)
+public class DefaultReleasability extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -61,7 +72,7 @@
      * Party to which the release statement applies.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> addressees;
+    private Collection<DefaultResponsibility> addressees;
 
     /**
      * Release statement.
@@ -87,61 +98,44 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Releasability)
      */
-    public DefaultReleasability(final Releasability object) {
+    public DefaultReleasability(final DefaultReleasability object) {
         super(object);
         if (object != null) {
-            addressees                = copyCollection(object.getAddressees(), Responsibility.class);
+            addressees                = copyCollection(object.getAddressees(), DefaultResponsibility.class);
             statement                 = object.getStatement();
             disseminationConstraints  = copyCollection(object.getDisseminationConstraints(), Restriction.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultReleasability}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultReleasability} instance is created using the
-     *       {@linkplain #DefaultReleasability(Releasability) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultReleasability castOrCopy(final Releasability object) {
-        if (object == null || object instanceof DefaultReleasability) {
-            return (DefaultReleasability) object;
-        }
-        return new DefaultReleasability(object);
-    }
-
-    /**
      * Returns the parties to which the release statement applies.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return parties to which the release statement applies.
      */
-    @Override
     @XmlElement(name = "addressee")
-    public Collection<Responsibility> getAddressees() {
-        return addressees = nonNullCollection(addressees, Responsibility.class);
+    @UML(identifier="addressee", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultResponsibility> getAddressees() {
+        return addressees = nonNullCollection(addressees, DefaultResponsibility.class);
     }
 
     /**
      * Sets the parties to which the release statement applies.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new parties.
      */
-    public void setAddressees(final Collection<? extends Responsibility> newValues) {
-        addressees = writeCollection(newValues, addressees, Responsibility.class);
+    public void setAddressees(final Collection<? extends DefaultResponsibility> newValues) {
+        addressees = writeCollection(newValues, addressees, DefaultResponsibility.class);
     }
 
     /**
@@ -149,8 +143,8 @@
      *
      * @return release statement, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "statement")
+    @UML(identifier="statement", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getStatement() {
         return statement;
     }
@@ -170,8 +164,8 @@
      *
      * @return components in determining releasability.
      */
-    @Override
     @XmlElement(name = "disseminationConstraints")
+    @UML(identifier="disseminationConstraints", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Restriction> getDisseminationConstraints() {
         return disseminationConstraints = nonNullCollection(disseminationConstraints, Restriction.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
index 0aa5264..340219c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
@@ -24,8 +24,11 @@
 import org.opengis.metadata.content.RangeDimension;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.AttributeGroup;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -36,6 +39,14 @@
  * <div class="preformat">{@code MD_AttributeGroup}
  * {@code   └─contentType……} Content type</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code AttributeGroup} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -56,7 +67,8 @@
     "attributes"
 })
 @XmlRootElement(name = "MD_AttributeGroup")
-public class DefaultAttributeGroup extends ISOMetadata implements AttributeGroup {
+@UML(identifier="MD_AttributeGroup", specification=ISO_19115)
+public class DefaultAttributeGroup extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -97,10 +109,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AttributeGroup)
      */
-    public DefaultAttributeGroup(final AttributeGroup object) {
+    public DefaultAttributeGroup(final DefaultAttributeGroup object) {
         super(object);
         if (object != null) {
             contentTypes = copyCollection(object.getContentTypes(), CoverageContentType.class);
@@ -109,37 +119,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultAttributeGroup}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultAttributeGroup} instance is created using the
-     *       {@linkplain #DefaultAttributeGroup(AttributeGroup) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultAttributeGroup castOrCopy(final AttributeGroup object) {
-        if (object == null || object instanceof DefaultAttributeGroup) {
-            return (DefaultAttributeGroup) object;
-        }
-        return new DefaultAttributeGroup(object);
-    }
-
-    /**
      * Returns the types of information represented by the value(s).
      *
      * @return the types of information represented by the value(s).
      */
-    @Override
     @XmlElement(name = "contentType", required = true)
+    @UML(identifier="contentType", obligation=MANDATORY, specification=ISO_19115)
     public Collection<CoverageContentType> getContentTypes() {
         return contentTypes = nonNullCollection(contentTypes, CoverageContentType.class);
     }
@@ -158,8 +143,8 @@
      *
      * @return information on an attribute of the resource.
      */
-    @Override
     @XmlElement(name = "attribute")
+    @UML(identifier="attribute", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<RangeDimension> getAttributes() {
         return attributes = nonNullCollection(attributes, RangeDimension.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultBand.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultBand.java
index a8898f2..f103ef4 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultBand.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultBand.java
@@ -34,6 +34,11 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.content.PolarizationOrientation;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Range of wavelengths in the electromagnetic spectrum.
@@ -145,9 +150,12 @@
     public DefaultBand(final Band object) {
         super(object);
         if (object != null) {
-            boundMin                 = object.getBoundMin();
-            boundMax                 = object.getBoundMax();
-            boundUnits               = object.getBoundUnits();
+            if (object instanceof DefaultBand) {
+                final DefaultBand c = (DefaultBand) object;
+                boundMin   = c.getBoundMin();
+                boundMax   = c.getBoundMax();
+                boundUnits = c.getBoundUnits();
+            }
             peakResponse             = object.getPeakResponse();
             toneGradation            = object.getToneGradation();
             bandBoundaryDefinition   = object.getBandBoundaryDefinition();
@@ -190,10 +198,10 @@
      *
      * @since 0.5
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "boundMin")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="boundMin", obligation=OPTIONAL, specification=ISO_19115)
     public Double getBoundMin() {
         return boundMin;
     }
@@ -222,10 +230,10 @@
      *
      * @since 0.5
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "boundMax")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="boundMax", obligation=OPTIONAL, specification=ISO_19115)
     public Double getBoundMax() {
         return boundMax;
     }
@@ -254,9 +262,9 @@
      *
      * @see org.apache.sis.measure.Units#NANOMETRE
      */
-    @Override
     @XmlElement(name = "boundUnits")
     @XmlJavaTypeAdapter(UnitAdapter.Since2014.class)
+    @UML(identifier="boundUnits", obligation=OPTIONAL, specification=ISO_19115)
     public Unit<Length> getBoundUnits() {
         return boundUnits;
     }
@@ -295,6 +303,39 @@
     }
 
     /**
+     * Returns the units of data as a unit of length.
+     *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, the units of wavelength is rather {@code boundUnits}.
+     * The restriction for units of length in this {@code units} property may be relaxed in GeoAPI 4.0.
+     * </div>
+     *
+     * @return The units of data.
+     */
+    @Override
+    public Unit<Length> getUnits() {
+        final Unit<?> units = super.getUnits();
+        return (units != null) ? units.asType(Length.class) : null;
+    }
+
+    /**
+     * Sets the units of data as a unit of length.
+     *
+     * <div class="warning"><b>Upcoming precondition change — relaxation</b><br>
+     * The current implementation requires the unit to be an instance of {@code Unit<Length>},
+     * otherwise a {@link ClassCastException} is thrown. This is because the value returned by
+     * {@link #getUnits()} was restricted by ISO 19115:2003 to units of length.
+     * However this restriction may be relaxed in GeoAPI 4.0.
+     * </div>
+     *
+     * @param newValue The new units of data as an instance of {@code Unit<Length>}.
+     */
+    @Override
+    public void setUnits(final Unit<?> newValue) {
+        super.setUnits(newValue != null ? newValue.asType(Length.class) : null);
+    }
+
+    /**
      * Returns the wavelength at which the response is the highest.
      * The units of measurement is given by {@link #getBoundUnits()}.
      *
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
index 34f33f2..220b758 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
@@ -38,8 +38,10 @@
 import org.apache.sis.xml.bind.metadata.MD_Identifier;
 import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.AttributeGroup;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -100,7 +102,7 @@
      * Information on attribute groups of the resource.
      */
     @SuppressWarnings("serial")
-    private Collection<AttributeGroup> attributeGroups;
+    private Collection<DefaultAttributeGroup> attributeGroups;
 
     /**
      * Provides the description of the specific range elements of a coverage.
@@ -127,9 +129,11 @@
         super(object);
         if (object != null) {
             attributeDescription     = object.getAttributeDescription();
-            processingLevelCode      = object.getProcessingLevelCode();
-            attributeGroups          = copyCollection(object.getAttributeGroups(), AttributeGroup.class);
             rangeElementDescriptions = copyCollection(object.getRangeElementDescriptions(), RangeElementDescription.class);
+            if (object instanceof DefaultCoverageDescription) {
+                processingLevelCode  = ((DefaultCoverageDescription) object).getProcessingLevelCode();
+                attributeGroups      = copyCollection(((DefaultCoverageDescription) object).getAttributeGroups(), DefaultAttributeGroup.class);
+            }
         }
     }
 
@@ -194,9 +198,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "processingLevelCode")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="processingLevelCode", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getProcessingLevelCode() {
         return processingLevelCode;
     }
@@ -216,25 +220,35 @@
     /**
      * Returns information on attribute groups of the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AttributeGroup} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information on attribute groups of the resource.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<AttributeGroup> getAttributeGroups() {
-        return attributeGroups = nonNullCollection(attributeGroups, AttributeGroup.class);
+    @UML(identifier="attributeGroup", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultAttributeGroup> getAttributeGroups() {
+        return attributeGroups = nonNullCollection(attributeGroups, DefaultAttributeGroup.class);
     }
 
     /**
      * Sets information on attribute groups of the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AttributeGroup} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information on attribute groups of the resource.
      *
      * @since 0.5
      */
-    public void setAttributeGroups(final Collection<? extends AttributeGroup> newValues) {
-        attributeGroups = writeCollection(newValues, attributeGroups, AttributeGroup.class);
+    public void setAttributeGroups(final Collection<? extends DefaultAttributeGroup> newValues) {
+        attributeGroups = writeCollection(newValues, attributeGroups, DefaultAttributeGroup.class);
     }
 
     /**
@@ -252,9 +266,9 @@
     public CoverageContentType getContentType() {
         CoverageContentType type = null;
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            final Collection<AttributeGroup> groups = getAttributeGroups();
+            final Collection<DefaultAttributeGroup> groups = getAttributeGroups();
             if (groups != null) {                                               // May be null on marshalling.
-                for (final AttributeGroup g : groups) {
+                for (final DefaultAttributeGroup g : groups) {
                     final Collection<? extends CoverageContentType> contentTypes = g.getContentTypes();
                     if (contentTypes != null) {                                 // May be null on marshalling.
                         for (final CoverageContentType t : contentTypes) {
@@ -285,13 +299,11 @@
     public void setContentType(final CoverageContentType newValue) {
         checkWritePermission(valueIfDefined(attributeGroups));
         final Collection<CoverageContentType> newValues = CollectionsExt.singletonOrEmpty(newValue);
-        Collection<AttributeGroup> groups = attributeGroups;
+        Collection<DefaultAttributeGroup> groups = attributeGroups;
         if (groups != null) {
-            for (final AttributeGroup group : groups) {
-                if (group instanceof DefaultAttributeGroup) {
-                    ((DefaultAttributeGroup) group).setContentTypes(newValues);
-                    return;
-                }
+            for (final DefaultAttributeGroup group : groups) {
+                group.setContentTypes(newValues);
+                return; // Actually stop at the first instance.
             }
         }
         final DefaultAttributeGroup group = new DefaultAttributeGroup();
@@ -299,7 +311,7 @@
         if (groups != null) {
             groups.add(group);
         } else {
-            groups = Collections.singleton(group);
+            groups = Collections.<DefaultAttributeGroup>singleton(group);
         }
         setAttributeGroups(groups);
     }
@@ -318,24 +330,24 @@
     @XmlElement(name = "dimension", namespace = LegacyNamespaces.GMD)
     public final Collection<RangeDimension> getDimensions() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<RangeDimension,AttributeGroup>(getAttributeGroups()) {
+        return new LegacyPropertyAdapter<RangeDimension,DefaultAttributeGroup>(getAttributeGroups()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected AttributeGroup wrap(final RangeDimension value) {
+            @Override protected DefaultAttributeGroup wrap(final RangeDimension value) {
                 final DefaultAttributeGroup container = new DefaultAttributeGroup();
                 container.setAttributes(CollectionsExt.singletonOrEmpty(value));
                 return container;
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected RangeDimension unwrap(final AttributeGroup container) {
+            @Override protected RangeDimension unwrap(final DefaultAttributeGroup container) {
                 return getSingleton(container.getAttributes(), RangeDimension.class,
                         this, DefaultCoverageDescription.class, "getDimensions");
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final AttributeGroup container, final RangeDimension value) {
+            @Override protected boolean update(final DefaultAttributeGroup container, final RangeDimension value) {
                 if (container instanceof DefaultAttributeGroup) {
-                    ((DefaultAttributeGroup) container).setAttributes(CollectionsExt.singletonOrEmpty(value));
+                    container.setAttributes(CollectionsExt.singletonOrEmpty(value));
                     return true;
                 }
                 return false;
@@ -399,7 +411,7 @@
      * If (and only if) marshalling an older standard version, we omit this attribute.
      */
     @XmlElement(name = "attributeGroup")
-    private Collection<AttributeGroup> getAttributeGroup() {
+    private Collection<DefaultAttributeGroup> getAttributeGroup() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAttributeGroups() : null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index 5853bc5..f6f78e7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
@@ -34,8 +34,11 @@
 import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
 import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.FeatureTypeInfo;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -103,7 +106,7 @@
      * Subset of feature types from cited feature catalogue occurring in resource.
      */
     @SuppressWarnings("serial")
-    private Collection<FeatureTypeInfo> featureTypes;
+    private Collection<DefaultFeatureTypeInfo> featureTypes;
 
     /**
      * Complete bibliographic reference to one or more external feature catalogues.
@@ -131,9 +134,14 @@
         if (object != null) {
             compliant                 = object.isCompliant();
             includedWithDataset       = object.isIncludedWithDataset();
-            locales                   = copyMap(object.getLocalesAndCharsets(), Locale.class);
-            featureTypes              = copyCollection(object.getFeatureTypeInfo(), FeatureTypeInfo.class);
             featureCatalogueCitations = copyCollection(object.getFeatureCatalogueCitations(), Citation.class);
+            if (object instanceof DefaultFeatureCatalogueDescription) {
+                locales = copyMap(((DefaultFeatureCatalogueDescription) object).getLocalesAndCharsets(), Locale.class);
+                featureTypes = copyCollection(((DefaultFeatureCatalogueDescription) object).getFeatureTypeInfo(), DefaultFeatureTypeInfo.class);
+            } else {
+                setLanguages(copyCollection(object.getLanguages(), Locale.class));
+                setFeatureTypes(object.getFeatureTypes());
+            }
         }
     }
 
@@ -190,7 +198,7 @@
      *
      * @since 1.0
      */
-    @Override
+    @UML(identifier="locale", obligation=CONDITIONAL, specification=ISO_19115)
     // @XmlElement at the end of this class.
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
@@ -259,25 +267,35 @@
     /**
      * Returns the subset of feature types from cited feature catalogue occurring in resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code FeatureTypeInfo} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return subset of feature types occurring in resource.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<FeatureTypeInfo> getFeatureTypeInfo() {
-        return featureTypes = nonNullCollection(featureTypes, FeatureTypeInfo.class);
+    @UML(identifier="featureTypes", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultFeatureTypeInfo> getFeatureTypeInfo() {
+        return featureTypes = nonNullCollection(featureTypes, DefaultFeatureTypeInfo.class);
     }
 
     /**
      * Sets the subset of feature types from cited feature catalogue occurring in resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code FeatureTypeInfo} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new feature types.
      *
      * @since 0.5
      */
-    public void setFeatureTypeInfo(final Collection<? extends FeatureTypeInfo> newValues) {
-        featureTypes = writeCollection(newValues, featureTypes, FeatureTypeInfo.class);
+    public void setFeatureTypeInfo(final Collection<? extends DefaultFeatureTypeInfo> newValues) {
+        featureTypes = writeCollection(newValues, featureTypes, DefaultFeatureTypeInfo.class);
     }
 
     /**
@@ -293,21 +311,21 @@
     @XmlElement(name = "featureTypes", namespace = LegacyNamespaces.GMD)
     public final Collection<GenericName> getFeatureTypes() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<GenericName,FeatureTypeInfo>(getFeatureTypeInfo()) {
+        return new LegacyPropertyAdapter<GenericName,DefaultFeatureTypeInfo>(getFeatureTypeInfo()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected FeatureTypeInfo wrap(final GenericName value) {
+            @Override protected DefaultFeatureTypeInfo wrap(final GenericName value) {
                 return new DefaultFeatureTypeInfo(value);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected GenericName unwrap(final FeatureTypeInfo container) {
+            @Override protected GenericName unwrap(final DefaultFeatureTypeInfo container) {
                 return container.getFeatureTypeName();
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final FeatureTypeInfo container, final GenericName value) {
+            @Override protected boolean update(final DefaultFeatureTypeInfo container, final GenericName value) {
                 if (container instanceof DefaultFeatureTypeInfo) {
-                    ((DefaultFeatureTypeInfo) container).setFeatureTypeName(value);
+                    container.setFeatureTypeName(value);
                     return true;
                 }
                 return false;
@@ -370,7 +388,7 @@
      * If (and only if) marshalling an older standard version, we omit this attribute.
      */
     @XmlElement(name = "featureTypes")
-    private Collection<FeatureTypeInfo> getFeatureTypesInfo() {
+    private Collection<DefaultFeatureTypeInfo> getFeatureTypesInfo() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getFeatureTypeInfo() : null;
     }
 
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
index 93c4f3e..74ebd7f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
@@ -25,13 +25,24 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.FeatureTypeInfo;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
  * Information about the occurring feature type.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code FeatureTypeInfo} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -56,7 +67,8 @@
     "featureInstanceCount"
 })
 @XmlRootElement(name = "MD_FeatureTypeInfo")
-public class DefaultFeatureTypeInfo extends ISOMetadata implements FeatureTypeInfo {
+@UML(identifier="MD_FeatureTypeInfo", specification=ISO_19115)
+public class DefaultFeatureTypeInfo extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -100,10 +112,8 @@
      * metadata instances can also be obtained by unmarshalling an invalid XML document.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(FeatureTypeInfo)
      */
-    public DefaultFeatureTypeInfo(final FeatureTypeInfo object) {
+    public DefaultFeatureTypeInfo(final DefaultFeatureTypeInfo object) {
         super(object);
         if (object != null) {
             featureTypeName      = object.getFeatureTypeName();
@@ -112,39 +122,14 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultFeatureTypeInfo}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultFeatureTypeInfo} instance is created using the
-     *       {@linkplain #DefaultFeatureTypeInfo(FeatureTypeInfo) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultFeatureTypeInfo castOrCopy(final FeatureTypeInfo object) {
-        if (object == null || object instanceof DefaultFeatureTypeInfo) {
-            return (DefaultFeatureTypeInfo) object;
-        }
-        return new DefaultFeatureTypeInfo(object);
-    }
-
-    /**
      * Returns the name of the feature type.
      *
      * @return name of the feature type.
      *
      * @see org.apache.sis.feature.DefaultFeatureType#getName()
      */
-    @Override
     @XmlElement(name = "featureTypeName", required = true)
+    @UML(identifier="featureTypeName", obligation=MANDATORY, specification=ISO_19115)
     public GenericName getFeatureTypeName() {
         return featureTypeName;
     }
@@ -164,9 +149,9 @@
      *
      * @return the number of occurrence of feature instances for this feature types, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 1)
     @XmlElement(name = "featureInstanceCount")
+    @UML(identifier="featureInstanceCount", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getFeatureInstanceCount() {
         return featureInstanceCount;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
index aeb259f..506a0db 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
@@ -33,8 +33,11 @@
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.gco.InternationalStringAdapter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.SampleDimension;
+// Specific to the main branch:
+import org.opengis.metadata.content.Band;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -110,8 +113,10 @@
         super(object);
         if (object != null) {
             sequenceIdentifier = object.getSequenceIdentifier();
-            description        = object.getDescription();
-            names              = copyCollection(object.getNames(), Identifier.class);
+            description        = object.getDescriptor();
+            if (object instanceof DefaultRangeDimension) {
+                names = copyCollection(((DefaultRangeDimension) object).getNames(), Identifier.class);
+            }
         }
     }
 
@@ -121,7 +126,7 @@
      *
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link SampleDimension}, then this method
+     *   <li>Otherwise if the given object is an instance of {@code SampleDimension}, then this method
      *       delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.</li>
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultRangeDimension}, then it is returned unchanged.</li>
@@ -136,8 +141,8 @@
      *         given object itself), or {@code null} if the argument was null.
      */
     public static DefaultRangeDimension castOrCopy(final RangeDimension object) {
-        if (object instanceof SampleDimension) {
-            return DefaultSampleDimension.castOrCopy((SampleDimension) object);
+        if (object instanceof Band) {
+            return DefaultBand.castOrCopy((Band) object);
         }
         // Intentionally tested after the sub-interfaces.
         if (object == null || object instanceof DefaultRangeDimension) {
@@ -174,9 +179,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
@@ -230,8 +235,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="name", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Identifier> getNames() {
         return names = nonNullCollection(names, Identifier.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
index 2be6ff4..fe1e286 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
@@ -37,8 +37,11 @@
 import org.apache.sis.xml.bind.metadata.MI_RangeElementDescription;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.SampleDimension;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -49,6 +52,14 @@
  * <div class="preformat">{@code MD_SampleDimension}
  * {@code   └─units………………………} Units of data in each dimension included in the resource.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code SampleDimension} interface.
+ * </div>
+ *
  * <h2>Terminology</h2>
  * <i>Data values</i> should be physical values expressed in the unit of measurement
  * given by {@link #getUnits()}. <i>Cell values</i> are values stored in the device,
@@ -88,7 +99,8 @@
 })
 @XmlRootElement(name = "MD_SampleDimension")
 @XmlSeeAlso({DefaultBand.class, DefaultRangeDimension.class})
-public class DefaultSampleDimension extends DefaultRangeDimension implements SampleDimension {
+@UML(identifier="MD_SampleDimension", specification=ISO_19115)
+public class DefaultSampleDimension extends DefaultRangeDimension {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -194,57 +206,50 @@
      * metadata instances can also be obtained by unmarshalling an invalid XML document.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(SampleDimension)
      */
-    public DefaultSampleDimension(final SampleDimension object) {
+    public DefaultSampleDimension(final DefaultSampleDimension object) {
         super(object);
         if (object != null) {
-            minValue                 = object.getMinValue();
-            maxValue                 = object.getMaxValue();
-            meanValue                = object.getMeanValue();
-            numberOfValues           = object.getNumberOfValues();
-            standardDeviation        = object.getStandardDeviation();
-            units                    = object.getUnits();
-            scaleFactor              = object.getScaleFactor();
-            offset                   = object.getOffset();
-            transferFunctionType     = object.getTransferFunctionType();
-            bitsPerValue             = object.getBitsPerValue();
-            nominalSpatialResolution = object.getNominalSpatialResolution();
-            otherPropertyType        = object.getOtherPropertyType();
-            otherProperty            = object.getOtherProperty();
+            init(object);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is is an instance of {@link Band}, then this
-     *       method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultSampleDimension}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultSampleDimension} instance is created using the
-     *       {@linkplain #DefaultSampleDimension(SampleDimension) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
+     * Initializes this sample dimension to the values of the given object.
      */
-    public static DefaultSampleDimension castOrCopy(final SampleDimension object) {
-        if (object instanceof Band) {
-            return DefaultBand.castOrCopy((Band) object);
+    private void init(final DefaultSampleDimension object) {
+        minValue                 = object.getMinValue();
+        maxValue                 = object.getMaxValue();
+        meanValue                = object.getMeanValue();
+        numberOfValues           = object.getNumberOfValues();
+        standardDeviation        = object.getStandardDeviation();
+        units                    = object.getUnits();
+        scaleFactor              = object.getScaleFactor();
+        offset                   = object.getOffset();
+        transferFunctionType     = object.getTransferFunctionType();
+        bitsPerValue             = object.getBitsPerValue();
+        nominalSpatialResolution = object.getNominalSpatialResolution();
+        otherPropertyType        = object.getOtherPropertyType();
+        otherProperty            = object.getOtherProperty();
+    }
+
+    /**
+     * Bridge constructor for {@link DefaultBand#DefaultBand(Band)}.
+     */
+    DefaultSampleDimension(final Band object) {
+        super(object);
+        if (object != null) {
+            if (object instanceof DefaultSampleDimension) {
+                init((DefaultSampleDimension) object);
+            } else {
+                maxValue     = object.getMaxValue();
+                minValue     = object.getMinValue();
+                units        = object.getUnits();
+                scaleFactor  = object.getScaleFactor();
+                offset       = object.getOffset();
+                bitsPerValue = object.getBitsPerValue();
+            }
         }
-        //-- Intentionally tested after the sub-interfaces.
-        if (object == null || object instanceof DefaultSampleDimension) {
-            return (DefaultSampleDimension) object;
-        }
-        return new DefaultSampleDimension(object);
     }
 
     /**
@@ -252,10 +257,10 @@
      *
      * @return the number of values used in a thematic classification resource, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "numberOfValues")
     @XmlJavaTypeAdapter(GO_Integer.Since2014.class)
+    @UML(identifier="numberOfValues", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getNumberOfValues() {
         return numberOfValues;
     }
@@ -279,8 +284,8 @@
      *
      * @return minimum value of data values in each dimension included in the resource, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "minValue")
+    @UML(identifier="minValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMinValue() {
         return minValue;
     }
@@ -302,8 +307,8 @@
      *
      * @return maximum value of data values in each dimension included in the resource, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "maxValue")
+    @UML(identifier="maxValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMaxValue() {
         return maxValue;
     }
@@ -325,9 +330,9 @@
      *
      * @return the mean value of data values in each dimension included in the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "meanValue")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="meanValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMeanValue() {
         return meanValue;
     }
@@ -349,9 +354,9 @@
      *
      * @return standard deviation of data values in each dimension included in the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "standardDeviation")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="standardDeviation", obligation=OPTIONAL, specification=ISO_19115)
     public Double getStandardDeviation() {
         return standardDeviation;
     }
@@ -372,8 +377,8 @@
      *
      * @return the units of data in the dimension, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "units")
+    @UML(identifier="units", obligation=CONDITIONAL, specification=ISO_19115)
     public Unit<?> getUnits() {
         return units;
     }
@@ -393,8 +398,8 @@
      *
      * @return scale factor which has been applied to the cell value, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "scaleFactor")
+    @UML(identifier="scaleFactor", obligation=OPTIONAL, specification=ISO_19115)
     public Double getScaleFactor() {
         return scaleFactor;
     }
@@ -414,8 +419,8 @@
      *
      * @return the physical value corresponding to a cell value of zero, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "offset")
+    @UML(identifier="offset", obligation=OPTIONAL, specification=ISO_19115)
     public Double getOffset() {
         return offset;
     }
@@ -441,7 +446,6 @@
      *
      * @return type of transfer function, or {@code null}.
      */
-    @Override
     public TransferFunctionType getTransferFunctionType() {
         return transferFunctionType;
     }
@@ -463,9 +467,9 @@
      * @return maximum number of significant bits in the uncompressed representation
      *         for the value in each band of each pixel, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 1)
     @XmlElement(name = "bitsPerValue")
+    @UML(identifier="bitsPerValue", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getBitsPerValue() {
         return bitsPerValue;
     }
@@ -492,7 +496,6 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "rangeElementDescription")
     @XmlJavaTypeAdapter(MI_RangeElementDescription.Since2014.class)
     public Collection<RangeElementDescription> getRangeElementDescriptions() {
@@ -522,7 +525,6 @@
      *
      * @return smallest distance between which separate points can be distinguished, or {@code null}.
      */
-    @Override
     @ValueRange(minimum = 0, isMinIncluded = false)
     public Double getNominalSpatialResolution() {
         return nominalSpatialResolution;
@@ -547,9 +549,9 @@
      *
      * @return type of other attribute description, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "otherPropertyType")
     @XmlJavaTypeAdapter(GO_RecordType.Since2014.class)
+    @UML(identifier="otherPropertyType", obligation=OPTIONAL, specification=ISO_19115)
     public RecordType getOtherPropertyType() {
         return otherPropertyType;
     }
@@ -570,9 +572,9 @@
      *
      * @return instance of other/attributeType that defines attributes, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "otherProperty")
     @XmlJavaTypeAdapter(GO_Record.Since2014.class)
+    @UML(identifier="otherProperty", obligation=OPTIONAL, specification=ISO_19115)
     public Record getOtherProperty() {
         return otherProperty;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
index 26c845b..0ebb08e 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
@@ -22,9 +22,9 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.opengis.util.InternationalString;
 import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.distribution.DataFile;
-import org.opengis.util.InternationalString;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.bind.FilterByVersion;
@@ -126,11 +126,14 @@
     public DefaultDataFile(final DataFile object) {
         super(object);
         if (object != null) {
-            fileName        = object.getFileName();
-            fileDescription = object.getFileDescription();
-            fileType        = object.getFileType();
-            featureTypes    = copyCollection(object.getFeatureTypes(), LocalName.class);
-            fileFormat      = object.getFileFormat();
+            if (object instanceof DefaultDataFile) {
+                DefaultDataFile df = (DefaultDataFile) object;
+                fileName        = df.getFileName();
+                fileDescription = df.getFileDescription();
+                fileType        = df.getFileType();
+            }
+            featureTypes = copyCollection(object.getFeatureTypes(), LocalName.class);
+            fileFormat   = object.getFileFormat();
         }
     }
 
@@ -167,7 +170,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileName()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileName", required = true)
     public URI getFileName() {
         return fileName;
@@ -193,7 +195,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileDescription()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileDescription", required = true)
     public InternationalString getFileDescription() {
         return fileDescription;
@@ -219,7 +220,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileType()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileType", required = true)
     @XmlJavaTypeAdapter(MimeFileTypeAdapter.class)
     public String getFileType() {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
index 06708bb..955d031 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
@@ -36,6 +36,11 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Technical means and media by which a resource is obtained from the distributor.
@@ -135,9 +140,14 @@
             unitsOfDistribution = object.getUnitsOfDistribution();
             transferSize        = object.getTransferSize();
             onLines             = copyCollection(object.getOnLines(), OnlineResource.class);
-            offLines            = copyCollection(object.getOffLines(), Medium.class);
-            transferFrequency   = object.getTransferFrequency();
-            distributionFormats = copyCollection(object.getDistributionFormats(), Format.class);
+            if (object instanceof DefaultDigitalTransferOptions) {
+                final DefaultDigitalTransferOptions c = (DefaultDigitalTransferOptions) object;
+                offLines            = copyCollection(c.getOffLines(), Medium.class);
+                transferFrequency   = c.getTransferFrequency();
+                distributionFormats = copyCollection(c.getDistributionFormats(), Format.class);
+            } else {
+                offLines = singleton(object.getOffLine(), Medium.class);
+            }
         }
     }
 
@@ -241,8 +251,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "offLine")
+    @UML(identifier="offLine", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Medium> getOffLines() {
         Collection<Medium> c = offLines = nonNullCollection(offLines, Medium.class);
         if (c != null && c.size() > 1 && FilterByVersion.LEGACY_METADATA.accept()) {
@@ -296,7 +306,6 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "transferFrequency")
     @XmlJavaTypeAdapter(TM_Duration.Since2014.class)
     public TemporalAmount getTransferFrequency() {
@@ -322,8 +331,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="distributionFormat", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Format> getDistributionFormats() {
         return distributionFormats = nonNullCollection(distributionFormats, Format.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
index d922d17..b0f1367 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
@@ -30,6 +30,11 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.bind.gco.InternationalStringAdapter;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the distributor of and options for obtaining the resource.
@@ -115,10 +120,12 @@
     public DefaultDistribution(final Distribution object) {
         super(object);
         if (object != null) {
-            description         = object.getDescription();
             distributionFormats = copyCollection(object.getDistributionFormats(), Format.class);
             distributors        = copyCollection(object.getDistributors(), Distributor.class);
             transferOptions     = copyCollection(object.getTransferOptions(), DigitalTransferOptions.class);
+            if (object instanceof DefaultDistribution) {
+                description = ((DefaultDistribution) object).getDescription();
+            }
         }
     }
 
@@ -154,9 +161,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultFormat.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
index e522d47..67063f1 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
@@ -37,6 +37,12 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Description of the computer language construct that specifies the representation
@@ -154,11 +160,17 @@
     public DefaultFormat(final Format object) {
         super(object);
         if (object != null) {
-            formatSpecificationCitation = object.getFormatSpecificationCitation();
             amendmentNumber             = object.getAmendmentNumber();
             fileDecompressionTechnique  = object.getFileDecompressionTechnique();
-            media                       = copyCollection(object.getMedia(), Medium.class);
             formatDistributors          = copyCollection(object.getFormatDistributors(), Distributor.class);
+            if (object instanceof DefaultFormat) {
+                formatSpecificationCitation = ((DefaultFormat) object).getFormatSpecificationCitation();
+                media = copyCollection(((DefaultFormat) object).getMedia(), Medium.class);
+            } else {
+                setSpecification(object.getSpecification());
+                setVersion(object.getVersion());
+                setName(object.getName());
+            }
         }
     }
 
@@ -194,9 +206,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "formatSpecificationCitation", required = true)
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="formatSpecificationCitation", obligation=MANDATORY, specification=ISO_19115)
     public Citation getFormatSpecificationCitation() {
         return formatSpecificationCitation;
     }
@@ -401,9 +413,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "medium")
     @XmlJavaTypeAdapter(MD_Medium.Since2014.class)
+    @UML(identifier="medium", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Medium> getMedia() {
         return media = nonNullCollection(media, Medium.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultMedium.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
index 4b41ed3..746e184 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
@@ -44,9 +44,11 @@
 import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.AbstractSet;
-import java.util.Iterator;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
 
 
 /**
@@ -89,7 +91,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2657393801067168091L;
+    private static final long serialVersionUID = 7751002701087451894L;
 
     /**
      * Name of the medium on which the resource can be received.
@@ -101,7 +103,8 @@
      * Density at which the data is recorded.
      * If non-null, then the number shall be greater than zero.
      */
-    private Double density;
+    @SuppressWarnings("serial")
+    private Collection<Double> densities;
 
     /**
      * Units of measure for the recording density.
@@ -151,12 +154,14 @@
         super(object);
         if (object != null) {
             name          = object.getName();
-            density       = object.getDensity();
+            densities     = copyCollection(object.getDensities(), Double.class);
             densityUnits  = object.getDensityUnits();
             volumes       = object.getVolumes();
             mediumFormats = copyCollection(object.getMediumFormats(), MediumFormat.class);
             mediumNote    = object.getMediumNote();
-            identifiers   = singleton(object.getIdentifier(), Identifier.class);
+            if (object instanceof DefaultMedium) {
+                identifiers = singleton(((DefaultMedium) object).getIdentifier(), Identifier.class);
+            }
         }
     }
 
@@ -224,12 +229,12 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "density")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
     @ValueRange(minimum = 0, isMinIncluded = false)
+    @UML(identifier="density", obligation=OPTIONAL, specification=ISO_19115)
     public Double getDensity() {
-        return density;
+        return LegacyPropertyAdapter.getSingleton(densities, Double.class, null, DefaultMedium.class, "getDensity");
     }
 
     /**
@@ -242,9 +247,9 @@
      * @since 0.5
      */
     public void setDensity(final Double newValue) {
-        checkWritePermission(density);
+        checkWritePermission(valueIfDefined(densities));
         if (ensurePositive(DefaultMedium.class, "density", true, newValue)) {
-            density = newValue;
+            densities = writeCollection(CollectionsExt.singletonOrEmpty(newValue), densities, Double.class);
         }
     }
 
@@ -259,28 +264,7 @@
     @XmlElement(name = "density", namespace = LegacyNamespaces.GMD)
     public Collection<Double> getDensities() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new AbstractSet<Double>() {
-            /** Returns 0 if empty, or 1 if a density has been specified. */
-            @Override public int size() {
-                return getDensity() != null ? 1 : 0;
-            }
-
-            /** Returns an iterator over 0 or 1 element. Current iterator implementation is unmodifiable. */
-            @Override public Iterator<Double> iterator() {
-                return CollectionsExt.singletonOrEmpty(getDensity()).iterator();
-            }
-
-            /** Adds an element only if the set is empty. This method is invoked by JAXB at unmarshalling time. */
-            @Override public boolean add(final Double newValue) {
-                if (isEmpty()) {
-                    setDensity(newValue);
-                    return true;
-                } else {
-                    LegacyPropertyAdapter.warnIgnoredExtraneous(Double.class, DefaultMedium.class, "setDensities");
-                    return false;
-                }
-            }
-        };
+        return densities = nonNullCollection(densities, Double.class);
     }
 
     /**
@@ -290,7 +274,7 @@
      */
     @Deprecated(since="1.0")
     public void setDensities(final Collection<? extends Double> newValues) {
-        setDensity(LegacyPropertyAdapter.getSingleton(newValues, Double.class, null, DefaultMedium.class, "setDensities"));
+        densities = writeCollection(newValues, densities, Double.class);
     }
 
     /**
@@ -387,9 +371,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "identifier")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="identifier", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getIdentifier() {
         return super.getIdentifier();
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
index 2991ced..6689ee1 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
@@ -32,6 +32,11 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.privy.TemporalDate;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Common ways in which the resource may be obtained or received, and related instructions
@@ -132,8 +137,10 @@
             plannedAvailableDateTime = TemporalDate.toTemporal(object.getPlannedAvailableDateTime());
             orderingInstructions     = object.getOrderingInstructions();
             turnaround               = object.getTurnaround();
-            orderOptionsType         = object.getOrderOptionsType();
-            orderOptions             = object.getOrderOptions();
+            if (object instanceof DefaultStandardOrderProcess) {
+                orderOptionsType = ((DefaultStandardOrderProcess) object).getOrderOptionsType();
+                orderOptions     = ((DefaultStandardOrderProcess) object).getOrderOptions();
+            }
         }
     }
 
@@ -205,7 +212,6 @@
      *
      * @see #getFees()
      */
-    @Override
     public Currency getCurrency() {
         return currency;
     }
@@ -298,9 +304,9 @@
      *
      * @see org.apache.sis.util.iso.DefaultRecord#getRecordType()
      */
-    @Override
     @XmlElement(name = "orderOptionsType")
     @XmlJavaTypeAdapter(GO_RecordType.Since2014.class)
+    @UML(identifier="orderOptionsType", obligation=OPTIONAL, specification=ISO_19115)
     public RecordType getOrderOptionsType() {
         return orderOptionsType;
     }
@@ -328,9 +334,9 @@
      *       when he ordered the resource. We presume that this is not a record to be filled by the user for new
      *       orders, otherwise this method would need to be a factory rather than a getter.
      */
-    @Override
     @XmlElement(name = "orderOptions")
     @XmlJavaTypeAdapter(GO_Record.Since2014.class)
+    @UML(identifier="orderOptions", obligation=OPTIONAL, specification=ISO_19115)
     public Record getOrderOptions() {
         return orderOptions;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
index 6d540c9..9fe2c6a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
@@ -30,6 +30,11 @@
 import org.apache.sis.metadata.privy.ReferencingServices;
 import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Extent with respect to date/time and spatial boundaries.
@@ -113,7 +118,9 @@
         super(object);
         if (object != null) {
             spatialExtent  = copyCollection(object.getSpatialExtent(), GeographicExtent.class);
-            verticalExtent = object.getVerticalExtent();
+            if (object instanceof DefaultSpatialTemporalExtent) {
+                verticalExtent = ((DefaultSpatialTemporalExtent) object).getVerticalExtent();
+            }
         }
     }
 
@@ -169,8 +176,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "verticalExtent")
+    @UML(identifier="verticalExtent", obligation=OPTIONAL, specification=ISO_19115)
     public VerticalExtent getVerticalExtent() {
         return verticalExtent;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
index dea4892..ed81715 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
@@ -36,8 +36,8 @@
 import org.apache.sis.xml.NilObject;
 import org.apache.sis.xml.NilReason;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.temporal.Period;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
index d16a4b2..41d6018 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
@@ -34,9 +34,6 @@
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
-
 
 /**
  * Vertical domain of dataset.
@@ -275,14 +272,14 @@
      * bounds, then the corresponding bounds of the intersection result will also be NaN.</p>
      *
      * @param  other  the vertical extent to intersect with this extent.
-     * @throws MismatchedCoordinateMetadataException if the two extents do not use the same datum, ignoring metadata.
+     * @throws IllegalArgumentException if the two extents do not use the same datum, ignoring metadata.
      *
      * @see Extents#intersection(VerticalExtent, VerticalExtent)
      * @see org.apache.sis.geometry.GeneralEnvelope#intersect(Envelope)
      *
      * @since 0.8
      */
-    public void intersect(final VerticalExtent other) throws MismatchedCoordinateMetadataException {
+    public void intersect(final VerticalExtent other) throws IllegalArgumentException {
         checkWritePermission(value());
         Double min = other.getMinimumValue();
         Double max = other.getMaximumValue();
@@ -309,7 +306,7 @@
                 }
             }
         } catch (UnsupportedOperationException | FactoryException | ClassCastException | TransformException e) {
-            throw new MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.IncompatiblePropertyValue_1, "verticalCRS"), e);
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatiblePropertyValue_1, "verticalCRS"), e);
         }
         if (minimumValue != null && maximumValue != null && minimumValue > maximumValue) {
             minimumValue = maximumValue = NilReason.MISSING.createNilObject(Double.class);
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
index 4fa6593..4c4476a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
@@ -76,9 +76,10 @@
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 import static org.apache.sis.metadata.privy.ReferencingServices.AUTHALIC_RADIUS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+// Specific to the main branch:
+import org.opengis.metadata.identification.DataIdentification;
+import org.opengis.referencing.datum.VerticalDatumType;
+import org.apache.sis.pending.geoapi.evolution.Interim;
 
 
 /**
@@ -157,8 +158,8 @@
         if (metadata != null) {
             Set<Extent> union = null;
             for (final Identification id : nonNull(metadata.getIdentificationInfo())) {
-                if (id != null) {       // Should not be allowed, but we are paranoiac.
-                    final Collection<? extends Extent> extents = id.getExtents();
+                if (id instanceof DataIdentification) {
+                    final Collection<? extends Extent> extents = ((DataIdentification) id).getExtents();
                     if (extents != result && !isNullOrEmpty(extents)) {
                         if (result == null) {
                             result = extents;
@@ -202,9 +203,11 @@
         final Extents m = new Extents();
         try {
             for (final Identification id : nonNull(metadata.getIdentificationInfo())) {
-                if (id != null) for (final Extent extent : nonNull(id.getExtents())) {
-                    if (extent != null) {
-                        m.addHorizontal(extent);
+                if (id instanceof DataIdentification) {
+                    for (final Extent extent : nonNull(((DataIdentification) id).getExtents())) {
+                        if (extent != null) {
+                            m.addHorizontal(extent);
+                        }
                     }
                 }
             }
@@ -228,7 +231,7 @@
      * @return the union of all geographic bounding boxes found in all extents.
      * @throws InvalidMetadataException if an envelope cannot be transformed to a geographic bounding box.
      *
-     * @see CoordinateReferenceSystem#getDomains()
+     * @see org.apache.sis.referencing.AbstractIdentifiedObject#getDomains()
      * @see org.apache.sis.referencing.CRS#getDomainOfValidity(CoordinateReferenceSystem)
      *
      * @since 1.4
@@ -327,7 +330,7 @@
                  */
                 if (!Boolean.FALSE.equals(element.getInclusion())) {
                     for (final Geometry geometry : nonNull(((BoundingPolygon) extent).getPolygons())) {
-                        final Envelope envelope = geometry.getEnvelope();
+                        final Envelope envelope = Interim.getEnvelope(geometry);
                         if (envelope != null) {
                             final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
                             if (crs != null) {
@@ -370,13 +373,13 @@
      *
      * <ul class="verbose">
      *   <li><p><b>Choice based on realization method</b><br>
-     *   Only the extents associated (indirectly, through their CRS) to the same non-null {@link RealizationMethod}
+     *   Only the extents associated (indirectly, through their CRS) to the same non-null {@link VerticalDatumType}
      *   will be taken in account. If all realization methods are absent, then this method conservatively uses only
      *   the first vertical extent. Otherwise the realization method used for filtering the vertical extents is:</p>
      *
      *   <ul>
-     *     <li>{@link RealizationMethod#GEOID} if at least one extent uses this realization method.</li>
-     *     <li>Otherwise, {@link RealizationMethod#TIDAL} if at least one extent uses this realization method.</li>
+     *     <li>{@link VerticalDatumType#GEOIDAL} if at least one extent uses this realization method.</li>
+     *     <li>Otherwise, {@link VerticalDatumType#DEPTH} if at least one extent uses this realization method.</li>
      *     <li>Otherwise, the first non-null realization type found in iteration order.</li>
      *   </ul>
      *
@@ -418,18 +421,18 @@
     @OptionalCandidate
     public static MeasurementRange<Double> getVerticalRange(final Extent extent) {
         MeasurementRange<Double> range = null;
-        RealizationMethod selectedMethod = null;
+        VerticalDatumType selectedMethod = null;
         if (extent != null) {
             for (final VerticalExtent element : nonNull(extent.getVerticalElements())) {
                 double min = element.getMinimumValue();
                 double max = element.getMaximumValue();
                 final VerticalCRS crs = element.getVerticalCRS();
-                RealizationMethod method = null;
+                VerticalDatumType method = null;
                 Unit<?> unit = null;
                 if (crs != null) {
                     final VerticalDatum datum = crs.getDatum();
                     if (datum != null) {
-                        method = datum.getRealizationMethod().orElse(method);
+                        method = datum.getVerticalDatumType();
                     }
                     final CoordinateSystemAxis axis = crs.getCoordinateSystem().getAxis(0);
                     unit = axis.getUnit();
@@ -453,9 +456,9 @@
                      * height in which case we forget all previous ranges and use the new one instead.
                      */
                     if (method != selectedMethod) {
-                        if (selectedMethod == RealizationMethod.GEOID ||
-                                   (method != RealizationMethod.GEOID &&
-                                    method != RealizationMethod.TIDAL))
+                        if (selectedMethod == VerticalDatumType.GEOIDAL ||
+                                   (method != VerticalDatumType.GEOIDAL &&
+                                    method != VerticalDatumType.DEPTH))
                         {
                             continue;
                         }
@@ -755,7 +758,7 @@
      * @param  e2  the second extent, or {@code null}.
      * @return the intersection (may be any of the {@code e1} or {@code e2} argument if unchanged),
      *         or {@code null} if the two given extents are null.
-     * @throws MismatchedCoordinateMetadataException if the two extents do not use the same datum, ignoring metadata.
+     * @throws IllegalArgumentException if the two extents do not use the same datum, ignoring metadata.
      *
      * @see DefaultVerticalExtent#intersect(VerticalExtent)
      *
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/AbstractIdentification.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
index b6c5e65..cf319d9 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
@@ -52,8 +52,13 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.AssociatedResource;
+// Specific to the main branch:
+import java.util.List;
+import java.util.ArrayList;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -249,7 +254,7 @@
      * Provides aggregate dataset information.
      */
     @SuppressWarnings("serial")
-    private Collection<AssociatedResource> associatedResources;
+    private Collection<DefaultAssociatedResource> associatedResources;
 
     /**
      * Constructs an initially empty identification.
@@ -287,20 +292,25 @@
             credits                    = copyCollection(object.getCredits(), String.class);
             status                     = copyCollection(object.getStatus(), Progress.class);
             pointOfContacts            = copyCollection(object.getPointOfContacts(), ResponsibleParty.class);
-            spatialRepresentationTypes = copyCollection(object.getSpatialRepresentationTypes(), SpatialRepresentationType.class);
-            spatialResolutions         = copyCollection(object.getSpatialResolutions(), Resolution.class);
-            temporalResolutions        = copyCollection(object.getTemporalResolutions(), TemporalAmount.class);
-            topicCategories            = copyCollection(object.getTopicCategories(), TopicCategory.class);
-            extents                    = copyCollection(object.getExtents(), Extent.class);
-            additionalDocumentations   = copyCollection(object.getAdditionalDocumentations(), Citation.class);
-            processingLevel            = object.getProcessingLevel();
             resourceMaintenances       = copyCollection(object.getResourceMaintenances(), MaintenanceInformation.class);
             graphicOverviews           = copyCollection(object.getGraphicOverviews(), BrowseGraphic.class);
             resourceFormats            = copyCollection(object.getResourceFormats(), Format.class);
             descriptiveKeywords        = copyCollection(object.getDescriptiveKeywords(), Keywords.class);
             resourceSpecificUsages     = copyCollection(object.getResourceSpecificUsages(), Usage.class);
             resourceConstraints        = copyCollection(object.getResourceConstraints(), Constraints.class);
-            associatedResources        = copyCollection(object.getAssociatedResources(), AssociatedResource.class);
+            if (object instanceof AbstractIdentification) {
+                final AbstractIdentification c = (AbstractIdentification) object;
+                spatialRepresentationTypes = copyCollection(c.getSpatialRepresentationTypes(), SpatialRepresentationType.class);
+                spatialResolutions         = copyCollection(c.getSpatialResolutions(), Resolution.class);
+                temporalResolutions        = copyCollection(c.getTemporalResolutions(), TemporalAmount.class);
+                topicCategories            = copyCollection(c.getTopicCategories(), TopicCategory.class);
+                extents                    = copyCollection(c.getExtents(), Extent.class);
+                additionalDocumentations   = copyCollection(c.getAdditionalDocumentations(), Citation.class);
+                processingLevel            = c.getProcessingLevel();
+                associatedResources        = copyCollection(c.getAssociatedResources(), DefaultAssociatedResource.class);
+            } else {
+                setAggregationInfo(object.getAggregationInfo());
+            }
         }
     }
 
@@ -492,8 +502,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "spatialRepresentationType")
+    @UML(identifier="spatialRepresentationType", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<SpatialRepresentationType> getSpatialRepresentationTypes() {
         return spatialRepresentationTypes = nonNullCollection(spatialRepresentationTypes, SpatialRepresentationType.class);
     }
@@ -517,8 +527,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "spatialResolution")
+    @UML(identifier="spatialResolution", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Resolution> getSpatialResolutions() {
         return spatialResolutions = nonNullCollection(spatialResolutions, Resolution.class);
     }
@@ -541,8 +551,8 @@
      *
      * @since 1.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="temporalResolution", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<TemporalAmount> getTemporalResolutions() {
         return temporalResolutions = nonNullCollection(temporalResolutions, TemporalAmount.class);
     }
@@ -565,8 +575,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "topicCategory")
+    @UML(identifier="topicCategory", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<TopicCategory> getTopicCategories()  {
         return topicCategories = nonNullCollection(topicCategories, TopicCategory.class);
     }
@@ -589,8 +599,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "extent")
+    @UML(identifier="extent", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -613,8 +623,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentations() {
         return additionalDocumentations = nonNullCollection(additionalDocumentations, Citation.class);
     }
@@ -637,9 +647,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "processingLevel")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="processingLevel", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getProcessingLevel() {
         return processingLevel;
     }
@@ -785,25 +795,35 @@
     /**
      * Provides associated resource information.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AssociatedResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return associated resource information.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<AssociatedResource> getAssociatedResources() {
-        return associatedResources = nonNullCollection(associatedResources, AssociatedResource.class);
+    @UML(identifier="associatedResource", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultAssociatedResource> getAssociatedResources() {
+        return associatedResources = nonNullCollection(associatedResources, DefaultAssociatedResource.class);
     }
 
     /**
      * Sets associated resource information.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AssociatedResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new associated resources.
      *
      * @since 0.5
      */
-    public void setAssociatedResources(final Collection<? extends AssociatedResource> newValues) {
-        associatedResources = writeCollection(newValues, associatedResources, AssociatedResource.class);
+    public void setAssociatedResources(final Collection<? extends DefaultAssociatedResource> newValues) {
+        associatedResources = writeCollection(newValues, associatedResources, DefaultAssociatedResource.class);
     }
 
     /**
@@ -819,16 +839,20 @@
     @XmlElement(name = "aggregationInfo", namespace = LegacyNamespaces.GMD)
     public Collection<AggregateInformation> getAggregationInfo() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<AggregateInformation,AssociatedResource>(getAssociatedResources()) {
-            @Override protected AssociatedResource wrap(final AggregateInformation value) {
-                return value;
+        return new LegacyPropertyAdapter<AggregateInformation,DefaultAssociatedResource>(getAssociatedResources()) {
+            @Override protected DefaultAssociatedResource wrap(final AggregateInformation value) {
+                return DefaultAssociatedResource.castOrCopy(value);
             }
 
-            @Override protected AggregateInformation unwrap(final AssociatedResource container) {
-                return DefaultAggregateInformation.castOrCopy(container);
+            @Override protected AggregateInformation unwrap(final DefaultAssociatedResource container) {
+                if (container instanceof AggregateInformation) {
+                    return (AggregateInformation) container;
+                } else {
+                    return new DefaultAggregateInformation(container);
+                }
             }
 
-            @Override protected boolean update(final AssociatedResource container, final AggregateInformation value) {
+            @Override protected boolean update(final DefaultAssociatedResource container, final AggregateInformation value) {
                 return container == value;
             }
         }.validOrNull();
@@ -843,7 +867,20 @@
      */
     @Deprecated(since="1.0")
     public void setAggregationInfo(final Collection<? extends AggregateInformation> newValues) {
-        setAssociatedResources(newValues);
+        checkWritePermission(associatedResources);
+        /*
+         * We can not invoke getAggregationInfo().setValues(newValues) because this method
+         * is invoked by the constructor, which is itself invoked at JAXB marshalling time,
+         * in which case getAggregationInfo() may return null.
+         */
+        List<DefaultAssociatedResource> r = null;
+        if (newValues != null) {
+            r = new ArrayList<DefaultAssociatedResource>(newValues.size());
+            for (final AggregateInformation value : newValues) {
+                r.add(DefaultAssociatedResource.castOrCopy(value));
+            }
+        }
+        setAssociatedResources(r);
     }
 
 
@@ -866,9 +903,6 @@
      * Invoked by JAXB at both marshalling and unmarshalling time.
      * This attribute has been added by ISO 19115:2014 standard.
      * If (and only if) marshalling an older standard version, we omit this attribute.
-     *
-     * @todo Currently, the {@code XmlJavaTypeAdapter} used here just internally converts {@code Duration} objects
-     *       into {@code PeriodDuration} objects. Need to add support for {@code IntervalLength} in the future.
      */
     @XmlElement(name = "temporalResolution")
     private Collection<TemporalAmount> getTemporalResolution() {
@@ -881,7 +915,7 @@
     }
 
     @XmlElement(name = "associatedResource")
-    private Collection<AssociatedResource> getAssociatedResource() {
+    private Collection<DefaultAssociatedResource> getAssociatedResource() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAssociatedResources() : null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
index 81b7e9c..e3edc37 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
@@ -34,12 +34,9 @@
 import org.apache.sis.xml.bind.metadata.code.DS_AssociationTypeCode;
 import org.apache.sis.xml.bind.metadata.code.DS_InitiativeTypeCode;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.AssociatedResource;
-
 
 /**
- * Aggregate dataset information.
+ * Associated resource information.
  * The following properties are mandatory or conditional (i.e. mandatory under some circumstances)
  * in a well-formed metadata according ISO 19115:
  *
@@ -50,8 +47,14 @@
  * {@code   │   └─date……………………………} Reference date for the cited resource.
  * {@code   └─name………………………………………} Citation information about the associated resource.</div>
  *
- * According ISO 19115, at least one of {@linkplain #getAggregateDataSetName() aggregate dataset name}
- * and {@linkplain #getAggregateDataSetIdentifier() aggregate dataset identifier} shall be provided.
+ * According ISO 19115, at least one of {@linkplain #getName() name} and
+ * {@linkplain #getMetadataReference() metadata reference} shall be provided.
+ *
+ * <div class="warning"><b>Upcoming API change — renaming</b><br>
+ * As of ISO 19115:2014, {@code AggregateInformation} has been renamed {@code AssociatedResource}.
+ * This class will be replaced by {@link DefaultAssociatedResource} when GeoAPI will provide the
+ * {@code AssociatedResource} interface (tentatively in GeoAPI 3.1 or 4.0).
+ * </div>
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -67,10 +70,7 @@
  * @author  Cullen Rombach (Image Matters)
  * @version 1.4
  * @since   0.3
- *
- * @deprecated As of ISO 19115:2014, replaced by {@link DefaultAssociatedResource}.
  */
-@Deprecated(since="1.0")
 @XmlType(name = "MD_AggregateInformation_Type", namespace = LegacyNamespaces.GMD, propOrder = {
     "aggregateDataSetName",
     "aggregateDataSetIdentifier",
@@ -92,15 +92,26 @@
 
     /**
      * Constructs a new instance initialized with the values from the specified metadata object.
+     *
+     * @param object The metadata to copy values from.
+     */
+    DefaultAggregateInformation(final DefaultAssociatedResource object) {
+        super(object);
+    }
+
+    /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <em>shallow</em> copy constructor, because the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AssociatedResource)
      */
-    public DefaultAggregateInformation(final AssociatedResource object) {
+    public DefaultAggregateInformation(final AggregateInformation object) {
         super(object);
+        if (object != null && !(object instanceof DefaultAssociatedResource)) {
+            setAggregateDataSetName(object.getAggregateDataSetName());
+            setAggregateDataSetIdentifier(object.getAggregateDataSetIdentifier());
+        }
     }
 
     /**
@@ -112,7 +123,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultAggregateInformation}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code DefaultAggregateInformation} instance is created using the
-     *       {@linkplain #DefaultAggregateInformation(AssociatedResource) copy constructor} and returned.
+     *       {@linkplain #DefaultAggregateInformation(AggregateInformation) copy constructor} and returned.
      *       Note that this is a <em>shallow</em> copy operation, because the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -121,7 +132,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultAggregateInformation castOrCopy(final AssociatedResource object) {
+    public static DefaultAggregateInformation castOrCopy(final AggregateInformation object) {
         if (object == null || object instanceof DefaultAggregateInformation) {
             return (DefaultAggregateInformation) object;
         }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
index 760eb8f..1a24179 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
@@ -28,8 +28,13 @@
 import org.apache.sis.xml.bind.metadata.code.DS_InitiativeTypeCode;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.AssociatedResource;
+// Specific to the main branch:
+import org.opengis.metadata.identification.AggregateInformation;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -69,7 +74,8 @@
     "metadataReference"
 })
 @XmlRootElement(name = "MD_AssociatedResource")
-public class DefaultAssociatedResource extends ISOMetadata implements AssociatedResource {
+@UML(identifier="MD_AssociatedResource", specification=ISO_19115)
+public class DefaultAssociatedResource extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -116,41 +122,43 @@
 
     /**
      * Constructs a new instance initialized with the values from the specified metadata object.
+     * This is a constructor for {@link DefaultAggregateInformation} constructor only.
+     *
+     * @param object The metadata to copy values from.
+     */
+    DefaultAssociatedResource(final DefaultAssociatedResource object) {
+        this.associationType   = object.associationType;
+        this.initiativeType    = object.initiativeType;
+        this.name              = object.name;
+        this.metadataReference = object.metadataReference;
+    }
+
+    /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <em>shallow</em> copy constructor, because the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AssociatedResource)
      */
-    public DefaultAssociatedResource(final AssociatedResource object) {
+    DefaultAssociatedResource(final AggregateInformation object) {
         if (object != null) {
-            this.name              = object.getName();
             this.associationType   = object.getAssociationType();
             this.initiativeType    = object.getInitiativeType();
-            this.metadataReference = object.getMetadataReference();
+            if (object instanceof DefaultAssociatedResource) {
+                this.name              = ((DefaultAssociatedResource) object).getName();
+                this.metadataReference = ((DefaultAssociatedResource) object).getMetadataReference();
+            }
         }
     }
 
     /**
      * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultAssociatedResource}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultAssociatedResource} instance is created using the
-     *       {@linkplain #DefaultAssociatedResource(AssociatedResource) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
      *
      * @param  object  the object to get as a SIS implementation, or {@code null} if none.
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultAssociatedResource castOrCopy(final AssociatedResource object) {
+    static DefaultAssociatedResource castOrCopy(final AggregateInformation object) {
         if (object == null || object instanceof DefaultAssociatedResource) {
             return (DefaultAssociatedResource) object;
         }
@@ -162,9 +170,9 @@
      *
      * @return Citation information about the associated resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "name")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="name", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getName() {
         return name;
     }
@@ -184,9 +192,9 @@
      *
      * @return type of relation between the resources.
      */
-    @Override
     @XmlElement(name = "associationType", required = true)
     @XmlJavaTypeAdapter(DS_AssociationTypeCode.Since2014.class)
+    @UML(identifier="associationType", obligation=MANDATORY, specification=ISO_19115)
     public AssociationType getAssociationType() {
         return associationType;
     }
@@ -206,9 +214,9 @@
      *
      * @return the type of initiative under which the associated resource was produced, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "initiativeType")
     @XmlJavaTypeAdapter(DS_InitiativeTypeCode.Since2014.class)
+    @UML(identifier="initiativeType", obligation=OPTIONAL, specification=ISO_19115)
     public InitiativeType getInitiativeType() {
         return initiativeType;
     }
@@ -228,9 +236,9 @@
      *
      * @return reference to the metadata of the associated resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "metadataReference")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="metadataReference", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getMetadataReference() {
         return metadataReference;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
index d6be803..dbb04de 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
@@ -31,6 +31,11 @@
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.gcx.MimeFileTypeAdapter;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Graphic that provides an illustration of the dataset (should include a legend for the graphic).
@@ -129,8 +134,10 @@
             fileName         = object.getFileName();
             fileDescription  = object.getFileDescription();
             fileType         = object.getFileType();
-            imageConstraints = copyCollection(object.getImageConstraints(), Constraints.class);
-            linkages         = copyCollection(object.getLinkages(), OnlineResource.class);
+            if (object instanceof DefaultBrowseGraphic) {
+                imageConstraints = copyCollection(((DefaultBrowseGraphic) object).getImageConstraints(), Constraints.class);
+                linkages         = copyCollection(((DefaultBrowseGraphic) object).getLinkages(), OnlineResource.class);
+            }
         }
     }
 
@@ -235,8 +242,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="imageContraints", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Constraints> getImageConstraints() {
         return imageConstraints = nonNullCollection(imageConstraints, Constraints.class);
     }
@@ -259,8 +266,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="linkage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getLinkages() {
         return linkages = nonNullCollection(linkages, OnlineResource.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
index a87b1a6..214eac6 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
@@ -34,14 +34,23 @@
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.iso.Names;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.OperationMetadata;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
  * Links a given operation name with a resource identified by an "identifier".
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code CoupledResource} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -67,7 +76,8 @@
     "legacyName"                // Legacy ISO 19139:2007 way to write scoped name
 })
 @XmlRootElement(name = "SV_CoupledResource", namespace = Namespaces.SRV)
-public class DefaultCoupledResource extends ISOMetadata implements CoupledResource {
+@UML(identifier="SV_CoupledResource", specification=ISO_19115)
+public class DefaultCoupledResource extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -95,7 +105,7 @@
      * The service operation.
      */
     @SuppressWarnings("serial")
-    private OperationMetadata operation;
+    private DefaultOperationMetadata operation;
 
     /**
      * Constructs an initially empty coupled resource.
@@ -114,7 +124,7 @@
     public DefaultCoupledResource(final ScopedName name,
                                   final Citation reference,
                                   final DataIdentification resource,
-                                  final OperationMetadata operation)
+                                  final DefaultOperationMetadata operation)
     {
         this.scopedName         = name;
         this.resourceReferences = singleton(reference, Citation.class);
@@ -128,10 +138,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(CoupledResource)
      */
-    public DefaultCoupledResource(final CoupledResource object) {
+    public DefaultCoupledResource(final DefaultCoupledResource object) {
         super(object);
         if (object != null) {
             this.scopedName         = object.getScopedName();
@@ -142,38 +150,13 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultCoupledResource}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultCoupledResource} instance is created using the
-     *       {@linkplain #DefaultCoupledResource(CoupledResource) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultCoupledResource castOrCopy(final CoupledResource object) {
-        if (object == null || object instanceof DefaultCoupledResource) {
-            return (DefaultCoupledResource) object;
-        }
-        return new DefaultCoupledResource(object);
-    }
-
-    /**
      * Returns scoped identifier of the resource in the context of the given service instance.
      *
      * @return identifier of the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "scopedName")
     @XmlJavaTypeAdapter(GO_GenericName.Since2014.class)
+    @UML(identifier="scopedName", obligation=OPTIONAL, specification=ISO_19115)
     public ScopedName getScopedName() {
         return scopedName;
     }
@@ -193,8 +176,8 @@
      *
      * @return references to the resource on which the services operates.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resourceReference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getResourceReferences() {
         return resourceReferences = nonNullCollection(resourceReferences, Citation.class);
     }
@@ -213,8 +196,8 @@
      *
      * @return tightly coupled resources.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<DataIdentification> getResources() {
         return resources = nonNullCollection(resources, DataIdentification.class);
     }
@@ -231,23 +214,33 @@
     /**
      * Returns the service operation.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The return type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return the service operation, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "operation")
     @XmlJavaTypeAdapter(SV_OperationMetadata.Since2014.class)
-    public OperationMetadata getOperation() {
+    @UML(identifier="operation", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultOperationMetadata getOperation() {
         return operation;
     }
 
     /**
      * Sets a new service operation.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValue  the new service operation.
      */
-    public void setOperation(final OperationMetadata newValue) {
+    public void setOperation(final DefaultOperationMetadata newValue) {
         checkWritePermission(operation);
-        operation = newValue;
+        this.operation = newValue;
     }
 
 
@@ -272,7 +265,7 @@
     @XmlElement(name = "operationName", namespace = LegacyNamespaces.SRV)
     private String getOperationName() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            final OperationMetadata operation = getOperation();
+            final DefaultOperationMetadata operation = getOperation();
             if (operation != null) {
                 return operation.getOperationName();
             }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
index d4d70d2..c7ba331 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
@@ -39,6 +39,12 @@
 import java.util.stream.Collectors;
 import org.opengis.metadata.identification.CharacterSet;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.xml.bind.metadata.code.MD_CharacterSetCode;
+
 
 /**
  * Information required to identify a dataset.
@@ -156,7 +162,11 @@
     public DefaultDataIdentification(final DataIdentification object) {
         super(object);
         if (object != null) {
-            locales                 = copyMap(object.getLocalesAndCharsets(), Locale.class);
+            if (object instanceof DefaultDataIdentification) {
+                locales = copyMap(((DefaultDataIdentification) object).getLocalesAndCharsets(), Locale.class);
+            } else {
+                setLanguages(object.getLanguages());
+            }
             environmentDescription  = object.getEnvironmentDescription();
             supplementalInformation = object.getSupplementalInformation();
         }
@@ -196,7 +206,7 @@
      *
      * @since 1.0
      */
-    @Override
+    @UML(identifier="defaultLocale+otherLocale", obligation=CONDITIONAL, specification=ISO_19115)
     // @XmlElement at the end of this class.
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
@@ -264,7 +274,7 @@
     @Dependencies("getLocalesAndCharsets")
     // @XmlElement at the end of this class.
     public Collection<CharacterSet> getCharacterSets() {
-        return getLocalesAndCharsets().values().stream().map(CharacterSet::fromCharset).collect(Collectors.toSet());
+        return getLocalesAndCharsets().values().stream().map(MD_CharacterSetCode::fromCharset).collect(Collectors.toSet());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
index 6dfb650..9918263 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
@@ -26,9 +26,6 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.KeywordClass;
-
 
 /**
  * Specification of a class to categorize keywords in a domain-specific vocabulary
@@ -41,6 +38,14 @@
  * {@code       ├─title………………………} Name by which the cited resource is known.
  * {@code       └─date…………………………} Reference date for the cited resource.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code KeywordClass} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -61,7 +66,7 @@
     "ontology"
 })
 @XmlRootElement(name = "MD_KeywordClass")
-public class DefaultKeywordClass extends ISOMetadata implements KeywordClass {
+public class DefaultKeywordClass extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -107,10 +112,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(KeywordClass)
      */
-    public DefaultKeywordClass(final KeywordClass object) {
+    public DefaultKeywordClass(final DefaultKeywordClass object) {
         super(object);
         if (object != null) {
             className         = object.getClassName();
@@ -120,36 +123,10 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultKeywordClass}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultKeywordClass} instance is created using the
-     *       {@linkplain #DefaultKeywordClass(KeywordClass) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultKeywordClass castOrCopy(final KeywordClass object) {
-        if (object == null || object instanceof DefaultKeywordClass) {
-            return (DefaultKeywordClass) object;
-        }
-        return new DefaultKeywordClass(object);
-    }
-
-    /**
      * Returns a label for the keyword category in natural language.
      *
      * @return the keyword category in natural language.
      */
-    @Override
     @XmlElement(name = "className", required = true)
     public InternationalString getClassName() {
         return className;
@@ -170,7 +147,6 @@
      *
      * @return URI of concept in the ontology, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "conceptIdentifier")
     public URI getConceptIdentifier() {
         return conceptIdentifier;
@@ -191,7 +167,6 @@
      *
      * @return a reference that binds the keyword class to a formal conceptualization.
      */
-    @Override
     @XmlElement(name = "ontology", required = true)
     public Citation getOntology() {
         return ontology;
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywords.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
index 018e3af..4773446 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
@@ -29,8 +29,10 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.KeywordClass;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -91,7 +93,7 @@
      * to the standardized {@linkplain #getType() keyword type} codes.
      */
     @SuppressWarnings("serial")
-    private KeywordClass keywordClass;
+    private DefaultKeywordClass keywordClass;
 
     /**
      * Constructs an initially empty keywords.
@@ -227,25 +229,34 @@
      * Returns the user-defined categorization of groups of keywords that extend or
      * are orthogonal to the standardized {@linkplain #getType() keyword type} codes.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code KeywordClass} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return user-defined categorization of groups of keywords, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "keywordClass")
     @XmlJavaTypeAdapter(MD_KeywordClass.Since2014.class)
-    public KeywordClass getKeywordClass() {
+    @UML(identifier="keywordClass", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultKeywordClass getKeywordClass() {
         return keywordClass;
     }
 
     /**
      * Sets the user-defined categorization of groups of keywords.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code KeywordClass} interface when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1).</div>
+     *
      * @param newValue  new user-defined categorization of groups of keywords.
      *
      * @since 0.5
      */
-    public void setKeywordClass(final KeywordClass newValue) {
+    public void setKeywordClass(final DefaultKeywordClass newValue) {
         checkWritePermission(keywordClass);
         keywordClass = newValue;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
index 65609a0..ba87b29 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
@@ -26,9 +26,11 @@
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.OperationChainMetadata;
-import org.opengis.metadata.identification.OperationMetadata;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -43,6 +45,14 @@
  * {@code       └─connectPoint………………………………………………} Handle for accessing the service interface.
  * {@code           └─linkage…………………………………………………} Location for on-line access using a URL address or similar addressing scheme.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code OperationChainMetadata} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -64,11 +74,12 @@
     "operations"
 })
 @XmlRootElement(name = "SV_OperationChainMetadata", namespace = Namespaces.SRV)
-public class DefaultOperationChainMetadata extends ISOMetadata implements OperationChainMetadata {
+@UML(identifier="SV_OperationChainMetadata", specification=ISO_19115)
+public class DefaultOperationChainMetadata extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = 4132508877114835287L;
+    private static final long serialVersionUID = 4132508877114835286L;
 
     /**
      * The name as used by the service for this chain.
@@ -86,7 +97,7 @@
      * Information about the operations applied by the chain.
      */
     @SuppressWarnings("serial")
-    private List<OperationMetadata> operations;
+    private List<DefaultOperationMetadata> operations;
 
     /**
      * Constructs an initially empty operation chain metadata.
@@ -109,50 +120,23 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(OperationChainMetadata)
      */
-    public DefaultOperationChainMetadata(final OperationChainMetadata object) {
+    public DefaultOperationChainMetadata(final DefaultOperationChainMetadata object) {
         super(object);
         if (object != null) {
             this.name        = object.getName();
             this.description = object.getDescription();
-            this.operations  = copyList(object.getOperations(), OperationMetadata.class);
+            this.operations  = copyList(object.getOperations(), DefaultOperationMetadata.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOperationChainMetadata}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOperationChainMetadata} instance is created using the
-     *       {@linkplain #DefaultOperationChainMetadata(OperationChainMetadata) copy constructor}
-     *       and returned. Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOperationChainMetadata castOrCopy(final OperationChainMetadata object) {
-        if (object == null || object instanceof DefaultOperationChainMetadata) {
-            return (DefaultOperationChainMetadata) object;
-        }
-        return new DefaultOperationChainMetadata(object);
-    }
-
-    /**
      * Returns the name as used by the service for this chain.
      *
      * @return name as used by the service for this chain.
      */
-    @Override
     @XmlElement(name = "name", namespace = Namespaces.SRV, required = true)
+    @UML(identifier="name", obligation=MANDATORY, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
@@ -172,8 +156,8 @@
      *
      * @return narrative explanation of the services in the chain and resulting output, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "description", namespace = Namespaces.SRV)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
@@ -191,20 +175,30 @@
     /**
      * Returns information about the operations applied by the chain.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the operations applied by the chain.
      */
-    @Override
     @XmlElement(name = "operation", namespace = Namespaces.SRV, required = true)
-    public List<OperationMetadata> getOperations() {
-        return operations = nonNullList(operations, OperationMetadata.class);
+    @UML(identifier="operation", obligation=MANDATORY, specification=ISO_19115)
+    public List<DefaultOperationMetadata> getOperations() {
+        return operations = nonNullList(operations, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets the information about the operations applied by the chain.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information about the operations applied by the chain.
      */
-    public void setOperations(final List<? extends OperationMetadata> newValues) {
-        operations = writeList(newValues, operations, OperationMetadata.class);
+    public void setOperations(final List<? extends DefaultOperationMetadata> newValues) {
+        operations = writeList(newValues, operations, DefaultOperationMetadata.class);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
index aeb27cd..bfc6423 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
@@ -28,9 +28,14 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.opengis.metadata.identification.OperationMetadata;
+// Specific to the main branch:
+import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.xml.bind.metadata.code.DCPList;
 
 
 /**
@@ -43,6 +48,14 @@
  * {@code   └─connectPoint………………………………………………} Handle for accessing the service interface.
  * {@code       └─linkage…………………………………………………} Location for on-line access using a URL address or similar addressing scheme.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code OperationMetadata} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -69,11 +82,12 @@
     "dependsOn"
 })
 @XmlRootElement(name = "SV_OperationMetadata", namespace = Namespaces.SRV)
-public class DefaultOperationMetadata extends ISOMetadata implements OperationMetadata {
+@UML(identifier="SV_OperationMetadata", specification=ISO_19115)
+public class DefaultOperationMetadata extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = -6120853428175790473L;
+    private static final long serialVersionUID = -3513177609655567627L;
 
     /**
      * An unique identifier for this interface.
@@ -84,7 +98,7 @@
      * Distributed computing platforms on which the operation has been implemented.
      */
     @SuppressWarnings("serial")
-    private Collection<DistributedComputingPlatform> distributedComputingPlatforms;
+    private Collection<CodeList<?>> distributedComputingPlatforms;
 
     /**
      * Free text description of the intent of the operation and the results of the operation.
@@ -114,7 +128,7 @@
      * List of operation that must be completed immediately.
      */
     @SuppressWarnings("serial")
-    private List<OperationMetadata> dependsOn;
+    private List<DefaultOperationMetadata> dependsOn;
 
     /**
      * Constructs an initially empty operation metadata.
@@ -123,76 +137,33 @@
     }
 
     /**
-     * Constructs a new operation metadata initialized to the specified values.
-     *
-     * @param operationName  an unique identifier for this interface.
-     * @param platform       distributed computing platforms on which the operation has been implemented.
-     * @param connectPoint   handle for accessing the service interface.
-     */
-    public DefaultOperationMetadata(final String operationName,
-                                    final DistributedComputingPlatform platform,
-                                    final OnlineResource connectPoint)
-    {
-        this.operationName                 = operationName;
-        this.distributedComputingPlatforms = singleton(platform, DistributedComputingPlatform.class);
-        this.connectPoints                 = singleton(connectPoint, OnlineResource.class);
-    }
-
-    /**
      * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <em>shallow</em> copy constructor, because the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(OperationMetadata)
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public DefaultOperationMetadata(final OperationMetadata object) {
+    public DefaultOperationMetadata(final DefaultOperationMetadata object) {
         super(object);
         if (object != null) {
             this.operationName                 = object.getOperationName();
-            this.distributedComputingPlatforms = copyCollection(object.getDistributedComputingPlatforms(), DistributedComputingPlatform.class);
+            this.distributedComputingPlatforms = copyCollection(object.getDistributedComputingPlatforms(), (Class) CodeList.class);
             this.operationDescription          = object.getOperationDescription();
             this.invocationName                = object.getInvocationName();
             this.connectPoints                 = copyCollection(object.getConnectPoints(), OnlineResource.class);
             this.parameters                    = copySet(object.getParameters(), (Class) ParameterDescriptor.class);
-            this.dependsOn                     = copyList(object.getDependsOn(), OperationMetadata.class);
+            this.dependsOn                     = copyList(object.getDependsOn(), DefaultOperationMetadata.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOperationMetadata}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOperationMetadata} instance is created using the
-     *       {@linkplain #DefaultOperationMetadata(OperationMetadata) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOperationMetadata castOrCopy(final OperationMetadata object) {
-        if (object == null || object instanceof DefaultOperationMetadata) {
-            return (DefaultOperationMetadata) object;
-        }
-        return new DefaultOperationMetadata(object);
-    }
-
-    /**
      * Returns an unique identifier for this interface.
      *
      * @return an unique identifier for this interface.
      */
-    @Override
     @XmlElement(name = "operationName", required = true)
+    @UML(identifier="operationName", obligation=MANDATORY, specification=ISO_19115)
     public String getOperationName() {
         return operationName;
     }
@@ -210,21 +181,58 @@
     /**
      * Returns the distributed computing platforms (DCPs) on which the operation has been implemented.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The element type will be changed to the {@code DistributedComputingPlatform} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return distributed computing platforms on which the operation has been implemented.
      */
-    @Override
+    @SuppressWarnings("unchecked")
+    @XmlJavaTypeAdapter(DCPList.class)
     @XmlElement(name = "distributedComputingPlatform", required = true)
-    public Collection<DistributedComputingPlatform> getDistributedComputingPlatforms() {
-        return distributedComputingPlatforms = nonNullCollection(distributedComputingPlatforms, DistributedComputingPlatform.class);
+    @UML(identifier="distributedComputingPlatform", obligation=MANDATORY, specification=ISO_19115)
+    public Collection<CodeList<?>> getDistributedComputingPlatforms() {
+        return distributedComputingPlatforms = nonNullCollection(distributedComputingPlatforms, (Class) CodeList.class);
     }
 
     /**
      * Sets the distributed computing platforms on which the operation has been implemented.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The element type will be changed to the {@code DistributedComputingPlatform} code list when GeoAPI will provide
+     * it (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@snippet lang="java" :
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     *   }
+     * </div>
+     *
      * @param  newValues  the new distributed computing platforms on which the operation has been implemented.
      */
-    public void setDistributedComputingPlatforms(final Collection<? extends DistributedComputingPlatform> newValues) {
-        distributedComputingPlatforms = writeCollection(newValues, distributedComputingPlatforms, DistributedComputingPlatform.class);
+    @SuppressWarnings("unchecked")
+    public void setDistributedComputingPlatforms(final Collection<? extends CodeList<?>> newValues) {
+        distributedComputingPlatforms = writeCollection(newValues, distributedComputingPlatforms, (Class) CodeList.class);
     }
 
     /**
@@ -232,8 +240,8 @@
      *
      * @return free text description of the intent of the operation and the results of the operation, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "operationDescription")
+    @UML(identifier="operationDescription", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getOperationDescription() {
         return operationDescription;
     }
@@ -254,8 +262,8 @@
      * @return the name used to invoke this interface within the context of the distributed computing platforms,
      *         or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "invocationName")
+    @UML(identifier="invocationName", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getInvocationName() {
         return invocationName;
     }
@@ -275,8 +283,8 @@
      *
      * @return handle for accessing the service interface.
      */
-    @Override
     @XmlElement(name = "connectPoint", required = true)
+    @UML(identifier="connectPoint", obligation=MANDATORY, specification=ISO_19115)
     public Collection<OnlineResource> getConnectPoints() {
         return connectPoints = nonNullCollection(connectPoints, OnlineResource.class);
     }
@@ -299,9 +307,9 @@
      *
      * @return the parameters that are required for this interface, or an empty collection if none.
      */
-    @Override
     @XmlElement(name = "parameter")
     @SuppressWarnings({"unchecked", "rawtypes"})
+    @UML(identifier="parameters", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<ParameterDescriptor<?>> getParameters() {
         return parameters = nonNullCollection(parameters, (Class) ParameterDescriptor.class);
     }
@@ -319,20 +327,30 @@
     /**
      * Returns the list of operation that must be completed immediately before current operation is invoked.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return list of operation that must be completed immediately, or an empty list if none.
      */
-    @Override
     @XmlElement(name = "dependsOn")
-    public List<OperationMetadata> getDependsOn() {
-        return dependsOn = nonNullList(dependsOn, OperationMetadata.class);
+    @UML(identifier="dependsOn", obligation=OPTIONAL, specification=ISO_19115)
+    public List<DefaultOperationMetadata> getDependsOn() {
+        return dependsOn = nonNullList(dependsOn, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets the list of operation that must be completed before current operation is invoked.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new list of operation.
      */
-    public void setDependsOn(final List<? extends OperationMetadata> newValues) {
-        dependsOn = writeList(newValues, dependsOn, OperationMetadata.class);
+    public void setDependsOn(final List<? extends DefaultOperationMetadata> newValues) {
+        dependsOn = writeList(newValues, dependsOn, DefaultOperationMetadata.class);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultResolution.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultResolution.java
index 30eeff3..5d040dd 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultResolution.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultResolution.java
@@ -32,6 +32,11 @@
 import org.apache.sis.util.resources.Messages;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Level of detail expressed as a scale factor or a ground distance.
@@ -166,13 +171,13 @@
         super(object);
         if (object != null) {
             for (byte p=SCALE; p<=TEXT; p++) {
-                final Object c;
+                Object c = null;
                 switch (p) {
                     case SCALE:    c = object.getEquivalentScale(); break;
-                    case DISTANCE: c = object.getDistance();        break;
-                    case VERTICAL: c = object.getVertical();        break;
-                    case ANGULAR:  c = object.getAngularDistance(); break;
-                    case TEXT:     c = object.getLevelOfDetail();   break;
+                    case DISTANCE: c = object.getDistance(); break;
+                    case VERTICAL: if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getVertical(); break;
+                    case ANGULAR:  if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getAngularDistance(); break;
+                    case TEXT:     if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getLevelOfDetail(); break;
                     default:       throw new AssertionError(p);
                 }
                 if (c != null) {
@@ -289,9 +294,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "vertical")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="vertical", obligation=CONDITIONAL, specification=ISO_19115)
     @ValueRange(minimum=0, isMinIncluded=false)
     public Double getVertical() {
         return (property == VERTICAL) ? (Double) value : null;
@@ -322,9 +327,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "angularDistance")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="angularDistance", obligation=CONDITIONAL, specification=ISO_19115)
     @ValueRange(minimum=0, isMinIncluded=false)
     public Double getAngularDistance() {
         return (property == ANGULAR) ? (Double) value : null;
@@ -355,9 +360,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "levelOfDetail")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="levelOfDetail", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getLevelOfDetail() {
         return (property == TEXT) ? (InternationalString) value : null;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
index 1e86ce0..bf87bb7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
@@ -29,11 +29,15 @@
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.xml.bind.FilterByVersion;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.CouplingType;
-import org.opengis.metadata.identification.OperationChainMetadata;
-import org.opengis.metadata.identification.OperationMetadata;
+// Specific to the main branch:
+import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.xml.bind.metadata.code.SV_CouplingType;
 
 
 /**
@@ -115,13 +119,13 @@
     /**
      * Type of coupling between service and associated data (if exist).
      */
-    private CouplingType couplingType;
+    private CodeList<?> couplingType;
 
     /**
      * Further description of the data coupling in the case of tightly coupled services.
      */
     @SuppressWarnings("serial")
-    private Collection<CoupledResource> coupledResources;
+    private Collection<DefaultCoupledResource> coupledResources;
 
     /**
      * References to the resource on which the service operates.
@@ -145,7 +149,7 @@
      * Information about the operations that comprise the service.
      */
     @SuppressWarnings("serial")
-    private Collection<OperationMetadata> containsOperations;
+    private Collection<DefaultOperationMetadata> containsOperations;
 
     /**
      * Information on the resources that the service operates on.
@@ -157,7 +161,7 @@
      * Information about the chain applied by the service.
      */
     @SuppressWarnings("serial")
-    private Collection<OperationChainMetadata> containsChain;
+    private Collection<DefaultOperationChainMetadata> containsChain;
 
     /**
      * Constructs an initially empty service identification.
@@ -191,18 +195,19 @@
      */
     public DefaultServiceIdentification(final ServiceIdentification object) {
         super(object);
-        if (object != null) {
-            serviceType         = object.getServiceType();
-            serviceTypeVersions = copyCollection(object.getServiceTypeVersions(), String.class);
-            accessProperties    = object.getAccessProperties();
-            couplingType        = object.getCouplingType();
-            coupledResources    = copyCollection(object.getCoupledResources(), CoupledResource.class);
-            operatedDatasets    = copyCollection(object.getOperatedDatasets(), Citation.class);
-            profiles            = copyCollection(object.getProfiles(), Citation.class);
-            serviceStandards    = copyCollection(object.getServiceStandards(), Citation.class);
-            containsOperations  = copyCollection(object.getContainsOperations(), OperationMetadata.class);
-            operatesOn          = copyCollection(object.getOperatesOn(), DataIdentification.class);
-            containsChain       = copyCollection(object.getContainsChain(), OperationChainMetadata.class);
+        if (object instanceof DefaultServiceIdentification) {
+            final DefaultServiceIdentification c = (DefaultServiceIdentification) object;
+            serviceType         = c.getServiceType();
+            serviceTypeVersions = copyCollection(c.getServiceTypeVersions(), String.class);
+            accessProperties    = c.getAccessProperties();
+            couplingType        = c.getCouplingType();
+            coupledResources    = copyCollection(c.getCoupledResources(), DefaultCoupledResource.class);
+            operatedDatasets    = copyCollection(c.getOperatedDatasets(), Citation.class);
+            profiles            = copyCollection(c.getProfiles(), Citation.class);
+            serviceStandards    = copyCollection(c.getServiceStandards(), Citation.class);
+            containsOperations  = copyCollection(c.getContainsOperations(), DefaultOperationMetadata.class);
+            operatesOn          = copyCollection(c.getOperatesOn(), DataIdentification.class);
+            containsChain       = copyCollection(c.getContainsChain(), DefaultOperationChainMetadata.class);
         }
     }
 
@@ -239,8 +244,8 @@
      *
      * @return a service type name.
      */
-    @Override
     @XmlElement(name = "serviceType", required = true)
+    @UML(identifier="serviceType", obligation=MANDATORY, specification=ISO_19115)
     public GenericName getServiceType() {
         return serviceType;
     }
@@ -260,8 +265,8 @@
      *
      * @return the versions of the service.
      */
-    @Override
     @XmlElement(name = "serviceTypeVersion")
+    @UML(identifier="serviceTypeVersion", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<String> getServiceTypeVersions() {
         return serviceTypeVersions = nonNullCollection(serviceTypeVersions, String.class);
     }
@@ -282,8 +287,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "accessProperties")
+    @UML(identifier="accessProperties", obligation=OPTIONAL, specification=ISO_19115)
     public StandardOrderProcess getAccessProperties() {
         return accessProperties;
 
@@ -304,20 +309,55 @@
     /**
      * Returns type of coupling between service and associated data (if exist).
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The return type will be changed to the {@code CouplingType} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return type of coupling between service and associated data, or {@code null} if none.
      */
-    @Override
+    @XmlJavaTypeAdapter(SV_CouplingType.class)
     @XmlElement(name = "couplingType")
-    public CouplingType getCouplingType() {
+    @UML(identifier="couplingType", obligation=CONDITIONAL, specification=ISO_19115)
+    public CodeList<?> getCouplingType() {
         return couplingType;
     }
 
     /**
      * Sets the type of coupling between service and associated data.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code CouplingType} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@snippet lang="java" :
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     *   }
+     * </div>
+     *
      * @param  newValue  the new type of coupling between service and associated data.
      */
-    public void setCouplingType(final CouplingType newValue) {
+    public void setCouplingType(final CodeList<?> newValue) {
         checkWritePermission(couplingType);
         couplingType = newValue;
     }
@@ -325,21 +365,31 @@
     /**
      * Returns further description(s) of the data coupling in the case of tightly coupled services.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code CoupledResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return further description(s) of the data coupling in the case of tightly coupled services.
      */
-    @Override
     @XmlElement(name = "coupledResource")
-    public Collection<CoupledResource> getCoupledResources() {
-        return coupledResources = nonNullCollection(coupledResources, CoupledResource.class);
+    @UML(identifier="coupledResource", obligation=CONDITIONAL, specification=ISO_19115)
+    public Collection<DefaultCoupledResource> getCoupledResources() {
+        return coupledResources = nonNullCollection(coupledResources, DefaultCoupledResource.class);
     }
 
     /**
      * Sets further description(s) of the data coupling in the case of tightly coupled services.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code CoupledResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new further description(s) of the data coupling.
      */
-    public void setCoupledResources(final Collection<? extends CoupledResource> newValues) {
-        coupledResources = writeCollection(newValues, coupledResources, CoupledResource.class);
+    public void setCoupledResources(final Collection<? extends DefaultCoupledResource> newValues) {
+        coupledResources = writeCollection(newValues, coupledResources, DefaultCoupledResource.class);
     }
 
     /**
@@ -349,8 +399,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="operatedDataset", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getOperatedDatasets() {
         return operatedDatasets = nonNullCollection(operatedDatasets, Citation.class);
     }
@@ -373,8 +423,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="profile", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getProfiles() {
         return profiles = nonNullCollection(profiles, Citation.class);
     }
@@ -395,8 +445,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="serviceStandard", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getServiceStandards() {
         return serviceStandards = nonNullCollection(serviceStandards, Citation.class);
     }
@@ -415,21 +465,31 @@
     /**
      * Provides information about the operations that comprise the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the operations that comprise the service.
      */
-    @Override
     @XmlElement(name = "containsOperations")
-    public Collection<OperationMetadata> getContainsOperations() {
-        return containsOperations = nonNullCollection(containsOperations, OperationMetadata.class);
+    @UML(identifier="containsOperations", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultOperationMetadata> getContainsOperations() {
+        return containsOperations = nonNullCollection(containsOperations, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets information(s) about the operations that comprise the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information(s) about the operations that comprise the service.
      */
-    public void setContainsOperations(final Collection<? extends OperationMetadata> newValues) {
-        containsOperations = writeCollection(newValues, containsOperations, OperationMetadata.class);
+    public void setContainsOperations(final Collection<? extends DefaultOperationMetadata> newValues) {
+        containsOperations = writeCollection(newValues, containsOperations, DefaultOperationMetadata.class);
     }
 
     /**
@@ -437,8 +497,8 @@
      *
      * @return information on the resources that the service operates on.
      */
-    @Override
     @XmlElement(name = "operatesOn")
+    @UML(identifier="operatesOn", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<DataIdentification> getOperatesOn() {
         return operatesOn = nonNullCollection(operatesOn, DataIdentification.class);
     }
@@ -455,25 +515,35 @@
     /**
      * Provides information about the chain applied by the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationChainMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the chain applied by the service.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<OperationChainMetadata> getContainsChain() {
-        return containsChain = nonNullCollection(containsChain, OperationChainMetadata.class);
+    @UML(identifier="containsChain", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultOperationChainMetadata> getContainsChain() {
+        return containsChain = nonNullCollection(containsChain, DefaultOperationChainMetadata.class);
     }
 
     /**
      * Sets the information about the chain applied by the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationChainMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information about the chain applied by the service.
      *
      * @since 0.5
      */
-    public void setContainsChain(final Collection<? extends OperationChainMetadata>  newValues) {
-        containsChain = writeCollection(newValues, containsChain, OperationChainMetadata.class);
+    public void setContainsChain(final Collection<? extends DefaultOperationChainMetadata>  newValues) {
+        containsChain = writeCollection(newValues, containsChain, DefaultOperationChainMetadata.class);
     }
 
 
@@ -513,7 +583,7 @@
     }
 
     @XmlElement(name = "containsChain")
-    private Collection<OperationChainMetadata> getOperationChain() {
+    private Collection<DefaultOperationChainMetadata> getOperationChain() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getContainsChain() : null;
     }
 
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
index fa65552..8c6ea0c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
@@ -34,6 +34,11 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Brief description of ways in which the resource(s) is/are currently or has been used.
@@ -162,9 +167,12 @@
             usageDate                 = TemporalDate.toTemporal(object.getUsageDate());
             userDeterminedLimitations = object.getUserDeterminedLimitations();
             userContactInfo           = copyCollection(object.getUserContactInfo(), ResponsibleParty.class);
-            responses                 = copyCollection(object.getResponses(), InternationalString.class);
-            additionalDocumentation   = copyCollection(object.getAdditionalDocumentation(), Citation.class);
-            identifiedIssues          = copyCollection(object.getIdentifiedIssues(), Citation.class);
+            if (object instanceof DefaultUsage) {
+                final DefaultUsage c = (DefaultUsage) object;
+                responses                 = copyCollection(c.getResponses(), InternationalString.class);
+                additionalDocumentation   = copyCollection(c.getAdditionalDocumentation(), Citation.class);
+                identifiedIssues          = copyCollection(c.getIdentifiedIssues(), Citation.class);
+            }
         }
     }
 
@@ -293,8 +301,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="response", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<InternationalString> getResponses() {
         return responses = nonNullCollection(responses, InternationalString.class);
     }
@@ -317,8 +325,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentation() {
         return additionalDocumentation = nonNullCollection(additionalDocumentation, Citation.class);
     }
@@ -342,8 +350,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="identifiedIssues", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getIdentifiedIssues() {
         return identifiedIssues = nonNullCollection(identifiedIssues, Citation.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/OperationName.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/OperationName.java
index 110dcdf..31e09be 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/OperationName.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/OperationName.java
@@ -21,52 +21,32 @@
 import java.util.Collection;
 import org.apache.sis.util.privy.Strings;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Collections;
-import java.io.Serializable;
-import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.opengis.metadata.identification.OperationMetadata;
-
 
 /**
- * An {@code OperationMetadata} placeholder to be replaced later by a reference to another {@link OperationMetadata}.
+ * An {@code OperationMetadata} placeholder to be replaced later by a reference to another {@code OperationMetadata}.
  * This temporary place holder is used when the operation name is unmarshalled before the actual operation definition.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class OperationName implements OperationMetadata, Serializable {
+final class OperationName extends DefaultOperationMetadata {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -7958898214063034276L;
-
-    /**
-     * The operation name.
-     */
-    private final String operationName;
+    private static final long serialVersionUID = 7221543581387125873L;
 
     /**
      * Creates a new placeholder for the operation of the given name.
      */
     OperationName(final String operationName) {
-        this.operationName = operationName;
+        setOperationName(operationName);
     }
 
     /**
-     * Returns the operation name.
-     */
-    @Override public String                                   getOperationName()                 {return operationName;}
-    @Override public Collection<DistributedComputingPlatform> getDistributedComputingPlatforms() {return Collections.emptySet();}
-    @Override public Collection<OnlineResource>               getConnectPoints()                 {return Collections.emptySet();}
-
-    /**
      * Returns a string representation of this placeholder.
      */
     @Override
     public String toString() {
-        return Strings.bracket("OperationMetadata", operationName);
+        return Strings.bracket("OperationMetadata", getOperationName());
     }
 
     /**
@@ -79,25 +59,23 @@
      * <p>This method is invoked at unmarshalling time for resolving the {@code OperationMetadata} instance which
      * were identified only by a name in a {@code <srv:operationName>} element.</p>
      */
-    static void resolve(final Collection<OperationMetadata> containsOperations, final Collection<CoupledResource> coupledResources) {
-        final Map<String,OperationMetadata> byName = new HashMap<>();
-        for (final OperationMetadata operation : containsOperations) {
+    static void resolve(final Collection<DefaultOperationMetadata> containsOperations, final Collection<DefaultCoupledResource> coupledResources) {
+        final Map<String,DefaultOperationMetadata> byName = new HashMap<>();
+        for (final DefaultOperationMetadata operation : containsOperations) {
             add(byName, operation.getOperationName(), operation);
         }
-        for (final CoupledResource resource : coupledResources) {
-            if (resource instanceof DefaultCoupledResource) {
-                OperationMetadata operation = resource.getOperation();
-                if (operation instanceof OperationName) {
-                    final String name = operation.getOperationName();
+        for (final DefaultCoupledResource resource : coupledResources) {
+            DefaultOperationMetadata operation = resource.getOperation();
+            if (operation instanceof OperationName) {
+                final String name = operation.getOperationName();
+                operation = byName.get(name);
+                if (operation == null) {
                     operation = byName.get(name);
                     if (operation == null) {
-                        operation = byName.get(name);
-                        if (operation == null) {
-                            continue;
-                        }
+                        continue;
                     }
-                    ((DefaultCoupledResource) resource).setOperation(operation);
                 }
+                resource.setOperation(operation);
             }
         }
     }
@@ -106,9 +84,9 @@
      * Adds the given operation in the given map under the given name. If an entry already exists for the given name,
      * then this method sets the value to {@code null} for meaning that we have duplicated values for that name.
      */
-    private static void add(final Map<String,OperationMetadata> byName, final String name, final OperationMetadata operation) {
+    private static void add(final Map<String,DefaultOperationMetadata> byName, final String name, final DefaultOperationMetadata operation) {
         final boolean exists = byName.containsKey(name);
-        final OperationMetadata previous = byName.put(name, operation);
+        final DefaultOperationMetadata previous = byName.put(name, operation);
         if (previous != operation && (previous != null || exists)) {
             byName.put(name, null);                                         // Mark the entry as duplicated.
         }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/package-info.java
index 6db83b5..e21af16 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/package-info.java
@@ -47,8 +47,8 @@
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.AssociationType Association type}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.InitiativeType  Initiative type}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.TopicCategory   Topic category}<br>
- * {@code  ├─} {@linkplain org.opengis.metadata.identification.CouplingType    Coupling type}<br>
- * {@code  └─} {@linkplain org.opengis.metadata.identification.DistributedComputingPlatform Distributed computing platform}<br>
+ * {@code  ├─} Coupling type}<br>
+ * {@code  └─} Distributed computing platform}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                 {@linkplain org.apache.sis.metadata.iso.identification.AbstractIdentification        Identification} «abstract»<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultResolution             Resolution}<br>
@@ -63,12 +63,12 @@
  * {@code      └─} {@linkplain org.opengis.metadata.identification.InitiativeType                       Initiative type} «code list»<br>
  *                 {@linkplain org.apache.sis.metadata.iso.identification.DefaultDataIdentification     Data identification}<br>
  *                 {@linkplain org.apache.sis.metadata.iso.identification.DefaultServiceIdentification  Service identification}<br>
- * {@code  ├─}     {@linkplain org.opengis.metadata.identification.CouplingType                         Coupling type} «code list»<br>
+ * {@code  ├─}     Coupling type} «code list»<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultCoupledResource        Coupled resource}<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultOperationMetadata      Operation metadata}<br>
- * {@code  │   ├─} {@linkplain org.opengis.metadata.identification.DistributedComputingPlatform         Distributed computing platform} «code list»<br>
+ * {@code  │   ├─} Distributed computing platform} «code list»<br>
  * {@code  │   └─} {@linkplain org.apache.sis.parameter.DefaultParameterDescriptor                      Parameter descriptor}<br>
- * {@code  │       └─} {@linkplain org.opengis.parameter.ParameterDirection                             Parameter direction} «enum»<br>
+ * {@code  │       └─} Parameter direction} «enum»<br>
  * {@code  └─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata Operation chain metadata}<br>
  * </td></tr></table>
  *
@@ -115,12 +115,10 @@
     @XmlJavaTypeAdapter(CI_Citation.class),
     @XmlJavaTypeAdapter(CI_OnlineResource.class),
     @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
-    @XmlJavaTypeAdapter(DCPList.class),
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
     @XmlJavaTypeAdapter(GO_GenericName.class),
     @XmlJavaTypeAdapter(MD_AggregateInformation.class),
-    @XmlJavaTypeAdapter(MD_AssociatedResource.class),
     @XmlJavaTypeAdapter(MD_BrowseGraphic.class),
     @XmlJavaTypeAdapter(MD_CharacterSetCode.class),
     @XmlJavaTypeAdapter(MD_Constraints.class),
@@ -138,7 +136,6 @@
     @XmlJavaTypeAdapter(MD_TopicCategoryCode.class),
     @XmlJavaTypeAdapter(MD_Usage.class),
     @XmlJavaTypeAdapter(SV_CoupledResource.class),
-    @XmlJavaTypeAdapter(SV_CouplingType.class),
     @XmlJavaTypeAdapter(SV_OperationMetadata.class),
     @XmlJavaTypeAdapter(SV_OperationChainMetadata.class),
     @XmlJavaTypeAdapter(SV_Parameter.class),
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultLineage.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
index 4faa4c3..e448b84 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
@@ -32,8 +32,11 @@
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.metadata.MD_Scope;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -144,10 +147,12 @@
         super(object);
         if (object != null) {
             statement               = object.getStatement();
-            scope                   = object.getScope();
-            additionalDocumentation = copyCollection(object.getAdditionalDocumentation(), Citation.class);
             processSteps            = copyCollection(object.getProcessSteps(), ProcessStep.class);
             sources                 = copyCollection(object.getSources(), Source.class);
+            if (object instanceof DefaultLineage) {
+                scope                   = ((DefaultLineage) object).getScope();
+                additionalDocumentation = copyCollection(((DefaultLineage) object).getAdditionalDocumentation(), Citation.class);
+            }
         }
     }
 
@@ -206,9 +211,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
@@ -232,8 +237,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentation() {
         return additionalDocumentation = nonNullCollection(additionalDocumentation, Citation.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
index a91ec94..9fa6dcf 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
@@ -42,8 +42,11 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -188,12 +191,14 @@
             rationale             = object.getRationale();
             stepDateTime          = TemporalUtilities.createInstant(object.getDate());
             processors            = copyCollection(object.getProcessors(), ResponsibleParty.class);
-            references            = copyCollection(object.getReferences(), Citation.class);
             sources               = copyCollection(object.getSources(), Source.class);
-            scope                 = object.getScope();
             outputs               = copyCollection(object.getOutputs(), Source.class);
             processingInformation = object.getProcessingInformation();
             reports               = copyCollection(object.getReports(), ProcessStepReport.class);
+            if (object instanceof DefaultProcessStep) {
+                references = copyCollection(((DefaultProcessStep) object).getReferences(), Citation.class);
+                scope      = ((DefaultProcessStep) object).getScope();
+            }
         }
     }
 
@@ -271,7 +276,6 @@
      *
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "stepDateTime")
     @XmlJavaTypeAdapter(TM_Primitive.Since2014.class)
     public TemporalPrimitive getStepDateTime() {
@@ -301,14 +305,7 @@
     @Deprecated(since="1.0")
     @XmlElement(name = "dateTime", namespace = LegacyNamespaces.GMD)
     public Date getDate() {
-        if (FilterByVersion.LEGACY_METADATA.accept()) {
-            Date date = TemporalUtilities.getAnyDate(getStepDateTime());
-            if (date == null) {
-                date = ProcessStep.super.getDate();
-            }
-            return date;
-        }
-        return null;
+        return FilterByVersion.LEGACY_METADATA.accept() ? TemporalUtilities.getAnyDate(getStepDateTime()) : null;
     }
 
     /**
@@ -362,8 +359,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="reference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getReferences() {
         return references = nonNullCollection(references, Citation.class);
     }
@@ -386,9 +383,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultSource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultSource.java
index f1b4889..8eb22bb 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultSource.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultSource.java
@@ -45,8 +45,12 @@
 import org.apache.sis.metadata.internal.Dependencies;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -190,14 +194,19 @@
         super(object);
         if (object != null) {
             description             = object.getDescription();
-            sourceSpatialResolution = object.getSourceSpatialResolution();
             sourceReferenceSystem   = object.getSourceReferenceSystem();
             sourceCitation          = object.getSourceCitation();
-            sourceMetadata          = copyCollection(object.getSourceMetadata(), Citation.class);
-            scope                   = object.getScope();
             sourceSteps             = copyCollection(object.getSourceSteps(), ProcessStep.class);
             processedLevel          = object.getProcessedLevel();
             resolution              = object.getResolution();
+            if (object instanceof DefaultSource) {
+                sourceSpatialResolution = ((DefaultSource) object).getSourceSpatialResolution();
+                sourceMetadata          = copyCollection(((DefaultSource) object).getSourceMetadata(), Citation.class);
+                scope                   = ((DefaultSource) object).getScope();
+            } else {
+                setScaleDenominator(object.getScaleDenominator());
+                setSourceExtents(object.getSourceExtents());
+            }
         }
     }
 
@@ -254,9 +263,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "sourceSpatialResolution")
     @XmlJavaTypeAdapter(MD_Resolution.Since2014.class)
+    @UML(identifier="sourceSpatialResolution", obligation=OPTIONAL, specification=ISO_19115)
     public Resolution getSourceSpatialResolution() {
         return sourceSpatialResolution;
     }
@@ -376,8 +385,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="sourceMetadata", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getSourceMetadata() {
         return sourceMetadata = nonNullCollection(sourceMetadata, Citation.class);
     }
@@ -401,9 +410,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=CONDITIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
@@ -441,7 +450,7 @@
                         scope = new DefaultScope(scope);
                         this.scope = scope;
                     } else {
-                        return Collections.unmodifiableCollection(scope.getExtents());
+                        return Collections.singleton(scope.getExtent());
                     }
                 }
                 return ((DefaultScope) scope).getExtents();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/AttributeTypeAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/AttributeTypeAdapter.java
index d24789d..b3272dd 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/AttributeTypeAdapter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/AttributeTypeAdapter.java
@@ -34,6 +34,7 @@
      * defined by ISO 19115-3:2016 schema.
      */
     @Override
+    @SuppressWarnings("deprecation")
     public AttributeType unmarshal(GO_CharacterString value) {
         return new LegacyFeatureType(LegacyFeatureType.ADAPTER.unmarshal(value));
     }
@@ -43,6 +44,7 @@
      * {@link DefaultScopeDescription}.
      */
     @Override
+    @SuppressWarnings("deprecation")
     public GO_CharacterString marshal(AttributeType value) {
         return LegacyFeatureType.ADAPTER.marshal(LegacyFeatureType.wrap(value));
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
index 889d8a7..2d813be 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
@@ -43,8 +43,11 @@
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.temporal.PeriodDuration;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -91,6 +94,11 @@
     private static final long serialVersionUID = -7934472150551882812L;
 
     /**
+     * New code list item defined in ISO 19115:2014.
+     */
+    private static final DateType NEXT_UPDATE = DateType.valueOf("NEXT_UPDATE");
+
+    /**
      * Frequency with which changes and additions are made to the resource after the
      * initial resource is completed.
      */
@@ -157,11 +165,18 @@
         super(object);
         if (object != null) {
             maintenanceAndUpdateFrequency   = object.getMaintenanceAndUpdateFrequency();
-            maintenanceDates                = copyCollection(object.getMaintenanceDates(), CitationDate.class);
             userDefinedMaintenanceFrequency = object.getUserDefinedMaintenanceFrequency();
-            maintenanceScopes               = copyCollection(object.getMaintenanceScopes(), Scope.class);
             maintenanceNotes                = copyCollection(object.getMaintenanceNotes(), InternationalString.class);
-            contacts                        = copyCollection(object.getContacts(), ResponsibleParty.class);
+            if (object instanceof DefaultMaintenanceInformation) {
+                final DefaultMaintenanceInformation c = (DefaultMaintenanceInformation) object;
+                maintenanceDates                = copyCollection(c.getMaintenanceDates(), CitationDate.class);
+                maintenanceScopes               = copyCollection(c.getMaintenanceScopes(), Scope.class);
+                contacts                        = copyCollection(c.getContacts(), ResponsibleParty.class);
+            } else {
+                setDateOfNextUpdate(object.getDateOfNextUpdate());
+                setUpdateScopes(object.getUpdateScopes());
+                setUpdateScopeDescriptions(object.getUpdateScopeDescriptions());
+            }
         }
     }
 
@@ -220,8 +235,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="maintenanceDate", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<CitationDate> getMaintenanceDates() {
         return maintenanceDates = nonNullCollection(maintenanceDates, CitationDate.class);
     }
@@ -244,8 +259,8 @@
      * @return scheduled revision date, or {@code null}.
      *
      * @deprecated As of ISO 19115:2014, replaced by {@link #getMaintenanceDates()} in order to enable inclusion
-     *             of a {@link DateType} to describe the type of the date. Note that {@link DateType#NEXT_UPDATE}
-     *             was added to that code list.
+     *             of a {@link DateType} to describe the type of the date. The associated date type is
+     *             {@code DateType.valueOf("NEXT_UPDATE")}.
      */
     @Override
     @Deprecated(since="1.0")
@@ -254,9 +269,9 @@
     public Date getDateOfNextUpdate() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
             final Collection<CitationDate> dates = getMaintenanceDates();
-            if (dates != null) {                                                    // May be null on XML marshalling.
+            if (dates != null) {                                                // May be null on XML marshalling.
                 for (final CitationDate date : dates) {
-                    if (date.getDateType() == DateType.NEXT_UPDATE) {
+                    if (date.getDateType() == NEXT_UPDATE) {
                         return date.getDate();
                     }
                 }
@@ -279,7 +294,7 @@
             final Iterator<CitationDate> it = dates.iterator();
             while (it.hasNext()) {
                 final CitationDate date = it.next();
-                if (date.getDateType() == DateType.NEXT_UPDATE) {
+                if (date.getDateType() == NEXT_UPDATE) {
                     if (newValue == null) {
                         it.remove();
                         return;
@@ -292,7 +307,7 @@
         }
         if (newValue != null) {
             @SuppressWarnings("removal")
-            final var date = new DefaultCitationDate(newValue, DateType.NEXT_UPDATE);
+            final var date = new DefaultCitationDate(newValue, NEXT_UPDATE);
             if (dates != null) {
                 dates.add(date);
             } else {
@@ -340,8 +355,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="maintenanceScope", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Scope> getMaintenanceScopes() {
         return maintenanceScopes = nonNullCollection(maintenanceScopes, Scope.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScope.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
index a58349d..f576f30 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
@@ -26,8 +26,14 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.metadata.internal.Dependencies;
+import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
+import org.apache.sis.util.privy.CollectionsExt;
 
 
 /**
@@ -117,8 +123,12 @@
         super(object);
         if (object != null) {
             level            = object.getLevel();
-            extents          = copyCollection(object.getExtents(), Extent.class);
             levelDescription = copyCollection(object.getLevelDescription(), ScopeDescription.class);
+            if (object instanceof DefaultScope) {
+                extents = copyCollection(((DefaultScope) object).getExtents(), Extent.class);
+            } else {
+                extents = singleton(object.getExtent(), Extent.class);
+            }
         }
     }
 
@@ -175,8 +185,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "extent")
+    @UML(identifier="extent", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -193,6 +203,34 @@
     }
 
     /**
+     * Information about the spatial, vertical and temporal extent of the data specified by the scope.
+     * This method fetches the value from the {@linkplain #getExtents() extents} collection.
+     *
+     * @return Information about the extent of the data, or {@code null}.
+     *
+     * @deprecated As of ISO 19115:2014, replaced by {@link #getExtents()}.
+     */
+    @Override
+    @Deprecated
+    @Dependencies("getExtents")
+    public Extent getExtent() {
+        return LegacyPropertyAdapter.getSingleton(getExtents(), Extent.class, null, DefaultScope.class, "getExtent");
+    }
+
+    /**
+     * Sets information about the spatial, vertical and temporal extent of the data specified by the scope.
+     * This method stores the value in the {@linkplain #setExtents(Collection) extents} collection.
+     *
+     * @param newValue The new extent.
+     *
+     * @deprecated As of ISO 19115:2014, replaced by {@link #setExtents(Collection)}.
+     */
+    @Deprecated
+    public void setExtent(final Extent newValue) {
+        setExtents(CollectionsExt.singletonOrEmpty(newValue));
+    }
+
+    /**
      * Returns detailed descriptions about the level of the data specified by the scope.
      *
      * @return detailed description about the level of the data.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
index 9e61cd6..0395700 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
@@ -237,7 +237,7 @@
                 return cast(value, type);
             } else if (!(value instanceof Set) || !((Set<?>) value).isEmpty()) {
                 return Semaphores.query(Semaphores.NULL_COLLECTION)
-                       ? null : new ExcludedSet<E>(NAMES[code-1], NAMES[property-1]);
+                       ? null : new ExcludedSet<>(NAMES[code-1], NAMES[property-1]);
             }
         }
         /*
@@ -562,6 +562,7 @@
      *
      * @since 1.0
      */
+    @SuppressWarnings("deprecation")
     public void setLevelDescription(final ScopeCode level, final Set<? extends CharSequence> newValues) {
         if (level == ScopeCode.DATASET) {
             String description = null;
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/FeatureTypeAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/FeatureTypeAdapter.java
index 2bf7d32..34605c7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/FeatureTypeAdapter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/FeatureTypeAdapter.java
@@ -34,6 +34,7 @@
      * defined by ISO 19115-3:2016 schema.
      */
     @Override
+    @SuppressWarnings("deprecation")
     public FeatureType unmarshal(GO_CharacterString value) {
         return new LegacyFeatureType(LegacyFeatureType.ADAPTER.unmarshal(value));
     }
@@ -43,6 +44,7 @@
      * {@link DefaultScopeDescription}.
      */
     @Override
+    @SuppressWarnings("deprecation")
     public GO_CharacterString marshal(FeatureType value) {
         return LegacyFeatureType.ADAPTER.marshal(LegacyFeatureType.wrap(value));
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/package-info.java
index e66ec57..c5774b8 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/package-info.java
@@ -41,8 +41,8 @@
  * {@code  ├─} {@linkplain org.apache.sis.metadata.iso.DefaultExtendedElementInformation   Extended element information}<br>
  * {@code  └─} {@linkplain org.apache.sis.metadata.iso.DefaultIdentifier                   Identifier}<br>
  * {@linkplain org.opengis.util.CodeList Code list}<br>
- * {@code  ├─} {@linkplain org.opengis.metadata.Datatype     Data type}<br>
- * {@code  └─} {@linkplain org.opengis.annotation.Obligation Obligation}<br>
+ * {@code  ├─} {@linkplain org.opengis.metadata.Datatype   Data type}<br>
+ * {@code  └─} {@linkplain org.opengis.metadata.Obligation Obligation}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                     {@linkplain org.apache.sis.metadata.iso.DefaultMetadata                     Metadata}<br>
  * {@code  ├─}         {@linkplain org.apache.sis.metadata.iso.DefaultMetadataScope                Metadata scope}<br>
@@ -51,7 +51,7 @@
  * {@code  ├─}         {@linkplain org.apache.sis.metadata.iso.DefaultMetadataExtensionInformation Metadata extension information}<br>
  * {@code  │   └─}     {@linkplain org.apache.sis.metadata.iso.DefaultExtendedElementInformation   Extended element information}<br>
  * {@code  │       ├─} {@linkplain org.opengis.metadata.Datatype                                   Data type} «code list»<br>
- * {@code  │       └─} {@linkplain org.opengis.annotation.Obligation                               Obligation} «code list»<br>
+ * {@code  │       └─} {@linkplain org.opengis.metadata.Obligation                                 Obligation} «code list»<br>
  * {@code  └─}         {@linkplain org.apache.sis.metadata.iso.DefaultIdentifier                   Identifier}<br>
  * </td></tr></table>
  *
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractDataEvaluation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractDataEvaluation.java
index 8ff2b9c..32bb11b 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractDataEvaluation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractDataEvaluation.java
@@ -20,16 +20,13 @@
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlSeeAlso;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.DataEvaluation;
-import org.opengis.metadata.quality.FullInspection;
-import org.opengis.metadata.quality.IndirectEvaluation;
-import org.opengis.metadata.quality.SampleBasedInspection;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Data evaluation method.
- * See the {@link DataEvaluation} GeoAPI interface for more details.
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -52,7 +49,8 @@
     DefaultIndirectEvaluation.class,
     DefaultSampleBasedInspection.class
 })
-public class AbstractDataEvaluation extends DefaultEvaluationMethod implements DataEvaluation {
+@UML(identifier="DQ_DataEvaluation", specification=UNSPECIFIED)
+public class AbstractDataEvaluation extends DefaultEvaluationMethod {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -70,49 +68,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(DataEvaluation)
      */
-    public AbstractDataEvaluation(final DataEvaluation object) {
+    public AbstractDataEvaluation(final AbstractDataEvaluation object) {
         super(object);
     }
-
-    /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link IndirectEvaluation},
-     *       {@link SampleBasedInspection} or {@link FullInspection}, then this method delegates to
-     *       the {@code castOrCopy(…)} method of the corresponding SIS subclass.
-     *       Note that if the given object implements more than one of the above-cited interfaces,
-     *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code AbstractDataEvaluation}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code AbstractDataEvaluation} instance is created using the
-     *       {@linkplain #AbstractDataEvaluation(DataEvaluation) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static AbstractDataEvaluation castOrCopy(final DataEvaluation object) {
-        if (object instanceof FullInspection) {
-            return DefaultFullInspection.castOrCopy((FullInspection) object);
-        }
-        if (object instanceof SampleBasedInspection) {
-            return DefaultSampleBasedInspection.castOrCopy((SampleBasedInspection) object);
-        }
-        if (object instanceof IndirectEvaluation) {
-            return DefaultIndirectEvaluation.castOrCopy((IndirectEvaluation) object);
-        }
-        if (object == null || object instanceof AbstractDataEvaluation) {
-            return (AbstractDataEvaluation) object;
-        }
-        return new AbstractDataEvaluation(object);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractElement.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractElement.java
index 1042670..8db20c8 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractElement.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractElement.java
@@ -31,12 +31,12 @@
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.quality.Result;
 import org.opengis.metadata.quality.Element;
-import org.opengis.metadata.quality.Usability;
 import org.opengis.metadata.quality.Completeness;
 import org.opengis.metadata.quality.ThematicAccuracy;
 import org.opengis.metadata.quality.PositionalAccuracy;
 import org.opengis.metadata.quality.LogicalConsistency;
 import org.opengis.metadata.quality.EvaluationMethodType;
+import org.opengis.metadata.quality.Usability;
 import org.opengis.util.InternationalString;
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.gco.InternationalStringAdapter;
@@ -46,11 +46,11 @@
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.TemporalQuality;
-import org.opengis.metadata.quality.EvaluationMethod;
-import org.opengis.metadata.quality.MeasureReference;
-import org.opengis.metadata.quality.Metaquality;
+// Specific to the main branch:
+import org.opengis.metadata.quality.TemporalAccuracy;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
@@ -118,14 +118,12 @@
     /**
      * Reference to measure used.
      */
-    @SuppressWarnings("serial")
-    private MeasureReference measureReference;
+    private DefaultMeasureReference measureReference;
 
     /**
      * Evaluation information.
      */
-    @SuppressWarnings("serial")
-    private EvaluationMethod evaluationMethod;
+    private DefaultEvaluationMethod evaluationMethod;
 
     /**
      * Value (or set of values) obtained from applying a data quality measure.
@@ -167,14 +165,17 @@
     public AbstractElement(final Element object) {
         super(object);
         if (object != null) {
-            standaloneQualityReportDetails = object.getStandaloneQualityReportDetails();
-            if ((measureReference = object.getMeasureReference()) == null) {
-                final var candidate = new DefaultMeasureReference();
-                if (candidate.setLegacy(object)) measureReference = candidate;
+            if (object instanceof AbstractElement) {
+                final AbstractElement impl = (AbstractElement) object;
+                standaloneQualityReportDetails = impl.getStandaloneQualityReportDetails();
+                evaluationMethod = impl.getEvaluationMethod();
+                derivedElements  = copyCollection(impl.getDerivedElements(), Element.class);
+                if ((measureReference = impl.getMeasureReference()) == null) {
+                    final var candidate = new DefaultMeasureReference();
+                    if (candidate.setLegacy(object)) measureReference = candidate;
+                }
             }
-            evaluationMethod = object.getEvaluationMethod();
-            results          = copyCollection(object.getResults(), Result.class);
-            derivedElements  = copyCollection(object.getDerivedElements(), Element.class);
+            results = copyCollection(object.getResults(), Result.class);
         }
     }
 
@@ -185,8 +186,7 @@
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
      *   <li>Otherwise if the given object is an instance of {@link PositionalAccuracy},
-     *       {@link TemporalQuality}, {@link ThematicAccuracy}, {@link LogicalConsistency},
-     *       {@link Completeness}, {@link Usability} or {@link Metaquality},
+     *       {@link ThematicAccuracy}, {@link LogicalConsistency} or {@link Completeness},
      *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
      *       Note that if the given object implements more than one of the above-cited interfaces,
      *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
@@ -206,8 +206,8 @@
         if (object instanceof PositionalAccuracy) {
             return AbstractPositionalAccuracy.castOrCopy((PositionalAccuracy) object);
         }
-        if (object instanceof TemporalQuality) {
-            return AbstractTemporalQuality.castOrCopy((TemporalQuality) object);
+        if (object instanceof TemporalAccuracy) {
+            return AbstractTemporalQuality.castOrCopy((TemporalAccuracy) object);
         }
         if (object instanceof ThematicAccuracy) {
             return AbstractThematicAccuracy.castOrCopy((ThematicAccuracy) object);
@@ -221,9 +221,6 @@
         if (object instanceof Usability) {
             return DefaultUsability.castOrCopy((Usability) object);
         }
-        if (object instanceof Metaquality) {
-            return AbstractMetaquality.castOrCopy((Metaquality) object);
-        }
         // Intentionally tested after the sub-interfaces.
         if (object == null || object instanceof AbstractElement) {
             return (AbstractElement) object;
@@ -239,9 +236,9 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "standaloneQualityReportDetails")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="standaloneQualityReportDetails", obligation=OPTIONAL, specification=UNSPECIFIED)
     public InternationalString getStandaloneQualityReportDetails() {
         return standaloneQualityReportDetails;
     }
@@ -265,10 +262,10 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "measure", required = false)
-    public MeasureReference getMeasureReference() {
-        return (measureReference != null) ? measureReference : Element.super.getMeasureReference();
+    @UML(identifier="measure", obligation=OPTIONAL, specification=UNSPECIFIED)
+    public DefaultMeasureReference getMeasureReference() {
+        return measureReference;
     }
 
     /**
@@ -278,7 +275,7 @@
      *
      * @since 1.3
      */
-    public void setMeasureReference(final MeasureReference newValues) {
+    public void setMeasureReference(final DefaultMeasureReference newValues) {
         checkWritePermission(measureReference);
         measureReference = newValues;
     }
@@ -289,8 +286,8 @@
      *
      * @see #getEvaluationMethodProperty(Function)
      */
-    private <V> V getMeasureReferenceProperty(final Function<MeasureReference,V> getter) {
-        final MeasureReference m = getMeasureReference();
+    private <V> V getMeasureReferenceProperty(final Function<DefaultMeasureReference,V> getter) {
+        final DefaultMeasureReference m = getMeasureReference();
         return (m != null) && FilterByVersion.LEGACY_METADATA.accept() ? getter.apply(m) : null;
     }
 
@@ -302,10 +299,10 @@
      */
     private <V> void setMeasureReferenceProperty(final BiConsumer<DefaultMeasureReference,V> setter, final V newValue) {
         if (newValue != null) {
-            if (!(measureReference instanceof DefaultMeasureReference)) {
-                measureReference = new DefaultMeasureReference(measureReference);
+            if (measureReference == null) {
+                measureReference = new DefaultMeasureReference();
             }
-            setter.accept((DefaultMeasureReference) measureReference, newValue);
+            setter.accept(measureReference, newValue);
         }
     }
 
@@ -324,17 +321,14 @@
         if (!FilterByVersion.LEGACY_METADATA.accept()) {
             return null;
         }
-        MeasureReference m = getMeasureReference();
+        DefaultMeasureReference m = getMeasureReference();
         if (m == null) {
             if (state() == State.FINAL) {
                 return Collections.emptyList();
             }
             setMeasureReference(m = new DefaultMeasureReference());
         }
-        if (m instanceof DefaultMeasureReference) {
-            return ((DefaultMeasureReference) m).getNamesOfMeasure();
-        }
-        return Collections.unmodifiableCollection(m.getNamesOfMeasure());
+        return m.getNamesOfMeasure();
     }
 
     /**
@@ -363,7 +357,7 @@
     @Dependencies("getMeasureReference")
     @XmlElement(name = "measureIdentification", namespace = LegacyNamespaces.GMD)
     public Identifier getMeasureIdentification() {
-        return getMeasureReferenceProperty(MeasureReference::getMeasureIdentification);
+        return getMeasureReferenceProperty(DefaultMeasureReference::getMeasureIdentification);
     }
 
     /**
@@ -390,7 +384,7 @@
     @Dependencies("getMeasureReference")
     @XmlElement(name = "measureDescription", namespace = LegacyNamespaces.GMD)
     public InternationalString getMeasureDescription() {
-        return getMeasureReferenceProperty(MeasureReference::getMeasureDescription);
+        return getMeasureReferenceProperty(DefaultMeasureReference::getMeasureDescription);
     }
 
     /**
@@ -412,9 +406,9 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "evaluationMethod", required = false)
-    public EvaluationMethod getEvaluationMethod() {
+    @UML(identifier="evaluationMethod", obligation=OPTIONAL, specification=UNSPECIFIED)
+    public DefaultEvaluationMethod getEvaluationMethod() {
         return evaluationMethod;
     }
 
@@ -425,7 +419,7 @@
      *
      * @since 1.3
      */
-    public void setEvaluationMethod(final EvaluationMethod newValue) {
+    public void setEvaluationMethod(final DefaultEvaluationMethod newValue) {
         checkWritePermission(evaluationMethod);
         evaluationMethod = newValue;
     }
@@ -436,8 +430,8 @@
      *
      * @see #getMeasureReferenceProperty(Function)
      */
-    private <V> V getEvaluationMethodProperty(final Function<EvaluationMethod,V> getter) {
-        final EvaluationMethod m = getEvaluationMethod();
+    private <V> V getEvaluationMethodProperty(final Function<DefaultEvaluationMethod,V> getter) {
+        final DefaultEvaluationMethod m = getEvaluationMethod();
         return (m != null) && FilterByVersion.LEGACY_METADATA.accept() ? getter.apply(m) : null;
     }
 
@@ -449,10 +443,10 @@
      */
     private <V> void setEvaluationMethodProperty(final BiConsumer<DefaultEvaluationMethod,V> setter, final V newValue) {
         if (newValue != null) {
-            if (!(evaluationMethod instanceof DefaultEvaluationMethod)) {
-                evaluationMethod = new DefaultEvaluationMethod(evaluationMethod);
+            if (evaluationMethod == null) {
+                evaluationMethod = new DefaultEvaluationMethod();
             }
-            setter.accept((DefaultEvaluationMethod) evaluationMethod, newValue);
+            setter.accept(evaluationMethod, newValue);
         }
     }
 
@@ -468,7 +462,7 @@
     @Dependencies("getEvaluationMethod")
     @XmlElement(name = "evaluationMethodType", namespace = LegacyNamespaces.GMD)
     public EvaluationMethodType getEvaluationMethodType() {
-        return getEvaluationMethodProperty(EvaluationMethod::getEvaluationMethodType);
+        return getEvaluationMethodProperty(DefaultEvaluationMethod::getEvaluationMethodType);
     }
 
     /**
@@ -495,7 +489,7 @@
     @Dependencies("getEvaluationMethod")
     @XmlElement(name = "evaluationMethodDescription", namespace = LegacyNamespaces.GMD)
     public InternationalString getEvaluationMethodDescription() {
-        return getEvaluationMethodProperty(EvaluationMethod::getEvaluationMethodDescription);
+        return getEvaluationMethodProperty(DefaultEvaluationMethod::getEvaluationMethodDescription);
     }
 
     /**
@@ -522,7 +516,7 @@
     @Dependencies("getEvaluationMethod")
     @XmlElement(name = "evaluationProcedure", namespace = LegacyNamespaces.GMD)
     public Citation getEvaluationProcedure() {
-        return getEvaluationMethodProperty(EvaluationMethod::getEvaluationProcedure);
+        return getEvaluationMethodProperty(DefaultEvaluationMethod::getEvaluationProcedure);
     }
 
     /**
@@ -552,7 +546,7 @@
     @XmlElement(name = "dateTime", namespace = LegacyNamespaces.GMD)
     public Collection<Date> getDates() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            EvaluationMethod m = getEvaluationMethod();
+            DefaultEvaluationMethod m = getEvaluationMethod();
             if (m == null) {
                 if (state() == State.FINAL) {
                     return Collections.emptyList();
@@ -609,8 +603,8 @@
      *
      * @since 1.3
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="derivedElement", obligation=OPTIONAL, specification=UNSPECIFIED)
     public Collection<Element> getDerivedElements() {
         return derivedElements = nonNullCollection(derivedElements, Element.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractMetaquality.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractMetaquality.java
index 3bec132..b01ace4 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractMetaquality.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractMetaquality.java
@@ -20,11 +20,9 @@
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlSeeAlso;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Metaquality;
-import org.opengis.metadata.quality.Confidence;
-import org.opengis.metadata.quality.Representativity;
-import org.opengis.metadata.quality.Homogeneity;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
@@ -56,7 +54,8 @@
     DefaultRepresentativity.class,
     DefaultHomogeneity.class
 })
-public class AbstractMetaquality extends AbstractElement implements Metaquality {
+@UML(identifier="DQ_Metaquality", specification=UNSPECIFIED)
+public class AbstractMetaquality extends AbstractElement {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -74,50 +73,9 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Metaquality)
      */
     @SuppressWarnings("unchecked")
-    public AbstractMetaquality(final Metaquality object) {
+    public AbstractMetaquality(final AbstractMetaquality object) {
         super(object);
     }
-
-    /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link Confidence}, {@link Representativity} or {@link Homogeneity},
-     *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
-     *       Note that if the given object implements more than one of the above-cited interfaces,
-     *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code AbstractMetaquality}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code AbstractMetaquality} instance is created using the
-     *       {@linkplain #AbstractMetaquality(Metaquality) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static AbstractMetaquality castOrCopy(final Metaquality object) {
-        if (object instanceof Confidence) {
-            return DefaultConfidence.castOrCopy((Confidence) object);
-        }
-        if (object instanceof Representativity) {
-            return DefaultRepresentativity.castOrCopy((Representativity) object);
-        }
-        if (object instanceof Homogeneity) {
-            return DefaultHomogeneity.castOrCopy((Homogeneity) object);
-        }
-        // Intentionally tested after the sub-interfaces.
-        if (object == null || object instanceof AbstractMetaquality) {
-            return (AbstractMetaquality) object;
-        }
-        return new AbstractMetaquality(object);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractResult.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractResult.java
index 3380c3d..7971ee9 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractResult.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractResult.java
@@ -29,9 +29,11 @@
 import org.apache.sis.xml.bind.metadata.MD_Scope;
 import org.apache.sis.xml.bind.gco.GO_Temporal;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.DescriptiveResult;
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
@@ -98,9 +100,10 @@
      */
     public AbstractResult(final Result object) {
         super(object);
-        if (object != null) {
-            resultScope = object.getResultScope();
-            dateTime    = object.getDateTime();
+        if (object instanceof AbstractResult) {
+            final AbstractResult impl = (AbstractResult) object;
+            resultScope = impl.getResultScope();
+            dateTime    = impl.getDateTime();
         }
     }
 
@@ -111,7 +114,7 @@
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
      *   <li>Otherwise if the given object is an instance of {@link ConformanceResult},
-     *       {@link QuantitativeResult}, {@link DescriptiveResult} or {@link CoverageResult},
+     *       {@link QuantitativeResult} or {@link CoverageResult},
      *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
      *       Note that if the given object implements more than one of the above-cited interfaces,
      *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
@@ -134,9 +137,6 @@
         if (object instanceof ConformanceResult) {
             return DefaultConformanceResult.castOrCopy((ConformanceResult) object);
         }
-        if (object instanceof DescriptiveResult) {
-            return DefaultDescriptiveResult.castOrCopy((DescriptiveResult) object);
-        }
         if (object instanceof CoverageResult) {
             return DefaultCoverageResult.castOrCopy((CoverageResult) object);
         }
@@ -154,9 +154,9 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "resultScope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="resultScope", obligation=OPTIONAL, specification=UNSPECIFIED)
     public Scope getResultScope() {
         return resultScope;
     }
@@ -182,9 +182,9 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "dateTime")
     @XmlJavaTypeAdapter(GO_Temporal.Since2014.class)
+    @UML(identifier="dateTime", obligation=OPTIONAL, specification=UNSPECIFIED)
     public Temporal getDateTime() {
         return dateTime;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractTemporalQuality.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractTemporalQuality.java
index 7284686..f0d9e6d 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractTemporalQuality.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractTemporalQuality.java
@@ -24,8 +24,9 @@
 import org.opengis.metadata.quality.AccuracyOfATimeMeasurement;
 import org.opengis.metadata.quality.TemporalAccuracy;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.TemporalQuality;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
@@ -58,7 +59,8 @@
     DefaultTemporalValidity.class
 })
 @SuppressWarnings("deprecation")
-public class AbstractTemporalQuality extends AbstractElement implements TemporalQuality {
+@UML(identifier="DQ_TemporalQuality", specification=UNSPECIFIED)
+public class AbstractTemporalQuality extends AbstractElement implements TemporalAccuracy {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -77,9 +79,9 @@
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
      *
-     * @see #castOrCopy(TemporalQuality)
+     * @see #castOrCopy(TemporalAccuracy)
      */
-    public AbstractTemporalQuality(final TemporalQuality object) {
+    public AbstractTemporalQuality(final TemporalAccuracy object) {
         super(object);
     }
 
@@ -97,7 +99,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code AbstractTemporalQuality}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code AbstractTemporalQuality} instance is created using the
-     *       {@linkplain #AbstractTemporalQuality(TemporalQuality) copy constructor} and returned.
+     *       {@linkplain #AbstractTemporalQuality(TemporalAccuracy) copy constructor} and returned.
      *       Note that this is a <em>shallow</em> copy operation, because the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -106,7 +108,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static AbstractTemporalQuality castOrCopy(final TemporalQuality object) {
+    public static AbstractTemporalQuality castOrCopy(final TemporalAccuracy object) {
         if (object instanceof AccuracyOfATimeMeasurement) {
             return DefaultAccuracyOfATimeMeasurement.castOrCopy((AccuracyOfATimeMeasurement) object);
         }
@@ -116,9 +118,6 @@
         if (object instanceof TemporalValidity) {
             return DefaultTemporalValidity.castOrCopy((TemporalValidity) object);
         }
-        if (object instanceof TemporalAccuracy) {
-            return AbstractTemporalAccuracy.castOrCopy((TemporalAccuracy) object);
-        }
         if (object == null || object instanceof AbstractTemporalQuality) {
             return (AbstractTemporalQuality) object;
         }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractThematicAccuracy.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractThematicAccuracy.java
index dde77a7..40b5346 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractThematicAccuracy.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/AbstractThematicAccuracy.java
@@ -23,8 +23,8 @@
 import org.opengis.metadata.quality.ThematicClassificationCorrectness;
 import org.opengis.metadata.quality.QuantitativeAttributeAccuracy;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.NonQuantitativeAttributeCorrectness;
+// Specific to the main branch:
+import org.opengis.metadata.quality.NonQuantitativeAttributeAccuracy;
 
 
 /**
@@ -87,7 +87,7 @@
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
      *   <li>Otherwise if the given object is an instance of {@link QuantitativeAttributeAccuracy},
-     *       {@link NonQuantitativeAttributeCorrectness} or {@link ThematicClassificationCorrectness},
+     *       {@link NonQuantitativeAttributeAccuracy} or {@link ThematicClassificationCorrectness},
      *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding
      *       SIS subclass. Note that if the given object implements more than one of the above-cited
      *       interfaces, then the {@code castOrCopy(…)} method to be used is unspecified.</li>
@@ -107,8 +107,8 @@
         if (object instanceof QuantitativeAttributeAccuracy) {
             return DefaultQuantitativeAttributeAccuracy.castOrCopy((QuantitativeAttributeAccuracy) object);
         }
-        if (object instanceof NonQuantitativeAttributeCorrectness) {
-            return DefaultNonQuantitativeAttributeCorrectness.castOrCopy((NonQuantitativeAttributeCorrectness) object);
+        if (object instanceof NonQuantitativeAttributeAccuracy) {
+            return DefaultNonQuantitativeAttributeCorrectness.castOrCopy((NonQuantitativeAttributeAccuracy) object);
         }
         if (object instanceof ThematicClassificationCorrectness) {
             return DefaultThematicClassificationCorrectness.castOrCopy((ThematicClassificationCorrectness) object);
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultAggregationDerivation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultAggregationDerivation.java
index df88548..e1a39d9 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultAggregationDerivation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultAggregationDerivation.java
@@ -19,13 +19,13 @@
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.AggregationDerivation;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Aggregation or derivation method.
- * See the {@link AggregationDerivation} GeoAPI interface for more details.
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -43,7 +43,8 @@
  */
 @XmlType(name = "DQ_AggregationDerivation_Type")
 @XmlRootElement(name = "DQ_AggregationDerivation")
-public class DefaultAggregationDerivation extends DefaultEvaluationMethod implements AggregationDerivation {
+@UML(identifier="DQ_AggregationDerivation", specification=UNSPECIFIED)
+public class DefaultAggregationDerivation extends DefaultEvaluationMethod {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -61,35 +62,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AggregationDerivation)
      */
-    public DefaultAggregationDerivation(final AggregationDerivation object) {
+    public DefaultAggregationDerivation(final DefaultAggregationDerivation object) {
         super(object);
     }
-
-    /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultAggregationDerivation}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultAggregationDerivation} instance is created using the
-     *       {@linkplain #DefaultAggregationDerivation(AggregationDerivation) copy constructor}
-     *       and returned. Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultAggregationDerivation castOrCopy(final AggregationDerivation object) {
-        if (object == null || object instanceof DefaultAggregationDerivation) {
-            return (DefaultAggregationDerivation) object;
-        }
-        return new DefaultAggregationDerivation(object);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultBasicMeasure.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultBasicMeasure.java
index 6b67edd..cc69315 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultBasicMeasure.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultBasicMeasure.java
@@ -23,14 +23,15 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Description;
-import org.opengis.metadata.quality.BasicMeasure;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Data quality basic measure.
- * See the {@link BasicMeasure} GeoAPI interface for more details.
  * The following property is mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQM_BasicMeasure}
@@ -60,7 +61,8 @@
     "valueType"
 })
 @XmlRootElement(name = "DQM_BasicMeasure", namespace = Namespaces.DQM)
-public class DefaultBasicMeasure extends ISOMetadata implements BasicMeasure {
+@UML(identifier="DQM_BasicMeasure", specification=UNSPECIFIED)
+public class DefaultBasicMeasure extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -82,7 +84,7 @@
      * Illustration of the use of a data quality measure.
      */
     @SuppressWarnings("serial")
-    private Description example;
+    private DefaultMeasureDescription example;
 
     /**
      * Value type for the result of the basic measure.
@@ -102,10 +104,8 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(BasicMeasure)
      */
-    public DefaultBasicMeasure(final BasicMeasure object) {
+    public DefaultBasicMeasure(final DefaultBasicMeasure object) {
         super(object);
         if (object != null) {
             name       = object.getName();
@@ -116,37 +116,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code BasicMeasure}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code BasicMeasure} instance is created using the
-     *       {@linkplain #DefaultBasicMeasure(BasicMeasure) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultBasicMeasure castOrCopy(final BasicMeasure object) {
-        if (object == null || object instanceof DefaultBasicMeasure) {
-            return (DefaultBasicMeasure) object;
-        }
-        return new DefaultBasicMeasure(object);
-    }
-
-    /**
      * Returns the name of the data quality basic measure.
      *
      * @return name of the data quality basic measure.
      */
-    @Override
     @XmlElement(name = "name", required = true)
+    @UML(identifier="name", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getName() {
         return name;
     }
@@ -166,8 +141,8 @@
      *
      * @return definition of the data quality basic measure.
      */
-    @Override
     @XmlElement(name = "definition", required = true)
+    @UML(identifier="definition", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getDefinition() {
         return definition;
     }
@@ -187,9 +162,9 @@
      *
      * @return usage example, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "example")
-    public Description getExample() {
+    @UML(identifier="example", obligation=OPTIONAL, specification=UNSPECIFIED)
+    public DefaultMeasureDescription getExample() {
         return example;
     }
 
@@ -198,7 +173,7 @@
      *
      * @param  newValues  the new basic measure example.
      */
-    public void setExample(final Description newValues) {
+    public void setExample(final DefaultMeasureDescription newValues) {
         checkWritePermission(example);
         example = newValues;
     }
@@ -208,8 +183,8 @@
      *
      * @return value type of the result for the basic measure.
      */
-    @Override
     @XmlElement(name = "valueType", required = true)
+    @UML(identifier="valueType", obligation=MANDATORY, specification=UNSPECIFIED)
     public TypeName getValueType() {
         return valueType;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultConfidence.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultConfidence.java
index 3566066..f642dd4 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultConfidence.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultConfidence.java
@@ -19,13 +19,13 @@
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Confidence;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Trustworthiness of a data quality result.
- * See the {@link Confidence} GeoAPI interface for more details.
  * The following properties are mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQ_Confidence}
@@ -48,7 +48,8 @@
  */
 @XmlType(name = "DQ_Confidence_Type")
 @XmlRootElement(name = "DQ_Confidence")
-public class DefaultConfidence extends AbstractMetaquality implements Confidence {
+@UML(identifier="DQ_Confidence", specification=UNSPECIFIED)
+public class DefaultConfidence extends AbstractMetaquality {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -65,35 +66,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Confidence)
      */
-    public DefaultConfidence(final Confidence object) {
+    public DefaultConfidence(final DefaultConfidence object) {
         super(object);
     }
-
-    /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultConfidence}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultConfidence} instance is created using the
-     *       {@linkplain #DefaultConfidence(Confidence) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultConfidence castOrCopy(final Confidence object) {
-        if (object == null || object instanceof DefaultConfidence) {
-            return (DefaultConfidence) object;
-        }
-        return new DefaultConfidence(object);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultCoverageResult.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultCoverageResult.java
index b69dada..89eb5cf 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultCoverageResult.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultCoverageResult.java
@@ -30,6 +30,11 @@
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.privy.LegacyNamespaces;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
+
 
 /**
  * Result of a data quality measure organising the measured values as a coverage.
@@ -134,9 +139,11 @@
             spatialRepresentationType   = object.getSpatialRepresentationType();
             resultSpatialRepresentation = object.getResultSpatialRepresentation();
             resultContentDescription    = object.getResultContentDescription();
-            resultContent               = copyCollection(object.getResultContent(), RangeDimension.class);
             resultFormat                = object.getResultFormat();
             resultFile                  = object.getResultFile();
+            if (object instanceof DefaultCoverageResult) {
+                resultContent = copyCollection(((DefaultCoverageResult) object).getResultContent(), RangeDimension.class);
+            }
         }
     }
 
@@ -215,8 +222,8 @@
      *
      * @since 1.3
      */
-    @Override
 //  @XmlElement(name = "resultContent")     // Pending new ISO 19157 version.
+    @UML(identifier="resultContent", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public Collection<RangeDimension> getResultContent() {
         return resultContent = nonNullCollection(resultContent, RangeDimension.class);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
index 9f8f254..4d64c33 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
@@ -30,8 +30,11 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.quality.Scope;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.StandaloneQualityReportInformation;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
+import org.apache.sis.metadata.iso.ISOMetadata;
 
 
 /**
@@ -97,7 +100,7 @@
      * Can be used for providing more details than reported as standard metadata.
      */
     @SuppressWarnings("serial")
-    private StandaloneQualityReportInformation standaloneQualityReport;
+    private DefaultEvaluationReportInformation standaloneQualityReport;
 
     /**
      * Constructs an initially empty data quality.
@@ -113,6 +116,7 @@
      *
      * @since 0.5
      */
+    @SuppressWarnings("deprecation")
     public DefaultDataQuality(final ScopeCode level) {
         if (level != null) {
             scope = new DefaultScope(level);
@@ -141,10 +145,12 @@
     public DefaultDataQuality(final DataQuality object) {
         super(object);
         if (object != null) {
-            scope                   = object.getScope();
-            reports                 = copyCollection(object.getReports(), Element.class);
-            standaloneQualityReport = object.getStandaloneQualityReport();
-            lineage                 = object.getLineage();
+            scope   = object.getScope();
+            reports = copyCollection(object.getReports(), Element.class);
+            lineage = object.getLineage();
+            if (object instanceof DefaultDataQuality) {
+                standaloneQualityReport = ((DefaultDataQuality) object).getStandaloneQualityReport();
+            }
         }
     }
 
@@ -222,9 +228,9 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "standaloneQualityReport")
-    public StandaloneQualityReportInformation getStandaloneQualityReport() {
+    @UML(identifier="standaloneQualityReport", obligation=OPTIONAL, specification=UNSPECIFIED)
+    public DefaultEvaluationReportInformation getStandaloneQualityReport() {
         return standaloneQualityReport;
     }
 
@@ -235,7 +241,7 @@
      *
      * @since 1.3
      */
-    public void setStandaloneQualityReport(final StandaloneQualityReportInformation newValue) {
+    public void setStandaloneQualityReport(final DefaultEvaluationReportInformation newValue) {
         checkWritePermission(standaloneQualityReport);
         standaloneQualityReport = newValue;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDescriptiveResult.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDescriptiveResult.java
index a41261d..00366eb 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDescriptiveResult.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultDescriptiveResult.java
@@ -22,13 +22,14 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.DescriptiveResult;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Data quality descriptive result.
- * See the {@link DescriptiveResult} GeoAPI interface for more details.
  * The following properties are mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQ_DescriptiveResult}
@@ -52,7 +53,8 @@
     "statement"
 })
 @XmlRootElement(name = "DQ_DescriptiveResult")
-public class DefaultDescriptiveResult extends AbstractResult implements DescriptiveResult {
+@UML(identifier="DQ_DescriptiveResult", specification=UNSPECIFIED)
+public class DefaultDescriptiveResult extends AbstractResult {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -85,10 +87,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(DescriptiveResult)
      */
-    public DefaultDescriptiveResult(final DescriptiveResult object) {
+    public DefaultDescriptiveResult(final DefaultDescriptiveResult object) {
         super(object);
         if (object != null) {
             statement = object.getStatement();
@@ -96,37 +96,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultDescriptiveResult}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultDescriptiveResult} instance is created using the
-     *       {@linkplain #DefaultDescriptiveResult(DescriptiveResult) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultDescriptiveResult castOrCopy(final DescriptiveResult object) {
-        if (object == null || object instanceof DefaultDescriptiveResult) {
-            return (DefaultDescriptiveResult) object;
-        }
-        return new DefaultDescriptiveResult(object);
-    }
-
-    /**
      * Returns the textual expression of the descriptive result.
      *
      * @return textual expression of the result.
      */
-    @Override
     @XmlElement(name = "statement", required = true)
+    @UML(identifier="statement", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getStatement() {
         return statement;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
index 2d5f6c2..4be27a2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
@@ -36,15 +36,14 @@
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.EvaluationMethod;
-import org.opengis.metadata.quality.DataEvaluation;
-import org.opengis.metadata.quality.AggregationDerivation;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Description of the evaluation method and procedure applied.
- * See the {@link EvaluationMethod} GeoAPI interface for more details.
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -72,7 +71,8 @@
     AbstractDataEvaluation.class,
     DefaultAggregationDerivation.class
 })
-public class DefaultEvaluationMethod extends ISOMetadata implements EvaluationMethod {
+@UML(identifier="DQ_EvaluationMethod", specification=UNSPECIFIED)
+public class DefaultEvaluationMethod extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -280,10 +280,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(EvaluationMethod)
      */
-    public DefaultEvaluationMethod(final EvaluationMethod object) {
+    public DefaultEvaluationMethod(final DefaultEvaluationMethod object) {
         super(object);
         if (object != null) {
             evaluationMethodType        = object.getEvaluationMethodType();
@@ -295,48 +293,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link DataEvaluation} or {@link AggregationDerivation},
-     *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
-     *       Note that if the given object implements more than one of the above-cited interfaces,
-     *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultEvaluationMethod}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultEvaluationMethod} instance is created using the
-     *       {@linkplain #DefaultEvaluationMethod(EvaluationMethod) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultEvaluationMethod castOrCopy(final EvaluationMethod object) {
-        if (object instanceof DataEvaluation) {
-            return AbstractDataEvaluation.castOrCopy((DataEvaluation) object);
-        }
-        if (object instanceof AggregationDerivation) {
-            return DefaultAggregationDerivation.castOrCopy((AggregationDerivation) object);
-        }
-        // Intentionally tested after the sub-interfaces.
-        if (object == null || object instanceof DefaultEvaluationMethod) {
-            return (DefaultEvaluationMethod) object;
-        }
-        return new DefaultEvaluationMethod(object);
-    }
-
-    /**
      * Returns the type of method used to evaluate quality of the data.
      *
      * @return type of method used to evaluate quality, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "evaluationMethodType")
+    @UML(identifier="evaluationMethodType", obligation=OPTIONAL, specification=UNSPECIFIED)
     public EvaluationMethodType getEvaluationMethodType() {
         return evaluationMethodType;
     }
@@ -356,8 +318,8 @@
      *
      * @return description of the evaluation method, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "evaluationMethodDescription")
+    @UML(identifier="evaluationMethodDescription", obligation=OPTIONAL, specification=UNSPECIFIED)
     public InternationalString getEvaluationMethodDescription() {
         return evaluationMethodDescription;
     }
@@ -377,8 +339,8 @@
      *
      * @return reference to the procedure information, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "evaluationProcedure")
+    @UML(identifier="evaluationProcedure", obligation=OPTIONAL, specification=UNSPECIFIED)
     public Citation getEvaluationProcedure() {
         return evaluationProcedure;
     }
@@ -398,8 +360,8 @@
      *
      * @return documents referenced in data quality evaluation method.
      */
-    @Override
     @XmlElement(name = "referenceDoc")
+    @UML(identifier="referenceDoc", obligation=OPTIONAL, specification=UNSPECIFIED)
     public Collection<Citation> getReferenceDocuments() {
         return referenceDocuments = nonNullCollection(referenceDocuments, Citation.class);
     }
@@ -420,8 +382,8 @@
      *
      * @return date or range of dates on which a data quality measure was applied.
      */
-    @Override
     @XmlElement(name = "dateTime")
+    @UML(identifier="dateTime", obligation=OPTIONAL, specification=UNSPECIFIED)
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public Collection<Temporal> getDates() {
         if (Semaphores.query(Semaphores.NULL_COLLECTION)) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
index d29283b..e53b528 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
@@ -22,13 +22,14 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.StandaloneQualityReportInformation;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Reference to an external standalone quality report.
- * See the {@link StandaloneQualityReportInformation} GeoAPI interface for more details.
  * The following property is mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQ_Element}
@@ -54,7 +55,8 @@
     "abstract"
 })
 @XmlRootElement(name = "DQ_StandaloneQualityReportInformation")
-public class DefaultEvaluationReportInformation extends ISOMetadata implements StandaloneQualityReportInformation {
+@UML(identifier="DQ_StandaloneQualityReportInformation", specification=UNSPECIFIED)
+public class DefaultEvaluationReportInformation extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -84,10 +86,8 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(StandaloneQualityReportInformation)
      */
-    public DefaultEvaluationReportInformation(final StandaloneQualityReportInformation object) {
+    public DefaultEvaluationReportInformation(final DefaultEvaluationReportInformation object) {
         super(object);
         if (object != null) {
             reportReference  = object.getReportReference();
@@ -96,37 +96,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultEvaluationReportInformation}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultEvaluationReportInformation} instance is created using the
-     *       {@linkplain #DefaultEvaluationReportInformation(StandaloneQualityReportInformation) copy constructor}
-     *       and returned. Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultEvaluationReportInformation castOrCopy(final StandaloneQualityReportInformation object) {
-        if (object instanceof StandaloneQualityReportInformation) {
-            return DefaultEvaluationReportInformation.castOrCopy((DefaultEvaluationReportInformation) object);
-        }
-        return new DefaultEvaluationReportInformation(object);
-    }
-
-    /**
      * Returns the reference to the associated standalone quality report.
      *
      * @return reference of the standalone quality report.
      */
-    @Override
     @XmlElement(name = "reportReference", required = true)
+    @UML(identifier="reportReference", obligation=MANDATORY, specification=UNSPECIFIED)
     public Citation getReportReference() {
         return reportReference;
     }
@@ -146,8 +121,8 @@
      *
      * @return abstract of the standalone quality report.
      */
-    @Override
     @XmlElement(name = "abstract", required = true)
+    @UML(identifier="abstract", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getAbstract() {
         return summary;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultFullInspection.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultFullInspection.java
index e90f302..d51797c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultFullInspection.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultFullInspection.java
@@ -19,13 +19,13 @@
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.FullInspection;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Full inspection.
- * See the {@link FullInspection} GeoAPI interface for more details.
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -43,7 +43,8 @@
  */
 @XmlType(name = "DQ_FullInspection_Type")
 @XmlRootElement(name = "DQ_FullInspection")
-public class DefaultFullInspection extends AbstractDataEvaluation implements FullInspection {
+@UML(identifier="DQ_FullInspection", specification=UNSPECIFIED)
+public class DefaultFullInspection extends AbstractDataEvaluation {
      /**
      * Serial number for inter-operability with different versions.
      */
@@ -61,35 +62,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(FullInspection)
      */
-    public DefaultFullInspection(final FullInspection object) {
+    public DefaultFullInspection(final DefaultFullInspection object) {
         super(object);
     }
-
-    /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultFullInspection}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultFullInspection} instance is created using the
-     *       {@linkplain #DefaultFullInspection(FullInspection) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultFullInspection castOrCopy(final FullInspection object) {
-        if (object == null || object instanceof DefaultFullInspection) {
-            return (DefaultFullInspection) object;
-        }
-        return new DefaultFullInspection(object);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultHomogeneity.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultHomogeneity.java
index 914eece..7155a84 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultHomogeneity.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultHomogeneity.java
@@ -19,13 +19,13 @@
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Homogeneity;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Expected or tested uniformity of the results obtained for a data quality evaluation.
- * See the {@link Homogeneity} GeoAPI interface for more details.
  * The following properties are mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQ_Homogeneity}
@@ -48,7 +48,8 @@
  */
 @XmlType(name = "DQ_Homogeneity_Type")
 @XmlRootElement(name = "DQ_Homogeneity")
-public class DefaultHomogeneity extends AbstractMetaquality implements Homogeneity {
+@UML(identifier="DQ_Homogeneity", specification=UNSPECIFIED)
+public class DefaultHomogeneity extends AbstractMetaquality {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -65,35 +66,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Homogeneity)
      */
-    public DefaultHomogeneity(final Homogeneity object) {
+    public DefaultHomogeneity(final DefaultHomogeneity object) {
         super(object);
     }
-
-    /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultHomogeneity}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultHomogeneity} instance is created using the
-     *       {@linkplain #DefaultHomogeneity(Homogeneity) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultHomogeneity castOrCopy(final Homogeneity object) {
-        if (object == null || object instanceof DefaultHomogeneity) {
-            return (DefaultHomogeneity) object;
-        }
-        return new DefaultHomogeneity(object);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultIndirectEvaluation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultIndirectEvaluation.java
index 507721f..a37eefe 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultIndirectEvaluation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultIndirectEvaluation.java
@@ -22,13 +22,14 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.IndirectEvaluation;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Indirect evaluation.
- * See the {@link IndirectEvaluation} GeoAPI interface for more details.
  * The following properties are mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQ_IndirectEvaluation}
@@ -52,7 +53,8 @@
     "deductiveSource"
 })
 @XmlRootElement(name = "DQ_IndirectEvaluation")
-public class DefaultIndirectEvaluation extends AbstractDataEvaluation implements IndirectEvaluation {
+@UML(identifier="DQ_IndirectEvaluation", specification=UNSPECIFIED)
+public class DefaultIndirectEvaluation extends AbstractDataEvaluation {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -85,10 +87,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(IndirectEvaluation)
      */
-    public DefaultIndirectEvaluation(final IndirectEvaluation object) {
+    public DefaultIndirectEvaluation(final DefaultIndirectEvaluation object) {
         super(object);
         if (object != null) {
             deductiveSource = object.getDeductiveSource();
@@ -96,37 +96,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultIndirectEvaluation}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultIndirectEvaluation} instance is created using the
-     *       {@linkplain #DefaultIndirectEvaluation(IndirectEvaluation) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultIndirectEvaluation castOrCopy(final IndirectEvaluation object) {
-        if (object == null || object instanceof DefaultIndirectEvaluation) {
-            return (DefaultIndirectEvaluation) object;
-        }
-        return new DefaultIndirectEvaluation(object);
-    }
-
-    /**
      * Returns the information on which data are used as sources in deductive evaluation method.
      *
      * @return information on which data are used.
      */
-    @Override
     @XmlElement(name = "deductiveSource", required = true)
+    @UML(identifier="deductiveSource", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getDeductiveSource() {
         return deductiveSource;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureDescription.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureDescription.java
index 892101f..c5b99f0 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureDescription.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureDescription.java
@@ -24,8 +24,11 @@
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Description;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
@@ -54,7 +57,8 @@
     "extendedDescription"
 })
 @XmlRootElement(name = "DQM_Description", namespace = Namespaces.DQM)
-public class DefaultMeasureDescription extends ISOMetadata implements Description {
+@UML(identifier="DQM_Description", specification=UNSPECIFIED)
+public class DefaultMeasureDescription extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -93,10 +97,8 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Description)
      */
-    public DefaultMeasureDescription(final Description object) {
+    public DefaultMeasureDescription(final DefaultMeasureDescription object) {
         super(object);
         if (object != null) {
             textDescription     = object.getTextDescription();
@@ -105,37 +107,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultMeasureDescription}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultMeasureDescription} instance is created using the
-     *       {@linkplain #DefaultMeasureDescription(Description) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultMeasureDescription castOrCopy(final Description object) {
-        if (object == null || object instanceof DefaultMeasureDescription) {
-            return (DefaultMeasureDescription) object;
-        }
-        return new DefaultMeasureDescription(object);
-    }
-
-    /**
      * Returns the text description.
      *
      * @return text description.
      */
-    @Override
     @XmlElement(name = "textDescription", required = true)
+    @UML(identifier="textDescription", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getTextDescription() {
         return textDescription;
     }
@@ -155,8 +132,8 @@
      *
      * @return description illustration, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "extendedDescription")
+    @UML(identifier="extendedDescription", obligation=OPTIONAL, specification=UNSPECIFIED)
     public BrowseGraphic getExtendedDescription() {
         return extendedDescription;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java
index f540568..924fa1f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultMeasureReference.java
@@ -25,13 +25,15 @@
 import org.opengis.metadata.quality.Element;
 import org.apache.sis.util.privy.CollectionsExt;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.MeasureReference;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Reference to the measure used.
- * See the {@link MeasureReference} GeoAPI interface for more details.
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -53,7 +55,8 @@
     "measureDescription"
 })
 @XmlRootElement(name = "DQ_MeasureReference")
-public class DefaultMeasureReference extends ISOMetadata implements MeasureReference {
+@UML(identifier="DQ_MeasureReference", specification=UNSPECIFIED)
+public class DefaultMeasureReference extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -89,10 +92,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(MeasureReference)
      */
-    public DefaultMeasureReference(final MeasureReference object) {
+    public DefaultMeasureReference(final DefaultMeasureReference object) {
         super(object);
         if (object != null) {
             measureIdentification = object.getMeasureIdentification();
@@ -102,31 +103,6 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultMeasureReference}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultMeasureReference} instance is created using the
-     *       {@linkplain #DefaultMeasureReference(MeasureReference) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultMeasureReference castOrCopy(final MeasureReference object) {
-        if (object == null || object instanceof DefaultMeasureReference) {
-            return (DefaultMeasureReference) object;
-        }
-        return new DefaultMeasureReference(object);
-    }
-
-    /**
      * Initializes a measure reference from the deprecated properties of the given element.
      * This is used for transition from legacy ISO 19115 to newer ISO 19157 model.
      */
@@ -142,8 +118,8 @@
      *
      * @return code identifying a registered measure, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "measureIdentification")
+    @UML(identifier="measureIdentification", obligation=OPTIONAL, specification=UNSPECIFIED)
     public Identifier getMeasureIdentification() {
         return measureIdentification;
     }
@@ -163,8 +139,8 @@
      *
      * @return names of the test applied to the data.
      */
-    @Override
     @XmlElement(name = "nameOfMeasure")
+    @UML(identifier="nameOfMeasure", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public Collection<InternationalString> getNamesOfMeasure() {
         return namesOfMeasure = nonNullCollection(namesOfMeasure, InternationalString.class);
     }
@@ -183,8 +159,8 @@
      *
      * @return description of the measure, or {@code null}.
      */
-    @Override
     @XmlElement(name = "measureDescription")
+    @UML(identifier="measureDescription", obligation=OPTIONAL, specification=UNSPECIFIED)
     public InternationalString getMeasureDescription() {
         return measureDescription;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultNonQuantitativeAttributeCorrectness.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultNonQuantitativeAttributeCorrectness.java
index 0e88084..2540a74 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultNonQuantitativeAttributeCorrectness.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultNonQuantitativeAttributeCorrectness.java
@@ -21,13 +21,13 @@
 import jakarta.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.quality.NonQuantitativeAttributeAccuracy;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.NonQuantitativeAttributeCorrectness;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Correctness of non-quantitative attributes.
- * See the {@link NonQuantitativeAttributeCorrectness} GeoAPI interface for more details.
  * The following property is mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQ_CompletenessOmission}
@@ -53,8 +53,9 @@
     DefaultNonQuantitativeAttributeAccuracy.class
 })
 @SuppressWarnings("deprecation")
+@UML(identifier="DQ_NonQuantitativeAttributeCorrectness", specification=UNSPECIFIED)
 public class DefaultNonQuantitativeAttributeCorrectness extends AbstractThematicAccuracy
-        implements NonQuantitativeAttributeCorrectness
+        implements NonQuantitativeAttributeAccuracy
 {
     /**
      * Serial number for inter-operability with different versions.
@@ -74,9 +75,9 @@
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
      *
-     * @see #castOrCopy(NonQuantitativeAttributeCorrectness)
+     * @see #castOrCopy(NonQuantitativeAttributeAccuracy)
      */
-    public DefaultNonQuantitativeAttributeCorrectness(final NonQuantitativeAttributeCorrectness object) {
+    public DefaultNonQuantitativeAttributeCorrectness(final NonQuantitativeAttributeAccuracy object) {
         super(object);
     }
 
@@ -89,7 +90,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultNonQuantitativeAttributeCorrectness}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code DefaultNonQuantitativeAttributeCorrectness} instance is created using the
-     *       {@linkplain #DefaultNonQuantitativeAttributeCorrectness(NonQuantitativeAttributeCorrectness) copy constructor}
+     *       {@linkplain #DefaultNonQuantitativeAttributeCorrectness(NonQuantitativeAttributeAccuracy) copy constructor}
      *       and returned. Note that this is a <em>shallow</em> copy operation, because the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -98,10 +99,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultNonQuantitativeAttributeCorrectness castOrCopy(final NonQuantitativeAttributeCorrectness object) {
-        if (object instanceof NonQuantitativeAttributeAccuracy) {
-            return DefaultNonQuantitativeAttributeAccuracy.castOrCopy((NonQuantitativeAttributeAccuracy) object);
-        }
+    public static DefaultNonQuantitativeAttributeCorrectness castOrCopy(final NonQuantitativeAttributeAccuracy object) {
         if (object == null || object instanceof DefaultNonQuantitativeAttributeCorrectness) {
             return (DefaultNonQuantitativeAttributeCorrectness) object;
         }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultQualityMeasure.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultQualityMeasure.java
index 6358b39..93f45c6 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultQualityMeasure.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultQualityMeasure.java
@@ -26,17 +26,16 @@
 import org.opengis.metadata.Identifier;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Measure;
-import org.opengis.metadata.quality.BasicMeasure;
-import org.opengis.metadata.quality.Description;
-import org.opengis.metadata.quality.SourceReference;
-import org.opengis.metadata.quality.ValueStructure;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Data quality measure.
- * See the {@link Measure} GeoAPI interface for more details.
  * The following properties are mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQM_Measure}
@@ -68,14 +67,14 @@
     "definition",
     "description",
     "valueType",
-    "valueStructure",
     "examples",
     "basicMeasure",
     "sourceReferences",
     "parameters"
 })
 @XmlRootElement(name = "DQM_Measure", namespace = Namespaces.DQM)
-public class DefaultQualityMeasure extends ISOMetadata implements Measure {
+@UML(identifier="DQM_Measure", specification=UNSPECIFIED)
+public class DefaultQualityMeasure extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -109,7 +108,7 @@
      * Definition of the fundamental concept for the data quality measure.
      */
     @SuppressWarnings("serial")
-    private BasicMeasure basicMeasure;
+    private DefaultBasicMeasure basicMeasure;
 
     /**
      * Definition of the fundamental concept for the data quality measure.
@@ -123,13 +122,13 @@
      * needed to establish the result of applying the measure.
      */
     @SuppressWarnings("serial")
-    private Description description;
+    private DefaultMeasureDescription description;
 
     /**
      * Reference to the source of an item that has been adopted from an external source.
      */
     @SuppressWarnings("serial")
-    private Collection<SourceReference> sourceReferences;
+    private Collection<DefaultSourceReference> sourceReferences;
 
     /**
      * Value type for reporting a data quality result.
@@ -138,11 +137,6 @@
     private TypeName valueType;
 
     /**
-     * Structure for reporting a complex data quality result.
-     */
-    private ValueStructure valueStructure;
-
-    /**
      * Auxiliary variable used by the data quality measure, including its name, definition and optionally its description.
      */
     @SuppressWarnings("serial")
@@ -152,7 +146,7 @@
      * Illustration of the use of a data quality measure.
      */
     @SuppressWarnings("serial")
-    private Collection<Description> examples;
+    private Collection<DefaultMeasureDescription> examples;
 
     /**
      * Constructs an initially empty element.
@@ -166,11 +160,9 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Measure)
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public DefaultQualityMeasure(final Measure object) {
+    public DefaultQualityMeasure(final DefaultQualityMeasure object) {
         super(object);
         if (object != null) {
             measureIdentifier = object.getMeasureIdentifier();
@@ -180,46 +172,20 @@
             definition        = object.getDefinition();
             description       = object.getDescription();
             valueType         = object.getValueType();
-            valueStructure    = object.getValueStructure();
-            examples          = copyCollection(object.getExamples(), Description.class);
+            examples          = copyCollection(object.getExamples(), DefaultMeasureDescription.class);
             basicMeasure      = object.getBasicMeasure();
-            sourceReferences  = copyCollection(object.getSourceReferences(), SourceReference.class);
+            sourceReferences  = copyCollection(object.getSourceReferences(), DefaultSourceReference.class);
             parameters        = copyCollection(object.getParameters(), (Class) ParameterDescriptor.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultQualityMeasure}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultQualityMeasure} instance is created using the
-     *       {@linkplain #DefaultQualityMeasure(Measure) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultQualityMeasure castOrCopy(final Measure object) {
-        if (object instanceof DefaultQualityMeasure) {
-            return (DefaultQualityMeasure) object;
-        }
-        return new DefaultQualityMeasure(object);
-    }
-
-    /**
      * Returns the value uniquely identifying the measure within a namespace.
      *
      * @return value uniquely identifying the measure within a namespace.
      */
-    @Override
     @XmlElement(name = "measureIdentifier", required = true)
+    @UML(identifier="measureIdentifier", obligation=MANDATORY, specification=UNSPECIFIED)
     public Identifier getMeasureIdentifier() {
         return measureIdentifier;
     }
@@ -239,8 +205,8 @@
      *
      * @return name of the data quality measure applied to the data.
      */
-    @Override
     @XmlElement(name = "name", required = true)
+    @UML(identifier="name", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getName() {
         return name;
     }
@@ -261,8 +227,8 @@
      *
      * @return others recognized names, abbreviations or short names.
      */
-    @Override
     @XmlElement(name = "alias")
+    @UML(identifier="alias", obligation=OPTIONAL, specification=UNSPECIFIED)
     public Collection<InternationalString> getAliases() {
         return aliases = nonNullCollection(aliases, InternationalString.class);
     }
@@ -281,8 +247,8 @@
      *
      * @return names of the data quality element for which quality is reported.
      */
-    @Override
     @XmlElement(name = "elementName", required = true)
+    @UML(identifier="elementName", obligation=MANDATORY, specification=UNSPECIFIED)
     public Collection<TypeName> getElementNames() {
         return elementNames = nonNullCollection(elementNames, TypeName.class);
     }
@@ -301,9 +267,9 @@
      *
      * @return predefined basic measure on which this measure is based, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "basicMeasure")
-    public BasicMeasure getBasicMeasure() {
+    @UML(identifier="basicMeasure", obligation=CONDITIONAL, specification=UNSPECIFIED)
+    public DefaultBasicMeasure getBasicMeasure() {
         return basicMeasure;
     }
 
@@ -312,7 +278,7 @@
      *
      * @param  newValue  the new basic measure.
      */
-    public void setBasicMeasure(final BasicMeasure newValue)  {
+    public void setBasicMeasure(final DefaultBasicMeasure newValue)  {
         checkWritePermission(basicMeasure);
         basicMeasure = newValue;
     }
@@ -324,8 +290,8 @@
      *
      * @return definition of the fundamental concept for the data quality measure.
      */
-    @Override
     @XmlElement(name = "definition", required = true)
+    @UML(identifier="definition", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getDefinition() {
         return definition;
     }
@@ -347,9 +313,9 @@
      *
      * @return description of data quality measure, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "description")
-    public Description getDescription() {
+    @UML(identifier="description", obligation=CONDITIONAL, specification=UNSPECIFIED)
+    public DefaultMeasureDescription getDescription() {
        return description;
     }
 
@@ -358,7 +324,7 @@
      *
      * @param  newValue  the new measure description.
      */
-    public void setDescription(final Description newValue)  {
+    public void setDescription(final DefaultMeasureDescription newValue)  {
         checkWritePermission(description);
         description = newValue;
     }
@@ -368,10 +334,10 @@
      *
      * @return references to the source.
      */
-    @Override
     @XmlElement(name = "sourceReference")
-    public Collection<SourceReference> getSourceReferences() {
-        return sourceReferences = nonNullCollection(sourceReferences, SourceReference.class);
+    @UML(identifier="sourceReference", obligation=CONDITIONAL, specification=UNSPECIFIED)
+    public Collection<DefaultSourceReference> getSourceReferences() {
+        return sourceReferences = nonNullCollection(sourceReferences, DefaultSourceReference.class);
     }
 
     /**
@@ -379,8 +345,8 @@
      *
      * @param  newValues  the new source references.
      */
-    public void setSourceReferences(final Collection<? extends SourceReference> newValues) {
-        sourceReferences = writeCollection(newValues, sourceReferences, SourceReference.class);
+    public void setSourceReferences(final Collection<? extends DefaultSourceReference> newValues) {
+        sourceReferences = writeCollection(newValues, sourceReferences, DefaultSourceReference.class);
     }
 
     /**
@@ -388,8 +354,8 @@
      *
      * @return value type for reporting a data quality result.
      */
-    @Override
     @XmlElement(name = "valueType", required = true)
+    @UML(identifier="valueType", obligation=MANDATORY, specification=UNSPECIFIED)
     public TypeName getValueType() {
         return valueType;
     }
@@ -405,27 +371,6 @@
     }
 
     /**
-     * Returns the structure for reporting a complex data quality result.
-     *
-     * @return structure for reporting a complex data quality result, or {@code null} if none.
-     */
-    @Override
-    @XmlElement(name = "valueStructure")
-    public ValueStructure getValueStructure() {
-        return valueStructure;
-    }
-
-    /**
-     * Sets the structure for reporting a complex data quality result.
-     *
-     * @param  newValue  the new measure value structure.
-     */
-    public void setValueStructure(final ValueStructure newValue)  {
-        checkWritePermission(valueStructure);
-        valueStructure = newValue;
-    }
-
-    /**
      * Returns auxiliary variable(s) used by the data quality measure.
      * It shall include its name, definition and value type.
      *
@@ -435,9 +380,9 @@
      *
      * @return auxiliary variable(s) used by data quality measure.
      */
-    @Override
     @XmlElement(name = "parameter")
     @SuppressWarnings({"unchecked", "rawtypes"})
+    @UML(identifier="parameter", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public Collection<ParameterDescriptor<?>> getParameters() {
         return parameters = nonNullCollection(parameters, (Class) ParameterDescriptor.class);
     }
@@ -457,10 +402,10 @@
      *
      * @return examples of applying the measure or the result obtained for the measure.
      */
-    @Override
     @XmlElement(name = "example")
-    public Collection<Description> getExamples() {
-        return examples = nonNullCollection(examples, Description.class);
+    @UML(identifier="example", obligation=OPTIONAL, specification=UNSPECIFIED)
+    public Collection<DefaultMeasureDescription> getExamples() {
+        return examples = nonNullCollection(examples, DefaultMeasureDescription.class);
     }
 
     /**
@@ -468,7 +413,7 @@
      *
      * @param  newValues  the new examples.
      */
-    public void setExamples(final Collection<? extends Description> newValues) {
-        examples = writeCollection(newValues, examples, Description.class);
+    public void setExamples(final Collection<? extends DefaultMeasureDescription> newValues) {
+        examples = writeCollection(newValues, examples, DefaultMeasureDescription.class);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultRepresentativity.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultRepresentativity.java
index dab48a9..d48ab94 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultRepresentativity.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultRepresentativity.java
@@ -19,13 +19,13 @@
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Representativity;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
  * Degree to which the sample used has produced a result which is representation of the data.
- * See the {@link Representativity} GeoAPI interface for more details.
  * The following properties are mandatory in a well-formed metadata according ISO 19157:
  *
  * <div class="preformat">{@code DQ_Representativity}
@@ -48,7 +48,8 @@
  */
 @XmlType(name = "DQ_Representativity_Type")
 @XmlRootElement(name = "DQ_Representativity")
-public class DefaultRepresentativity extends AbstractMetaquality implements Representativity {
+@UML(identifier="DQ_Representativity", specification=UNSPECIFIED)
+public class DefaultRepresentativity extends AbstractMetaquality {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -65,35 +66,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Representativity)
      */
-    public DefaultRepresentativity(final Representativity object) {
+    public DefaultRepresentativity(final DefaultRepresentativity object) {
         super(object);
     }
-
-    /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultRepresentativity}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultRepresentativity} instance is created using the
-     *       {@linkplain #DefaultRepresentativity(Representativity) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultRepresentativity castOrCopy(final Representativity object) {
-        if (object == null || object instanceof DefaultRepresentativity) {
-            return (DefaultRepresentativity) object;
-        }
-        return new DefaultRepresentativity(object);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSampleBasedInspection.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSampleBasedInspection.java
index f7e95cb..a064e53 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSampleBasedInspection.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSampleBasedInspection.java
@@ -21,8 +21,10 @@
 import jakarta.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.SampleBasedInspection;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
@@ -54,7 +56,8 @@
     "samplingRatio"
 })
 @XmlRootElement(name = "DQ_SampleBasedInspection")
-public class DefaultSampleBasedInspection extends AbstractDataEvaluation implements SampleBasedInspection {
+@UML(identifier="DQ_SampleBasedInspection", specification=UNSPECIFIED)
+public class DefaultSampleBasedInspection extends AbstractDataEvaluation {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -90,10 +93,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(SampleBasedInspection)
      */
-    public DefaultSampleBasedInspection(final SampleBasedInspection object) {
+    public DefaultSampleBasedInspection(final DefaultSampleBasedInspection object) {
         super(object);
         if (object != null) {
             samplingScheme = object.getSamplingScheme();
@@ -103,37 +104,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultSampleBasedInspection}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultSampleBasedInspection} instance is created using the
-     *       {@linkplain #DefaultSampleBasedInspection(SampleBasedInspection) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultSampleBasedInspection castOrCopy(final SampleBasedInspection object) {
-        if (object == null || object instanceof DefaultSampleBasedInspection) {
-            return (DefaultSampleBasedInspection) object;
-        }
-        return new DefaultSampleBasedInspection(object);
-    }
-
-    /**
      * Returns the information of the type of sampling scheme and description of the sampling procedure.
      *
      * @return sampling scheme and sampling procedure.
      */
-    @Override
     @XmlElement(name = "samplingScheme", required = true)
+    @UML(identifier="samplingScheme", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getSamplingScheme() {
         return samplingScheme;
     }
@@ -153,8 +129,8 @@
      *
      * @return information on lots.
      */
-    @Override
     @XmlElement(name = "lotDescription", required = true)
+    @UML(identifier="lotDescription", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getLotDescription() {
         return lotDescription;
     }
@@ -174,8 +150,8 @@
      *
      * @return average number of samples extracted for inspection.
      */
-    @Override
     @XmlElement(name = "samplingRatio", required = true)
+    @UML(identifier="samplingRatio", obligation=MANDATORY, specification=UNSPECIFIED)
     public InternationalString getSamplingRatio() {
         return samplingRatio;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultScope.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultScope.java
index 30d1f40..bf70d67 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultScope.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultScope.java
@@ -20,13 +20,6 @@
 import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Collection;
-import org.opengis.metadata.extent.Extent;
-import org.apache.sis.metadata.internal.Dependencies;
-import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
-import org.apache.sis.util.privy.CollectionsExt;
-
 
 /**
  * Description of the data specified by the scope.
@@ -80,7 +73,7 @@
      *
      * @see #castOrCopy(Scope)
      */
-    public DefaultScope(final org.opengis.metadata.maintenance.Scope object) {
+    public DefaultScope(final Scope object) {
         super(object);
     }
 
@@ -102,38 +95,10 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultScope castOrCopy(final org.opengis.metadata.maintenance.Scope object) {
+    public static DefaultScope castOrCopy(final Scope object) {
         if (object == null || object instanceof DefaultScope) {
             return (DefaultScope) object;
         }
         return new DefaultScope(object);
     }
-
-    /**
-     * Information about the spatial, vertical and temporal extent of the data specified by the scope.
-     * This method fetches the value from the {@linkplain #getExtents() extents} collection.
-     *
-     * @return information about the extent of the data, or {@code null}.
-     *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #getExtents()}.
-     */
-    @Override
-    @Deprecated(since="1.0")
-    @Dependencies("getExtents")
-    public Extent getExtent() {
-        return LegacyPropertyAdapter.getSingleton(getExtents(), Extent.class, null, DefaultScope.class, "getExtent");
-    }
-
-    /**
-     * Sets information about the spatial, vertical and temporal extent of the data specified by the scope.
-     * This method stores the value in the {@linkplain #setExtents(Collection) extents} collection.
-     *
-     * @param  newValue  the new extent.
-     *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #setExtents(Collection)}.
-     */
-    @Deprecated(since="1.0")
-    public void setExtent(final Extent newValue) {
-        setExtents(CollectionsExt.singletonOrEmpty(newValue));
-    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSourceReference.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSourceReference.java
index b8cf6ca..6a69eb7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSourceReference.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSourceReference.java
@@ -22,8 +22,10 @@
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.SourceReference;
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 
 
 /**
@@ -49,7 +51,8 @@
  */
 @XmlType(name = "DQM_SourceReference_Type", namespace = Namespaces.DQM)
 @XmlRootElement(name = "DQM_SourceReference", namespace = Namespaces.DQM)
-public class DefaultSourceReference extends ISOMetadata implements SourceReference {
+@UML(identifier="DQM_SourceReference", specification=UNSPECIFIED)
+public class DefaultSourceReference extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -73,10 +76,8 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(SourceReference)
      */
-    public DefaultSourceReference(final SourceReference object) {
+    public DefaultSourceReference(final DefaultSourceReference object) {
         super(object);
         if (object != null) {
             citation = object.getCitation();
@@ -84,37 +85,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultSourceReference}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultSourceReference} instance is created using the
-     *       {@linkplain #DefaultSourceReference(SourceReference) copy constructor} and returned.
-     *       Note that this is a <em>shallow</em> copy operation, because the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultSourceReference castOrCopy(final SourceReference object) {
-        if (object == null || object instanceof DefaultSourceReference) {
-            return (DefaultSourceReference) object;
-        }
-        return new DefaultSourceReference(object);
-    }
-
-    /**
      * Returns the references to the source.
      *
      * @return reference to the source.
      */
-    @Override
     @XmlElement(name = "citation", required = true)
+    @UML(identifier="citation", obligation=MANDATORY, specification=UNSPECIFIED)
     public Citation getCitation() {
         return citation;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/package-info.java
index 682c784..657aeb2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/package-info.java
@@ -67,7 +67,6 @@
 //  @XmlJavaTypeAdapter(DQM_Measure.class),             // Not directly referenced, but a "weak" association exists.
     @XmlJavaTypeAdapter(DQM_Parameter.class),
     @XmlJavaTypeAdapter(DQM_SourceReference.class),
-    @XmlJavaTypeAdapter(DQM_ValueStructure.class),
     @XmlJavaTypeAdapter(GO_Temporal.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
     @XmlJavaTypeAdapter(GO_GenericName.class),
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/AbstractSpatialRepresentation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/AbstractSpatialRepresentation.java
index ade3127..1aa2a10 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/AbstractSpatialRepresentation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/AbstractSpatialRepresentation.java
@@ -27,8 +27,8 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.bind.metadata.MD_Scope;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
 
 
 /**
@@ -84,8 +84,8 @@
      */
     public AbstractSpatialRepresentation(final SpatialRepresentation object) {
         super(object);
-        if (object != null) {
-            scope = object.getScope();
+        if (object instanceof AbstractSpatialRepresentation) {
+            scope = ((AbstractSpatialRepresentation) object).getScope();
         }
     }
 
@@ -96,7 +96,6 @@
      *
      * @since 1.3
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
     public Scope getScope() {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultDimension.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
index 662f68e..041c08b 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
@@ -32,6 +32,11 @@
 import org.apache.sis.util.ArgumentChecks;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Axis properties.
@@ -142,8 +147,10 @@
             dimensionName        = object.getDimensionName();
             dimensionSize        = object.getDimensionSize();
             resolution           = object.getResolution();
-            dimensionTitle       = object.getDimensionTitle();
-            dimensionDescription = object.getDimensionDescription();
+            if (object instanceof DefaultDimension) {
+                dimensionTitle       = ((DefaultDimension) object).getDimensionTitle();
+                dimensionDescription = ((DefaultDimension) object).getDimensionDescription();
+            }
         }
     }
 
@@ -253,9 +260,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "dimensionTitle")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="dimensionTitle", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDimensionTitle() {
         return dimensionTitle;
     }
@@ -279,9 +286,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "dimensionDescription")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="dimensionDescription", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDimensionDescription() {
         return dimensionDescription;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
index b49daea..c98cf18 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
@@ -31,7 +31,7 @@
  * defined in the {@link org.opengis.metadata} package and sub-packages. That standard is identified in SIS by the
  * {@link org.apache.sis.metadata.MetadataStandard#ISO_19115} constant. Other standards are defined as well,
  * for example the {@link org.apache.sis.metadata.MetadataStandard#ISO_19123} constant stands for the standards
- * defined by the interfaces in the {@link org.opengis.coverage} package and sub-packages.
+ * defined by the interfaces in the {@code org.opengis.coverage} package and sub-packages.
  *
  * <p>For each interface, the collection of declared getter methods defines its <dfn>properties</dfn>
  * (or <dfn>attributes</dfn>). If a {@link org.opengis.annotation.UML} annotation is attached to the getter method,
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/Identifiers.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/Identifiers.java
index 13a4201..d42c6ca 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/Identifiers.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/Identifiers.java
@@ -33,6 +33,9 @@
 import org.apache.sis.xml.NilObject;
 import org.apache.sis.xml.NilReason;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Methods working on {@link Identifier} instances.
@@ -96,7 +99,7 @@
      * </ul>
      *
      * Use {@code toCodeSpace(…)} method when assigning values to be returned by methods like
-     * {@link Identifier#getCodeSpace()}, since those values are likely to be compared without special
+     * {@link ReferenceIdentifier#getCodeSpace()}, since those values are likely to be compared without special
      * care about ignorable identifier characters. But if the intent is to format a more complex string
      * like WKT or {@code toString()}, then we suggest to use {@code getIdentifier(citation, true)} instead,
      * which will produce the same result but preserving the ignorable characters, which can be useful
@@ -123,8 +126,12 @@
                          * Unicode identifiers. If a codespace exists, then the code does not need to begin
                          * with a "Unicode identifier start" (it may be a "Unicode identifier part").
                          */
-                        final String cs = Strings.trimOrNull(id.getCodeSpace());
-                        if (cs == null) {
+                        String cs = null;
+                        if (id instanceof ReferenceIdentifier) {
+                            cs = Strings.trimOrNull(((ReferenceIdentifier) id).getCodeSpace());
+                        }
+                        if (cs == null || cs.isEmpty()) {
+                            cs = null;
                             isUnicode = CharSequences.isUnicodeIdentifier(candidate);
                         } else {
                             isUnicode = CharSequences.isUnicodeIdentifier(cs);
@@ -212,8 +219,8 @@
                 return Citations.identifierMatches(authority, other);
             }
         }
-        if (codeSpace != null) {
-            final String other = identifier.getCodeSpace();
+        if (codeSpace != null && identifier instanceof ReferenceIdentifier) {
+            final String other = ((ReferenceIdentifier) identifier).getCodeSpace();
             if (other != null) {
                 return CharSequences.equalsFiltered(codeSpace, other, Characters.Filter.UNICODE_IDENTIFIER, true);
             }
@@ -236,12 +243,12 @@
      * @return {@code TRUE} or {@code FALSE} on match or mismatch respectively, or {@code null} if this method
      *         cannot determine if there is a match or mismatch.
      */
-    public static Boolean hasCommonIdentifier(final Iterable<? extends Identifier> id1,
-                                              final Iterable<? extends Identifier> id2)
+    public static Boolean hasCommonIdentifier(final Iterable<? extends ReferenceIdentifier> id1,
+                                              final Iterable<? extends ReferenceIdentifier> id2)
     {
         if (id1 != null && id2 != null) {
             boolean hasFound = false;
-            for (final Identifier identifier : id1) {
+            for (final ReferenceIdentifier identifier : id1) {
                 final Citation authority = identifier.getAuthority();
                 final String   codeSpace = identifier.getCodeSpace();
                 for (final Identifier other : id2) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java
index 1fe8975..47c3f21 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java
@@ -122,6 +122,14 @@
     }
 
     /**
+     * Returns {@code null} since names are not versioned.
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this object.
      */
     @Override
@@ -193,7 +201,7 @@
      * @param  toSearch     the identifier to check for equality.
      * @return {@code true} if the identifier to search is found in the given set of identifiers.
      */
-    public static boolean isHeuristicMatchForIdentifier(final Iterable<? extends Identifier> identifiers, final String toSearch) {
+    public static boolean isHeuristicMatchForIdentifier(final Iterable<? extends ReferenceIdentifier> identifiers, final String toSearch) {
         if (toSearch != null && identifiers != null) {
             int s = toSearch.indexOf(DefaultNameSpace.DEFAULT_SEPARATOR);
             if (s < 0) {
@@ -208,7 +216,7 @@
             do {
                 final String codespace = toSearch.substring(0, s).trim();
                 final String code = toSearch.substring(++s).trim();
-                for (final Identifier id : identifiers) {
+                for (final ReferenceIdentifier id : identifiers) {
                     if (codespace.equalsIgnoreCase(id.getCodeSpace()) && code.equalsIgnoreCase(id.getCode())) {
                         return true;
                     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/ReferencingServices.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/ReferencingServices.java
index eed74e2..c71336c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/ReferencingServices.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/ReferencingServices.java
@@ -35,6 +35,9 @@
 import org.apache.sis.system.OptionalDependency;
 import org.apache.sis.system.Modules;
 
+// Specific to the main branch:
+import org.opengis.util.TypeName;
+
 
 /**
  * Provides access to services defined in the {@code org.apache.sis.referencing} module.
@@ -249,6 +252,16 @@
     ///////////////////////////////////////////////////////////////////////////////////////
 
     /**
+     * Returns the name of the type of values.
+     *
+     * @param  parameter  parameter for which to get the name of type of values, or {@code null}.
+     * @return name of type of values, or {@code null} if not supported by given implementation.
+     */
+    public TypeName getValueType(ParameterDescriptor<?> parameter) {
+        return null;
+    }
+
+    /**
      * Returns a fully implemented parameter descriptor.
      *
      * @param  <T>        the type of values.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleAttributeType.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleAttributeType.java
index 962e717..c970126 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleAttributeType.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleAttributeType.java
@@ -22,21 +22,17 @@
 import org.opengis.util.InternationalString;
 import org.opengis.util.TypeName;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-
 
 /**
  * A simple attribute type containing only a name and a class of values.
  * Such simple type are suitable for use in ISO 19103 {@link org.opengis.util.RecordType}
- * in addition to ISO 19109 {@link org.opengis.feature.FeatureType}.
+ * in addition to ISO 19109 {@code org.opengis.feature.FeatureType}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
  * @param <V>  the type of attribute value.
  */
-public final class SimpleAttributeType<V> implements AttributeType<V>, Type, Serializable {
+public final class SimpleAttributeType<V> implements Type, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -69,7 +65,6 @@
      *
      * @return the name of this attribute type.
      */
-    @Override
     public GenericName getName() {
         return name;
     }
@@ -89,7 +84,6 @@
      *
      * @return the class of value for attributes of this type.
      */
-    @Override
     public Class<V> getValueClass() {
         return valueClass;
     }
@@ -99,7 +93,6 @@
      *
      * @return always 1.
      */
-    @Override
     public int getMinimumOccurs() {
         return 1;
     }
@@ -109,7 +102,6 @@
      *
      * @return always 1.
      */
-    @Override
     public int getMaximumOccurs() {
         return 1;
     }
@@ -119,7 +111,6 @@
      *
      * @return always {@code null}.
      */
-    @Override
     public V getDefaultValue() {
         return null;
     }
@@ -129,17 +120,26 @@
      *
      * @return always {@code null}.
      */
-    @Override
     public InternationalString getDefinition() {
         return null;
     }
 
     /**
-     * Unsupported operation.
+     * Not used for this simple attribute type.
+     *
+     * @return always {@code null}.
      */
-    @Override
-    public Attribute<V> newInstance() {
-        throw new UnsupportedOperationException();
+    public InternationalString getDesignation() {
+        return null;
+    }
+
+    /**
+     * Not used for this simple attribute type.
+     *
+     * @return always {@code null}.
+     */
+    public InternationalString getDescription() {
+        return null;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleCitation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleCitation.java
index ef28b60..32ffb58 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleCitation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleCitation.java
@@ -23,6 +23,16 @@
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.privy.Strings;
 
+// Specific to the main branch:
+import java.util.Date;
+import java.util.Collection;
+import java.util.Collections;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.CitationDate;
+import org.opengis.metadata.citation.PresentationForm;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.citation.Series;
+
 
 /**
  * A trivial implementation of {@link Citation} containing only a title.
@@ -66,6 +76,26 @@
     }
 
     /**
+     * Methods inherited from the {@link Citation} interface which are not of interest to this
+     * {@code SimpleCitation} implementation.
+     *
+     * @return an empty list.
+     */
+    @Override public Collection<? extends InternationalString>  getAlternateTitles()         {return Collections.emptyList();}
+    @Override public Collection<? extends CitationDate>         getDates()                   {return Collections.emptyList();}
+    @Override public InternationalString                        getEdition()                 {return null;}
+    @Override public Date                                       getEditionDate()             {return null;}
+    @Override public Collection<? extends Identifier>           getIdentifiers()             {return Collections.emptyList();}
+    @Override public Collection<? extends ResponsibleParty>     getCitedResponsibleParties() {return Collections.emptyList();}
+    @Override public Collection<PresentationForm>               getPresentationForms()       {return Collections.emptyList();}
+    @Override public Series                                     getSeries()                  {return null;}
+    @Override public InternationalString                        getOtherCitationDetails()    {return null;}
+    @Override public String                                     getISBN()                    {return null;}
+    @Override public String                                     getISSN()                    {return null;}
+    @Deprecated
+    @Override public InternationalString                        getCollectiveTitle()         {return null;}
+
+    /**
      * Compares the given object with this citation for equality.
      *
      * @param  obj  the object to compare with this citation.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleExtent.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleExtent.java
index 0c3f18d..195b46c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleExtent.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleExtent.java
@@ -23,6 +23,9 @@
 import org.opengis.metadata.extent.VerticalExtent;
 import static org.apache.sis.util.privy.CollectionsExt.singletonOrEmpty;
 
+// Specific to the main branch:
+import org.opengis.util.InternationalString;
+
 
 /**
  * A trivial implementation of {@link Extent} containing only geographic, vertical and temporal extent.
@@ -63,6 +66,7 @@
         this.temporalElements   = temporalElements;
     }
 
+    @Override public InternationalString          getDescription()        {return null;}
     @Override public Collection<GeographicExtent> getGeographicElements() {return singletonOrEmpty(geographicElements);}
     @Override public Collection<VerticalExtent>   getVerticalElements()   {return singletonOrEmpty(verticalElements);}
     @Override public Collection<TemporalExtent>   getTemporalElements()   {return singletonOrEmpty(temporalElements);}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleFormat.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleFormat.java
index c60f33a..5ac0d5a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleFormat.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleFormat.java
@@ -22,8 +22,8 @@
 import org.opengis.metadata.distribution.Format;
 import org.apache.sis.util.privy.Strings;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Citation;
+// Specific to the main branch:
+import org.opengis.metadata.distribution.Distributor;
 
 
 /**
@@ -67,13 +67,68 @@
     }
 
     /**
-     * Citation / URL of the specification format.
+     * @deprecated Replaced by {@link #getTitle()}
      *
-     * @return citation / URL of the specification format.
+     * @return name of a subset, profile, or product specification of the format, or {@code null}.
      */
     @Override
-    public Citation getFormatSpecificationCitation() {
-        return this;
+    @Deprecated
+    public InternationalString getSpecification() {
+        return getTitle();
+    }
+
+    /**
+     * @deprecated Replaced by {@link #getAlternateTitles()}
+     *
+     * @return name of the data transfer format(s).
+     */
+    @Override
+    @Deprecated
+    public InternationalString getName() {
+        return super.getTitle();
+    }
+
+    /**
+     * @deprecated Replaced by {@link #getEdition()}
+     *
+     * @return version of the format.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getVersion() {
+        return getEdition();
+    }
+
+    /**
+     * Amendment number of the format version.
+     *
+     * @return amendment number of the format version, or {@code null}.
+     */
+    @Override
+    public InternationalString getAmendmentNumber() {
+        return null;
+    }
+
+    /**
+     * Recommendations of algorithms or processes that can be applied to read
+     * or expand resources to which compression techniques have been applied.
+     *
+     * @return processes that can be applied to read resources to which compression techniques have been applied,
+     *         or {@code null}.
+     */
+    @Override
+    public InternationalString getFileDecompressionTechnique() {
+        return null;
+    }
+
+    /**
+     * Provides information about the distributor's format.
+     *
+     * @return information about the distributor's format.
+     */
+    @Override
+    public Collection<? extends Distributor> getFormatDistributors() {
+        return Collections.emptyList();
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
index c1b001c..6f0ae78 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
@@ -32,8 +32,11 @@
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.extent.Extent;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Optional;
+// Specific to the main branch:
+import java.util.Set;
+import java.util.Collection;
+import java.util.Collections;
+import org.opengis.util.GenericName;
 
 
 /**
@@ -92,6 +95,34 @@
     }
 
     /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns an empty set.
+     *
+     * <p>If a future version allows this method to returns a non-empty set,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the identifiers, or an empty set if none.
+     */
+    @Override
+    public final Set<ReferenceIdentifier> getIdentifiers() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns an empty set.
+     *
+     * <p>If a future version allows this method to returns a non-empty set,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the aliases, or an empty set if none.
+     */
+    @Override
+    public final Collection<GenericName> getAlias() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Method required by most {@link IdentifiedObject} sub-interfaces.
      * Current implementation returns {@code null}.
      *
@@ -124,15 +155,17 @@
     }
 
     /**
-     * Returns a narrative explanation of the role of this object.
-     * The default implementation returns {@link Identifier#getDescription()}.
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns {@code null}.
      *
-     * @return a narrative explanation of the role of this object.
+     * <p>If a future version allows this method to returns a non-null value,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the remarks, or {@code null} if none.
      */
-    public Optional<InternationalString> getDescription() {
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final Identifier name = this.name;
-        return Optional.ofNullable((name != null) ? name.getDescription() : null);
+    @Override
+    public final InternationalString getRemarks() {
+        return null;
     }
 
     /**
@@ -191,12 +224,23 @@
     }
 
     /**
+     * Throws an exception in all cases, since this object can't be formatted in a valid WKT.
+     *
+     * @return the Well Known Text.
+     * @throws UnsupportedOperationException always thrown.
+     */
+    @Override
+    public String toWKT() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns a pseudo-WKT representation for debugging purpose.
      */
     @Override
     public String toString() {
         @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final Identifier name = this.name;
+        final ReferenceIdentifier name = this.name;
         final String code, codespace;
         final Citation authority;
         if (name != null) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifier.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifier.java
index c2b49c3..5abb883 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifier.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifier.java
@@ -138,6 +138,15 @@
     }
 
     /**
+     * Returns a natural language description of the meaning of the code value.
+     *
+     * @return natural language description, or {@code null} if none.
+     */
+    public InternationalString getDescription() {
+        return null;
+    }
+
+    /**
      * An optional free text.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
index 2a89923..306aaa2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
@@ -32,10 +32,22 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Map;
-import java.nio.charset.Charset;
-import org.opengis.metadata.MetadataScope;
+// Specific to the main branch:
+import java.util.Date;
+import org.opengis.metadata.ApplicationSchemaInformation;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.MetadataExtensionInformation;
+import org.opengis.metadata.PortrayalCatalogueReference;
+import org.opengis.metadata.citation.Series;
+import org.opengis.metadata.maintenance.MaintenanceInformation;
+import org.opengis.metadata.spatial.SpatialRepresentation;
+import org.opengis.metadata.acquisition.AcquisitionInformation;
+import org.opengis.metadata.constraint.Constraints;
+import org.opengis.metadata.content.ContentInformation;
+import org.opengis.metadata.distribution.Distribution;
+import org.opengis.metadata.distribution.Format;
+import org.opengis.metadata.quality.DataQuality;
+import org.opengis.referencing.ReferenceSystem;
 
 
 /**
@@ -47,8 +59,8 @@
  * <p>Unless specified otherwise, all methods in this class returns {@code null} or an empty collection by default.
  * The exceptions to this rules are the following methods:</p>
  * <ul>
- *   <li>{@link #getMetadataScopes()} returns {@code this}</li>
- *   <li>{@link #getResourceScope()} returns {@link ScopeCode#DATASET}</li>
+ *   <li>{@code  getMetadataScopes()} returns {@code this}</li>
+ *   <li>{@code  getResourceScope()} returns {@link ScopeCode#DATASET}</li>
  *   <li>{@link #getIdentificationInfo()} returns {@code this}</li>
  *   <li>{@link #getCitation()} returns {@code this}</li>
  *   <li>{@link #getSpatialRepresentationTypes()} returns {@link SpatialRepresentationType#VECTOR}</li>
@@ -67,7 +79,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  */
-public class SimpleMetadata implements Metadata, MetadataScope, DataIdentification, Citation {
+public class SimpleMetadata implements Metadata, DataIdentification, Citation {
     /**
      * Creates a new metadata object.
      */
@@ -75,34 +87,82 @@
     }
 
     /**
+     * Unique identifier for this metadata record.
+     */
+    @Override
+    public String getFileIdentifier() {
+        return null;
+    }
+
+    /**
      * Language(s) used for documenting metadata.
      * Also the language(s) used within the data.
      */
     @Override
-    public Map<Locale,Charset> getLocalesAndCharsets() {
-        return Collections.emptyMap();
+    public Collection<Locale> getLanguages() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Locale' like a CodeList.
     }
 
     /**
-     * The scope or type of resource for which metadata is provided.
-     * This method returns {@code this} for allowing call to {@link #getResourceScope()}.
-     *
-     * @see #getResourceScope()
-     * @see #getName()
+     * Language(s) used for documenting metadata.
+     * Also the language(s) used within the data.
      */
     @Override
-    public Collection<MetadataScope> getMetadataScopes() {
-        return Collections.singleton(this);
+    public Locale getLanguage() {
+        return null;
     }
 
     /**
-     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by default. This is part of the information
-     * provided by {@link #getMetadataScopes()}. The {@code DATASET} default value is consistent with the fact that
+     * Language(s) used for documenting metadata.
+     * Also the language(s) used within the data.
+     */
+    @Override
+    public Collection<Locale> getLocales() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Locale' like a CodeList.
+    }
+
+    /**
+     * The character coding standard used for the metadata set.
+     * Also the character coding standard(s) used for the dataset.
+     */
+    @Override
+    public Collection<CharacterSet> getCharacterSets() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Charset' like a CodeList.
+    }
+
+    /**
+     * The character coding standard used for the metadata set.
+     * Also the character coding standard(s) used for the dataset.
+     */
+    @Override
+    public CharacterSet getCharacterSet() {
+        return null;
+    }
+
+    /**
+     * Identification of the parent metadata record.
+     */
+    @Override
+    public String getParentIdentifier() {
+        return null;
+    }
+
+    /**
+     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by default.
+     * The {@code DATASET} default value is consistent with the fact that
      * {@code SimpleMetadata} implements {@link DataIdentification}.
      */
     @Override
-    public ScopeCode getResourceScope() {
-        return ScopeCode.DATASET;
+    public Collection<ScopeCode> getHierarchyLevels() {
+        return Collections.singleton(ScopeCode.DATASET);
+    }
+
+    /**
+     * Description of the metadata scope.
+     */
+    @Override
+    public Collection<String> getHierarchyLevelNames() {
+        return Collections.emptySet();
     }
 
     /**
@@ -117,7 +177,56 @@
      * Date(s) associated with the metadata.
      */
     @Override
-    public Collection<CitationDate> getDateInfo() {
+    public Date getDateStamp() {
+        return null;
+    }
+
+    /**
+     * Citation(s) for the standard(s) to which the metadata conform.
+     */
+    @Override
+    public String getMetadataStandardName() {
+        return null;
+    }
+
+    /**
+     * As of ISO 19115:2014, replaced by {@code getMetadataStandards()}
+     * followed by {@link Citation#getEdition()}.
+     */
+    @Override
+    public String getMetadataStandardVersion() {
+        return null;
+    }
+
+    /**
+     * Online location(s) where the metadata is available.
+     */
+    @Override
+    public String getDataSetUri() {
+        return null;
+    }
+
+    /**
+     * Digital representation of spatial information in the dataset.
+     */
+    @Override
+    public Collection<SpatialRepresentation> getSpatialRepresentationInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the spatial and temporal reference systems used in the dataset.
+     */
+    @Override
+    public Collection<ReferenceSystem> getReferenceSystemInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information describing metadata extensions.
+     */
+    @Override
+    public Collection<MetadataExtensionInformation> getMetadataExtensionInfo() {
         return Collections.emptyList();
     }
 
@@ -131,7 +240,6 @@
      * @see #getPointOfContacts()
      * @see #getSpatialRepresentationTypes()
      * @see #getSpatialResolutions()
-     * @see #getTemporalResolutions()
      * @see #getTopicCategories()
      * @see #getExtents()
      * @see #getResourceFormats()
@@ -142,6 +250,70 @@
         return Collections.singleton(this);
     }
 
+    /**
+     * Information about the feature and coverage characteristics.
+     */
+    @Override
+    public Collection<ContentInformation> getContentInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the distributor of and options for obtaining the resource(s).
+     */
+    @Override
+    public Distribution getDistributionInfo() {
+        return null;
+    }
+
+    /**
+     * Overall assessment of quality of a resource(s).
+     */
+    @Override
+    public Collection<DataQuality> getDataQualityInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the catalogue of rules defined for the portrayal of a resource(s).
+     */
+    @Override
+    public Collection<PortrayalCatalogueReference> getPortrayalCatalogueInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Restrictions on the access and use of metadata.
+     */
+    @Override
+    public Collection<Constraints> getMetadataConstraints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the conceptual schema of a dataset.
+     */
+    @Override
+    public Collection<ApplicationSchemaInformation> getApplicationSchemaInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the acquisition of the data.
+     */
+    @Override
+    public Collection<AcquisitionInformation> getAcquisitionInformation() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the frequency of metadata updates, and the scope of those updates.
+     */
+    @Override
+    public MaintenanceInformation getMetadataMaintenance() {
+        return null;
+    }
+
 
     /* -------------------------------------------------------------------------------------------------
      * Implementation of the DataIdentification object returned by Metadata.getIdentificationInfo().
@@ -167,6 +339,42 @@
     }
 
     /**
+     * Summary of the intentions with which the resource was developed.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getPurpose() {
+        return null;
+    }
+
+    /**
+     * Recognition of those who contributed to the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<String> getCredits() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Status of the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Progress> getStatus() {
+        return Collections.emptySet();              // We use 'Set' because 'Progress' is a CodeList.
+    }
+
+    /**
+     * Identification of, and means of communication with, person(s) and organisations associated with the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<ResponsibleParty> getPointOfContacts() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Methods used to spatially represent geographic information.
      * This is part of the information returned by {@link #getIdentificationInfo()}.
      * Default implementation returns {@link SpatialRepresentationType#VECTOR}.
@@ -178,6 +386,15 @@
     }
 
     /**
+     * Factor which provides a general understanding of the density of spatial data in the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Resolution> getSpatialResolutions() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Main theme(s) of the resource.
      * This is part of the information returned by {@link #getIdentificationInfo()}.
      * Default implementation returns {@link TopicCategory#LOCATION}.
@@ -197,6 +414,89 @@
         return Collections.emptyList();
     }
 
+    /**
+     * Information about the frequency of resource updates, and the scope of those updates.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<MaintenanceInformation> getResourceMaintenances() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Graphic that illustrates the resource(s) (should include a legend for the graphic).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<BrowseGraphic> getGraphicOverviews() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the format of the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Format> getResourceFormats() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Category keywords, their type, and reference source.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Keywords> getDescriptiveKeywords() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Basic information about specific application(s) for which the resource(s)
+     * has/have been or is being used by different users.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Usage> getResourceSpecificUsages() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about constraints which apply to the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Constraints> getResourceConstraints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * @deprecated As of ISO 19115:2014, replaced by {@code getAssociatedResources()}.
+     */
+    @Override
+    @Deprecated
+    public Collection<AggregateInformation> getAggregationInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the resource in the producer's processing environment, including items
+     * such as the software, the computer operating system, file name, and the dataset size.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getEnvironmentDescription() {
+        return null;
+    }
+
+    /**
+     * Any other descriptive information about the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getSupplementalInformation() {
+        return null;
+    }
+
 
     /* -------------------------------------------------------------------------------------------------
      * Implementation of the Citation object returned by DataIdentification.getCitation().
@@ -212,6 +512,61 @@
     }
 
     /**
+     * Short names or other language names by which the cited information is known.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<InternationalString> getAlternateTitles() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Reference dates for the cited resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<CitationDate> getDates() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Version of the cited resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public InternationalString getEdition() {
+        return null;
+    }
+
+    /**
+     * Date of the edition.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Date getEditionDate() {
+        return null;
+    }
+
+    /**
+     * Unique identifier for the resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<Identifier> getIdentifiers() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Role, name, contact and position information for individuals or organisations
+     * that are responsible for the resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<ResponsibleParty> getCitedResponsibleParties() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Mode in which the resource is represented.
      * This is part of the information returned by {@link #getCitation()}.
      * Default implementation returns {@link PresentationForm#TABLE_DIGITAL}.
@@ -221,4 +576,49 @@
     public Collection<PresentationForm> getPresentationForms() {
         return Collections.singleton(PresentationForm.TABLE_DIGITAL);
     }
+
+    /**
+     * Information about the series, or aggregate dataset, of which the dataset is a part.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Series getSeries() {
+        return null;
+    }
+
+    /**
+     * Other information required to complete the citation that is not recorded elsewhere.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public InternationalString getOtherCitationDetails() {
+        return null;
+    }
+
+    /**
+     * @deprecated Removed as of ISO 19115:2014.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getCollectiveTitle() {
+        return null;
+    }
+
+    /**
+     * International Standard Book Number.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public String getISBN() {
+        return null;
+    }
+
+    /**
+     * International Standard Serial Number.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public String getISSN() {
+        return null;
+    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
index b6d43c1..9781845 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
@@ -36,8 +36,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 branch:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 
 
 /**
@@ -160,9 +160,14 @@
                  */
                 Object value;
                 try {
-                    method = supercede(method);
-                    if (method == null) return null;
+                    final long nb = nullValues;
                     value = fetchValue(source.getLookupInfo(method.getDeclaringClass()), method);
+                    if (value == null) {
+                        nullValues = nb;
+                        method = supercede(method);
+                        if (method == null) return null;
+                        value = fetchValue(source.getLookupInfo(method.getDeclaringClass()), method);
+                    }
                 } catch (ReflectiveOperationException | SQLException | MetadataStoreException e) {
                     throw new BackingStoreException(error(method), e);
                 }
@@ -331,7 +336,7 @@
     private static Method supercede(Method method) throws NoSuchMethodException {
         if (method.getDeclaringClass() == ResponsibleParty.class) {
             if ("getRole".equals(method.getName())) {
-                method = Responsibility.class.getMethod("getRole");
+                method = DefaultResponsibility.class.getMethod("getRole");
             } else {
                 /*
                  * `getIndividualName()`, `getOrganisationName()`, `getPositionName()` and
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java
index f2e9856..5a97fc3 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java
@@ -32,8 +32,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -80,7 +80,7 @@
         ArgumentChecks.ensureNonNull("type", type);
         ArgumentChecks.ensureNonEmpty("identifier", identifier);
         Object value;
-        if (ControlledVocabulary.class.isAssignableFrom(type)) {
+        if (CodeList.class.isAssignableFrom(type)) {
             try {
                 value = getCodeList(type, identifier);
             } catch (IllegalArgumentException e) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
index b1e7fff..7f67448 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
@@ -77,8 +77,8 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.evolution.Interim;
 
 
 /**
@@ -669,8 +669,8 @@
              * be present in the database in order to ensure foreigner key constraints, but
              * those tables are not used in any way by the org.apache.sis.metadata.sql package.
              */
-            if (metadata instanceof ControlledVocabulary) {
-                identifier = Types.getCodeName((ControlledVocabulary) metadata);
+            if (metadata instanceof CodeList<?>) {
+                identifier = Types.getCodeName((CodeList<?>) metadata);
             } else if (metadata instanceof Enum<?>) {
                 identifier = ((Enum<?>) metadata).name();
             } else {
@@ -736,8 +736,8 @@
              * Note that if a metadata dependency is not found, we can stop the whole process immediately.
              */
             if (value != null) {
-                if (value instanceof ControlledVocabulary) {
-                    value = Types.getCodeName((ControlledVocabulary) value);
+                if (value instanceof CodeList<?>) {
+                    value = Types.getCodeName((CodeList<?>) value);
                 } else if (value instanceof Enum<?>) {
                     value = ((Enum<?>) value).name();
                 } else {
@@ -847,7 +847,7 @@
      *
      * @param  <T>         the parameterized type of the {@code type} argument.
      * @param  type        the interface to implement (e.g. {@link org.opengis.metadata.citation.Citation}),
-     *                     or the {@link ControlledVocabulary} type ({@link CodeList} or some {@link Enum}).
+     *                     or the {@code ControlledVocabulary} type ({@link CodeList} or some {@link Enum}).
      * @param  identifier  the identifier of the record for the metadata entity to be created.
      *                     This is usually the primary key of the record to search for.
      * @return an implementation of the required interface, or the code list element.
@@ -878,7 +878,7 @@
      */
     private Object lookup(final Class<?> type, final String identifier, boolean verify) throws MetadataStoreException {
         Object value;
-        if (ControlledVocabulary.class.isAssignableFrom(type)) {
+        if (CodeList.class.isAssignableFrom(type)) {
             try {
                 value = getCodeList(type, identifier);
             } catch (IllegalArgumentException e) {
@@ -983,7 +983,7 @@
          * the name between bracket is a subtype of the given `type` argument.
          */
         final Class<?> type           = TableHierarchy.subType(info.getMetadataType(), toSearch.identifier);
-        final Class<?> returnType     = method.getReturnType();
+        final Class<?> returnType     = Interim.getReturnType(method);
         final boolean  wantCollection = Collection.class.isAssignableFrom(returnType);
         final Class<?> elementType    = (wantCollection || Classes.isParameterizedProperty(returnType)) ? Classes.boundOfParameterizedProperty(method) : returnType;
         final boolean  isMetadata     = standard.isMetadata(elementType);
@@ -1070,12 +1070,8 @@
      * @throws IllegalArgumentException if there is no value for the given name and the code cannot be created.
      */
     @SuppressWarnings("unchecked")
-    static ControlledVocabulary getCodeList(final Class<?> type, final String name) {
-        if (type.isEnum()) {
-            return (ControlledVocabulary) CodeLists.forEnumName(type.asSubclass(Enum.class), name);
-        } else {
-            return CodeLists.getOrCreate(type.asSubclass(CodeList.class), name);
-        }
+    static CodeList<?> getCodeList(final Class<?> type, final String name) {
+        return CodeLists.getOrCreate(type.asSubclass(CodeList.class), name);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataWriter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataWriter.java
index 54a7bc5..7c24697 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataWriter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataWriter.java
@@ -31,9 +31,9 @@
 import java.sql.SQLException;
 import javax.sql.DataSource;
 import java.lang.reflect.Modifier;
+import org.opengis.util.FactoryException;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.util.FactoryException;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
@@ -52,8 +52,9 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.xml.IdentifiedObject;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -191,8 +192,8 @@
                 boolean success = false;
                 try {
                     try (Statement stmt = connection.createStatement()) {
-                        if (metadata instanceof ControlledVocabulary) {
-                            identifier = addCode(stmt, (ControlledVocabulary) metadata);
+                        if (metadata instanceof CodeList<?>) {
+                            identifier = addCode(stmt, (CodeList<?>) metadata);
                         } else {
                             identifier = add(stmt, metadata, new IdentityHashMap<>(), null);
                         }
@@ -316,7 +317,7 @@
                  */
                 int maxLength = maximumValueLength;
                 Class<?> rt = colTypes.get(column);
-                final boolean isCodeList = ControlledVocabulary.class.isAssignableFrom(rt);     // Accept also some enums.
+                final boolean isCodeList = CodeList.class.isAssignableFrom(rt);
                 if (isCodeList || standard.isMetadata(rt)) {
                     /*
                      * Found a reference to another metadata. Remind that column for creating a foreign key
@@ -406,8 +407,8 @@
         for (final Map.Entry<String,Object> entry : asSingletons.entrySet()) {
             Object value = entry.getValue();
             final Class<?> type = value.getClass();
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
-                value = addCode(stmt, (ControlledVocabulary) value);
+            if (CodeList.class.isAssignableFrom(type)) {
+                value = addCode(stmt, (CodeList<?>) value);
             } else if (type.isEnum()) {
                 value = ((Enum<?>) value).name();
             } else if (standard.isMetadata(type)) {
@@ -484,7 +485,7 @@
             for (final Map.Entry<String,FKey> entry : foreigners.entrySet()) {
                 final FKey fkey = entry.getValue();
                 Class<?> rt = fkey.tableType;
-                final boolean isCodeList = ControlledVocabulary.class.isAssignableFrom(rt);
+                final boolean isCodeList = CodeList.class.isAssignableFrom(rt);
                 final String primaryKey;
                 if (isCodeList) {
                     primaryKey = CODE_COLUMN;
@@ -644,11 +645,9 @@
     /**
      * Adds a code list if it is not already present. This is used only in order to enforce
      * foreigner key constraints in the database. The value of CodeList tables are not used
-     * at parsing time. Enumerations are handled as if they were CodeLists; we do not use
-     * the native SQL {@code ENUM} type for making easier to add new values when a standard
-     * is updated.
+     * at parsing time.
      */
-    private String addCode(final Statement stmt, final ControlledVocabulary code) throws SQLException {
+    private String addCode(final Statement stmt, final CodeList<?> code) throws SQLException {
         assert Thread.holdsLock(this);
         final String table = getTableName(code.getClass());
         final Set<String> columns = getExistingColumns(table);
@@ -703,9 +702,11 @@
         for (final Identifier id : identifiers) {
             identifier = Strings.trimOrNull(id.getCode());
             if (identifier != null) {
-                final String cs = Strings.trimOrNull(id.getCodeSpace());
-                if (cs != null) {
-                    identifier = cs + Constants.DEFAULT_SEPARATOR + identifier;
+                if (id instanceof ReferenceIdentifier) {
+                    final String cs = Strings.trimOrNull(((ReferenceIdentifier) id).getCodeSpace());
+                    if (cs != null) {
+                        identifier = cs + Constants.DEFAULT_SEPARATOR + identifier;
+                    }
                 }
                 break;
             }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/Interim.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/Interim.java
new file mode 100644
index 0000000..54a86ce
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/Interim.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.evolution;
+
+import java.lang.reflect.Method;
+import static java.util.logging.Logger.getLogger;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
+import org.apache.sis.util.Static;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.system.Modules;
+
+
+/**
+ * Temporary methods used until a new major GeoAPI release provides the missing functionalities.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   0.8
+ */
+public final class Interim extends Static {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private Interim() {
+    }
+
+    /**
+     * Returns the return type of the given method, or the interim type if the method is annotated
+     * with {@link InterimType}.
+     *
+     * @param  method  the method from which to get the return type.
+     * @return the return type or the interim type.
+     */
+    public static Class<?> getReturnType(final Method method) {
+        final InterimType an = method.getAnnotation(InterimType.class);
+        return (an != null) ? an.value() : method.getReturnType();
+    }
+
+    /**
+     * Invokes {@code Geometry.getEnvelope()} if that method exists.
+     *
+     * @param  geometry  the geometry from which to get the envelope.
+     * @return the geometry envelope, or {@code null} if none.
+     */
+    public static Envelope getEnvelope(final Geometry geometry) {
+        try {
+            return (Envelope) geometry.getClass().getMethod("getEnvelope").invoke(geometry);
+        } catch (ReflectiveOperationException | ClassCastException e) {
+            Logging.recoverableException(getLogger(Modules.METADATA), Interim.class, "getEnvelope", e);
+            return null;
+        }
+    }
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/InterimType.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/InterimType.java
new file mode 100644
index 0000000..8c48b7a
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/InterimType.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.evolution;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+
+
+/**
+ * Identifies an interim class to use until an official GeoAPI class or interface is released.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ *
+ * @see Interim#getReturnType(Method)
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface InterimType {
+    /**
+     * The interim Apache SIS class to use until the GeoAPI class or interface is released.
+     *
+     * @return Apache SIS class to use in the interim.
+     */
+    Class<?> value();
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/UnsupportedCodeList.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/UnsupportedCodeList.java
new file mode 100644
index 0000000..e949e74
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/UnsupportedCodeList.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2014 desruisseaux.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.evolution;
+
+import java.util.List;
+import java.util.ArrayList;
+import org.opengis.annotation.UML;
+import org.opengis.util.CodeList;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
+
+/**
+ * Placeholder for code list not yet available in GeoAPI.
+ * Currently defines constants mostly for {@code org.opengis.metadata.citation.TelephoneType},
+ * but constants for other code list can be constructed like below:
+ *
+ * {@snippet lang="java" :
+ *   operation.getDistributedComputingPlatforms().add(UnsupportedCodeList.valueOf("SOAP"));
+ *   }
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   0.5
+ */
+public final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 7205015191869240829L;
+
+    /**
+     * The list of constants defined in this code list.
+     */
+    private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>(3);
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="voice", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> VOICE = new UnsupportedCodeList("VOICE");
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="facsimile", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> FACSIMILE = new UnsupportedCodeList("FACSIMILE");
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="WebServices", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> WEB_SERVICES = new UnsupportedCodeList("WEB_SERVICES");
+
+    /**
+     * Constructor for new code list element.
+     *
+     * @param name The code list name.
+     */
+    private UnsupportedCodeList(String name) {
+        super(name, VALUES);
+    }
+
+    /**
+     * Returns the list of codes of the same kind than this code list element.
+     *
+     * @return All code values for this code list.
+     */
+    @Override
+    public UnsupportedCodeList[] family() {
+        synchronized (VALUES) {
+            return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+        }
+    }
+
+    /**
+     * Returns the telephone type that matches the given string, or returns a new one if none match it.
+     *
+     * @param code The name of the code to fetch or to create.
+     * @return A code matching the given name.
+     */
+    public static UnsupportedCodeList valueOf(String code) {
+        return valueOf(UnsupportedCodeList.class, code);
+    }
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/UnsupportedCodeListAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/UnsupportedCodeListAdapter.java
new file mode 100644
index 0000000..d0cf585
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/UnsupportedCodeListAdapter.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.evolution;
+
+import jakarta.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.util.CodeList;
+import org.apache.sis.util.iso.Types;
+import org.apache.sis.xml.bind.Context;
+import org.apache.sis.xml.bind.cat.CodeListUID;
+
+
+/**
+ * An adapter for {@link UnsupportedCodeList}, in order to implement the ISO 19115-3 standard.
+ * See {@link org.apache.sis.xml.bind.cat.CodeListAdapter} for more information.
+ *
+ * @param <ValueType> The subclass implementing this adapter.
+ *
+ * @author  Cédric Briançon (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.5
+ * @since   0.5
+ */
+public abstract class UnsupportedCodeListAdapter<ValueType extends UnsupportedCodeListAdapter<ValueType>>
+        extends XmlAdapter<ValueType,CodeList<?>>
+{
+    /**
+     * The value of the {@link CodeList}.
+     */
+    protected CodeListUID identifier;
+
+    /**
+     * Empty constructor for subclasses only.
+     */
+    protected UnsupportedCodeListAdapter() {
+    }
+
+    /**
+     * Creates a wrapper for a {@link CodeList}, in order to handle the format specified in ISO-19139.
+     *
+     * @param  value  the value of the {@link CodeList} to be marshalled.
+     */
+    protected UnsupportedCodeListAdapter(final CodeListUID value) {
+        identifier = value;
+    }
+
+    /**
+     * Wraps the code into an adapter.
+     * Most implementations will be like below:
+     *
+     * {@snippet lang="java" :
+     *     return new ValueType(value);
+     *     }
+     *
+     * @param  value  the value of {@link CodeList} to be marshalled.
+     * @return the wrapper for the code list value.
+     */
+    protected abstract ValueType wrap(CodeListUID value);
+
+    /**
+     * Returns the name of the code list class.
+     *
+     * @return the code list class name.
+     */
+    protected abstract String getCodeListName();
+
+    /**
+     * Substitutes the adapter value read from an XML stream by the object which will
+     * contains the value. JAXB calls automatically this method at unmarshalling time.
+     *
+     * @param  adapter  the adapter for this metadata value.
+     * @return a code list which represents the metadata value.
+     */
+    @Override
+    public final CodeList<?> unmarshal(final ValueType adapter) {
+        if (adapter == null) {
+            return null;
+        }
+        return Types.forCodeName(UnsupportedCodeList.class, adapter.identifier.toString(), true);
+    }
+
+    /**
+     * Substitutes the code list by the adapter to be marshalled into an XML file or stream.
+     * JAXB calls automatically this method at marshalling time.
+     *
+     * @param  value  the code list value.
+     * @return the adapter for the given code list.
+     */
+    @Override
+    public final ValueType marshal(final CodeList<?> value) {
+        if (value == null) {
+            return null;
+        }
+        final String name = value.name();
+        final int length = name.length();
+        final StringBuilder buffer = new StringBuilder(length);
+        final String codeListValue = toIdentifier(name, buffer, false);
+        buffer.setLength(0);
+        return wrap(new CodeListUID(Context.current(), getCodeListName(), codeListValue,
+                null, toIdentifier(name, buffer, true)));
+    }
+
+    /**
+     * Converts the given Java constant name to something hopefully close to the UML identifier,
+     * or close to the textual value to put in the XML. This method convert the Java constant name
+     * to camel case if {@code isValue} is {@code true}, or to lower cases with word separated by
+     * spaces if {@code isValue} is {@code true}.
+     *
+     * @param  name    The Java constant name (e.g. {@code WEB_SERVICES}).
+     * @param  buffer  An initially empty buffer to use for creating the identifier.
+     * @param  isValue {@code false} for the {@code codeListValue} attribute, or {@code true} for the XML value.
+     * @return The identifier (e.g. {@code "webServices"} or {@code "Web services"}).
+     */
+    protected String toIdentifier(final String name, final StringBuilder buffer, final boolean isValue) {
+        final int length = name.length();
+        boolean toUpper = isValue;
+        for (int i=0; i<length;) {
+            int c = name.codePointAt(i);
+            i += Character.charCount(c);
+            if (c == '_') {
+                if (isValue) {
+                    c = ' ';
+                } else {
+                    toUpper = true;
+                    continue;
+                }
+            }
+            if (toUpper) {
+                c = Character.toUpperCase(c);
+                toUpper = false;
+            } else {
+                c = Character.toLowerCase(c);
+            }
+            buffer.appendCodePoint(c);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Invoked by JAXB on marshalling. Subclasses must override this
+     * method with the appropriate {@code @XmlElement} annotation.
+     *
+     * @return The {@code CodeList} value to be marshalled.
+     */
+    public abstract CodeListUID getElement();
+
+    /*
+     * We do not define setter method (even abstract) since it seems to confuse JAXB.
+     * It is subclasses responsibility to define the setter method.
+     */
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/package-info.java
new file mode 100644
index 0000000..eca7531
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provides a transition path for new GeoAPI elements not yet published in a formal release.
+ * We try to avoid putting a copy of those new elements in Apache SIS, since it would break
+ * compatibility when they would be removed in favor of GeoAPI elements. The approach taken
+ * is rather to use in the new API the most immediate parent available in a GeoAPI release.
+ * For example for new code list classes, this is {@code CodeList<?>}. The Javadoc for such
+ * API shall contain a warning. See {@code warning-templates.txt} for some proposals.
+ *
+ * <p><STRONG>Do not use!</STRONG></p>
+ *
+ * This package is for internal use by SIS only. Classes in this package
+ * may change in incompatible ways in any future version without notice.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   0.5
+ */
+package org.apache.sis.pending.geoapi.evolution;
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/warning-templates.txt b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/warning-templates.txt
new file mode 100644
index 0000000..1172b94
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/evolution/warning-templates.txt
@@ -0,0 +1,33 @@
+Suggestions for the Javadoc of methods having a CodeList<?> return type
+(replace "NewCodeList" and "3.1" by appropriate values):
+
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code NewCodeList} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@snippet lang="java" :
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     *   }
+     * </div>
+     *
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
index 6f4cff5..afc79c7 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
@@ -19,10 +19,8 @@
 import java.util.Objects;
 import java.time.temporal.Temporal;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.time.Duration;
-import java.time.temporal.TemporalAmount;
-import org.opengis.temporal.Period;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.temporal.Period;
 
 
 /**
@@ -31,7 +29,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DefaultPeriod extends Primitive implements Period {
+public final class DefaultPeriod implements Period {
     /** Bounds making this period. */
     private final Temporal beginning, ending;
 
@@ -51,11 +49,6 @@
         return ending;
     }
 
-    /** Duration of this temporal geometric primitive. */
-    @Override public TemporalAmount length() {
-        return (beginning != null && ending != null) ? Duration.between(beginning, ending) : null;
-    }
-
     /** String representation. */
     @Override public String toString() {
         return "[" + beginning + " … " + ending + ']';
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriodDuration.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriodDuration.java
index 0687530..fb98bc5 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriodDuration.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriodDuration.java
@@ -20,11 +20,6 @@
 import java.time.temporal.TemporalAmount;
 import org.opengis.temporal.PeriodDuration;
 
-// Specific to the geoapi-3.1 branch:
-import java.time.temporal.Temporal;
-import java.time.temporal.TemporalUnit;
-import java.util.List;
-
 
 /**
  * Default implementation of GeoAPI period duration. This is a temporary class;
@@ -46,11 +41,6 @@
         this.duration = duration;
     }
 
-    @Override public List<TemporalUnit>   getUnits()          {return duration.getUnits();}
-    @Override public long     get         (TemporalUnit unit) {return duration.get(unit);}
-    @Override public Temporal addTo       (Temporal temporal) {return duration.addTo(temporal);}
-    @Override public Temporal subtractFrom(Temporal temporal) {return duration.subtractFrom(temporal);}
-
     /** String representation. */
     @Override public String toString() {
         return duration.toString();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/Primitive.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/Primitive.java
deleted file mode 100644
index f85bd9f..0000000
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/Primitive.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.pending.temporal;
-
-import java.time.temporal.TemporalAmount;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalGeometricPrimitive;
-import org.opengis.temporal.TemporalPrimitive;
-
-// Specific to the geoapi-3.1 branch:
-import org.opengis.referencing.ReferenceIdentifier;
-
-
-/**
- * Base implementation of GeoAPI temporal primitives. This is a temporary class;
- * GeoAPI temporal interfaces are expected to change a lot in a future revision.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-abstract class Primitive implements TemporalGeometricPrimitive, ReferenceIdentifier {
-    /**
-     * For sub-class constructors.
-     */
-    Primitive() {
-    }
-
-    /**
-     * The primary name by which this object is identified.
-     * This field is inherited from ISO 19111 {@code IdentifiedObject} and is in principle mandatory.
-     */
-    @Override
-    public final ReferenceIdentifier getName() {
-        return this;
-    }
-
-    /**
-     * Returns the string representation as the code for this object. This is not a correct identifier code,
-     * but we use that as a trick for forcing {@link org.apache.sis.util.collection.TreeTableFormat} to show
-     * the temporal value, because the formatter handles {@link org.opengis.referencing.IdentifiedObject} in
-     * a special way.
-     */
-    @Override public final String getCode() {
-        return toString();
-    }
-
-    /** Position of this primitive relative to another primitive. */
-    @Override public final RelativePosition relativePosition(TemporalPrimitive other) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** Absolute value of the difference between temporal positions. */
-    @Override public final TemporalAmount distance(TemporalGeometricPrimitive other) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
index b812f6c..9cde4cf 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
@@ -21,8 +21,8 @@
 import org.opengis.temporal.TemporalPrimitive;
 import org.apache.sis.util.privy.TemporalDate;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.temporal.Period;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultNameFactory.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultNameFactory.java
index a890297..5d3e83a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultNameFactory.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultNameFactory.java
@@ -256,7 +256,6 @@
      *
      * @since 1.3
      */
-    @Override
     public TypeName createTypeName(final NameSpace scope, final CharSequence name, final Type javaType) {
         return pool.unique(new DefaultTypeName(scope, name, javaType));
     }
@@ -274,7 +273,6 @@
      * @see Names#createMemberName(CharSequence, String, CharSequence, TypeName)
      * @see Names#createMemberName(CharSequence, String, CharSequence, Class)
      */
-    @Override
     public MemberName createMemberName(final NameSpace scope, final CharSequence name, final TypeName attributeType) {
         return pool.unique(new DefaultMemberName(scope, name, attributeType));
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java
index e9a51af..c9e73ed 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecord.java
@@ -174,9 +174,25 @@
      *
      * @return the dictionary of all (<var>name</var>, <var>value</var>) pairs in this record.
      *
-     * @see RecordType#getFieldTypes()
+     * @see RecordType#getMemberTypes()
+     *
+     * @deprecated Renamed {@link #getFields()} for consistency with the 2015 revision of ISO 19103 standard.
      */
     @Override
+    @Deprecated
+    public Map<MemberName, Object> getAttributes() {
+        return getFields();
+    }
+
+    /**
+     * Returns the dictionary of all (<var>name</var>, <var>value</var>) pairs in this record.
+     * This method returns a view which will delegate all {@code get} and {@code put} operations to
+     * the {@link #locate(MemberName)} and {@link #set(MemberName, Object)} methods respectively.
+     *
+     * @return the dictionary of all (<var>name</var>, <var>value</var>) pairs in this record.
+     *
+     * @since 1.1
+     */
     public Map<MemberName, Object> getFields() {
         if (values == null) {                         // Should never be null, except temporarily at XML unmarshalling time.
             return Map.of();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordSchema.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordSchema.java
index 8b29bd6..f556a04 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordSchema.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordSchema.java
@@ -39,9 +39,6 @@
 import org.apache.sis.converter.SurjectiveConverter;
 import org.apache.sis.util.privy.Strings;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-
 
 /**
  * A collection of record types in a given namespace.
@@ -87,14 +84,13 @@
     /**
      * The factory to use for creating names.
      * This is the factory given at construction time.
+     *
+     * <div class="warning"><b>Upcoming API change</b> — generalization<br>
+     * This field type will be changed to the {@link NameFactory} interface when that interface
+     * will provide a {@code createMemberName(…)} method (tentatively in GeoAPI 3.1).
+     * </div>
      */
-    protected final NameFactory nameFactory;
-
-    /**
-     * The helper class to use for mapping Java classes to {@code TypeName} instances, or {@code null} if not needed.
-     * This helper class is needed only if {@link #nameFactory} is not an instance of {@link DefaultNameFactory}.
-     */
-    private final TypeNames typeFactory;
+    protected final DefaultNameFactory nameFactory;
 
     /**
      * The namespace of {@link RecordType} to be created by this class.
@@ -126,17 +122,21 @@
     /**
      * Creates a new schema of the given name.
      *
+     * <div class="warning"><b>Upcoming API change</b> — generalization<br>
+     * This type of the first argument will be changed to the {@link NameFactory} interface when
+     * that interface will provide a {@code createMemberName(…)} method (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param nameFactory  the factory to use for creating names, or {@code null} for the default factory.
      * @param parent       the parent namespace, or {@code null} if none.
      * @param schemaName   the name of the new schema.
      */
-    public DefaultRecordSchema(NameFactory nameFactory, final NameSpace parent, final CharSequence schemaName) {
+    public DefaultRecordSchema(DefaultNameFactory nameFactory, final NameSpace parent, final CharSequence schemaName) {
         ArgumentChecks.ensureNonNull("schemaName", schemaName);
         if (nameFactory == null) {
             nameFactory = DefaultNameFactory.provider();
         }
         this.nameFactory    = nameFactory;
-        this.typeFactory    = (nameFactory instanceof DefaultNameFactory) ? null : new TypeNames(nameFactory);
         this.namespace      = nameFactory.createNameSpace(nameFactory.createLocalName(parent, schemaName), null);
         this.description    = new WeakValueHashMap<>(TypeName.class);
         this.attributeTypes = new ConcurrentHashMap<>();
@@ -192,7 +192,7 @@
          * If a record type already exists for the given name, verify that it contains the same fields.
          */
         final Iterator<Map.Entry<CharSequence,Class<?>>> it1 = fields.entrySet().iterator();
-        final Iterator<Map.Entry<MemberName,Type>> it2 = record.getFieldTypes().entrySet().iterator();
+        final Iterator<Map.Entry<MemberName,Type>> it2 = record.getMemberTypes().entrySet().iterator();
         boolean hasNext;
         while ((hasNext = it1.hasNext()) == it2.hasNext()) {
             if (!hasNext) {
@@ -203,7 +203,7 @@
             if (!e2.getKey().tip().toString().equals(e1.toString())) {
                 break;                                                  // Member names differ.
             }
-            if (!((AttributeType) e2.getValue()).getValueClass().equals(e1.getValue())) {
+            if (!((SimpleAttributeType) e2.getValue()).getValueClass().equals(e1.getValue())) {
                 break;                                                  // Value classes differ.
             }
         }
@@ -227,12 +227,7 @@
             if (valueClass == Void.TYPE) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "valueClass", "void"));
             }
-            final TypeName name;
-            if (nameFactory instanceof DefaultNameFactory) {
-                name = ((DefaultNameFactory) nameFactory).toTypeName(valueClass);
-            } else {
-                name = typeFactory.toTypeName(nameFactory, valueClass);
-            }
+            final TypeName name = nameFactory.toTypeName(valueClass);
             type = new SimpleAttributeType<>(name, valueClass);
             final Type old = attributeTypes.putIfAbsent(valueClass, type);
             if (old != null) {      // May happen if the type has been computed concurrently.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
index 69646b3..0078921 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
@@ -44,9 +44,6 @@
 import org.apache.sis.converter.SurjectiveConverter;
 import org.apache.sis.metadata.internal.RecordSchemaSIS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.NameFactory;
-
 
 /**
  * An immutable definition of the type of a {@linkplain DefaultRecord record}.
@@ -98,7 +95,7 @@
      *
      * @see #getContainer()
      */
-    @SuppressWarnings({"serial", "deprecation"})
+    @SuppressWarnings("serial")
     private final RecordSchema container;
 
     /**
@@ -113,11 +110,11 @@
      *
      * @param other  the {@code RecordType} to copy.
      */
-    @SuppressWarnings({"deprecation", "this-escape"})
+    @SuppressWarnings("this-escape")
     public DefaultRecordType(final RecordType other) {
         typeName   = other.getTypeName();
         container  = other.getContainer();
-        fieldTypes = computeTransientFields(other.getFieldTypes());
+        fieldTypes = computeTransientFields(other.getMemberTypes());
     }
 
     /**
@@ -196,7 +193,7 @@
      */
     @Deprecated(since = "1.5", forRemoval = true)
     DefaultRecordType(final TypeName typeName, final RecordSchema container,
-            final Map<? extends CharSequence, ? extends Type> fields, final NameFactory nameFactory)
+            final Map<? extends CharSequence, ? extends Type> fields, final DefaultNameFactory nameFactory)
     {
         this.typeName  = typeName;
         this.container = container;
@@ -333,8 +330,28 @@
      * </div>
      *
      * @return the dictionary of (<var>name</var>, <var>type</var>) pairs, or an empty map if none.
+     *
+     * @deprecated Renamed {@link #getFieldTypes()} for consistency with the 2015 revision of ISO 19103 standard.
      */
     @Override
+    @Deprecated
+    public Map<MemberName,Type> getMemberTypes() {
+        return getFieldTypes();
+    }
+
+    /**
+     * Returns the dictionary of all (<var>name</var>, <var>type</var>) pairs in this record type.
+     * The returned map is unmodifiable.
+     *
+     * <div class="note"><b>Comparison with Java reflection:</b>
+     * If we think about this {@code RecordType} as equivalent to a {@code Class} instance, then
+     * this method can be though as the related to the Java {@link Class#getFields()} method.
+     * </div>
+     *
+     * @return the dictionary of (<var>name</var>, <var>type</var>) pairs, or an empty map if none.
+     *
+     * @since 1.1
+     */
     public Map<MemberName,Type> getFieldTypes() {
         return ObjectConverters.derivedValues(fieldIndices(), MemberName.class, new SurjectiveConverter<Integer,Type>() {
             @Override public Class<Integer> getSourceClass() {return Integer.class;}
@@ -404,7 +421,7 @@
      */
     @Override
     public boolean isInstance(final Record record) {
-        return (record != null) && getMembers().containsAll(record.getFields().keySet());
+        return (record != null) && getMembers().containsAll(record.getAttributes().keySet());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultTypeName.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultTypeName.java
index 365d02d..f838c7b 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultTypeName.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultTypeName.java
@@ -249,7 +249,7 @@
         if (object == null || object instanceof DefaultTypeName) {
             return (DefaultTypeName) object;
         }
-        return new DefaultTypeName(object.scope(), object.toInternationalString(), object.toJavaType().orElse(null));
+        return new DefaultTypeName(object.scope(), object.toInternationalString(), null);
     }
 
     /**
@@ -262,7 +262,6 @@
      *
      * @since 1.3
      */
-    @Override
     public Optional<Type> toJavaType() {
         return Optional.ofNullable(javaType);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Names.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Names.java
index 80709bb..79eda67 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Names.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Names.java
@@ -332,7 +332,7 @@
     {
         ensureNonNull("localPart", localPart);
         ensureNonNull("attributeType", attributeType);
-        final NameFactory factory = DefaultNameFactory.provider();
+        final DefaultNameFactory factory = DefaultNameFactory.provider();
         return factory.createMemberName(createNameSpace(factory, namespace, separator), localPart, attributeType);
     }
 
@@ -421,9 +421,11 @@
         if (type == null) {
             return null;
         }
-        final Type t = type.toJavaType().orElse(null);
-        if (t instanceof Class<?>) {
-            return (Class<?>) t;
+        if (type instanceof DefaultTypeName) {
+            final Type t = ((DefaultTypeName) type).toJavaType().orElse(null);
+            if (t instanceof Class<?>) {
+                return (Class<?>) t;
+            }
         }
         final Class<?> c;
         try {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/RecordDefinition.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/RecordDefinition.java
index 2cc1241..29ea763 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/RecordDefinition.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/RecordDefinition.java
@@ -31,8 +31,8 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import org.apache.sis.pending.jdk.JDK19;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import org.apache.sis.metadata.simple.SimpleAttributeType;
 
 
 /**
@@ -76,7 +76,7 @@
          */
         Adapter(final RecordType recordType) {
             this.recordType = recordType;
-            computeTransientFields(recordType.getFieldTypes());
+            computeTransientFields(recordType.getMemberTypes());
         }
 
         /**
@@ -88,7 +88,7 @@
          */
         private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
             in.defaultReadObject();
-            computeTransientFields(recordType.getFieldTypes());
+            computeTransientFields(recordType.getMemberTypes());
         }
 
         /**
@@ -151,8 +151,8 @@
         int i = 0;
         for (final Map.Entry<? extends MemberName, ? extends Type> entry : fieldTypes.entrySet()) {
             final Type type = entry.getValue();
-            if (type instanceof AttributeType) {
-                final Class<?> c = ((AttributeType) type).getValueClass();
+            if (type instanceof SimpleAttributeType) {
+                final Class<?> c = ((SimpleAttributeType) type).getValueClass();
                 if (c != Object.class) {
                     if (valueClasses == null) {
                         valueClasses = new Class<?>[size];
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java
index fabfd4b..f92b64a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java
@@ -49,9 +49,8 @@
 import org.apache.sis.pending.jdk.JDK19;
 import org.apache.sis.system.Modules;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.annotation.ResourceBundles;
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import java.io.InputStream;
 
 
 /**
@@ -60,14 +59,14 @@
  *
  * <ul>
  *   <li>Methods for fetching the ISO name or description of a code list:<ul>
- *     <li>{@link #getStandardName(Class)}            for ISO name</li>
- *     <li>{@link #getListName(ControlledVocabulary)} for ISO name</li>
- *     <li>{@link #getDescription(Class)}             for a description</li>
+ *     <li>{@link #getStandardName(Class)}   for ISO name</li>
+ *     <li>{@link #getListName(CodeList)}    for ISO name</li>
+ *     <li>{@link #getDescription(Class)}    for a description</li>
  *   </ul></li>
  *   <li>Methods for fetching the ISO name or description of a code value:<ul>
- *     <li>{@link #getCodeName(ControlledVocabulary)}    for ISO name,</li>
- *     <li>{@link #getCodeTitle(ControlledVocabulary)}   for a label or title</li>
- *     <li>{@link #getDescription(ControlledVocabulary)} for a more verbose description</li>
+ *     <li>{@link #getCodeName(CodeList)}    for ISO name,</li>
+ *     <li>{@link #getCodeTitle(CodeList)}   for a label or title</li>
+ *     <li>{@link #getDescription(CodeList)} for a more verbose description</li>
  *   </ul></li>
  *   <li>Methods for fetching an instance from a name (converse of above {@code get} methods):<ul>
  *     <li>{@link #forCodeName(Class, String, boolean)}</li>
@@ -80,7 +79,7 @@
  * by a {@link CodeList} value. Such substitution can be done with:
  *
  * <ul>
- *   <li>{@link #getCodeTitle(ControlledVocabulary)} for getting the {@link InternationalString} instance
+ *   <li>{@link #getCodeTitle(CodeList)} for getting the {@link InternationalString} instance
  *       to store in a metadata property.</li>
  *   <li>{@link #forCodeTitle(CharSequence)} for retrieving the {@link CodeList} previously stored as an
  *       {@code InternationalString}.</li>
@@ -139,7 +138,7 @@
      *   <li><code>getStandardName({@linkplain org.opengis.metadata.citation.Citation}.class)</code>
      *       (an interface) returns {@code "CI_Citation"}.</li>
      *   <li><code>getStandardName({@linkplain org.opengis.referencing.cs.AxisDirection}.class)</code>
-     *       (a code list) returns {@code "AxisDirection"}.</li>
+     *       (a code list) returns {@code "CS_AxisDirection"}.</li>
      * </ul>
      *
      * <h4>Implementation note</h4>
@@ -181,7 +180,7 @@
      * <h4>Examples</h4>
      * <ul>
      *   <li>{@code getListName(ParameterDirection.IN_OUT)}      returns {@code "SV_ParameterDirection"}.</li>
-     *   <li>{@code getListName(AxisDirection.NORTH)}            returns {@code "AxisDirection"}.</li>
+     *   <li>{@code getListName(AxisDirection.NORTH)}            returns {@code "CS_AxisDirection"}.</li>
      *   <li>{@code getListName(TopicCategory.INLAND_WATERS)}    returns {@code "MD_TopicCategoryCode"}.</li>
      *   <li>{@code getListName(ImagingCondition.BLURRED_IMAGE)} returns {@code "MD_ImagingConditionCode"}.</li>
      * </ul>
@@ -189,11 +188,11 @@
      * @param  code  the code for which to get the class name, or {@code null}.
      * @return the ISO (preferred) or Java (fallback) class name, or {@code null} if the given code is null.
      */
-    public static String getListName(final ControlledVocabulary code) {
+    public static String getListName(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
-        final Class<?> type = (code instanceof Enum<?>) ? ((Enum<?>) code).getDeclaringClass() : code.getClass();
+        final Class<?> type = code.getClass();
         final String id = getStandardName(type);
         return (id != null) ? id : type.getSimpleName();
     }
@@ -213,12 +212,12 @@
      * @param  code  the code for which to get the name, or {@code null}.
      * @return the UML identifiers or programmatic name for the given code, or {@code null} if the given code is null.
      *
-     * @see #getCodeLabel(ControlledVocabulary)
-     * @see #getCodeTitle(ControlledVocabulary)
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getCodeLabel(CodeList)
+     * @see #getCodeTitle(CodeList)
+     * @see #getDescription(CodeList)
      * @see #forCodeName(Class, String, boolean)
      */
-    public static String getCodeName(final ControlledVocabulary code) {
+    public static String getCodeName(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
@@ -230,7 +229,7 @@
      * Returns a unlocalized title for the given enumeration or code list value.
      * This method builds a title using heuristics rules, which should give reasonable
      * results without the need of resource bundles. For better results, consider using
-     * {@link #getCodeTitle(ControlledVocabulary)} instead.
+     * {@link #getCodeTitle(CodeList)} instead.
      *
      * <p>The current heuristic implementation iterates over {@linkplain CodeList#names() all code names},
      * selects the longest one excluding the {@linkplain CodeList#name() field name} if possible, then
@@ -246,11 +245,11 @@
      * @param  code  the code from which to get a title, or {@code null}.
      * @return a unlocalized title for the given code, or {@code null} if the given code is null.
      *
-     * @see #getCodeName(ControlledVocabulary)
-     * @see #getCodeTitle(ControlledVocabulary)
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getCodeName(CodeList)
+     * @see #getCodeTitle(CodeList)
+     * @see #getDescription(CodeList)
      */
-    public static String getCodeLabel(final ControlledVocabulary code) {
+    public static String getCodeLabel(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
@@ -269,7 +268,7 @@
 
     /**
      * Returns the title of the given enumeration or code list value. Title are usually much shorter than descriptions.
-     * English titles are often the same as the {@linkplain #getCodeLabel(ControlledVocabulary) code labels}.
+     * English titles are often the same as the {@linkplain #getCodeLabel(CodeList) code labels}.
      *
      * <p>The code or enumeration value given in argument to this method can be retrieved from the returned title
      * with the {@link #forCodeTitle(CharSequence)} method. See <cite>Substituting a free text by a code list</cite>
@@ -278,10 +277,10 @@
      * @param  code  the code for which to get the title, or {@code null}.
      * @return the title, or {@code null} if the given code is null.
      *
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getDescription(CodeList)
      * @see #forCodeTitle(CharSequence)
      */
-    public static InternationalString getCodeTitle(final ControlledVocabulary code) {
+    public static InternationalString getCodeTitle(final CodeList<?> code) {
         return (code != null) ? new CodeTitle(code) : null;
     }
 
@@ -293,11 +292,11 @@
      * @param  code  the code for which to get the localized description, or {@code null}.
      * @return the description, or {@code null} if none or if the given code is null.
      *
-     * @see #getCodeTitle(ControlledVocabulary)
+     * @see #getCodeTitle(CodeList)
      * @see #getDescription(Class)
      */
     @OptionalCandidate
-    public static InternationalString getDescription(final ControlledVocabulary code) {
+    public static InternationalString getDescription(final CodeList<?> code) {
         if (code != null && hasResources(code.getClass())) {
             return new Description(Description.resourceKey(code));
         }
@@ -311,7 +310,7 @@
      * @param  type  the GeoAPI interface or code list from which to get the description, or {@code null}.
      * @return the description, or {@code null} if none or if the given type is {@code null}.
      *
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getDescription(CodeList)
      */
     @OptionalCandidate
     public static InternationalString getDescription(final Class<?> type) {
@@ -354,6 +353,11 @@
         private static final long serialVersionUID = -6202647167398898834L;
 
         /**
+         * The class loader to use for fetching GeoAPI resources.
+         */
+        private static final ClassLoader CLASSLOADER = Types.class.getClassLoader();
+
+        /**
          * Creates a new international string from the specified resource bundle and key.
          *
          * @param key  the key for the resource to fetch.
@@ -367,7 +371,7 @@
          */
         @Override
         protected ResourceBundle getBundle(final Locale locale) {
-            return ResourceBundles.descriptions(locale);
+            return ResourceBundle.getBundle("org.opengis.metadata.Descriptions", locale, CLASSLOADER);
         }
 
         /**
@@ -394,7 +398,7 @@
         /**
          * Returns the resource key for the given code list.
          */
-        static String resourceKey(final ControlledVocabulary code) {
+        static String resourceKey(final CodeList<?> code) {
             String key = getCodeName(code);
             if (key.indexOf(SEPARATOR) < 0) {
                 key = getListName(code) + SEPARATOR + key;
@@ -420,15 +424,14 @@
         /**
          * The code list for which to create a title.
          */
-        @SuppressWarnings("serial")         // Enum and CodeList implementations are serializable.
-        final ControlledVocabulary code;
+        final CodeList<?> code;
 
         /**
          * Creates a new international string for the given code list element.
          *
          * @param  code  the code list for which to create a title.
          */
-        CodeTitle(final ControlledVocabulary code) {
+        CodeTitle(final CodeList<?> code) {
             super(resourceKey(code));
             this.code = code;
         }
@@ -438,7 +441,7 @@
          */
         @Override
         protected ResourceBundle getBundle(final Locale locale) {
-            return ResourceBundles.codeLists(locale);
+            return ResourceBundle.getBundle(CodeLists.RESOURCES, locale);
         }
 
         /**
@@ -461,34 +464,22 @@
     }
 
     /**
-     * Returns all known values for the given type of code list or enumeration.
+     * Returns all known values for the given type of code list.
      * Note that the size of the returned array may growth between different invocations of this method,
      * since users can add their own codes to an existing list.
      *
-     * <h4>Performance note</h4>
-     * This method works with both {@link Enum} and {@link CodeList}. However if the type is known to be
-     * an {@code Enum}, then the standard {@link Class#getEnumConstants()} method is more efficient.
-     *
      * @param  <T>       the compile-time type given as the {@code codeType} parameter.
-     * @param  codeType  the type of code list or enumeration.
-     * @return the list of values for the given code list or enumeration, or an empty array if none.
+     * @param  codeType  the type of code list.
+     * @return the list of values for the given code list, or an empty array if none.
      *
      * @see Class#getEnumConstants()
      *
      * @deprecated This method depends on reflection, which is restricted in the context of Java Module System.
      *             Instead, {@code T.values()} static methods should be invoked directly as much as possible.
      */
-    @SuppressWarnings("unchecked")
     @Deprecated(since="1.5", forRemoval=true)
-    public static <T extends ControlledVocabulary> T[] getCodeValues(final Class<T> codeType) {
-        if (CodeList.class.isAssignableFrom(codeType)) {
-            return (T[]) CodeList.values((Class) codeType);
-        }
-        final T[] codes = codeType.getEnumConstants();
-        if (codes != null) {
-            return codes;
-        }
-        return (T[]) Array.newInstance(codeType, 0);
+    public static <T extends CodeList<?>> T[] getCodeValues(final Class<T> codeType) {
+        return CodeLists.values(codeType);
     }
 
     /**
@@ -498,8 +489,8 @@
      *
      * <h4>Examples</h4>
      * <ul>
-     *   <li>{@code forStandardName("CI_Citation")}   returns <code>{@linkplain org.opengis.metadata.citation.Citation}.class</code></li>
-     *   <li>{@code forStandardName("AxisDirection")} returns <code>{@linkplain org.opengis.referencing.cs.AxisDirection}.class</code></li>
+     *   <li>{@code forStandardName("CI_Citation")}      returns <code>{@linkplain org.opengis.metadata.citation.Citation}.class</code></li>
+     *   <li>{@code forStandardName("CS_AxisDirection")} returns <code>{@linkplain org.opengis.referencing.cs.AxisDirection}.class</code></li>
      * </ul>
      *
      * <h4>Implementation note</h4>
@@ -528,9 +519,15 @@
             return null;
         }
         if (typeForNames == null) {
-            final Properties props;
+            final Class<Types> c = Types.class;
+            final InputStream in = c.getResourceAsStream("class-index.properties");
+            if (in == null) {
+                throw new MissingResourceException("class-index.properties", c.getName(), identifier);
+            }
+            final Properties props = new Properties();
             try {
-                props = ResourceBundles.classIndex();
+                props.load(in);
+                in.close();
             } catch (IOException e) {
                 throw new BackingStoreException(e);
             }
@@ -682,7 +679,7 @@
      * The current implementation performs the following choice:
      *
      * <ul>
-     *   <li>If the given title is a value returned by a previous call to {@link #getCodeTitle(ControlledVocabulary)},
+     *   <li>If the given title is a value returned by a previous call to {@link #getCodeTitle(CodeList)},
      *       returns the code or enumeration value used for creating that title.</li>
      *   <li>Otherwise returns {@code null}.</li>
      * </ul>
@@ -690,11 +687,11 @@
      * @param  title  the title for which to get a code or enumeration value, or {@code null}.
      * @return the code or enumeration value associated with the given title, or {@code null}.
      *
-     * @see #getCodeTitle(ControlledVocabulary)
+     * @see #getCodeTitle(CodeList)
      *
      * @since 0.7
      */
-    public static ControlledVocabulary forCodeTitle(final CharSequence title) {
+    public static CodeList<?> forCodeTitle(final CharSequence title) {
         return (title instanceof CodeTitle) ? ((CodeTitle) title).code : null;
     }
 
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/class-index.properties b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/class-index.properties
new file mode 100644
index 0000000..ac30fa4
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/class-index.properties
@@ -0,0 +1,229 @@
+#
+# This is an automatically generated file. The same content is provided by GeoAPI 3.1
+# in the org.opengis.annotation package. Since this file does not exist in GeoAPI 3.0,
+# the Apache SIS branch for GeoAPI 3.0 maintains this copy (provided by the original
+# author).
+#
+CC_ConcatenatedOperation=org.opengis.referencing.operation.ConcatenatedOperation
+CC_Conversion=org.opengis.referencing.operation.Conversion
+CC_CoordinateOperation=org.opengis.referencing.operation.CoordinateOperation
+CC_Formula=org.opengis.referencing.operation.Formula
+CC_GeneralOperationParameter=org.opengis.parameter.GeneralParameterDescriptor
+CC_GeneralParameterValue=org.opengis.parameter.GeneralParameterValue
+CC_OperationMethod=org.opengis.referencing.operation.OperationMethod
+CC_OperationParameter=org.opengis.parameter.ParameterDescriptor
+CC_OperationParameterGroup=org.opengis.parameter.ParameterDescriptorGroup
+CC_ParameterValue=org.opengis.parameter.ParameterValue
+CC_ParameterValueGroup=org.opengis.parameter.ParameterValueGroup
+CC_PassThroughOperation=org.opengis.referencing.operation.PassThroughOperation
+CC_SingleOperation=org.opengis.referencing.operation.SingleOperation
+CC_Transformation=org.opengis.referencing.operation.Transformation
+CD_Datum=org.opengis.referencing.datum.Datum
+CD_Ellipsoid=org.opengis.referencing.datum.Ellipsoid
+CD_EngineeringDatum=org.opengis.referencing.datum.EngineeringDatum
+CD_GeodeticDatum=org.opengis.referencing.datum.GeodeticDatum
+CD_ImageDatum=org.opengis.referencing.datum.ImageDatum
+CD_PixelInCell=org.opengis.referencing.datum.PixelInCell
+CD_PrimeMeridian=org.opengis.referencing.datum.PrimeMeridian
+CD_TemporalDatum=org.opengis.referencing.datum.TemporalDatum
+CD_VerticalDatum=org.opengis.referencing.datum.VerticalDatum
+CD_VerticalDatumType=org.opengis.referencing.datum.VerticalDatumType
+CI_Address=org.opengis.metadata.citation.Address
+CI_Citation=org.opengis.metadata.citation.Citation
+CI_Contact=org.opengis.metadata.citation.Contact
+CI_Date=org.opengis.metadata.citation.CitationDate
+CI_DateTypeCode=org.opengis.metadata.citation.DateType
+CI_OnLineFunctionCode=org.opengis.metadata.citation.OnLineFunction
+CI_OnlineResource=org.opengis.metadata.citation.OnlineResource
+CI_PresentationFormCode=org.opengis.metadata.citation.PresentationForm
+CI_ResponsibleParty=org.opengis.metadata.citation.ResponsibleParty
+CI_RoleCode=org.opengis.metadata.citation.Role
+CI_Series=org.opengis.metadata.citation.Series
+CI_Telephone=org.opengis.metadata.citation.Telephone
+CS_AffineCS=org.opengis.referencing.cs.AffineCS
+CS_AxisDirection=org.opengis.referencing.cs.AxisDirection
+CS_CartesianCS=org.opengis.referencing.cs.CartesianCS
+CS_CoordinateSystem=org.opengis.referencing.cs.CoordinateSystem
+CS_CoordinateSystemAxis=org.opengis.referencing.cs.CoordinateSystemAxis
+CS_CylindricalCS=org.opengis.referencing.cs.CylindricalCS
+CS_EllipsoidalCS=org.opengis.referencing.cs.EllipsoidalCS
+CS_LinearCS=org.opengis.referencing.cs.LinearCS
+CS_PolarCS=org.opengis.referencing.cs.PolarCS
+CS_RangeMeaning=org.opengis.referencing.cs.RangeMeaning
+CS_SphericalCS=org.opengis.referencing.cs.SphericalCS
+CS_TimeCS=org.opengis.referencing.cs.TimeCS
+CS_UserDefinedCS=org.opengis.referencing.cs.UserDefinedCS
+CS_VerticalCS=org.opengis.referencing.cs.VerticalCS
+DQ_AbsoluteExternalPositionalAccuracy=org.opengis.metadata.quality.AbsoluteExternalPositionalAccuracy
+DQ_AccuracyOfATimeMeasurement=org.opengis.metadata.quality.AccuracyOfATimeMeasurement
+DQ_Completeness=org.opengis.metadata.quality.Completeness
+DQ_CompletenessCommission=org.opengis.metadata.quality.CompletenessCommission
+DQ_CompletenessOmission=org.opengis.metadata.quality.CompletenessOmission
+DQ_ConceptualConsistency=org.opengis.metadata.quality.ConceptualConsistency
+DQ_ConformanceResult=org.opengis.metadata.quality.ConformanceResult
+DQ_DataQuality=org.opengis.metadata.quality.DataQuality
+DQ_DomainConsistency=org.opengis.metadata.quality.DomainConsistency
+DQ_Element=org.opengis.metadata.quality.Element
+DQ_EvaluationMethodTypeCode=org.opengis.metadata.quality.EvaluationMethodType
+DQ_FormatConsistency=org.opengis.metadata.quality.FormatConsistency
+DQ_GriddedDataPositionalAccuracy=org.opengis.metadata.quality.GriddedDataPositionalAccuracy
+DQ_LogicalConsistency=org.opengis.metadata.quality.LogicalConsistency
+DQ_NonQuantitativeAttributeAccuracy=org.opengis.metadata.quality.NonQuantitativeAttributeAccuracy
+DQ_PositionalAccuracy=org.opengis.metadata.quality.PositionalAccuracy
+DQ_QuantitativeAttributeAccuracy=org.opengis.metadata.quality.QuantitativeAttributeAccuracy
+DQ_QuantitativeResult=org.opengis.metadata.quality.QuantitativeResult
+DQ_RelativeInternalPositionalAccuracy=org.opengis.metadata.quality.RelativeInternalPositionalAccuracy
+DQ_Result=org.opengis.metadata.quality.Result
+DQ_Scope=org.opengis.metadata.quality.Scope
+DQ_TemporalAccuracy=org.opengis.metadata.quality.TemporalAccuracy
+DQ_TemporalConsistency=org.opengis.metadata.quality.TemporalConsistency
+DQ_TemporalValidity=org.opengis.metadata.quality.TemporalValidity
+DQ_ThematicAccuracy=org.opengis.metadata.quality.ThematicAccuracy
+DQ_ThematicClassificationCorrectness=org.opengis.metadata.quality.ThematicClassificationCorrectness
+DQ_TopologicalConsistency=org.opengis.metadata.quality.TopologicalConsistency
+DS_AssociationTypeCode=org.opengis.metadata.identification.AssociationType
+DS_InitiativeTypeCode=org.opengis.metadata.identification.InitiativeType
+EX_BoundingPolygon=org.opengis.metadata.extent.BoundingPolygon
+EX_Extent=org.opengis.metadata.extent.Extent
+EX_GeographicBoundingBox=org.opengis.metadata.extent.GeographicBoundingBox
+EX_GeographicDescription=org.opengis.metadata.extent.GeographicDescription
+EX_GeographicExtent=org.opengis.metadata.extent.GeographicExtent
+EX_SpatialTemporalExtent=org.opengis.metadata.extent.SpatialTemporalExtent
+EX_TemporalExtent=org.opengis.metadata.extent.TemporalExtent
+EX_VerticalExtent=org.opengis.metadata.extent.VerticalExtent
+IO_IdentifiedObject=org.opengis.referencing.IdentifiedObject
+LE_Algorithm=org.opengis.metadata.lineage.Algorithm
+LE_NominalResolution=org.opengis.metadata.lineage.NominalResolution
+LE_ProcessStep=org.opengis.metadata.lineage.ProcessStep
+LE_ProcessStepReport=org.opengis.metadata.lineage.ProcessStepReport
+LE_Processing=org.opengis.metadata.lineage.Processing
+LE_Source=org.opengis.metadata.lineage.Source
+LI_Lineage=org.opengis.metadata.lineage.Lineage
+LI_ProcessStep=org.opengis.metadata.lineage.ProcessStep
+LI_Source=org.opengis.metadata.lineage.Source
+MD_AggregateInformation=org.opengis.metadata.identification.AggregateInformation
+MD_ApplicationSchemaInformation=org.opengis.metadata.ApplicationSchemaInformation
+MD_Band=org.opengis.metadata.content.Band
+MD_BrowseGraphic=org.opengis.metadata.identification.BrowseGraphic
+MD_CellGeometryCode=org.opengis.metadata.spatial.CellGeometry
+MD_CharacterSetCode=org.opengis.metadata.identification.CharacterSet
+MD_ClassificationCode=org.opengis.metadata.constraint.Classification
+MD_Constraints=org.opengis.metadata.constraint.Constraints
+MD_ContentInformation=org.opengis.metadata.content.ContentInformation
+MD_CoverageContentTypeCode=org.opengis.metadata.content.CoverageContentType
+MD_CoverageDescription=org.opengis.metadata.content.CoverageDescription
+MD_DataIdentification=org.opengis.metadata.identification.DataIdentification
+MD_DatatypeCode=org.opengis.metadata.Datatype
+MD_DigitalTransferOptions=org.opengis.metadata.distribution.DigitalTransferOptions
+MD_Dimension=org.opengis.metadata.spatial.Dimension
+MD_DimensionNameTypeCode=org.opengis.metadata.spatial.DimensionNameType
+MD_Distribution=org.opengis.metadata.distribution.Distribution
+MD_Distributor=org.opengis.metadata.distribution.Distributor
+MD_ExtendedElementInformation=org.opengis.metadata.ExtendedElementInformation
+MD_FeatureCatalogueDescription=org.opengis.metadata.content.FeatureCatalogueDescription
+MD_FeatureTypeList=org.opengis.metadata.FeatureTypeList
+MD_Format=org.opengis.metadata.distribution.Format
+MD_GeometricObjectTypeCode=org.opengis.metadata.spatial.GeometricObjectType
+MD_GeometricObjects=org.opengis.metadata.spatial.GeometricObjects
+MD_Georectified=org.opengis.metadata.spatial.Georectified
+MD_Georeferenceable=org.opengis.metadata.spatial.Georeferenceable
+MD_GridSpatialRepresentation=org.opengis.metadata.spatial.GridSpatialRepresentation
+MD_Identification=org.opengis.metadata.identification.Identification
+MD_Identifier=org.opengis.metadata.Identifier
+MD_ImageDescription=org.opengis.metadata.content.ImageDescription
+MD_ImagingConditionCode=org.opengis.metadata.content.ImagingCondition
+MD_KeywordTypeCode=org.opengis.metadata.identification.KeywordType
+MD_Keywords=org.opengis.metadata.identification.Keywords
+MD_LegalConstraints=org.opengis.metadata.constraint.LegalConstraints
+MD_MaintenanceFrequencyCode=org.opengis.metadata.maintenance.MaintenanceFrequency
+MD_MaintenanceInformation=org.opengis.metadata.maintenance.MaintenanceInformation
+MD_Medium=org.opengis.metadata.distribution.Medium
+MD_MediumFormatCode=org.opengis.metadata.distribution.MediumFormat
+MD_MediumNameCode=org.opengis.metadata.distribution.MediumName
+MD_Metadata=org.opengis.metadata.Metadata
+MD_MetadataExtensionInformation=org.opengis.metadata.MetadataExtensionInformation
+MD_ObligationCode=org.opengis.metadata.Obligation
+MD_PixelOrientationCode=org.opengis.metadata.spatial.PixelOrientation
+MD_PortrayalCatalogueReference=org.opengis.metadata.PortrayalCatalogueReference
+MD_ProgressCode=org.opengis.metadata.identification.Progress
+MD_RangeDimension=org.opengis.metadata.content.RangeDimension
+MD_RepresentativeFraction=org.opengis.metadata.identification.RepresentativeFraction
+MD_Resolution=org.opengis.metadata.identification.Resolution
+MD_RestrictionCode=org.opengis.metadata.constraint.Restriction
+MD_ScopeCode=org.opengis.metadata.maintenance.ScopeCode
+MD_ScopeDescription=org.opengis.metadata.maintenance.ScopeDescription
+MD_SecurityConstraints=org.opengis.metadata.constraint.SecurityConstraints
+MD_SpatialRepresentation=org.opengis.metadata.spatial.SpatialRepresentation
+MD_SpatialRepresentationTypeCode=org.opengis.metadata.spatial.SpatialRepresentationType
+MD_StandardOrderProcess=org.opengis.metadata.distribution.StandardOrderProcess
+MD_TopicCategoryCode=org.opengis.metadata.identification.TopicCategory
+MD_TopologyLevelCode=org.opengis.metadata.spatial.TopologyLevel
+MD_Usage=org.opengis.metadata.identification.Usage
+MD_VectorSpatialRepresentation=org.opengis.metadata.spatial.VectorSpatialRepresentation
+MI_AcquisitionInformation=org.opengis.metadata.acquisition.AcquisitionInformation
+MI_Band=org.opengis.metadata.content.Band
+MI_BandDefinition=org.opengis.metadata.content.BandDefinition
+MI_ContextCode=org.opengis.metadata.acquisition.Context
+MI_CoverageDescription=org.opengis.metadata.content.CoverageDescription
+MI_EnvironmentalRecord=org.opengis.metadata.acquisition.EnvironmentalRecord
+MI_Event=org.opengis.metadata.acquisition.Event
+MI_GCP=org.opengis.metadata.spatial.GCP
+MI_GCPCollection=org.opengis.metadata.spatial.GCPCollection
+MI_GeolocationInformation=org.opengis.metadata.spatial.GeolocationInformation
+MI_GeometryTypeCode=org.opengis.metadata.acquisition.GeometryType
+MI_Georectified=org.opengis.metadata.spatial.Georectified
+MI_Georeferenceable=org.opengis.metadata.spatial.Georeferenceable
+MI_ImageDescription=org.opengis.metadata.content.ImageDescription
+MI_Instrument=org.opengis.metadata.acquisition.Instrument
+MI_Metadata=org.opengis.metadata.Metadata
+MI_Objective=org.opengis.metadata.acquisition.Objective
+MI_ObjectiveTypeCode=org.opengis.metadata.acquisition.ObjectiveType
+MI_Operation=org.opengis.metadata.acquisition.Operation
+MI_OperationTypeCode=org.opengis.metadata.acquisition.OperationType
+MI_Plan=org.opengis.metadata.acquisition.Plan
+MI_Platform=org.opengis.metadata.acquisition.Platform
+MI_PlatformPass=org.opengis.metadata.acquisition.PlatformPass
+MI_PolarizationOrientationCode=org.opengis.metadata.content.PolarizationOrientation
+MI_PriorityCode=org.opengis.metadata.acquisition.Priority
+MI_RangeElementDescription=org.opengis.metadata.content.RangeElementDescription
+MI_RequestedDate=org.opengis.metadata.acquisition.RequestedDate
+MI_Requirement=org.opengis.metadata.acquisition.Requirement
+MI_SequenceCode=org.opengis.metadata.acquisition.Sequence
+MI_TransferFunctionTypeCode=org.opengis.metadata.content.TransferFunctionType
+MI_TriggerCode=org.opengis.metadata.acquisition.Trigger
+QE_CoverageResult=org.opengis.metadata.quality.CoverageResult
+QE_Usability=org.opengis.metadata.quality.Usability
+RS_Identifier=org.opengis.referencing.ReferenceIdentifier
+RS_ReferenceSystem=org.opengis.referencing.ReferenceSystem
+SC_CRS=org.opengis.referencing.crs.CoordinateReferenceSystem
+SC_CompoundCRS=org.opengis.referencing.crs.CompoundCRS
+SC_DerivedCRS=org.opengis.referencing.crs.DerivedCRS
+SC_EngineeringCRS=org.opengis.referencing.crs.EngineeringCRS
+SC_GeneralDerivedCRS=org.opengis.referencing.crs.GeneralDerivedCRS
+SC_GeocentricCRS=org.opengis.referencing.crs.GeocentricCRS
+SC_GeodeticCRS=org.opengis.referencing.crs.GeodeticCRS
+SC_GeographicCRS=org.opengis.referencing.crs.GeographicCRS
+SC_ImageCRS=org.opengis.referencing.crs.ImageCRS
+SC_ProjectedCRS=org.opengis.referencing.crs.ProjectedCRS
+SC_SingleCRS=org.opengis.referencing.crs.SingleCRS
+SC_TemporalCRS=org.opengis.referencing.crs.TemporalCRS
+SC_VerticalCRS=org.opengis.referencing.crs.VerticalCRS
+SV_ServiceIdentification=org.opengis.metadata.identification.ServiceIdentification
+
+#
+# Additional types not yet defined in GeoAPI.
+#
+CI_Party=org.apache.sis.metadata.iso.citation.AbstractParty
+CI_Individual=org.apache.sis.metadata.iso.citation.DefaultIndividual
+CI_Organisation=org.apache.sis.metadata.iso.citation.DefaultOrganisation
+CI_Responsibility=org.apache.sis.metadata.iso.citation.DefaultResponsibility
+CI_TelephoneTypeCode=org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList
+MD_AssociatedResource=org.apache.sis.metadata.iso.identification.DefaultAssociatedResource
+MD_AttributeGroup=org.apache.sis.metadata.iso.content.DefaultAttributeGroup
+MD_FeatureTypeInfo=org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo
+MD_Releasability=org.apache.sis.metadata.iso.constraint.DefaultReleasability
+MD_SampleDimension=org.apache.sis.metadata.iso.content.DefaultSampleDimension
+MD_MetadataScope=org.apache.sis.metadata.iso.DefaultMetadataScope
+SV_CoupledResource=org.apache.sis.metadata.iso.identification.DefaultCoupledResource
+SV_OperationChainMetadata=org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata
+SV_OperationMetadata=org.apache.sis.metadata.iso.identification.DefaultOperationMetadata
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/LegacyCodes.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/LegacyCodes.java
index 63bb8bc..9820b69 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/LegacyCodes.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/LegacyCodes.java
@@ -21,13 +21,8 @@
 import java.util.Locale;
 import org.apache.sis.util.collection.Containers;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Properties;
-import java.io.InputStream;
-import java.io.IOException;
-import org.opengis.metadata.Metadata;
-import org.apache.sis.xml.bind.Context;
-import org.apache.sis.util.logging.Logging;
+// Specific to the main branch:
+import org.opengis.metadata.identification.CharacterSet;
 
 
 /**
@@ -49,19 +44,16 @@
      */
     static final Map<String,String> IANA_TO_LEGACY, LEGACY_TO_IANA;
     static {
-        final Properties codes = new Properties();
-        try (InputStream in = Metadata.class.getResourceAsStream("2003/charset-codes.properties")) {
-            codes.load(in);
-        } catch (IOException e) {
-            Logging.unexpectedException(Context.LOGGER, ValueConverter.class, "toCharset[Code]", e);
-        }
-        final int capacity = Containers.hashMapCapacity(codes.size());
+        final CharacterSet[] codes = CharacterSet.values();
+        final int capacity = Containers.hashMapCapacity(codes.length);
         IANA_TO_LEGACY = new HashMap<>(capacity);
         LEGACY_TO_IANA = new HashMap<>(capacity);
-        for (final Map.Entry<Object,Object> entry : codes.entrySet()) {
-            final String legacy = ((String) entry.getKey()).intern();
-            final String name   = ((String) entry.getValue()).intern();
-            IANA_TO_LEGACY.put(name  .toUpperCase(Locale.US), legacy);      // IANA names are restricted to US-ASCII.
+        for (final CharacterSet code : codes) {
+            final String   legacy = code.identifier().intern();
+            final String[] names  = code.names();
+            String name = names[names.length - 1];
+            if (name.equals("ebcdic")) name = "EBCDIC"; // Missing IANA name in GeoAPI CharacterSet.
+            IANA_TO_LEGACY.put(name  .toUpperCase(Locale.US), legacy); // IANA names are restricted to US-ASCII.
             LEGACY_TO_IANA.put(legacy.toLowerCase(Locale.US), name);
             IANA_TO_LEGACY.put(name, legacy);
             LEGACY_TO_IANA.put(legacy, name);
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/TransformedEvent.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/TransformedEvent.java
index 414e092..3cb10b0 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/TransformedEvent.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/TransformedEvent.java
@@ -126,7 +126,7 @@
     /**
      * Wrapper over a namespace emitted during the reading or writing of an XML document.
      * This wrapper is used for changing the namespace URI. The wrapped {@link #event}
-     * should be a {@link Namespace}, but this class accepts also the {@link Attribute}
+     * should be a {@link Namespace}, but this class accepts also the {@code Attribute}
      * super-type for allowing the {@link Type} attribute to create synthetic namespaces.
      */
     static final class NS extends TransformedEvent<Attribute> implements Namespace {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapEntry.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapEntry.java
index 7997e62..f566d79 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapEntry.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapEntry.java
@@ -21,6 +21,9 @@
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.metadata.iso.citation.Citations;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * An entry in {@link org.apache.sis.xml.IdentifierMap}. This class implements both the {@link AbstractMap.Entry}
@@ -29,7 +32,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class IdentifierMapEntry extends AbstractMap.SimpleEntry<Citation,String> implements Identifier {
+final class IdentifierMapEntry extends AbstractMap.SimpleEntry<Citation,String> implements ReferenceIdentifier {
     /**
      * For cross-version compatibility.
      */
@@ -69,15 +72,26 @@
     }
 
     /**
+     * Returns {@code null} since this class does not hold version information.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Same as the above, but as an immutable entry. We use this implementation when the
      * entry has been created on-the-fly at iteration time rather than being stored in the
      * identifier collection.
      */
-    static final class Immutable extends AbstractMap.SimpleImmutableEntry<Citation,String> implements Identifier {
+    static final class Immutable extends AbstractMap.SimpleImmutableEntry<Citation,String> implements ReferenceIdentifier {
         private static final long serialVersionUID = -6857931598565368465L;
         Immutable(Citation authority, String code) {super(authority, code);}
         @Override public Citation            getAuthority()   {return getKey();}
         @Override public String              getCode()        {return getValue();}
         @Override public String              getCodeSpace()   {return Citations.toCodeSpace(getAuthority());}
+        @Override public String              getVersion()     {return null;}
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/SpecializedIdentifier.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/SpecializedIdentifier.java
index b1f6810..ca2e078 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/SpecializedIdentifier.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/SpecializedIdentifier.java
@@ -31,8 +31,8 @@
 import org.apache.sis.util.privy.CloneAccess;
 import org.apache.sis.metadata.iso.citation.Citations;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -44,7 +44,7 @@
  *
  * @param <T>  the value type, typically {@link XLink}, {@link UUID} or {@link String}.
  */
-public final class SpecializedIdentifier<T> implements Identifier, CloneAccess, Serializable {
+public final class SpecializedIdentifier<T> implements ReferenceIdentifier, CloneAccess, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -95,7 +95,7 @@
      *
      * @see IdentifierMapAdapter#put(Citation, String)
      */
-    static Identifier parse(final Citation authority, final String code) {
+    static ReferenceIdentifier parse(final Citation authority, final String code) {
         if (authority instanceof NonMarshalledAuthority) {
             final int ordinal = ((NonMarshalledAuthority) authority).ordinal;
             switch (ordinal) {
@@ -196,6 +196,16 @@
     }
 
     /**
+     * Returns {@code null} since this class does not hold version information.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this identifier.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListUID.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListUID.java
index e99dd9c..d75499f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListUID.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListUID.java
@@ -26,9 +26,9 @@
 import org.apache.sis.xml.bind.Context;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ISO_NAMESPACE;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
-import org.opengis.annotation.ResourceBundles;
+// Specific to the main branch:
+import java.util.ResourceBundle;
+import org.apache.sis.util.privy.CodeLists;
 
 
 /**
@@ -226,7 +226,7 @@
      * @param context  the current (un)marshalling context, or {@code null} if none.
      * @param code     the code list to wrap.
      */
-    public CodeListUID(final Context context, final ControlledVocabulary code) {
+    public CodeListUID(final Context context, final CodeList<?> code) {
         final String classID = Types.getListName(code);
         final String fieldID = Types.getCodeName(code);
         codeList = schema(context, classID);
@@ -241,7 +241,8 @@
         if (locale != null) {
             final String key = classID + '.' + fieldID;
             try {
-                value = ResourceBundles.codeLists(locale).getString(key);
+                value = ResourceBundle.getBundle(CodeLists.RESOURCES,
+                        locale, CodeList.class.getClassLoader()).getString(key);
             } catch (MissingResourceException e) {
                 Context.warningOccured(context, CodeListAdapter.class, "marshal", e, false);
             }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/EnumAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/EnumAdapter.java
index 60fd69a..7e46230 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/EnumAdapter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/EnumAdapter.java
@@ -18,10 +18,6 @@
 
 import jakarta.xml.bind.annotation.adapters.XmlAdapter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
-import org.apache.sis.util.iso.Types;
-
 
 /**
  * An adapter for {@link Enum}, in order to implement the ISO 19115-3 standard.
@@ -83,7 +79,7 @@
      * @param  e  the enumeration constant.
      * @return the text to write in the XML element.
      */
-    protected static String value(final ControlledVocabulary e) {
-        return Types.getCodeName(e);
+    protected static String value(final Enum<?> e) {
+        return e.name();
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java
index a6c39e4..019130b 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java
@@ -42,9 +42,6 @@
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
-
 
 /**
  * JAXB wrapper for string value in a {@code <gco:CharacterString>}, {@code <gcx:Anchor>},
@@ -261,7 +258,7 @@
         if (type != ENUM) {
             return null;
         }
-        final ControlledVocabulary code = Types.forCodeTitle(text);
+        final CodeList<?> code = Types.forCodeTitle(text);
         final String name = Types.getListName(code);
         /*
          * The namespace has have various value like CIT, SRV, MDQ, MRI, MSR, LAN, etc.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
index 4596c8b..d291a2a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
@@ -26,8 +26,8 @@
 import org.apache.sis.pending.temporal.TemporalUtilities;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.temporal.Period;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriod.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriod.java
index e1ce2c7..317af71 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriod.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriod.java
@@ -24,8 +24,8 @@
 import org.apache.sis.util.privy.Strings;
 import static org.apache.sis.xml.privy.LegacyNamespaces.VERSION_3_0;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.temporal.Period;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Party.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Party.java
index f63c67e..a9052e5 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Party.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Party.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.citation.AbstractParty;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class CI_Party extends PropertyType<CI_Party, Party> {
+public final class CI_Party extends PropertyType<CI_Party, AbstractParty> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -38,21 +35,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code Party.class}
      */
     @Override
-    protected Class<Party> getBoundType() {
-        return Party.class;
+    protected Class<AbstractParty> getBoundType() {
+        return AbstractParty.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CI_Party(final Party value) {
+    private CI_Party(final AbstractParty value) {
         super(value);
     }
 
@@ -64,7 +59,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected CI_Party wrap(final Party value) {
+    protected CI_Party wrap(final AbstractParty value) {
         return new CI_Party(value);
     }
 
@@ -77,7 +72,7 @@
      */
     @XmlElementRef
     public AbstractParty getElement() {
-        return AbstractParty.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Responsibility.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Responsibility.java
index 62a41dc..a84f944 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Responsibility.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_Responsibility.java
@@ -23,9 +23,6 @@
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -34,7 +31,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Cullen Rombach (Image Matters)
  */
-public final class CI_Responsibility extends PropertyType<CI_Responsibility, Responsibility> {
+public final class CI_Responsibility extends PropertyType<CI_Responsibility, DefaultResponsibility> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -42,21 +39,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code Responsibility.class}
      */
     @Override
-    protected Class<Responsibility> getBoundType() {
-        return Responsibility.class;
+    protected Class<DefaultResponsibility> getBoundType() {
+        return DefaultResponsibility.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CI_Responsibility(final Responsibility value) {
+    private CI_Responsibility(final DefaultResponsibility value) {
         super(value);
     }
 
@@ -68,7 +63,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected CI_Responsibility wrap(final Responsibility value) {
+    protected CI_Responsibility wrap(final DefaultResponsibility value) {
         return new CI_Responsibility(value);
     }
 
@@ -87,9 +82,13 @@
                 // Need to build new DefaultResponsibility object here — simply casting doesn't work.
                 return new DefaultResponsibility(metadata);
             }
-            return DefaultResponsibility.castOrCopy(metadata);
+            return metadata;
         } else if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return DefaultResponsibleParty.castOrCopy(metadata);
+            if (metadata instanceof DefaultResponsibleParty) {
+                return metadata;
+            } else {
+                return new DefaultResponsibleParty(metadata);
+            }
         } else {
             return null;
         }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_BasicMeasure.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_BasicMeasure.java
index 866e3b9..ce7eaff 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_BasicMeasure.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_BasicMeasure.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.quality.DefaultBasicMeasure;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.BasicMeasure;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DQM_BasicMeasure extends PropertyType<DQM_BasicMeasure, BasicMeasure> {
+public final class DQM_BasicMeasure extends PropertyType<DQM_BasicMeasure, DefaultBasicMeasure> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -42,17 +39,17 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code BasicMeasure.class}
+     * @return {@code DefaultBasicMeasure.class}
      */
     @Override
-    protected Class<BasicMeasure> getBoundType() {
-        return BasicMeasure.class;
+    protected Class<DefaultBasicMeasure> getBoundType() {
+        return DefaultBasicMeasure.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private DQM_BasicMeasure(final BasicMeasure metadata) {
+    private DQM_BasicMeasure(final DefaultBasicMeasure metadata) {
         super(metadata);
     }
 
@@ -64,7 +61,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected DQM_BasicMeasure wrap(final BasicMeasure metadata) {
+    protected DQM_BasicMeasure wrap(final DefaultBasicMeasure metadata) {
         return new DQM_BasicMeasure(metadata);
     }
 
@@ -77,7 +74,7 @@
      */
     @XmlElementRef
     public DefaultBasicMeasure getElement() {
-        return DefaultBasicMeasure.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Description.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Description.java
index 12c5a93..f4fb3ca 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Description.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Description.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.quality.DefaultMeasureDescription;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Description;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DQM_Description extends PropertyType<DQM_Description, Description> {
+public final class DQM_Description extends PropertyType<DQM_Description, DefaultMeasureDescription> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -42,17 +39,17 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code Description.class}
+     * @return {@code DefaultMeasureDescription.class}
      */
     @Override
-    protected Class<Description> getBoundType() {
-        return Description.class;
+    protected Class<DefaultMeasureDescription> getBoundType() {
+        return DefaultMeasureDescription.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private DQM_Description(final Description metadata) {
+    private DQM_Description(final DefaultMeasureDescription metadata) {
         super(metadata);
     }
 
@@ -64,7 +61,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected DQM_Description wrap(final Description metadata) {
+    protected DQM_Description wrap(final DefaultMeasureDescription metadata) {
         return new DQM_Description(metadata);
     }
 
@@ -77,7 +74,7 @@
      */
     @XmlElementRef
     public DefaultMeasureDescription getElement() {
-        return DefaultMeasureDescription.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Measure.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Measure.java
index 7a3487e..9c67e78 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Measure.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_Measure.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.quality.DefaultQualityMeasure;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.Measure;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DQM_Measure extends PropertyType<DQM_Measure, Measure> {
+public final class DQM_Measure extends PropertyType<DQM_Measure, DefaultQualityMeasure> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -42,17 +39,17 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code Measure.class}
+     * @return {@code DefaultQualityMeasure.class}
      */
     @Override
-    protected Class<Measure> getBoundType() {
-        return Measure.class;
+    protected Class<DefaultQualityMeasure> getBoundType() {
+        return DefaultQualityMeasure.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private DQM_Measure(final Measure metadata) {
+    private DQM_Measure(final DefaultQualityMeasure metadata) {
         super(metadata);
     }
 
@@ -64,7 +61,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected DQM_Measure wrap(final Measure metadata) {
+    protected DQM_Measure wrap(final DefaultQualityMeasure metadata) {
         return new DQM_Measure(metadata);
     }
 
@@ -77,7 +74,7 @@
      */
     @XmlElementRef
     public DefaultQualityMeasure getElement() {
-        return DefaultQualityMeasure.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_SourceReference.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_SourceReference.java
index 524692c..479f457 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_SourceReference.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQM_SourceReference.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.quality.DefaultSourceReference;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.SourceReference;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DQM_SourceReference extends PropertyType<DQM_SourceReference, SourceReference> {
+public final class DQM_SourceReference extends PropertyType<DQM_SourceReference, DefaultSourceReference> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -42,17 +39,17 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code SourceReference.class}
+     * @return {@code DefaultSourceReference.class}
      */
     @Override
-    protected Class<SourceReference> getBoundType() {
-        return SourceReference.class;
+    protected Class<DefaultSourceReference> getBoundType() {
+        return DefaultSourceReference.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private DQM_SourceReference(final SourceReference metadata) {
+    private DQM_SourceReference(final DefaultSourceReference metadata) {
         super(metadata);
     }
 
@@ -64,7 +61,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected DQM_SourceReference wrap(final SourceReference metadata) {
+    protected DQM_SourceReference wrap(final DefaultSourceReference metadata) {
         return new DQM_SourceReference(metadata);
     }
 
@@ -77,7 +74,7 @@
      */
     @XmlElementRef
     public DefaultSourceReference getElement() {
-        return DefaultSourceReference.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_EvaluationMethod.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_EvaluationMethod.java
index 2a034e0..f7401b2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_EvaluationMethod.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_EvaluationMethod.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.quality.DefaultEvaluationMethod;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.EvaluationMethod;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -34,7 +31,7 @@
  * @author  Alexis Gaillard (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DQ_EvaluationMethod extends PropertyType<DQ_EvaluationMethod, EvaluationMethod> {
+public final class DQ_EvaluationMethod extends PropertyType<DQ_EvaluationMethod, DefaultEvaluationMethod> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,17 +43,17 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code EvaluationMethod.class}
+     * @return {@code DefaultEvaluationMethod.class}
      */
     @Override
-    protected Class<EvaluationMethod> getBoundType() {
-        return EvaluationMethod.class;
+    protected Class<DefaultEvaluationMethod> getBoundType() {
+        return DefaultEvaluationMethod.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private DQ_EvaluationMethod(final EvaluationMethod metadata) {
+    private DQ_EvaluationMethod(final DefaultEvaluationMethod metadata) {
         super(metadata);
     }
 
@@ -69,7 +66,7 @@
      *         or {@code null} if marshalling a too old version of the standard.
      */
     @Override
-    protected DQ_EvaluationMethod wrap(final EvaluationMethod metadata) {
+    protected DQ_EvaluationMethod wrap(final DefaultEvaluationMethod metadata) {
         return accept2014() ? new DQ_EvaluationMethod(metadata) : null;
     }
 
@@ -82,7 +79,7 @@
      */
     @XmlElementRef
     public DefaultEvaluationMethod getElement() {
-        return DefaultEvaluationMethod.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_MeasureReference.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_MeasureReference.java
index 7d5696a..f753921 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_MeasureReference.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_MeasureReference.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.quality.DefaultMeasureReference;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.MeasureReference;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -34,7 +31,7 @@
  * @author  Alexis Gaillard (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DQ_MeasureReference extends PropertyType<DQ_MeasureReference, MeasureReference> {
+public final class DQ_MeasureReference extends PropertyType<DQ_MeasureReference, DefaultMeasureReference> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,17 +43,17 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code MeasureReference.class}
+     * @return {@code DefaultMeasureReference.class}
      */
     @Override
-    protected Class<MeasureReference> getBoundType() {
-        return MeasureReference.class;
+    protected Class<DefaultMeasureReference> getBoundType() {
+        return DefaultMeasureReference.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private DQ_MeasureReference(final MeasureReference metadata) {
+    private DQ_MeasureReference(final DefaultMeasureReference metadata) {
         super(metadata);
     }
 
@@ -69,7 +66,7 @@
      *         or {@code null} if marshalling a too old version of the standard.
      */
     @Override
-    protected DQ_MeasureReference wrap(final MeasureReference metadata) {
+    protected DQ_MeasureReference wrap(final DefaultMeasureReference metadata) {
         return accept2014() ? new DQ_MeasureReference(metadata) : null;
     }
 
@@ -82,7 +79,7 @@
      */
     @XmlElementRef
     public DefaultMeasureReference getElement() {
-        return DefaultMeasureReference.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_StandaloneQualityReportInformation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_StandaloneQualityReportInformation.java
index ebab033..9f70e15 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_StandaloneQualityReportInformation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/DQ_StandaloneQualityReportInformation.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.quality.DefaultEvaluationReportInformation;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.quality.StandaloneQualityReportInformation;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -35,7 +32,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  */
 public final class DQ_StandaloneQualityReportInformation extends
-        PropertyType<DQ_StandaloneQualityReportInformation, StandaloneQualityReportInformation>
+        PropertyType<DQ_StandaloneQualityReportInformation, DefaultEvaluationReportInformation>
 {
     /**
      * Empty constructor for JAXB only.
@@ -48,17 +45,17 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code StandaloneQualityReportInformation.class}
+     * @return {@code DefaultEvaluationReportInformation.class}
      */
     @Override
-    protected Class<StandaloneQualityReportInformation> getBoundType() {
-        return StandaloneQualityReportInformation.class;
+    protected Class<DefaultEvaluationReportInformation> getBoundType() {
+        return DefaultEvaluationReportInformation.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private DQ_StandaloneQualityReportInformation(final StandaloneQualityReportInformation metadata) {
+    private DQ_StandaloneQualityReportInformation(final DefaultEvaluationReportInformation metadata) {
         super(metadata);
     }
 
@@ -71,7 +68,7 @@
      *         or {@code null} if marshalling a too old version of the standard.
      */
     @Override
-    protected DQ_StandaloneQualityReportInformation wrap(final StandaloneQualityReportInformation metadata) {
+    protected DQ_StandaloneQualityReportInformation wrap(final DefaultEvaluationReportInformation metadata) {
         return accept2014() ? new DQ_StandaloneQualityReportInformation(metadata) : null;
     }
 
@@ -84,7 +81,7 @@
      */
     @XmlElementRef
     public DefaultEvaluationReportInformation getElement() {
-        return DefaultEvaluationReportInformation.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_AssociatedResource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_AssociatedResource.java
deleted file mode 100644
index 21dbd12..0000000
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_AssociatedResource.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.xml.bind.metadata;
-
-import jakarta.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.AssociatedResource;
-import org.apache.sis.metadata.iso.identification.DefaultAssociatedResource;
-import org.apache.sis.xml.bind.gco.PropertyType;
-
-
-/**
- * JAXB adapter mapping implementing class to the GeoAPI interface. See
- * package documentation for more information about JAXB and interface.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class MD_AssociatedResource extends PropertyType<MD_AssociatedResource, AssociatedResource> {
-    /**
-     * Empty constructor for JAXB only.
-     */
-    public MD_AssociatedResource() {
-    }
-
-    /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
-     *
-     * @return {@code AssociatedResource.class}
-     */
-    @Override
-    protected Class<AssociatedResource> getBoundType() {
-        return AssociatedResource.class;
-    }
-
-    /**
-     * Constructor for the {@link #wrap} method only.
-     */
-    private MD_AssociatedResource(final AssociatedResource value) {
-        super(value);
-    }
-
-    /**
-     * Invoked by {@link PropertyType} at marshalling time for wrapping the given metadata value
-     * in a {@code <mri:MD_AssociatedResource>} XML element.
-     *
-     * @param  value  the metadata element to marshal.
-     * @return a {@code PropertyType} wrapping the given the metadata element.
-     */
-    @Override
-    protected MD_AssociatedResource wrap(final AssociatedResource value) {
-        return new MD_AssociatedResource(value);
-    }
-
-    /**
-     * Invoked by JAXB at marshalling time for getting the actual metadata to write
-     * inside the {@code <mri:MD_AssociatedResource>} XML element.
-     * This is the value or a copy of the value given in argument to the {@code wrap} method.
-     *
-     * @return the metadata to be marshalled.
-     */
-    @XmlElementRef
-    public DefaultAssociatedResource getElement() {
-        return DefaultAssociatedResource.castOrCopy(metadata);
-    }
-
-    /**
-     * Invoked by JAXB at unmarshalling time for storing the result temporarily.
-     *
-     * @param  value  the unmarshalled metadata.
-     */
-    public void setElement(final DefaultAssociatedResource value) {
-        metadata = value;
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_AttributeGroup.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_AttributeGroup.java
index 4961bc7..cd61533 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_AttributeGroup.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_AttributeGroup.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.AttributeGroup;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class MD_AttributeGroup extends PropertyType<MD_AttributeGroup, AttributeGroup> {
+public final class MD_AttributeGroup extends PropertyType<MD_AttributeGroup, DefaultAttributeGroup> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -38,21 +35,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code AttributeGroup.class}
      */
     @Override
-    protected Class<AttributeGroup> getBoundType() {
-        return AttributeGroup.class;
+    protected Class<DefaultAttributeGroup> getBoundType() {
+        return DefaultAttributeGroup.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_AttributeGroup(final AttributeGroup value) {
+    private MD_AttributeGroup(final DefaultAttributeGroup value) {
         super(value);
     }
 
@@ -64,7 +59,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_AttributeGroup wrap(final AttributeGroup value) {
+    protected MD_AttributeGroup wrap(final DefaultAttributeGroup value) {
         return new MD_AttributeGroup(value);
     }
 
@@ -77,7 +72,7 @@
      */
     @XmlElementRef
     public DefaultAttributeGroup getElement() {
-        return DefaultAttributeGroup.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_FeatureTypeInfo.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_FeatureTypeInfo.java
index 30fd3ca..6bfe798 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_FeatureTypeInfo.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_FeatureTypeInfo.java
@@ -20,9 +20,6 @@
 import org.apache.sis.xml.bind.gco.PropertyType;
 import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.FeatureTypeInfo;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Cullen Rombach (Image Matters)
  */
-public final class MD_FeatureTypeInfo extends PropertyType<MD_FeatureTypeInfo, FeatureTypeInfo> {
+public final class MD_FeatureTypeInfo extends PropertyType<MD_FeatureTypeInfo, DefaultFeatureTypeInfo> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -45,14 +42,14 @@
      * @return {@code FeatureTypeInfo.class}
      */
     @Override
-    protected Class<FeatureTypeInfo> getBoundType() {
-        return FeatureTypeInfo.class;
+    protected Class<DefaultFeatureTypeInfo> getBoundType() {
+        return DefaultFeatureTypeInfo.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_FeatureTypeInfo(final FeatureTypeInfo value) {
+    private MD_FeatureTypeInfo(final DefaultFeatureTypeInfo value) {
         super(value);
     }
 
@@ -64,7 +61,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_FeatureTypeInfo wrap(final FeatureTypeInfo value) {
+    protected MD_FeatureTypeInfo wrap(final DefaultFeatureTypeInfo value) {
         return new MD_FeatureTypeInfo(value);
     }
 
@@ -77,7 +74,7 @@
      */
     @XmlElementRef
     public DefaultFeatureTypeInfo getElement() {
-        return DefaultFeatureTypeInfo.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Identifier.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Identifier.java
index 53bf9a4..30ce411 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Identifier.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Identifier.java
@@ -23,6 +23,9 @@
 import org.apache.sis.xml.bind.gco.PropertyType;
 import org.apache.sis.xml.bind.metadata.replace.RS_Identifier;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -78,14 +81,15 @@
      */
     @XmlElementRef
     public final DefaultIdentifier getElement() {
-        if (FilterByVersion.LEGACY_METADATA.accept() && metadata != null) {
+        if (FilterByVersion.LEGACY_METADATA.accept() && metadata instanceof ReferenceIdentifier) {
             /*
              * In legacy specification, "code space" and "version" were not defined in <gmd:MD_Identifier> but were
              * defined in <gmd:RS_Identifier> subclass. In newer specification there is no longer such special case.
              * Note that "description" did not existed anywhere in legacy specification.
              */
-            if (metadata.getCodeSpace() != null || metadata.getVersion() != null) {
-                return RS_Identifier.wrap(metadata);
+            final ReferenceIdentifier id = (ReferenceIdentifier) metadata;
+            if (id.getCodeSpace() != null || id.getVersion() != null) {
+                return RS_Identifier.wrap(id);
             }
         }
         return DefaultIdentifier.castOrCopy(metadata);
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_KeywordClass.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_KeywordClass.java
index 3d61223..dec9b56 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_KeywordClass.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_KeywordClass.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.identification.DefaultKeywordClass;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.KeywordClass;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public class MD_KeywordClass extends PropertyType<MD_KeywordClass, KeywordClass> {
+public class MD_KeywordClass extends PropertyType<MD_KeywordClass, DefaultKeywordClass> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -45,14 +42,14 @@
      * @return {@code KeywordClass.class}
      */
     @Override
-    protected final Class<KeywordClass> getBoundType() {
-        return KeywordClass.class;
+    protected final Class<DefaultKeywordClass> getBoundType() {
+        return DefaultKeywordClass.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_KeywordClass(final KeywordClass value) {
+    private MD_KeywordClass(final DefaultKeywordClass value) {
         super(value);
     }
 
@@ -64,7 +61,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_KeywordClass wrap(final KeywordClass value) {
+    protected MD_KeywordClass wrap(final DefaultKeywordClass value) {
         return new MD_KeywordClass(value);
     }
 
@@ -77,7 +74,7 @@
      */
     @XmlElementRef
     public final DefaultKeywordClass getElement() {
-        return DefaultKeywordClass.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -103,7 +100,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected MD_KeywordClass wrap(final KeywordClass value) {
+        @Override protected MD_KeywordClass wrap(final DefaultKeywordClass value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_MetadataScope.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_MetadataScope.java
index 8d678b2..b37bbcf 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_MetadataScope.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_MetadataScope.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.DefaultMetadataScope;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.MetadataScope;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class MD_MetadataScope extends PropertyType<MD_MetadataScope, MetadataScope> {
+public final class MD_MetadataScope extends PropertyType<MD_MetadataScope, DefaultMetadataScope> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -38,21 +35,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code MetadataScope.class}
      */
     @Override
-    protected Class<MetadataScope> getBoundType() {
-        return MetadataScope.class;
+    protected Class<DefaultMetadataScope> getBoundType() {
+        return DefaultMetadataScope.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_MetadataScope(final MetadataScope value) {
+    private MD_MetadataScope(final DefaultMetadataScope value) {
         super(value);
     }
 
@@ -64,7 +59,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_MetadataScope wrap(final MetadataScope value) {
+    protected MD_MetadataScope wrap(final DefaultMetadataScope value) {
         return new MD_MetadataScope(value);
     }
 
@@ -77,7 +72,7 @@
      */
     @XmlElementRef
     public DefaultMetadataScope getElement() {
-        return DefaultMetadataScope.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Releasability.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Releasability.java
index d68610b..7b065fd 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Releasability.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Releasability.java
@@ -20,9 +20,6 @@
 import org.apache.sis.xml.bind.gco.PropertyType;
 import org.apache.sis.metadata.iso.constraint.DefaultReleasability;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.constraint.Releasability;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface.
@@ -31,7 +28,7 @@
  * @author  Cullen Rombach (Image Matters)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public class MD_Releasability extends PropertyType<MD_Releasability, Releasability> {
+public class MD_Releasability extends PropertyType<MD_Releasability, DefaultReleasability> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,14 +43,14 @@
      * @return {@code Releasability.class}
      */
     @Override
-    protected final Class<Releasability> getBoundType() {
-        return Releasability.class;
+    protected final Class<DefaultReleasability> getBoundType() {
+        return DefaultReleasability.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_Releasability(final Releasability value) {
+    private MD_Releasability(final DefaultReleasability value) {
         super(value);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_Releasability wrap(final Releasability value) {
+    protected MD_Releasability wrap(final DefaultReleasability value) {
         return new MD_Releasability(value);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public final DefaultReleasability getElement() {
-        return DefaultReleasability.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -104,7 +101,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected MD_Releasability wrap(final Releasability value) {
+        @Override protected MD_Releasability wrap(final DefaultReleasability value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Scope.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Scope.java
index de496bb..e19489d 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Scope.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/MD_Scope.java
@@ -23,8 +23,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import java.net.URISyntaxException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
 
 
 /**
@@ -104,7 +104,7 @@
 
         /** Converts an adapter read from an XML stream. */
         @Override public Scope unmarshal(final MD_Scope value) throws URISyntaxException {
-            return org.apache.sis.metadata.iso.quality.DefaultScope.castOrCopy(super.unmarshal(value));
+            return DefaultScope.castOrCopy(super.unmarshal(value));
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_CoupledResource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_CoupledResource.java
index 54cf07e..801dd30 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_CoupledResource.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_CoupledResource.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.identification.DefaultCoupledResource;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.CoupledResource;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class SV_CoupledResource extends PropertyType<SV_CoupledResource, CoupledResource> {
+public final class SV_CoupledResource extends PropertyType<SV_CoupledResource, DefaultCoupledResource> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -38,21 +35,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code CoupledResource.class}
      */
     @Override
-    protected Class<CoupledResource> getBoundType() {
-        return CoupledResource.class;
+    protected Class<DefaultCoupledResource> getBoundType() {
+        return DefaultCoupledResource.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_CoupledResource(final CoupledResource value) {
+    private SV_CoupledResource(final DefaultCoupledResource value) {
         super(value);
     }
 
@@ -64,7 +59,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_CoupledResource wrap(final CoupledResource value) {
+    protected SV_CoupledResource wrap(final DefaultCoupledResource value) {
         return new SV_CoupledResource(value);
     }
 
@@ -77,7 +72,7 @@
      */
     @XmlElementRef
     public DefaultCoupledResource getElement() {
-        return DefaultCoupledResource.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationChainMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationChainMetadata.java
index 165f949..f355b60 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationChainMetadata.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationChainMetadata.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.OperationChainMetadata;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class SV_OperationChainMetadata extends PropertyType<SV_OperationChainMetadata, OperationChainMetadata> {
+public final class SV_OperationChainMetadata extends PropertyType<SV_OperationChainMetadata, DefaultOperationChainMetadata> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -38,21 +35,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code OperationChainMetadata.class}
      */
     @Override
-    protected Class<OperationChainMetadata> getBoundType() {
-        return OperationChainMetadata.class;
+    protected Class<DefaultOperationChainMetadata> getBoundType() {
+        return DefaultOperationChainMetadata.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_OperationChainMetadata(final OperationChainMetadata value) {
+    private SV_OperationChainMetadata(final DefaultOperationChainMetadata value) {
         super(value);
     }
 
@@ -64,7 +59,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_OperationChainMetadata wrap(final OperationChainMetadata value) {
+    protected SV_OperationChainMetadata wrap(final DefaultOperationChainMetadata value) {
         return new SV_OperationChainMetadata(value);
     }
 
@@ -77,7 +72,7 @@
      */
     @XmlElementRef
     public DefaultOperationChainMetadata getElement() {
-        return DefaultOperationChainMetadata.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationMetadata.java
index 0a5369a..3e16a40 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationMetadata.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/SV_OperationMetadata.java
@@ -20,9 +20,6 @@
 import org.apache.sis.metadata.iso.identification.DefaultOperationMetadata;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.OperationMetadata;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public class SV_OperationMetadata extends PropertyType<SV_OperationMetadata, OperationMetadata> {
+public class SV_OperationMetadata extends PropertyType<SV_OperationMetadata, DefaultOperationMetadata> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -38,21 +35,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code OperationMetadata.class}
      */
     @Override
-    protected final Class<OperationMetadata> getBoundType() {
-        return OperationMetadata.class;
+    protected final Class<DefaultOperationMetadata> getBoundType() {
+        return DefaultOperationMetadata.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_OperationMetadata(final OperationMetadata value) {
+    private SV_OperationMetadata(final DefaultOperationMetadata value) {
         super(value);
     }
 
@@ -64,7 +59,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_OperationMetadata wrap(final OperationMetadata value) {
+    protected SV_OperationMetadata wrap(final DefaultOperationMetadata value) {
         return new SV_OperationMetadata(value);
     }
 
@@ -77,7 +72,7 @@
      */
     @XmlElementRef
     public final DefaultOperationMetadata getElement() {
-        return DefaultOperationMetadata.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -103,7 +98,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected SV_OperationMetadata wrap(final OperationMetadata value) {
+        @Override protected SV_OperationMetadata wrap(final DefaultOperationMetadata value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/CI_TelephoneTypeCode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/CI_TelephoneTypeCode.java
index 082ec07..10fd655 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/CI_TelephoneTypeCode.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/CI_TelephoneTypeCode.java
@@ -20,20 +20,20 @@
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.xml.bind.cat.CodeListUID;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.TelephoneType;
-import org.apache.sis.xml.bind.cat.CodeListAdapter;
+// Specific to the main branch:
+import org.apache.sis.xml.bind.FilterByVersion;
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeListAdapter;
 
 
 /**
- * JAXB adapter for {@link TelephoneType}
+ * JAXB adapter for {@code TelephoneType}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
  * @author  Cullen Rombach (Image Matters)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public class CI_TelephoneTypeCode extends CodeListAdapter<CI_TelephoneTypeCode, TelephoneType> {
+public class CI_TelephoneTypeCode extends UnsupportedCodeListAdapter<CI_TelephoneTypeCode> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -63,8 +63,8 @@
      * @return the code list class.
      */
     @Override
-    protected final Class<TelephoneType> getCodeListClass() {
-        return TelephoneType.class;
+    protected String getCodeListName() {
+        return "CI_TelephoneTypeCode";
     }
 
     /**
@@ -102,7 +102,7 @@
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
         @Override protected CI_TelephoneTypeCode wrap(final CodeListUID value) {
-            return accept2014() ? super.wrap(value) : null;
+            return FilterByVersion.CURRENT_METADATA.accept() ? super.wrap(value) : null;
         }
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/DCPList.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/DCPList.java
index eb10e90..8e20939 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/DCPList.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/DCPList.java
@@ -20,19 +20,18 @@
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.xml.bind.cat.CodeListUID;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.apache.sis.xml.bind.cat.CodeListAdapter;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeListAdapter;
 
 
 /**
- * JAXB adapter for {@link DistributedComputingPlatform}
+ * JAXB adapter for {@code DistributedComputingPlatform}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class DCPList extends CodeListAdapter<DCPList, DistributedComputingPlatform> {
+public final class DCPList extends UnsupportedCodeListAdapter<DCPList> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -62,8 +61,29 @@
      * @return the code list class.
      */
     @Override
-    protected Class<DistributedComputingPlatform> getCodeListClass() {
-        return DistributedComputingPlatform.class;
+    protected String getCodeListName() {
+        return "DCPList";
+    }
+
+    /**
+     * Converts the given Java constant name to something hopefully close to the UML identifier,
+     * or close to the textual value to put in the XML.
+     *
+     * @param  name    The Java constant name (e.g. {@code WEB_SERVICES}).
+     * @param  buffer  An initially empty buffer to use for creating the identifier.
+     * @param  isValue {@code false} for the {@code codeListValue} attribute, or {@code true} for the XML value.
+     * @return The identifier (e.g. {@code "WebServices"} or {@code "Web services"}).
+     */
+    @Override
+    protected String toIdentifier(final String name, final StringBuilder buffer, final boolean isValue) {
+        if (name.startsWith("WEB_")) {
+            super.toIdentifier(name, buffer, isValue);
+            buffer.setCharAt(0, 'W');
+            return buffer.toString();
+        } else {
+            // Other names are abbreviations (e.g. XML, SQL, FTP, etc.), so return unchanged.
+            return name;
+        }
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/DQM_ValueStructure.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/DQM_ValueStructure.java
deleted file mode 100644
index b378970..0000000
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/DQM_ValueStructure.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.xml.bind.metadata.code;
-
-import jakarta.xml.bind.annotation.XmlElement;
-import org.opengis.metadata.quality.ValueStructure;
-import org.apache.sis.xml.Namespaces;
-import org.apache.sis.xml.bind.cat.CodeListAdapter;
-import org.apache.sis.xml.bind.cat.CodeListUID;
-
-
-/**
- * JAXB adapter for {@link ValueStructure}
- * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
- * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class DQM_ValueStructure extends CodeListAdapter<DQM_ValueStructure, ValueStructure> {
-    /**
-     * Empty constructor for JAXB only.
-     */
-    public DQM_ValueStructure() {
-    }
-
-    /**
-     * Creates a new adapter for the given value.
-     */
-    private DQM_ValueStructure(final CodeListUID value) {
-        super(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * @return the wrapper for the code list value.
-     */
-    @Override
-    protected DQM_ValueStructure wrap(final CodeListUID value) {
-        return new DQM_ValueStructure(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * @return the code list class.
-     */
-    @Override
-    protected Class<ValueStructure> getCodeListClass() {
-        return ValueStructure.class;
-    }
-
-    /**
-     * Invoked by JAXB on marshalling.
-     *
-     * @return the value to be marshalled.
-     */
-    @Override
-    @XmlElement(name = "DQM_ValueStructure", namespace = Namespaces.DQM)
-    public CodeListUID getElement() {
-        return identifier;
-    }
-
-    /**
-     * Invoked by JAXB on unmarshalling.
-     *
-     * @param  value  the unmarshalled value.
-     */
-    public void setElement(final CodeListUID value) {
-        identifier = value;
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_CharacterSetCode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_CharacterSetCode.java
index 16d16f6..9a7f67e 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_CharacterSetCode.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_CharacterSetCode.java
@@ -26,6 +26,9 @@
 import org.apache.sis.xml.bind.Context;
 import org.apache.sis.xml.bind.cat.CodeListUID;
 
+// Specific to the main branch:
+import org.opengis.metadata.identification.CharacterSet;
+
 
 /**
  * JAXB adapter for {@link Charset}
@@ -102,4 +105,25 @@
     public void setElement(final CodeListUID value) {
         identifier = value;
     }
+
+    /**
+     * Converts the given Java Character Set to {@code CharacterSet}.
+     *
+     * @param  cs  the character set, or {@code null}.
+     * @return a code list for the given character set, or {@code null} if the given {@code cs} was null.
+     */
+    public static CharacterSet fromCharset(final Charset cs) {
+        if (cs == null) {
+            return null;
+        }
+        final String name = cs.name();
+        for (final CharacterSet candidate : CharacterSet.values()) {
+            for (final String n : candidate.names()) {
+                if (name.equals(n)) {
+                    return candidate;
+                }
+            }
+        }
+        return CharacterSet.valueOf(name);
+    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_RestrictionCode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_RestrictionCode.java
index 635475e..557795f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_RestrictionCode.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_RestrictionCode.java
@@ -91,8 +91,8 @@
      * @param  value  the unmarshalled value.
      */
     public void setElement(final CodeListUID value) {
-        if (value != null && "license".equalsIgnoreCase(value.codeListValue)) {
-            value.codeListValue = "licence";    // For matching current spelling (ISO 19115-3:2016).
+        if (value != null && "licence".equalsIgnoreCase(value.codeListValue)) {
+            value.codeListValue = "license";    // For matching legacy spelling (ISO 19139:2007).
         }
         identifier = value;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/SV_CouplingType.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/SV_CouplingType.java
index 18ae9dd..33fa687 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/SV_CouplingType.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/SV_CouplingType.java
@@ -20,19 +20,18 @@
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.xml.bind.cat.CodeListUID;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.CouplingType;
-import org.apache.sis.xml.bind.cat.CodeListAdapter;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeListAdapter;
 
 
 /**
- * JAXB adapter for {@link CouplingType}
+ * JAXB adapter for {@code CouplingType}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class SV_CouplingType extends CodeListAdapter<SV_CouplingType, CouplingType> {
+public final class SV_CouplingType extends UnsupportedCodeListAdapter<SV_CouplingType> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -62,8 +61,8 @@
      * @return the code list class.
      */
     @Override
-    protected Class<CouplingType> getCodeListClass() {
-        return CouplingType.class;
+    protected String getCodeListName() {
+        return "SV_CouplingType";
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/SV_ParameterDirection.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/SV_ParameterDirection.java
deleted file mode 100644
index 1da66b1..0000000
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/SV_ParameterDirection.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.xml.bind.metadata.code;
-
-import jakarta.xml.bind.annotation.XmlElement;
-import org.opengis.parameter.ParameterDirection;
-import org.apache.sis.xml.Namespaces;
-import org.apache.sis.xml.bind.cat.EnumAdapter;
-
-
-/**
- * JAXB adapter for {@link ParameterDirection}
- * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
- * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class SV_ParameterDirection extends EnumAdapter<SV_ParameterDirection, ParameterDirection> {
-    /**
-     * The enumeration value.
-     */
-    @XmlElement(name = "SV_ParameterDirection", namespace = Namespaces.SRV)
-    public String value;
-
-    /**
-     * Empty constructor for JAXB only.
-     */
-    public SV_ParameterDirection() {
-    }
-
-    /**
-     * Returns the wrapped value.
-     *
-     * @param  wrapper  the wrapper.
-     * @return the wrapped value.
-     */
-    @Override
-    public final ParameterDirection unmarshal(final SV_ParameterDirection wrapper) {
-        return ParameterDirection.valueOf(name(wrapper.value));
-    }
-
-    /**
-     * Wraps the given value.
-     *
-     * @param  e  the value to wrap.
-     * @return the wrapped value.
-     */
-    @Override
-    public final SV_ParameterDirection marshal(final ParameterDirection e) {
-        if (e == null) {
-            return null;
-        }
-        final SV_ParameterDirection wrapper = new SV_ParameterDirection();
-        wrapper.value = value(e);
-        return wrapper;
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/Parameter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/Parameter.java
index bc9760a..c8f349f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/Parameter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/Parameter.java
@@ -25,8 +25,9 @@
 import org.apache.sis.metadata.privy.ReferencingServices;
 import org.apache.sis.util.ComparisonMode;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.apache.sis.util.Utilities.deepEquals;
+// Specific to the main branch:
+import java.util.Set;
+import javax.measure.Unit;
 
 
 /**
@@ -84,10 +85,8 @@
      *
      * @return the type name of value component(s) in this parameter, or {@code null} if unknown.
      */
-    @Override
     public TypeName getValueType() {
-        final ParameterDescriptor<T> p = descriptor;
-        return (p != null) ? p.getValueType() : null;
+        return ReferencingServices.getInstance().getValueType(descriptor);
     }
 
     /**
@@ -123,8 +122,18 @@
         return p.createValue();
     }
 
+    /**
+     * Optional properties.
+     * @return {@code null}.
+     */
+    @Override public Set<T>        getValidValues()  {return null;}     // Really null, not an empty set. See method contract.
+    @Override public Comparable<T> getMinimumValue() {return null;}
+    @Override public Comparable<T> getMaximumValue() {return null;}
+    @Override public T             getDefaultValue() {return null;}
+    @Override public Unit<?>       getUnit()         {return null;}
+
     /*
-     * Do not override getValidValues(), getMinimumValue(), getMaximumValue(), getDefaultValue() or getUnit()
+     * Do not redirect getValidValues(), getMinimumValue(), getMaximumValue(), getDefaultValue() or getUnit()
      * in order to keep property values stable before and after the `descriptor` field has been initialized.
      * The `equals(Object)` method assumes that all those methods return null.
      */
@@ -153,14 +162,11 @@
                     return Objects.equals(toString(getName()), toString(that.getName()));
                     // super.equals(…) already compared `getName()` in other modes.
                 }
-                return deepEquals(that.getValueType(),   getValueType(),   mode) &&
-                       deepEquals(that.getDescription(), getDescription(), mode) &&
-                                  that.getDirection()     == getDirection()     &&
-                                  that.getMinimumOccurs() == getMinimumOccurs() &&
-                                  that.getMaximumOccurs() == getMaximumOccurs() &&
-                                  that.getValidValues()   == null &&
-                                  that.getMinimumValue()  == null &&
-                                  that.getMaximumValue()  == null;
+                return that.getMinimumOccurs() == getMinimumOccurs() &&
+                       that.getMaximumOccurs() == getMaximumOccurs() &&
+                       that.getValidValues()   == null &&
+                       that.getMinimumValue()  == null &&
+                       that.getMaximumValue()  == null;
             }
         }
         return false;
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/QualityParameter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/QualityParameter.java
index 9f92939..feb608a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/QualityParameter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/QualityParameter.java
@@ -36,11 +36,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Optional;
-import org.opengis.coverage.Coverage;
-import org.opengis.metadata.quality.Description;
-import org.opengis.metadata.quality.ValueStructure;
+// Specific to the main branch:
+import org.apache.sis.metadata.privy.ReferencingServices;
 
 
 /**
@@ -68,8 +65,7 @@
     "code",
     "definition",
     "description",
-    "valueType",
-    "valueStructure"
+    "valueType"
 })
 @XmlRootElement(name = "DQM_Parameter", namespace = Namespaces.DQM)
 public final class QualityParameter extends Parameter {
@@ -88,7 +84,7 @@
 
     /**
      * Definition of the data quality parameter.
-     * Stored in {@link Identifier#getDescription()}.
+     * Stored in {@link ReferenceIdentifier#getDescription()}.
      *
      * @see #getName()
      */
@@ -100,8 +96,7 @@
      * Description of the data quality parameter.
      */
     @XmlElement
-    @SuppressWarnings("serial")                 // Most Apache SIS implementations are serializable.
-    public Description description;
+    public DefaultMeasureDescription description;
 
     /**
      * Value type of the data quality parameter (shall be one of the data types defined in ISO/TS 19103:2005).
@@ -115,14 +110,6 @@
     public TypeName valueType;
 
     /**
-     * Structure of the data quality parameter.
-     *
-     * @see #getValueClass()
-     */
-    @XmlElement
-    public ValueStructure valueStructure;
-
-    /**
      * Creates an initially empty parameter.
      * This constructor is needed by JAXB at unmarshalling time.
      */
@@ -139,13 +126,8 @@
         final Identifier id = parameter.getName();
         if (id != null) {
             code = id.getCode();
-            definition = id.getDescription();
         }
-        parameter.getDescription().ifPresent((text) -> {
-            description = new DefaultMeasureDescription(text);
-        });
-        valueType = parameter.getValueType();
-        valueStructure = ValueStructure.valueOf(parameter.getValueClass()).orElse(null);
+        valueType = ReferencingServices.getInstance().getValueType(parameter);
     }
 
     /**
@@ -161,6 +143,9 @@
         return new QualityParameter(parameter);
     }
 
+    @Override public int getMinimumOccurs() {return 0;}
+    @Override public int getMaximumOccurs() {return 1;}
+
     /**
      * Returns the name as an {@code Identifier}, which is the type requested by ISO 19111.
      * Note that this is different than the type requested by ISO 19157, which is {@link String}.
@@ -179,18 +164,6 @@
     }
 
     /**
-     * Returns a narrative explanation of the role of the parameter.
-     *
-     * @return a narrative explanation of the role of the parameter.
-     */
-    @Override
-    public Optional<InternationalString> getDescription() {
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final Description description = this.description;
-        return Optional.ofNullable((description != null) ? description.getTextDescription() : null);
-    }
-
-    /**
      * Infers the value class from the type name.
      * This method is the reason why we cannot parameterize this {@code QualityParameter} class
      * (see <cite>Note about raw-type usage</cite> in class javadoc), because there is no way we
@@ -202,8 +175,7 @@
     public Class<?> getValueClass() {
         Class<?> type = super.getValueClass();
         if (type == null) {
-            final ValueStructure s = valueStructure;
-            type = (s != null) ? s.toJavaType().orElse(null) : Names.toClass(valueType);
+            type = Names.toClass(valueType);
         }
         return type;
     }
@@ -230,9 +202,6 @@
      *   <li>Otherwise the given class is used as if it was already a component type (i.e. a singleton item).</li>
      * </ul>
      *
-     * This method is used for mapping {@link Class} to ({@link ValueStructure}, {@link TypeName}) pair.
-     * The other member of the pair is given by {@link ValueStructure#valueOf(Class)}.
-     *
      * @todo {@code Coverage} case needs to be added. It would be handle like {@link Matrix}.
      *
      * @param  valueClass  the type of values for which to infer a {@link TypeName} instance.
@@ -243,7 +212,7 @@
             valueClass = valueClass.getComponentType();
         } else if (Iterable.class.isAssignableFrom(valueClass) || Map.class.isAssignableFrom(valueClass)) {
             valueClass = Classes.boundOfParameterizedDeclaration(valueClass);
-        } else if (Matrix.class.isAssignableFrom(valueClass) || Coverage.class.isAssignableFrom(valueClass)) {
+        } else if (Matrix.class.isAssignableFrom(valueClass)) {
             valueClass = Double.class;
         }
         return Names.createTypeName(valueClass);
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ReferenceSystemMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ReferenceSystemMetadata.java
index 47c6061..f306045 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ReferenceSystemMetadata.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ReferenceSystemMetadata.java
@@ -30,9 +30,6 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.apache.sis.util.collection.Containers;
-
 
 /**
  * An implementation of {@link ReferenceSystem} marshalled as specified in ISO 19115.
@@ -146,7 +143,7 @@
                 // Compare the name because it was ignored by super.equals(…) in "ignore metadata" mode.
                 return Utilities.deepEquals(getName(), that.getName(), mode);
             }
-            return Containers.isNullOrEmpty(that.getDomains());
+            return that.getDomainOfValidity() == null && that.getScope() == null;
         }
         return false;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java
index 2c96b8d..7a50418 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java
@@ -20,6 +20,10 @@
 import org.opengis.annotation.Specification;
 import org.opengis.util.CodeList;
 
+// Specific to the main branch:
+import java.util.List;
+import java.util.ArrayList;
+
 
 /**
  * The code list for {@code <gmi:MI_SensorTypeCode>}.
@@ -35,34 +39,59 @@
      */
     private static final long serialVersionUID = 3510875680392393838L;
 
-    /*
-     * We need to construct values with `valueOf(String)` instead of the constructor
-     * because this package is not exported to GeoAPI. See `CodeList` class javadoc.
+    /**
+     * List of all enumerations of this type.
+     * Must be declared before any enum declaration.
      */
+    private static final List<SensorType> VALUES = new ArrayList<>();
 
     /**
      * The sensor is a radiometer.
      */
-    public static final SensorType RADIOMETER = valueOf("RADIOMETER");
+    public static final SensorType RADIOMETER = new SensorType("RADIOMETER");
 
     /**
-     * Constructs an element of the given name.
+     * Constructs an element of the given name. The new element is
+     * automatically added to the list returned by {@link #values()}.
      *
      * @param  name  the name of the new element.
      *         This name must not be in use by another element of this type.
      */
     private SensorType(final String name) {
-        super(name);
+        super(name, VALUES);
+    }
+
+    /**
+     * Returns the list of {@code SensorType}s.
+     *
+     * @return the list of codes declared in the current JVM.
+     */
+    public static SensorType[] values() {
+        synchronized (VALUES) {
+            return VALUES.toArray(SensorType[]::new);
+        }
+    }
+
+    /**
+     * Disables the search for UML identifiers because we do not export this package to GeoAPI.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public String identifier() {
+        return null;
     }
 
     /**
      * Returns the list of codes of the same kind as this code list element.
+     * Invoking this method is equivalent to invoking {@link #values()}, except that
+     * this method can be invoked on an instance of the parent {@code CodeList} class.
      *
      * @return all code {@linkplain #values() values} for this code list.
      */
     @Override
     public SensorType[] family() {
-        return values(SensorType.class);
+        return values();
     }
 
     /**
@@ -72,6 +101,6 @@
      * @return a code matching the given name.
      */
     public static SensorType valueOf(String code) {
-        return valueOf(SensorType.class, code, SensorType::new).get();
+        return valueOf(SensorType.class, code);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ServiceParameter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ServiceParameter.java
index 8621ef0..2802356 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ServiceParameter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/ServiceParameter.java
@@ -38,9 +38,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.parameter.ParameterDirection;
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.apache.sis.metadata.privy.ReferencingServices;
 
 
 /**
@@ -68,7 +67,6 @@
 @XmlType(name = "SV_Parameter_Type", namespace = Namespaces.SRV, propOrder = {
     "memberName",           // The  ISO 19115-3:2016 way to marshal name.
     "legacyName",           // Legacy ISO 19139:2007 way to marshal name.
-    "direction",
     "description",
     "optionality",
     "optionalityLabel",     // Legacy ISO 19139:2007 way to marshal optionality.
@@ -108,14 +106,6 @@
     public MemberName memberName;
 
     /**
-     * Indication if the parameter is an input to the service, an output or both.
-     *
-     * @see #getDirection()
-     */
-    @XmlElement(required = true)
-    public ParameterDirection direction;
-
-    /**
      * A narrative explanation of the role of the parameter.
      *
      * @see #getDescription()
@@ -163,8 +153,6 @@
     private ServiceParameter(final ParameterDescriptor<?> parameter) {
         super(parameter);
         memberName    = getMemberName(parameter);
-        direction     = parameter.getDirection();
-        description   = parameter.getDescription().orElse(null);
         optionality   = parameter.getMinimumOccurs() > 0;
         repeatability = parameter.getMaximumOccurs() > 1;
     }
@@ -192,7 +180,7 @@
      *   <li>Otherwise this method searches for the first {@linkplain ParameterDescriptor#getAlias() alias}
      *       which is an instance of {@code MemberName}. If found, that alias is returned.</li>
      *   <li>If no alias is found, then this method tries to build a member name from the primary name and the
-     *       {@linkplain ParameterDescriptor#getValueType() value type} (if available) or the
+     *       {@linkplain org.apache.sis.parameter.DefaultParameterDescriptor#getValueType() value type} (if available) or the
      *       {@linkplain ParameterDescriptor#getValueClass() value class}.</li>
      * </ul>
      *
@@ -205,7 +193,7 @@
      */
     public static MemberName getMemberName(final ParameterDescriptor<?> parameter) {
         if (parameter != null) {
-            final Identifier id = parameter.getName();
+            final ReferenceIdentifier id = parameter.getName();
             if (id instanceof MemberName) {
                 return (MemberName) id;
             }
@@ -218,7 +206,7 @@
                 final String code = id.getCode();
                 if (code != null) {
                     final String namespace = id.getCodeSpace();
-                    final TypeName type = parameter.getValueType();
+                    final TypeName type = ReferencingServices.getInstance().getValueType(parameter);
                     if (type != null) {
                         return Names.createMemberName(namespace, null, code, type);
                     } else {
@@ -327,21 +315,10 @@
     }
 
     /**
-     * Returns an indication if the parameter is an input to the service, an output or both.
-     *
-     * @return indication if the parameter is an input or output to the service, or {@code null} if unspecified.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        return direction;
-    }
-
-    /**
      * Returns a narrative explanation of the role of the parameter.
      *
      * @return a narrative explanation of the role of the parameter.
      */
-    @Override
     public Optional<InternationalString> getDescription() {
         return Optional.ofNullable(description);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/package-info.java
index ddcd041..28732fa 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/package-info.java
@@ -44,8 +44,6 @@
     @XmlJavaTypeAdapter(GO_Boolean.class),
     @XmlJavaTypeAdapter(MD_Identifier.class),
     @XmlJavaTypeAdapter(DQM_Description.class),
-    @XmlJavaTypeAdapter(DQM_ValueStructure.class),
-    @XmlJavaTypeAdapter(SV_ParameterDirection.class),
 
     // Java types, primitive types and basic OGC types handling
     @XmlJavaTypeAdapter(StringAdapter.class),
@@ -66,7 +64,3 @@
 import org.apache.sis.xml.bind.metadata.MD_Identifier;
 import org.apache.sis.xml.bind.metadata.DQM_Description;
 import org.apache.sis.xml.bind.gco.*;
-
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.apache.sis.xml.bind.metadata.code.SV_ParameterDirection;
-import org.apache.sis.xml.bind.metadata.code.DQM_ValueStructure;
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/Assertions.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/Assertions.java
index 7ac728f..d84214c 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/Assertions.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/Assertions.java
@@ -34,10 +34,13 @@
 import org.apache.sis.xml.test.DocumentComparator;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Responsibility;
-import org.opengis.metadata.maintenance.Scope;
-import org.opengis.metadata.content.FeatureTypeInfo;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+import org.apache.sis.metadata.iso.content.DefaultFeatureCatalogueDescription;
+import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
+import org.apache.sis.metadata.iso.lineage.DefaultSource;
+import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 
 
 /**
@@ -78,7 +81,7 @@
      */
     public static void assertPartyNameEquals(final String expected, final Citation citation, final String message) {
         assertNotNull(citation, message);
-        final Responsibility r = getSingleton(citation.getCitedResponsibleParties());
+        final DefaultResponsibility r = (DefaultResponsibility) getSingleton(citation.getCitedResponsibleParties());
         final InternationalString name = getSingleton(r.getParties()).getName();
         assertNotNull(name, message);
         assertEquals(expected, name.toString(Locale.US), message);
@@ -93,7 +96,7 @@
      * @param  catalog  the content info to validate.
      */
     public static void assertContentInfoEquals(final String name, final Integer count, final FeatureCatalogueDescription catalog) {
-        final FeatureTypeInfo info = getSingleton(catalog.getFeatureTypeInfo());
+        final DefaultFeatureTypeInfo info = getSingleton(((DefaultFeatureCatalogueDescription) catalog).getFeatureTypeInfo());
         assertEquals(name, String.valueOf(info.getFeatureTypeName()), "metadata.contentInfo.featureType");
         assertEquals(count, info.getFeatureInstanceCount(), "metadata.contentInfo.featureInstanceCount");
     }
@@ -108,7 +111,7 @@
      */
     public static void assertFeatureSourceEquals(final String name, final String[] features, final Source source) {
         assertEquals(name, String.valueOf(source.getSourceCitation().getTitle()), "metadata.lineage.source.sourceCitation.title");
-        final Scope scope = source.getScope();
+        final DefaultScope scope = (DefaultScope) ((DefaultSource) source).getScope();
         assertNotNull(scope, "metadata.lineage.source.scope");
         assertEquals(ScopeCode.FEATURE_TYPE, scope.getLevel(), "metadata.lineage.source.scope.level");
         final var actual = getSingleton(scope.getLevelDescription()).getFeatures().toArray(CharSequence[]::new);
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java
index 36e066e..37b4374 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java
@@ -39,9 +39,6 @@
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Individual;
-
 
 /**
  * Tests the {@link HashCode} class. This is also used as a relatively simple {@link MetadataVisitor} test.
@@ -97,7 +94,7 @@
         /*
          * Individual hash code is the sum of all its properties, none of them being a collection.
          */
-        int expected = Individual.class.hashCode() + person.hashCode();
+        int expected = DefaultIndividual.class.hashCode() + person.hashCode();
         assertEquals(Integer.valueOf(expected), hash(party));
         /*
          * The +31 below come from java.util.List contract, since above Individual is a list member.
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java
index e0609fc..2384d25 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataCopierTest.java
@@ -113,7 +113,7 @@
         final DefaultMetadata original = new DefaultMetadata();
         original.getLocalesAndCharsets().put(Locale.FRENCH,   StandardCharsets.UTF_8);
         original.getLocalesAndCharsets().put(Locale.JAPANESE, StandardCharsets.UTF_16);
-        final Metadata copy = copier.copy(Metadata.class, original);
+        final DefaultMetadata copy = (DefaultMetadata) copier.copy(Metadata.class, original);
         final Map<Locale,Charset> lc = copy.getLocalesAndCharsets();
         assertEquals(StandardCharsets.UTF_8,  lc.get(Locale.FRENCH));
         assertEquals(StandardCharsets.UTF_16, lc.get(Locale.JAPANESE));
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java
index 26f281d..322097d 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java
@@ -42,10 +42,6 @@
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.List;
-import org.opengis.coverage.grid.RectifiedGrid;
-
 
 /**
  * Tests the {@link MetadataStandard} class.
@@ -93,7 +89,7 @@
         assertFalse(isMetadata(IdentifiedObject.class));
         assertFalse(isMetadata(SimpleIdentifiedObject.class));
         assertFalse(isMetadata(GeographicCRS.class));
-        assertFalse(isMetadata(RectifiedGrid.class));
+//      assertFalse(isMetadata(RectifiedGrid.class));
         assertFalse(isMetadata(Double.class));
         assertFalse(isMetadata(Double.TYPE));
 
@@ -104,7 +100,7 @@
         assertTrue (isMetadata(IdentifiedObject.class));
         assertTrue (isMetadata(SimpleIdentifiedObject.class));
         assertTrue (isMetadata(GeographicCRS.class));
-        assertFalse(isMetadata(RectifiedGrid.class));
+//      assertFalse(isMetadata(RectifiedGrid.class));
 
         standard = MetadataStandard.ISO_19123;
         assertFalse(isMetadata(String.class));
@@ -113,7 +109,7 @@
         assertTrue (isMetadata(IdentifiedObject.class));       // Dependency
         assertTrue (isMetadata(SimpleIdentifiedObject.class)); // Dependency
         assertTrue (isMetadata(GeographicCRS.class));          // Dependency
-        assertTrue (isMetadata(RectifiedGrid.class));
+//      assertTrue (isMetadata(RectifiedGrid.class));
     }
 
     /**
@@ -312,38 +308,6 @@
     }
 
     /**
-     * Tests the {@link MetadataStandard#ISO_19123} constant. Getters shall
-     * be accessible even if there is no implementation on the module path.
-     */
-    @Test
-    public void testWithoutImplementation() {
-        standard = MetadataStandard.ISO_19123;
-        assertFalse(isMetadata(String.class));
-        assertTrue (isMetadata(Citation.class));         // Transitive dependency
-        assertTrue (isMetadata(DefaultCitation.class));  // Transitive dependency
-        assertTrue (isMetadata(RectifiedGrid.class));
-        /*
-         * Ensure that the getters have been found.
-         */
-        final Map<String,String> names = standard.asNameMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, KeyNamePolicy.JAVABEANS_PROPERTY);
-        assertFalse(names.isEmpty(), "Getters should have been found even if there is no implementation.");
-        assertEquals("dimension", names.get("dimension"));
-        assertEquals("cells", names.get("cell"));
-        /*
-         * Ensure that the type are recognized, especially RectifiedGrid.getOffsetVectors()
-         * which is of type List<double[]>.
-         */
-        Map<String,Class<?>> types;
-        types = standard.asTypeMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.PROPERTY_TYPE);
-        assertEquals(Integer.TYPE, types.get("dimension"));
-        assertEquals(List.class,   types.get("offsetVectors"));
-
-        types = standard.asTypeMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.ELEMENT_TYPE);
-        assertEquals(Integer.class,  types.get("dimension"));
-        assertEquals(double[].class, types.get("offsetVectors"));
-    }
-
-    /**
      * Tests serialization of predefined constants.
      */
     @Test
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyAccessorTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyAccessorTest.java
index 229c4a6..10f885b 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyAccessorTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyAccessorTest.java
@@ -67,10 +67,11 @@
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.AttributeGroup;
-import org.opengis.referencing.ObjectDomain;
-import org.opengis.referencing.datum.DatumEnsemble;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceSystem;
+import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
+import org.apache.sis.metadata.iso.identification.DefaultAssociatedResource;
 
 
 /**
@@ -191,8 +192,8 @@
 //          Citation.class, "getCollectiveTitle",         "collectiveTitle",         "collectiveTitle",       "Collective title",           InternationalString.class,   -- deprecated as of ISO 19115:2014
             Citation.class, "getISBN",                    "ISBN",                    "ISBN",                  "ISBN",                       String.class,
             Citation.class, "getISSN",                    "ISSN",                    "ISSN",                  "ISSN",                       String.class,
-            Citation.class, "getOnlineResources",         "onlineResources",         "onlineResource",        "Online resources",           OnlineResource[].class,
-            Citation.class, "getGraphics",                "graphics",                "graphic",               "Graphics",                   BrowseGraphic[].class);
+     DefaultCitation.class, "getOnlineResources",         "onlineResources",         "onlineResource",        "Online resources",           OnlineResource[].class,
+     DefaultCitation.class, "getGraphics",                "graphics",                "graphic",               "Graphics",                   BrowseGraphic[].class);
     }
 
     /**
@@ -214,29 +215,28 @@
             Identification.class, "getCredits",                    "credits",                    "credit",                    "Credits",                      String[].class,
             Identification.class, "getStatus",                     "status",                     "status",                    "Status",                       Progress[].class,
             Identification.class, "getPointOfContacts",            "pointOfContacts",            "pointOfContact",            "Point of contacts",            ResponsibleParty[].class,
-            Identification.class, "getSpatialRepresentationTypes", "spatialRepresentationTypes", "spatialRepresentationType", "Spatial representation types", SpatialRepresentationType[].class,
-            Identification.class, "getSpatialResolutions",         "spatialResolutions",         "spatialResolution",         "Spatial resolutions",          Resolution[].class,
-            Identification.class, "getTemporalResolutions",        "temporalResolutions",        "temporalResolution",        "Temporal resolutions",         TemporalAmount[].class,
-            Identification.class, "getTopicCategories",            "topicCategories",            "topicCategory",             "Topic categories",             TopicCategory[].class,
-            Identification.class, "getExtents",                    "extents",                    "extent",                    "Extents",                      Extent[].class,
-            Identification.class, "getAdditionalDocumentations",   "additionalDocumentations",   "additionalDocumentation",   "Additional documentations",    Citation[].class,
-            Identification.class, "getProcessingLevel",            "processingLevel",            "processingLevel",           "Processing level",             Identifier.class,
+        DataIdentification.class, "getSpatialRepresentationTypes", "spatialRepresentationTypes", "spatialRepresentationType", "Spatial representation types", SpatialRepresentationType[].class,
+        DataIdentification.class, "getSpatialResolutions",         "spatialResolutions",         "spatialResolution",         "Spatial resolutions",          Resolution[].class,
+    AbstractIdentification.class, "getTemporalResolutions",        "temporalResolutions",        "temporalResolution",        "Temporal resolutions",         TemporalAmount[].class,
+        DataIdentification.class, "getTopicCategories",            "topicCategories",            "topicCategory",             "Topic categories",             TopicCategory[].class,
+        DataIdentification.class, "getExtents",                    "extents",                    "extent",                    "Extents",                      Extent[].class,
+    AbstractIdentification.class, "getAdditionalDocumentations",   "additionalDocumentations",   "additionalDocumentation",   "Additional documentations",    Citation[].class,
+    AbstractIdentification.class, "getProcessingLevel",            "processingLevel",            "processingLevel",           "Processing level",             Identifier.class,
             Identification.class, "getResourceMaintenances",       "resourceMaintenances",       "resourceMaintenance",       "Resource maintenances",        MaintenanceInformation[].class,
             Identification.class, "getGraphicOverviews",           "graphicOverviews",           "graphicOverview",           "Graphic overviews",            BrowseGraphic[].class,
             Identification.class, "getResourceFormats",            "resourceFormats",            "resourceFormat",            "Resource formats",             Format[].class,
             Identification.class, "getDescriptiveKeywords",        "descriptiveKeywords",        "descriptiveKeywords",       "Descriptive keywords",         Keywords[].class,
             Identification.class, "getResourceSpecificUsages",     "resourceSpecificUsages",     "resourceSpecificUsage",     "Resource specific usages",     Usage[].class,
             Identification.class, "getResourceConstraints",        "resourceConstraints",        "resourceConstraints",       "Resource constraints",         Constraints[].class,
-            Identification.class, "getAssociatedResources",        "associatedResources",        "associatedResource",        "Associated resources",         AssociatedResource[].class,
+    AbstractIdentification.class, "getAssociatedResources",        "associatedResources",        "associatedResource",        "Associated resources",         DefaultAssociatedResource[].class,
         DataIdentification.class, "getEnvironmentDescription",     "environmentDescription",     "environmentDescription",    "Environment description",      InternationalString.class,
         DataIdentification.class, "getSupplementalInformation",    "supplementalInformation",    "supplementalInformation",   "Supplemental information",     InternationalString.class,
-        DataIdentification.class, "getLocalesAndCharsets",         "localesAndCharsets",         "defaultLocale+otherLocale", "Locales and charsets",         Map.class);
+ DefaultDataIdentification.class, "getLocalesAndCharsets",         "localesAndCharsets",         "defaultLocale+otherLocale", "Locales and charsets",         Map.class);
     }
 
     /**
      * Tests the constructor with a method which override another method with covariant return type.
-     * This test may need to be updated if a future GeoAPI release modifies the {@link GeographicCRS}
-     * interface or one of its parent interfaces.
+     * This test may need to be updated if a future GeoAPI release modifies the {@link GeographicCRS} interface.
      */
     @Test
     public void testConstructorWithCovariantReturnType() {
@@ -244,12 +244,12 @@
         //……Declaring type……………………………Method……………………………………………JavaBeans……………………………UML identifier……………………Sentence………………………………Type…………………………………………………………
             GeographicCRS.class,    "getCoordinateSystem", "coordinateSystem", "coordinateSystem",   "Coordinate system", EllipsoidalCS.class,       // Covariant return type
             GeodeticCRS.class,      "getDatum",            "datum",            "datum",              "Datum",             GeodeticDatum.class,       // Covariant return type
-            GeodeticCRS.class,      "getDatumEnsemble",    "datumEnsemble",    "datumEnsemble",      "Datum ensemble",    DatumEnsemble.class,       // Covariant return type
             IdentifiedObject.class, "getName",             "name",             "name",               "Name",              ReferenceIdentifier.class,
             IdentifiedObject.class, "getAlias",            "alias",            "alias",              "Alias",             GenericName[].class,
             IdentifiedObject.class, "getIdentifiers",      "identifiers",      "identifier",         "Identifiers",       ReferenceIdentifier[].class,
-            IdentifiedObject.class, "getDomains",          "domains",          "ObjectUsage.domain", "Domains",           ObjectDomain[].class,
-            IdentifiedObject.class, "getRemarks",          "remarks",          "remarks",            "Remarks",           InternationalString.class);
+            ReferenceSystem.class,  "getScope",            "scope",            "SC_CRS.scope",     "Scope",               InternationalString.class,
+            ReferenceSystem.class,  "getDomainOfValidity", "domainOfValidity", "domainOfValidity", "Domain of validity",  Extent.class,
+            IdentifiedObject.class, "getRemarks",          "remarks",          "remarks",          "Remarks",             InternationalString.class);
     }
 
     /**
@@ -377,7 +377,7 @@
         /*
          * Compares with the non-deprecated property.
          */
-        final Collection<AttributeGroup> groups = instance.getAttributeGroups();
+        final Collection<DefaultAttributeGroup> groups = instance.getAttributeGroups();
         assertSame(groups, accessor.get(indexOfReplacement, instance));
         assertEquals(CoverageContentType.IMAGE, getSingleton(getSingleton(groups).getContentTypes()));
         /*
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyConsistencyCheck.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyConsistencyCheck.java
index 5ce77cd..55f5504 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyConsistencyCheck.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyConsistencyCheck.java
@@ -34,9 +34,6 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.xml.test.AnnotationConsistencyCheck;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
-
 
 /**
  * Base class for tests done on metadata objects using reflection. This base class tests JAXB annotations
@@ -131,11 +128,11 @@
         if (Date.class.isAssignableFrom(type)) {
             return new Date(random.nextInt() * 1000L);
         }
-        if (ControlledVocabulary.class.isAssignableFrom(type)) try {
+        if (CodeList.class.isAssignableFrom(type)) try {
             if (type == CodeList.class) {
                 return null;
             }
-            final ControlledVocabulary[] codes = (ControlledVocabulary[]) type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
+            final CodeList<?>[] codes = (CodeList<?>[]) type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
             return codes[random.nextInt(codes.length)];
         } catch (ReflectiveOperationException e) {
             fail(e.toString());
@@ -190,7 +187,7 @@
     public void testPropertyValues() {
         random = TestUtilities.createRandomNumberGenerator();
         for (final Class<?> type : types) {
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 final Class<?> impl = getImplementation(type);
                 if (impl != null) {
                     assertTrue(type.isAssignableFrom(impl), "Not an implementation of expected interface.");
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
index 34b2cad..134717d 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
@@ -180,7 +180,7 @@
             "Some title",
             "First alternate title",
             "Some edition",
-            "PresentationForm.MAP_DIGITAL",
+            "PresentationForm[MAP_DIGITAL]",
             "Some other details"
         };
         assertEquals(-1, children.titleProperty);
@@ -202,8 +202,8 @@
             "First alternate title",
             "Second alternate title",
             "Some edition",
-            "PresentationForm.MAP_DIGITAL",
-            "PresentationForm.MAP_HARDCOPY",
+            "PresentationForm[MAP_DIGITAL]",
+            "PresentationForm[MAP_HARDCOPY]",
             "Some other details"
         };
         assertEquals(-1, children.titleProperty);
@@ -231,7 +231,7 @@
         final TreeNodeChildren children = (TreeNodeChildren) node.getChildren();
         final String[] expected = {
             // The "Date" node should be omitted because merged with the parent "Date" node.
-            "DateType.CREATION"
+            "DateType[CREATION]"
         };
         assertEquals(0, children.titleProperty);
         assertFalse (children.isEmpty());
@@ -257,9 +257,9 @@
             "Second alternate title",
             "Third alternate title",                // After addition
             "New edition",                          // After "addition" (actually change).
-            "PresentationForm.IMAGE_DIGITAL",       // After addition
-            "PresentationForm.MAP_DIGITAL",
-            "PresentationForm.MAP_HARDCOPY",
+            "PresentationForm[IMAGE_DIGITAL]",      // After addition
+            "PresentationForm[MAP_DIGITAL]",
+            "PresentationForm[MAP_HARDCOPY]",
             "Some other details"
         };
         toAdd.setValue(TableColumn.IDENTIFIER, "edition");
@@ -355,8 +355,8 @@
             null, // edition date
             null, // identifiers (collection)
             null, // cited responsibly parties (collection)
-            "PresentationForm.MAP_DIGITAL",
-            "PresentationForm.MAP_HARDCOPY",
+            "PresentationForm[MAP_DIGITAL]",
+            "PresentationForm[MAP_HARDCOPY]",
             null, // series
             "Some other details",
 //          null, // collective title                       -- deprecated as of ISO 19115:2014.
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeTest.java
index 5e97e57..4d8bb5d 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeTest.java
@@ -45,9 +45,6 @@
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-
 
 /**
  * Tests the {@link TreeNode} class.
@@ -194,8 +191,8 @@
     private void assertCitationContentEquals(final int offset, final TableColumn<?> column, final Object... expected) {
         if (valuePolicy == ValueExistencePolicy.COMPACT) {
             assertEquals(19, expected.length);
-            System.arraycopy(expected, 11+offset, expected, 10+offset,  8-offset);    // Compact the "Individual" element.
-            System.arraycopy(expected,  7+offset, expected,  6+offset, 11-offset);    // Compact the "Organisation" element.
+            System.arraycopy(expected, 12+offset, expected, 11+offset,  7-offset);    // Compact the "Individual" element.
+            System.arraycopy(expected,  8+offset, expected,  7+offset, 10-offset);    // Compact the "Organisation" element.
             System.arraycopy(expected,  1+offset, expected,    offset, 16-offset);    // Compact the "Title" element.
             Arrays.fill(expected, 16, 19, null);
         }
@@ -214,16 +211,16 @@
               "Alternate title (2 of 2)",
               "Edition",
               "Cited responsible party (1 of 2)",
-                "Organisation",
-                  "Name",                               // In COMPACT mode, this value is associated to "Organisation" node.
                 "Role",
+                "Party",
+                  "Name",                               // In COMPACT mode, this value is associated to "Organisation" node.
               "Cited responsible party (2 of 2)",
-                "Individual",
+                "Role",
+                "Party",
                   "Name",                               // In COMPACT mode, this value is associated to "Individual" node.
                   "Contact info",
                     "Address",
                       "Electronic mail address",
-                "Role",
               "Presentation form (1 of 2)",
               "Presentation form (2 of 2)",
               "Other citation details");
@@ -244,16 +241,16 @@
               "alternateTitle",
               "edition",
               "citedResponsibleParty",
+                "role",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
-                "role",
               "citedResponsibleParty",
+                "role",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
                   "contactInfo",
                     "address",
                       "electronicMailAddress",
-                "role",
               "presentationForm",
               "presentationForm",
               "otherCitationDetails");
@@ -274,16 +271,16 @@
               ONE,          // alternateTitle
               null,         // edition
               ZERO,         // citedResponsibleParty
+                null,       // role
                 ZERO,       // party (organisation)
                   null,     // name                         — in COMPACT mode, this value is associated to "party" node.
-                null,       // role
               ONE,          // citedResponsibleParty
+                null,       // role
                 ZERO,       // party (individual)
                   null,     // name                         — in COMPACT mode, this value is associated to "party" node.
                   ZERO,     // contactInfo
                     ZERO,   // address
                       ZERO, // electronicMailAddress
-                null,       // role
               ZERO,         // presentationForm
               ONE,          // presentationForm
               null);        // otherCitationDetails
@@ -301,16 +298,16 @@
               InternationalString.class,
               InternationalString.class,
               ResponsibleParty.class,
-                Party.class,                            // In COMPACT mode, value with be the one of "name" node instead.
-                  InternationalString.class,            // Name
                 Role.class,
+                AbstractParty.class,                    // In COMPACT mode, value with be the one of "name" node instead.
+                  InternationalString.class,            // Name
               ResponsibleParty.class,
-                Party.class,                            // In COMPACT mode, value with be the one of "name" node instead.
+                Role.class,
+                AbstractParty.class,                    // In COMPACT mode, value with be the one of "name" node instead.
                   InternationalString.class,            // Name
                   Contact.class,
                     Address.class,
                       String.class,
-                Role.class,
               PresentationForm.class,
               PresentationForm.class,
               InternationalString.class);
@@ -328,16 +325,16 @@
               "Second alternate title",
               "Some edition",
               null,                             // ResponsibleParty
+                Role.DISTRIBUTOR,
                 null,                           // Party (organisation)
                   "Some organisation",
-                Role.DISTRIBUTOR,
               null,                             // ResponsibleParty
+                Role.POINT_OF_CONTACT,
                 null,                           // Party (individual)
                   "Some person of contact",
                   null,                         // Contact
                     null,                       // Address
                       "Some email",
-                Role.POINT_OF_CONTACT,
               PresentationForm.MAP_DIGITAL,
               PresentationForm.MAP_HARDCOPY,
               "Some other details");
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java
index 3a76924..0e50bed 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java
@@ -84,9 +84,10 @@
             "Citation……………………………………………………………………………… Undercurrent\n" +
             "  ├─Alternate title………………………………………………… Andākarento\n" +
             "  ├─Cited responsible party (1 of 2)\n" +
-            "  │   ├─Individual…………………………………………………… Testsuya Toyoda\n" +
-            "  │   └─Role…………………………………………………………………… Author\n" +
+            "  │   ├─Role…………………………………………………………………… Author\n" +
+            "  │   └─Party………………………………………………………………… Testsuya Toyoda\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
+            "  │   ├─Role…………………………………………………………………… EDITOR\n" +
             "  │   ├─Extent……………………………………………………………… World\n" +
             "  │   │   └─Geographic element\n" +
             "  │   │       ├─West bound longitude…… 180°W\n" +
@@ -94,8 +95,7 @@
             "  │   │       ├─South bound latitude…… 90°S\n" +
             "  │   │       ├─North bound latitude…… 90°N\n" +
             "  │   │       └─Extent type code……………… True\n" +
-            "  │   ├─Organisation……………………………………………… Kōdansha\n" +
-            "  │   └─Role…………………………………………………………………… Editor\n" +
+            "  │   └─Party………………………………………………………………… Kōdansha\n" +
             "  ├─Presentation form (1 of 2)…………………… Document digital\n" +
             "  ├─Presentation form (2 of 2)…………………… Document hardcopy\n" +
             "  └─ISBN……………………………………………………………………………… 9782505004509\n", text);
@@ -164,7 +164,7 @@
 
         citation.setPresentationForms(List.of(
                 PresentationForm.IMAGE_DIGITAL,
-                PresentationForm.valueOf("AUDIO_DIGITAL"),  // Existing form
+                PresentationForm.valueOf("TABLE_DIGITAL"),  // Existing form
                 PresentationForm.valueOf("test")));         // Custom form
 
         final String text = format.format(citation.asTreeTable());
@@ -174,7 +174,7 @@
             "  ├─Alternate title (2 of 3)………… Orange\n" +
             "  ├─Alternate title (3 of 3)………… Kiwi\n" +
             "  ├─Presentation form (1 of 3)…… Image digital\n" +
-            "  ├─Presentation form (2 of 3)…… Audio digital\n" +
+            "  ├─Presentation form (2 of 3)…… Table digital\n" +
             "  └─Presentation form (3 of 3)…… Test\n",
             text);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java
index 8b3e026..ddacace 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java
@@ -70,14 +70,14 @@
             "  ├─Alternate title (2 of 2)…………………………………………… Second alternate title\n" +
             "  ├─Edition………………………………………………………………………………………… Some edition\n" +
             "  ├─Cited responsible party (1 of 2)\n" +
-            "  │   ├─Organisation………………………………………………………………… Some organisation\n" +
-            "  │   └─Role……………………………………………………………………………………… Distributor\n" +
+            "  │   ├─Role……………………………………………………………………………………… Distributor\n" +
+            "  │   └─Party…………………………………………………………………………………… Some organisation\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
-            "  │   ├─Individual……………………………………………………………………… Some person of contact\n" +
-            "  │   │   └─Contact info\n" +
-            "  │   │       └─Address\n" +
-            "  │   │           └─Electronic mail address…… Some email\n" +
-            "  │   └─Role……………………………………………………………………………………… Point of contact\n" +
+            "  │   ├─Role……………………………………………………………………………………… Point of contact\n" +
+            "  │   └─Party…………………………………………………………………………………… Some person of contact\n" +
+            "  │       └─Contact info\n" +
+            "  │           └─Address\n" +
+            "  │               └─Electronic mail address…… Some email\n" +
             "  ├─Presentation form (1 of 2)……………………………………… Map digital\n" +
             "  ├─Presentation form (2 of 2)……………………………………… Map hardcopy\n" +
             "  └─Other citation details………………………………………………… Some other details\n";
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/AllMetadataTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/AllMetadataTest.java
index db175ce..b977e2a 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/AllMetadataTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/AllMetadataTest.java
@@ -24,9 +24,8 @@
 // Test dependencies
 import org.junit.jupiter.api.Test;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.annotation.Stereotype;
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -41,14 +40,12 @@
     @SuppressWarnings("deprecation")
     public AllMetadataTest() {
         super(MetadataStandard.ISO_19115,
-            org.opengis.annotation.Obligation.class,
             org.opengis.metadata.ApplicationSchemaInformation.class,
             org.opengis.metadata.Datatype.class,
             org.opengis.metadata.ExtendedElementInformation.class,
             org.opengis.metadata.Identifier.class,
             org.opengis.metadata.Metadata.class,
             org.opengis.metadata.MetadataExtensionInformation.class,
-            org.opengis.metadata.MetadataScope.class,
             org.opengis.metadata.PortrayalCatalogueReference.class,
             org.opengis.metadata.acquisition.AcquisitionInformation.class,
             org.opengis.metadata.acquisition.Context.class,
@@ -75,9 +72,7 @@
             org.opengis.metadata.citation.DateType.class,
             org.opengis.metadata.citation.OnLineFunction.class,
             org.opengis.metadata.citation.OnlineResource.class,
-            org.opengis.metadata.citation.Party.class,
             org.opengis.metadata.citation.PresentationForm.class,
-            org.opengis.metadata.citation.Responsibility.class,
             org.opengis.metadata.citation.ResponsibleParty.class,
             org.opengis.metadata.citation.Role.class,
             org.opengis.metadata.citation.Series.class,
@@ -87,7 +82,6 @@
             org.opengis.metadata.constraint.LegalConstraints.class,
             org.opengis.metadata.constraint.Restriction.class,
             org.opengis.metadata.constraint.SecurityConstraints.class,
-            org.opengis.metadata.content.AttributeGroup.class,
             org.opengis.metadata.content.Band.class,
             org.opengis.metadata.content.BandDefinition.class,
             org.opengis.metadata.content.ContentInformation.class,
@@ -99,7 +93,6 @@
             org.opengis.metadata.content.PolarizationOrientation.class,
             org.opengis.metadata.content.RangeDimension.class,
             org.opengis.metadata.content.RangeElementDescription.class,
-            org.opengis.metadata.content.SampleDimension.class,
             org.opengis.metadata.content.TransferFunctionType.class,
             org.opengis.metadata.distribution.DataFile.class,
             org.opengis.metadata.distribution.DigitalTransferOptions.class,
@@ -119,20 +112,15 @@
             org.opengis.metadata.extent.TemporalExtent.class,
             org.opengis.metadata.extent.VerticalExtent.class,
             org.opengis.metadata.identification.AggregateInformation.class,
-            org.opengis.metadata.identification.AssociatedResource.class,
             org.opengis.metadata.identification.AssociationType.class,
             org.opengis.metadata.identification.BrowseGraphic.class,
             org.opengis.metadata.identification.CharacterSet.class,
-            org.opengis.metadata.identification.CoupledResource.class,
             org.opengis.metadata.identification.DataIdentification.class,
             org.opengis.metadata.identification.Identification.class,
             org.opengis.metadata.identification.InitiativeType.class,
             org.opengis.metadata.identification.Keywords.class,
-            org.opengis.metadata.identification.KeywordClass.class,
             org.opengis.metadata.identification.KeywordType.class,
             org.opengis.metadata.identification.Progress.class,
-            org.opengis.metadata.identification.OperationChainMetadata.class,
-            org.opengis.metadata.identification.OperationMetadata.class,
             org.opengis.metadata.identification.RepresentativeFraction.class,
             org.opengis.metadata.identification.Resolution.class,
             org.opengis.metadata.identification.ServiceIdentification.class,
@@ -147,57 +135,36 @@
             org.opengis.metadata.lineage.Source.class,
             org.opengis.metadata.maintenance.MaintenanceFrequency.class,
             org.opengis.metadata.maintenance.MaintenanceInformation.class,
-            org.opengis.metadata.maintenance.Scope.class,
             org.opengis.metadata.maintenance.ScopeCode.class,
             org.opengis.metadata.maintenance.ScopeDescription.class,
             org.opengis.metadata.quality.AbsoluteExternalPositionalAccuracy.class,
             org.opengis.metadata.quality.AccuracyOfATimeMeasurement.class,
-            org.opengis.metadata.quality.AggregationDerivation.class,
-            org.opengis.metadata.quality.BasicMeasure.class,
             org.opengis.metadata.quality.Completeness.class,
             org.opengis.metadata.quality.CompletenessCommission.class,
             org.opengis.metadata.quality.CompletenessOmission.class,
             org.opengis.metadata.quality.ConceptualConsistency.class,
-            org.opengis.metadata.quality.Confidence.class,
             org.opengis.metadata.quality.ConformanceResult.class,
             org.opengis.metadata.quality.CoverageResult.class,
-            org.opengis.metadata.quality.DataEvaluation.class,
             org.opengis.metadata.quality.DataQuality.class,
-//          org.opengis.metadata.quality.Description.class,                 // Pending ISO 19157:2022 renaming.
-            org.opengis.metadata.quality.DescriptiveResult.class,
             org.opengis.metadata.quality.DomainConsistency.class,
             org.opengis.metadata.quality.Element.class,
-            org.opengis.metadata.quality.EvaluationMethod.class,
             org.opengis.metadata.quality.EvaluationMethodType.class,
             org.opengis.metadata.quality.FormatConsistency.class,
-            org.opengis.metadata.quality.FullInspection.class,
             org.opengis.metadata.quality.GriddedDataPositionalAccuracy.class,
-            org.opengis.metadata.quality.Homogeneity.class,
-            org.opengis.metadata.quality.IndirectEvaluation.class,
             org.opengis.metadata.quality.LogicalConsistency.class,
-//          org.opengis.metadata.quality.Measure.class,                     // Pending ISO 19157:2022 renaming.
-            org.opengis.metadata.quality.MeasureReference.class,
-            org.opengis.metadata.quality.Metaquality.class,
             org.opengis.metadata.quality.NonQuantitativeAttributeAccuracy.class,
-            org.opengis.metadata.quality.NonQuantitativeAttributeCorrectness.class,
             org.opengis.metadata.quality.PositionalAccuracy.class,
             org.opengis.metadata.quality.QuantitativeAttributeAccuracy.class,
             org.opengis.metadata.quality.QuantitativeResult.class,
             org.opengis.metadata.quality.RelativeInternalPositionalAccuracy.class,
-            org.opengis.metadata.quality.Representativity.class,
             org.opengis.metadata.quality.Result.class,
-            org.opengis.metadata.quality.SampleBasedInspection.class,
-            org.opengis.metadata.quality.SourceReference.class,
-//          org.opengis.metadata.quality.StandaloneQualityReportInformation.class,      // Pending ISO 19157:2022 renaming.
             org.opengis.metadata.quality.TemporalAccuracy.class,
             org.opengis.metadata.quality.TemporalConsistency.class,
-            org.opengis.metadata.quality.TemporalQuality.class,
             org.opengis.metadata.quality.TemporalValidity.class,
             org.opengis.metadata.quality.ThematicAccuracy.class,
             org.opengis.metadata.quality.ThematicClassificationCorrectness.class,
             org.opengis.metadata.quality.TopologicalConsistency.class,
             org.opengis.metadata.quality.Usability.class,
-            org.opengis.metadata.quality.ValueStructure.class,
             org.opengis.metadata.spatial.CellGeometry.class,
             org.opengis.metadata.spatial.Dimension.class,
             org.opengis.metadata.spatial.DimensionNameType.class,
@@ -237,10 +204,13 @@
      * Returns the name of the XML root element for an interface described by the given UML.
      * This method does the generic work described in super-class, then applies special rules
      * specific to the metadata package.
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
     @Override
-    protected String getExpectedXmlRootElementName(final Stereotype stereotype, final UML uml) {
-        String name = super.getExpectedXmlRootElementName(stereotype, uml);
+    @Deprecated
+    protected String getExpectedXmlRootElementName(final UML uml) {
+        String name = super.getExpectedXmlRootElementName(uml);
         if (name.equals("DQ_CoverageResult")) name = "QE_CoverageResult";
         return name;
     }
@@ -249,10 +219,13 @@
      * Returns the name of the XML type for an interface described by the given UML.
      * This method does the generic work described in super-class, then applies special rules
      * specific to the metadata package.
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
     @Override
-    protected String getExpectedXmlTypeName(final Stereotype stereotype, final UML uml) {
-        String name = super.getExpectedXmlTypeName(stereotype, uml);
+    @Deprecated
+    protected String getExpectedXmlTypeName(final UML uml) {
+        String name = super.getExpectedXmlTypeName(uml);
         if (name.equals("DQ_CoverageResult_Type")) name = "QE_CoverageResult_Type";
         return name;
     }
@@ -274,9 +247,13 @@
              */
             return null;
         }
+        String identifier = type.getAnnotation(UML.class).identifier();
+        if (identifier.equals("MI_PolarizationOrientationCode")) {
+            identifier = "MI_PolarisationOrientationCode";
+            // https://issues.apache.org/jira/browse/SIS-398
+        }
         final String classname = "org.apache.sis.xml.bind.metadata." +
-                (ControlledVocabulary.class.isAssignableFrom(type) ? "code." : "") +
-                type.getAnnotation(UML.class).identifier();
+                (CodeList.class.isAssignableFrom(type) ? "code." : "") + identifier;
         final Class<?> wrapper = Class.forName(classname);
         Class<?>[] expectedFinalClasses = wrapper.getClasses();   // "Since2014" internal class.
         if (expectedFinalClasses.length == 0) {
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/CustomMetadataTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/CustomMetadataTest.java
index 449673a..3847257 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/CustomMetadataTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/CustomMetadataTest.java
@@ -40,6 +40,13 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.xml.test.TestCase;
 
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.constraint.Constraints;
+import org.opengis.metadata.distribution.Format;
+import org.opengis.metadata.maintenance.MaintenanceInformation;
+import org.opengis.metadata.spatial.SpatialRepresentationType;
+
 
 /**
  * Tests XML marshalling of custom implementation of metadata interfaces. The custom implementations
@@ -109,9 +116,25 @@
                 return factory.createInternationalString(names);
             }
 
-            @Override public Citation                  getCitation()           {return null;}
-            @Override public Collection<TopicCategory> getTopicCategories()    {return null;}
-            @Override public Collection<Extent>        getExtents()            {return null;}
+            @Override public InternationalString                   getSupplementalInformation()    {return null;}
+            @Override public Citation                              getCitation()                   {return null;}
+            @Override public InternationalString                   getPurpose()                    {return null;}
+            @Override public Collection<SpatialRepresentationType> getSpatialRepresentationTypes() {return null;}
+            @Override public Collection<Resolution>                getSpatialResolutions()         {return null;}
+            @Override public Collection<Locale>                    getLanguages()                  {return null;}
+            @Override public Collection<CharacterSet>              getCharacterSets()              {return null;}
+            @Override public Collection<TopicCategory>             getTopicCategories()            {return null;}
+            @Override public Collection<Extent>                    getExtents()                    {return null;}
+            @Override public Collection<String>                    getCredits()                    {return null;}
+            @Override public Collection<Progress>                  getStatus()                     {return null;}
+            @Override public Collection<ResponsibleParty>          getPointOfContacts()            {return null;}
+            @Override public Collection<MaintenanceInformation>    getResourceMaintenances()       {return null;}
+            @Override public Collection<BrowseGraphic>             getGraphicOverviews()           {return null;}
+            @Override public Collection<Format>                    getResourceFormats()            {return null;}
+            @Override public Collection<Keywords>                  getDescriptiveKeywords()        {return null;}
+            @Override public Collection<Usage>                     getResourceSpecificUsages()     {return null;}
+            @Override public Collection<Constraints>               getResourceConstraints()        {return null;}
+@Deprecated @Override public Collection<AggregateInformation>      getAggregationInfo()            {return null;}
         };
         final DefaultMetadata data = new DefaultMetadata();
         data.setIdentificationInfo(Set.of(identification));
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/DefaultMetadataTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/DefaultMetadataTest.java
index 0366df6..d983e58 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/DefaultMetadataTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/DefaultMetadataTest.java
@@ -40,9 +40,6 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.MetadataScope;
-
 
 /**
  * Tests {@link DefaultMetadata}, without Coordinate Reference System (CRS) information.
@@ -186,9 +183,9 @@
         /*
          * The above deprecated methods shall have created MetadataScope object. Verify that.
          */
-        final Collection<MetadataScope> scopes = metadata.getMetadataScopes();
-        final Iterator<MetadataScope> it = scopes.iterator();
-        MetadataScope scope = it.next();
+        final Collection<DefaultMetadataScope> scopes = metadata.getMetadataScopes();
+        final Iterator<DefaultMetadataScope> it = scopes.iterator();
+        DefaultMetadataScope scope = it.next();
         assertEquals("Bridges", scope.getName().toString());
         assertEquals(ScopeCode.FEATURE_TYPE, scope.getResourceScope());
         scope = it.next();
@@ -232,7 +229,7 @@
          */
         Date creation = date("2014-10-07 00:00:00");
         final var dates = new DefaultCitationDate[] {
-                new DefaultCitationDate(date("2014-10-09 00:00:00"), DateType.LAST_UPDATE),
+                new DefaultCitationDate(date("2014-10-09 00:00:00"), DateType.valueOf("LAST_UPDATE")),
                 new DefaultCitationDate(creation, DateType.CREATION)
         };
         metadata.setDateInfo(Arrays.asList(dates));
@@ -281,7 +278,5 @@
         final var metadata = new DefaultMetadata();
         metadata.setDataSetUri("file:/tmp/myfile.txt");
         assertEquals("file:/tmp/myfile.txt", metadata.getDataSetUri());
-        assertEquals("file:/tmp/myfile.txt", getSingleton(getSingleton(metadata.getIdentificationInfo())
-                .getCitation().getOnlineResources()).getLinkage().toString());
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/api-changes.properties b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/api-changes.properties
index a1687d2..ae642b7 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/api-changes.properties
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/api-changes.properties
@@ -22,3 +22,36 @@
 # with the changes in the international standard. The UML identifiers of added methods are "number"
 # and "numberType" respectively.
 #
+org.opengis.metadata.citation.Citation=-getCollectiveTitle +getGraphics:graphic +getOnlineResources:onlineResource
+org.opengis.metadata.citation.Contact=-getAddress +getAddresses:address +getContactType:contactType -getOnlineResource +getOnlineResources:onlineResource -getPhone +getPhones:phone
+org.opengis.metadata.citation.OnlineResource=+getProtocolRequest:protocolRequest
+org.opengis.metadata.citation.ResponsibleParty=-getContactInfo -getIndividualName -getOrganisationName -getPositionName
+org.opengis.metadata.citation.Telephone=-getFacsimiles +getNumber:number +getNumberType:numberType -getVoices
+org.opengis.metadata.constraint.Constraints=+getConstraintApplicationScope:constraintApplicationScope +getGraphics:graphic +getReferences:reference +getReleasability:releasability +getResponsibleParties:responsibleParty
+org.opengis.metadata.content.Band=+getBoundMax:boundMax +getBoundMin:boundMin +getBoundUnits:boundUnits
+org.opengis.metadata.content.CoverageDescription=+getAttributeGroups:attributeGroup -getContentType -getDimensions +getProcessingLevelCode:processingLevelCode
+org.opengis.metadata.content.FeatureCatalogueDescription=+getFeatureTypeInfo:featureTypes -getFeatureTypes -getLanguages +getLocalesAndCharsets:locale
+org.opengis.metadata.content.RangeDimension=+getDescription:description -getDescriptor +getNames:name
+org.opengis.metadata.distribution.DigitalTransferOptions=+getDistributionFormats:distributionFormat -getOffLine +getOffLines:offLine +getTransferFrequency:transferFrequency
+org.opengis.metadata.distribution.Distribution=+getDescription:description
+org.opengis.metadata.distribution.Format=+getFormatSpecificationCitation:formatSpecificationCitation +getMedia:medium -getName -getSpecification -getVersion
+org.opengis.metadata.distribution.Medium=-getDensities +getDensity:density +getIdentifier:identifier
+org.opengis.metadata.distribution.StandardOrderProcess=+getOrderOptionsType:orderOptionsType +getOrderOptions:orderOptions
+org.opengis.metadata.ExtendedElementInformation=-getDomainCode +getRationale:rationale -getRationales -getShortName
+org.opengis.metadata.extent.SpatialTemporalExtent=+getVerticalExtent:verticalExtent
+org.opengis.metadata.identification.AggregateInformation=-getAggregateDataSetIdentifier -getAggregateDataSetName +getMetadataReference:metadataReference +getName:name
+org.opengis.metadata.identification.BrowseGraphic=+getImageConstraints:imageContraints +getLinkages:linkage
+org.opengis.metadata.identification.DataIdentification=-getCharacterSets -getLanguages +getLocalesAndCharsets:defaultLocale+otherLocale
+org.opengis.metadata.identification.Identification=+getAdditionalDocumentations:additionalDocumentation -getAggregationInfo +getAssociatedResources:associatedResource +getExtents:extent +getProcessingLevel:processingLevel +getSpatialRepresentationTypes:spatialRepresentationType +getSpatialResolutions:spatialResolution ~+getTemporalResolutions:temporalResolution +getTopicCategories:topicCategory
+org.opengis.metadata.identification.Keywords=+getKeywordClass:keywordClass
+org.opengis.metadata.identification.Resolution=+getAngularDistance:angularDistance +getLevelOfDetail:levelOfDetail +getVertical:vertical
+org.opengis.metadata.identification.ServiceIdentification=+getAccessProperties:accessProperties +getContainsChain:containsChain +getContainsOperations:containsOperations +getCoupledResources:coupledResource +getCouplingType:couplingType +getOperatedDatasets:operatedDataset +getOperatesOn:operatesOn +getProfiles:profile +getServiceStandards:serviceStandard +getServiceType:serviceType +getServiceTypeVersions:serviceTypeVersion
+org.opengis.metadata.identification.Usage=+getAdditionalDocumentation:additionalDocumentation +getIdentifiedIssues:identifiedIssues +getResponses:response
+org.opengis.metadata.Identifier=+getCodeSpace:codeSpace +getDescription:description +getVersion:version
+org.opengis.metadata.lineage.Lineage=+getAdditionalDocumentation:additionalDocumentation +getScope:scope
+org.opengis.metadata.lineage.ProcessStep=-getDate +getReferences:reference +getScope:scope
+org.opengis.metadata.lineage.Source=-getScaleDenominator +getScope:scope -getSourceExtents +getSourceMetadata:sourceMetadata +getSourceSpatialResolution:sourceSpatialResolution
+org.opengis.metadata.maintenance.MaintenanceInformation=-getDateOfNextUpdate +getMaintenanceDates:maintenanceDate +getMaintenanceScopes:maintenanceScope -getUpdateScopeDescriptions -getUpdateScopes
+org.opengis.metadata.Metadata=+getAlternativeMetadataReferences:alternativeMetadataReference -getCharacterSet -getCharacterSets -getDataSetUri -getDateStamp +getDateInfo:dateInfo -getFileIdentifier -getHierarchyLevelNames -getHierarchyLevels -getLanguage -getLanguages -getLocales +getLocalesAndCharsets:defaultLocale+otherLocale +getMetadataIdentifier:metadataIdentifier +getMetadataLinkages:metadataLinkage +getMetadataProfiles:metadataProfile +getMetadataScopes:metadataScope -getMetadataStandardName -getMetadataStandardVersion +getMetadataStandards:metadataStandard -getParentIdentifier +getParentMetadata:parentMetadata +getResourceLineages:resourceLineage
+org.opengis.metadata.quality.Scope=-getExtent +getExtents:extent
+org.opengis.metadata.spatial.Dimension=+getDimensionDescription:dimensionDescription +getDimensionTitle:dimensionTitle
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java
index 8be419e..c79a6d7 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java
@@ -40,6 +40,9 @@
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Tests {@link Citations}.
@@ -122,20 +125,20 @@
      */
     @Test
     public void testGetIdentifier() {
-        assertEquals("Apache:SIS",  getIdentifier(SIS));
+        assertEquals("SIS",         getIdentifier(SIS));
         assertEquals("OGC",         getIdentifier(OGC));
         assertEquals("IOGP",        getIdentifier(IOGP));
         assertEquals("EPSG",        getIdentifier(EPSG));
-        assertEquals("ESRI:ArcGIS", getIdentifier(ESRI));
+        assertEquals("ArcGIS",      getIdentifier(ESRI));
         assertEquals("NetCDF",      getIdentifier(NETCDF));
         assertEquals("GeoTIFF",     getIdentifier(GEOTIFF));
         assertEquals("ISBN",        getIdentifier(ISBN));
         assertEquals("ISSN",        getIdentifier(ISSN));
-        assertEquals("OSGeo:PROJ",  getIdentifier(PROJ4));              // Not a valid Unicode identifier.
-        assertEquals("IHO:S-57",    getIdentifier(S57));                // Not a valid Unicode identifier.
-        assertEquals("ISO:19115-1", getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
-        assertEquals("ISO:19115-2", getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
-        assertEquals("OGC:WMS",     getIdentifier(WMS));
+        assertEquals("PROJ",        getIdentifier(PROJ4));              // Not a valid Unicode identifier.
+        assertEquals("S-57",        getIdentifier(S57));                // Not a valid Unicode identifier.
+        assertEquals("19115-1",     getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
+        assertEquals("19115-2",     getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
+        assertEquals("WMS",         getIdentifier(WMS));
     }
 
     /**
@@ -219,6 +222,7 @@
      * Tests {@code getCitedResponsibleParties()} on some {@code Citation} constants.
      */
     @Test
+    @org.junit.jupiter.api.Disabled("Requires GeoAPI 3.1.")
     public void testGetCitedResponsibleParty() {
         assertEquals("Open Geospatial Consortium",                       getCitedResponsibleParty(OGC));
         assertEquals("International Organization for Standardization",   getCitedResponsibleParty(ISO_19115.get(0)));
@@ -231,7 +235,7 @@
      * Returns the responsible party for the given constant.
      */
     private static String getCitedResponsibleParty(final Citation citation) {
-        return getSingleton(getSingleton(citation.getCitedResponsibleParties()).getParties()).getName().toString(Locale.US);
+        return getSingleton(citation.getCitedResponsibleParties()).getOrganisationName().toString(Locale.US);
     }
 
     /**
@@ -245,7 +249,7 @@
     public void testEPSG() {
         final Identifier identifier = getSingleton(EPSG.getIdentifiers());
         assertEquals("EPSG", toCodeSpace(EPSG));
-        assertEquals("IOGP", identifier.getCodeSpace());
+//      assertEquals("IOGP", identifier.getCodeSpace());
         assertEquals("EPSG", identifier.getCode());
     }
 
@@ -281,17 +285,24 @@
      */
     @Test
     public void testIdentifierMatches() {
-        final var ogc = new DefaultIdentifier("OGC", "06-042", null);
-        final var iso = new DefaultIdentifier("ISO", "19128", null);
+        final var ogc = new Id("OGC", "06-042");
+        final var iso = new Id("ISO", "19128");
         final var citation = new DefaultCitation("Web Map Server");
         citation.setIdentifiers(List.of(ogc, iso, new DefaultIdentifier("Foo", "06-042", null)));
         assertTrue (/* With full identifier */ Citations.identifierMatches(citation, ogc, ogc.getCode()));
         assertTrue (/* With full identifier */ Citations.identifierMatches(citation, iso, iso.getCode()));
-        assertFalse(/* With wrong code      */ Citations.identifierMatches(citation, new DefaultIdentifier("ISO", "19115", null), "19115"));
-        assertFalse(/* With wrong codespace */ Citations.identifierMatches(citation, new DefaultIdentifier("Foo", "19128", null), "19128"));
+        assertFalse(/* With wrong code      */ Citations.identifierMatches(citation, new Id("ISO", "19115"), "19115"));
+        assertFalse(/* With wrong codespace */ Citations.identifierMatches(citation, new Id("Foo", "19128"), "19128"));
         assertFalse(/* With wrong code      */ Citations.identifierMatches(citation, "Foo"));
         assertTrue (/* Without identifier   */ Citations.identifierMatches(citation, "19128"));
         assertTrue (/* With parsing         */ Citations.identifierMatches(citation, "ISO:19128"));
         assertFalse(/* With wrong codespace */ Citations.identifierMatches(citation, "Foo:19128"));
     }
+
+    @SuppressWarnings("serial")
+    private static final class Id extends DefaultIdentifier implements ReferenceIdentifier {
+        Id(String codeSpace, String code) {
+            super(codeSpace, code, null);
+        }
+    }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
index da6501d..4dc32ca 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
@@ -52,9 +52,8 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -101,7 +100,7 @@
         final DefaultResponsibleParty author = new DefaultResponsibleParty(Role.AUTHOR);
         author.setParties(Set.of(new DefaultIndividual("Testsuya Toyoda", null, null)));
 
-        final DefaultResponsibleParty editor = new DefaultResponsibleParty(Role.EDITOR);
+        final DefaultResponsibleParty editor = new DefaultResponsibleParty(Role.valueOf("EDITOR"));
         editor.setParties(Set.of(new DefaultOrganisation("Kōdansha", null, null, null)));
         editor.setExtents(Set.of(Extents.WORLD));
 
@@ -197,12 +196,12 @@
         /*
          * Verify the author metadata.
          */
-        final Responsibility re = CollectionsExt.first(original.getCitedResponsibleParties());
-        final Responsibility ra = CollectionsExt.first(clone   .getCitedResponsibleParties());
+        final ResponsibleParty re = CollectionsExt.first(original.getCitedResponsibleParties());
+        final ResponsibleParty ra = CollectionsExt.first(clone   .getCitedResponsibleParties());
         assertNotSame(re, ra);
         assertSame(re.getRole(), ra.getRole());
-        assertSame(getSingleton(re.getParties()).getName(),
-                   getSingleton(ra.getParties()).getName());
+        assertSame(re.getIndividualName(),
+                   ra.getIndividualName());
     }
 
     /**
@@ -254,12 +253,12 @@
         contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "ip-protocol");
         final DefaultCitation c = new DefaultCitation("Fight against poverty");
         final DefaultResponsibleParty r1 = new DefaultResponsibleParty(Role.ORIGINATOR);
-        final DefaultResponsibleParty r2 = new DefaultResponsibleParty(Role.FUNDER);
+        final DefaultResponsibleParty r2 = new DefaultResponsibleParty(Role.valueOf("funder"));
         r1.setParties(Set.of(new DefaultIndividual("Maid Marian", null, contact)));
         r2.setParties(Set.of(new DefaultIndividual("Robin Hood",  null, contact)));
         c.setCitedResponsibleParties(List.of(r1, r2));
-        c.getDates().add(new DefaultCitationDate(TestUtilities.date("2015-10-17 00:00:00"), DateType.ADOPTED));
-        c.getPresentationForms().add(PresentationForm.PHYSICAL_OBJECT);
+        c.getDates().add(new DefaultCitationDate(TestUtilities.date("2015-10-17 00:00:00"), DateType.valueOf("adopted")));
+        c.getPresentationForms().add(PresentationForm.valueOf("physicalObject"));
         /*
          * Check that XML file built by the marshaller is the same as the example file.
          */
@@ -308,30 +307,30 @@
 
         final CitationDate date = getSingleton(c.getDates());
         assertEquals(date.getDate(), TestUtilities.date("2015-10-17 00:00:00"));
-        assertEquals(DateType.ADOPTED, date.getDateType());
-        assertEquals(PresentationForm.PHYSICAL_OBJECT, getSingleton(c.getPresentationForms()));
+        assertEquals(DateType.valueOf("adopted"), date.getDateType());
+        assertEquals(PresentationForm.valueOf("physicalObject"), getSingleton(c.getPresentationForms()));
 
-        final Iterator<? extends Responsibility> it = c.getCitedResponsibleParties().iterator();
+        final Iterator<? extends ResponsibleParty> it = c.getCitedResponsibleParties().iterator();
         final Contact contact = assertResponsibilityEquals(Role.ORIGINATOR, "Maid Marian", it.next());
         assertEquals("Send carrier pigeon.", String.valueOf(contact.getContactInstructions()));
 
-        final OnlineResource resource = TestUtilities.getSingleton(contact.getOnlineResources());
+        final OnlineResource resource = contact.getOnlineResource();
         assertEquals("IP over Avian Carriers", String.valueOf(resource.getName()));
         assertEquals("High delay, low throughput, and low altitude service.", String.valueOf(resource.getDescription()));
         assertEquals("https://tools.ietf.org/html/rfc1149", String.valueOf(resource.getLinkage()));
         assertEquals(OnLineFunction.OFFLINE_ACCESS, resource.getFunction());
 
         // Thanks to xlink:href, the Contact shall be the same instance as above.
-        assertSame(contact, assertResponsibilityEquals(Role.FUNDER, "Robin Hood", it.next()));
+        assertSame(contact, assertResponsibilityEquals(Role.valueOf("funder"), "Robin Hood", it.next()));
         assertFalse(it.hasNext());
     }
 
     /**
      * Asserts that the given responsibility has the expected properties, then returns its contact info.
      */
-    private static Contact assertResponsibilityEquals(final Role role, final String name, final Responsibility actual) {
+    private static Contact assertResponsibilityEquals(final Role role, final String name, final ResponsibleParty actual) {
         assertEquals(role, actual.getRole());
-        final Party p = getSingleton(actual.getParties());
+        final AbstractParty p = getSingleton(((DefaultResponsibleParty) actual).getParties());
         assertEquals(name, String.valueOf(p.getName()));
         return getSingleton(p.getContactInfo());
     }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
index a358d61..8458e8b 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
@@ -28,8 +28,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.xml.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.TelephoneType;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList;
 
 
 /**
@@ -87,10 +87,10 @@
     @SuppressWarnings("deprecation")
     public void testSetPhones() {
         init();
-        final DefaultTelephone   tel1 = new DefaultTelephone("00.01", TelephoneType.SMS);
-        final DefaultTelephone   tel2 = new DefaultTelephone("00.02", TelephoneType.VOICE);
-        final DefaultTelephone   tel3 = new DefaultTelephone("00.03", TelephoneType.FACSIMILE);
-        final DefaultTelephone   tel4 = new DefaultTelephone("00.04", TelephoneType.VOICE);
+        final DefaultTelephone   tel1 = new DefaultTelephone("00.01", UnsupportedCodeList.valueOf("SMS"));
+        final DefaultTelephone   tel2 = new DefaultTelephone("00.02", UnsupportedCodeList.VOICE);
+        final DefaultTelephone   tel3 = new DefaultTelephone("00.03", UnsupportedCodeList.FACSIMILE);
+        final DefaultTelephone   tel4 = new DefaultTelephone("00.04", UnsupportedCodeList.VOICE);
         final DefaultTelephone[] tels = new DefaultTelephone[] {tel1, tel2, tel3, tel4};
         final DefaultContact  contact = new DefaultContact();
         contact.setPhones(List.of(tel1, tel2, tel3, tel4));
@@ -101,7 +101,7 @@
          */
         assertSame(tel2, contact.getPhone());       // Shall ignore the TelephoneType.SMS.
         assertEquals("IgnoredPropertyAssociatedTo_1", resourceKey);
-        assertArrayEquals(new String[] {"TelephoneType.SMS"}, parameters);
+        assertArrayEquals(new String[] {"SMS"}, parameters);
         verifyLegacyLists(tels);
     }
 
@@ -157,8 +157,6 @@
         final Telephone view;
         if (hideSIS) {
             view = new Telephone() {
-                @Override public String             getNumber()     {return tel.getNumber();}
-                @Override public TelephoneType      getNumberType() {return tel.getNumberType();}
                 @Override public Collection<String> getVoices()     {return tel.getVoices();}
                 @Override public Collection<String> getFacsimiles() {return tel.getFacsimiles();}
             };
@@ -173,9 +171,9 @@
         contact.setPhone(view);
         verifyLegacyLists(view);
         assertArrayEquals(new DefaultTelephone[] {
-                new DefaultTelephone("00.02", TelephoneType.VOICE),
-                new DefaultTelephone("00.04", TelephoneType.VOICE),
-                new DefaultTelephone("00.03", TelephoneType.FACSIMILE)
+                new DefaultTelephone("00.02", UnsupportedCodeList.VOICE),
+                new DefaultTelephone("00.04", UnsupportedCodeList.VOICE),
+                new DefaultTelephone("00.03", UnsupportedCodeList.FACSIMILE)
             }, contact.getPhones().toArray());
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
index 198f133..7023efc 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
@@ -61,7 +61,7 @@
                 "        <gco:CharacterString>An author</gco:CharacterString>\n" +
                 "      </gmd:individualName>\n" +
                 "      <gmd:role>\n" +
-                "        <gmd:CI_RoleCode codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#CI_RoleCode\" codeListValue=\"author\" codeSpace=\"eng\">Author</gmd:CI_RoleCode>\n" +
+                "        <gmd:CI_RoleCode codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#CI_RoleCode\" codeListValue=\"author\">Author</gmd:CI_RoleCode>\n" +
                 "      </gmd:role>\n" +
                 "    </gmd:CI_ResponsibleParty>\n" +
                 "  </gmd:citedResponsibleParty>\n" +
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
index ecd7f42..a39979e 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
@@ -71,7 +71,7 @@
     }
 
     /**
-     * Tests (un)marshalling of a XML fragment containing the {@link Restriction#LICENCE} code.
+     * Tests (un)marshalling of a XML fragment containing the {@link Restriction#LICENSE} code.
      * The spelling changed between ISO 19115:2003 and 19115:2014, from "license" to "licence".
      * We need to ensure that XML marshalling use the old spelling, until the XML schema is updated.
      *
@@ -84,16 +84,15 @@
                 "  <mco:useConstraints>\n" +
                 "    <mco:MD_RestrictionCode"
                         + " codeList=\"" + ISO_NAMESPACE + "19115/resources/Codelist/cat/codelists.xml#MD_RestrictionCode\""
-                        + " codeListValue=\"licence\""
-                        + " codeSpace=\"eng\">Licence</mco:MD_RestrictionCode>\n" +
+                        + " codeListValue=\"licence\">License</mco:MD_RestrictionCode>\n" +
                 "  </mco:useConstraints>\n" +
                 "</mco:MD_LegalConstraints>\n";
 
         final DefaultLegalConstraints c = new DefaultLegalConstraints();
-        c.setUseConstraints(Set.of(Restriction.LICENCE));
+        c.setUseConstraints(Set.of(Restriction.LICENSE));
         assertXmlEquals(xml, marshal(c), "xmlns:*");
         DefaultLegalConstraints actual = unmarshal(DefaultLegalConstraints.class, xml);
-        assertSame(Restriction.LICENCE, getSingleton(actual.getUseConstraints()));
+        assertSame(Restriction.LICENSE, getSingleton(actual.getUseConstraints()));
         assertEquals(c, actual);
         /*
          * Above code tested ISO 19115-3 (un)marshalling. Code below test legacy ISO 19139:2007 (un)marshalling.
@@ -104,14 +103,13 @@
                 "  <gmd:useConstraints>\n" +
                 "    <gmd:MD_RestrictionCode"
                         + " codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_RestrictionCode\""
-                        + " codeListValue=\"license\""                              // Note the "s" - from old ISO 19115:2013 spelling.
-                        + " codeSpace=\"eng\">Licence</gmd:MD_RestrictionCode>\n" + // Note the "c" - this one come from resource file.
+                        + " codeListValue=\"license\">License</gmd:MD_RestrictionCode>\n" +
                 "  </gmd:useConstraints>\n" +
                 "</gmd:MD_LegalConstraints>\n";
 
         assertXmlEquals(xml, marshal(c, VERSION_2007), "xmlns:*");
         actual = unmarshal(DefaultLegalConstraints.class, xml);
-        assertSame(Restriction.LICENCE, getSingleton(actual.getUseConstraints()));
+        assertSame(Restriction.LICENSE, getSingleton(actual.getUseConstraints()));
         assertEquals(c, actual);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/content/DefaultBandTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/content/DefaultBandTest.java
index 2191387..77d37d1 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/content/DefaultBandTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/content/DefaultBandTest.java
@@ -49,7 +49,7 @@
             + "  </mrc:nominalSpatialResolution>\n"
             + "  <mrc:detectedPolarisation>\n"
             + "    <mrc:MI_PolarisationOrientationCode "    // Spell with "s" in 2014 schema.
-            +       "codeList=\"" + ISO_NAMESPACE + "19115/resources/Codelist/cat/codelists.xml#MI_PolarisationOrientationCode\" codeListValue=\"vertical\">"
+            +       "codeList=\"" + ISO_NAMESPACE + "19115/resources/Codelist/cat/codelists.xml#MI_PolarizationOrientationCode\" codeListValue=\"vertical\">"
             +       "Vertical"
             +     "</mrc:MI_PolarisationOrientationCode>\n"
             + "  </mrc:detectedPolarisation>\n"
@@ -67,7 +67,7 @@
             + "  </gmi:nominalSpatialResolution>\n"
             + "  <gmi:detectedPolarisation>\n"
             + "    <gmi:MI_PolarizationOrientationCode "    // Spell with "z" in 2003 schema.
-            +       "codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MI_PolarisationOrientationCode\" codeListValue=\"vertical\">" +
+            +       "codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MI_PolarizationOrientationCode\" codeListValue=\"vertical\">" +
                     "Vertical"
             +     "</gmi:MI_PolarizationOrientationCode>\n"
             + "  </gmi:detectedPolarisation>\n"
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java
index 93928b1..708ee86 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/ExtentsTest.java
@@ -38,8 +38,8 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.mock.VerticalCRSMock;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import org.opengis.referencing.datum.VerticalDatumType;
 
 
 /**
@@ -85,7 +85,7 @@
         Unit<?> unit = null;
         for (final DefaultVerticalExtent e : extents) {
             unit = e.getVerticalCRS().getCoordinateSystem().getAxis(0).getUnit();
-            if (e.getVerticalCRS().getDatum().getRealizationMethod().orElse(null) == RealizationMethod.GEOID) break;
+            if (e.getVerticalCRS().getDatum().getVerticalDatumType() == VerticalDatumType.GEOIDAL) break;
         }
         final UnitConverter c = unit.getConverterToAny(Units.METRE);
         /*
@@ -200,14 +200,14 @@
     public static void testCentroid() {
         final DefaultGeographicBoundingBox bbox = new DefaultGeographicBoundingBox(140, 160, 30, 50);
         DirectPosition pos = Extents.centroid(bbox);
-        assertEquals(150, pos.getCoordinate(0), "longitude");
-        assertEquals( 40, pos.getCoordinate(1), "latitude");
+        assertEquals(150, pos.getOrdinate(0), "longitude");
+        assertEquals( 40, pos.getOrdinate(1), "latitude");
         /*
          * Test crossing anti-meridian.
          */
         bbox.setEastBoundLongitude(-160);
         pos = Extents.centroid(bbox);
-        assertEquals(170, pos.getCoordinate(0), "longitude");
-        assertEquals( 40, pos.getCoordinate(1), "latitude");
+        assertEquals(170, pos.getOrdinate(0), "longitude");
+        assertEquals( 40, pos.getOrdinate(1), "latitude");
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
index 9f5fe35..a43a174 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
@@ -30,9 +30,8 @@
 import org.apache.sis.xml.bind.metadata.replace.ServiceParameterTest;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.OperationMetadata;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList;
 
 
 /**
@@ -51,7 +50,9 @@
      * Creates the resource to use for testing purpose.
      */
     static DefaultCoupledResource create(final NameFactory factory) {
-        final var operation = new DefaultOperationMetadata("Get Map", DistributedComputingPlatform.WEB_SERVICES, null);
+        final var operation = new DefaultOperationMetadata();
+        operation.setOperationName("Get Map");
+        operation.setDistributedComputingPlatforms(Set.of(UnsupportedCodeList.valueOf("WEB_SERVICES")));
         operation.setParameters(Set.of((ParameterDescriptor<?>) ServiceParameterTest.create()));
         operation.setConnectPoints(Set.of(NilReason.MISSING.createNilObject(OnlineResource.class)));
 
@@ -67,7 +68,7 @@
     @Test
     public void testOperationNameResolve() {
         final DefaultCoupledResource resource  = create(DefaultNameFactory.provider());
-        final OperationMetadata      operation = resource.getOperation();
+        final DefaultOperationMetadata operation = resource.getOperation();
         /*
          * Test OperationName replacement when the name matches.
          */
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
index b460ced..334470c 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
@@ -32,13 +32,8 @@
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.parameter.ParameterDirection;
-import org.opengis.metadata.identification.CouplingType;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.OperationMetadata;
-import org.opengis.metadata.identification.ServiceIdentification;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList;
 
 
 /**
@@ -77,7 +72,7 @@
                 "A dummy service for testing purpose.");                // abstract
         id.setServiceTypeVersions(Set.of("1.0"));
         id.setCoupledResources(Set.of(resource));
-        id.setCouplingType(CouplingType.LOOSE);
+        id.setCouplingType(UnsupportedCodeList.valueOf("LOOSE"));
         id.setContainsOperations(Set.of(resource.getOperation()));
         return id;
     }
@@ -86,28 +81,27 @@
      * Compare values of the given service identifications against the value expected for the
      * instance created by {@link #create()} method.
      */
-    private static void verify(final ServiceIdentification id) {
+    private static void verify(final DefaultServiceIdentification id) {
         assertEquals("1.0",                                  getSingleton(id.getServiceTypeVersions()));
         assertEquals("Web Map Server",                       String.valueOf(id.getServiceType()));
         assertEquals("A dummy service for testing purpose.", String.valueOf(id.getAbstract()));
         assertEquals(NilReason.MISSING,                      NilReason.forObject(id.getCitation()));
-        assertEquals(CouplingType.LOOSE,                     id.getCouplingType());
+        assertEquals(UnsupportedCodeList.valueOf("loose"),   id.getCouplingType());
 
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
 //      assertEquals("scopedName",        "mySpace:ABC-123",   …)  skipped because not present in new ISO 19115-3:2016.
 //      assertEquals("resourceReference", "WMS specification", …)  skipped because not present in legacy ISO 19139:2007.
 
-        final OperationMetadata op = resource.getOperation();
+        final DefaultOperationMetadata op = resource.getOperation();
         assertNotNull(op);
         assertEquals("Get Map", op.getOperationName());
-        assertEquals(DistributedComputingPlatform.WEB_SERVICES, getSingleton(op.getDistributedComputingPlatforms()));
+        assertEquals(UnsupportedCodeList.valueOf("WEB_SERVICES"), getSingleton(op.getDistributedComputingPlatforms()));
         assertEquals(NilReason.MISSING, NilReason.forObject(getSingleton(op.getConnectPoints())));
 
         final ParameterDescriptor<?> param = getSingleton(op.getParameters());
         assertEquals("My service parameter", String.valueOf(param.getName()));
         assertEquals(0, param.getMinimumOccurs());
         assertEquals(1, param.getMaximumOccurs());
-        assertEquals(ParameterDirection.IN, param.getDirection());
     }
 
     /**
@@ -117,9 +111,9 @@
      */
     @Test
     public void testUnmarshal() throws JAXBException {
-        final ServiceIdentification id = unmarshalFile(ServiceIdentification.class, openTestFile(Format.XML2016));
+        final DefaultServiceIdentification id = unmarshalFile(DefaultServiceIdentification.class, openTestFile(Format.XML2016));
         verify(id);
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
         assertTitleEquals("WMS specification", getSingleton(resource.getResourceReferences()), "resourceReference");
     }
 
@@ -130,9 +124,9 @@
      */
     @Test
     public void testUnmarshalLegacy() throws JAXBException {
-        final ServiceIdentification id = unmarshalFile(ServiceIdentification.class, openTestFile(Format.XML2007));
+        final DefaultServiceIdentification id = unmarshalFile(DefaultServiceIdentification.class, openTestFile(Format.XML2007));
         verify(id);
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
         assertEquals("mySpace:ABC-123", String.valueOf(resource.getScopedName()));
     }
 
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
index dc180f7..2bf1afa 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
@@ -148,8 +148,8 @@
         final QuantitativeResult   pResult = (QuantitativeResult) TestUtilities.getSingleton(programmatic.getResults());
         final RecordType           uType   = uResult.getValueType();
         final RecordType           pType   = pResult.getValueType();
-        final Map<MemberName,Type> uFields = uType.getFieldTypes();
-        final Map<MemberName,Type> pFields = pType.getFieldTypes();
+        final Map<MemberName,Type> uFields = uType.getMemberTypes();
+        final Map<MemberName,Type> pFields = pType.getMemberTypes();
         final Iterator<MemberName> uIter   = uFields.keySet().iterator();
         final Iterator<MemberName> pIter   = pFields.keySet().iterator();
         assertEquals(uFields.size(), pFields.size());
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java
index 0ce6406..85f7db7 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java
@@ -25,8 +25,8 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.maintenance.Scope;
+// Specific to the main branch:
+import org.opengis.metadata.quality.Scope;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/IdentifiersTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/IdentifiersTest.java
index 0a2c00c..2fe41a3 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/IdentifiersTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/IdentifiersTest.java
@@ -28,6 +28,9 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Tests {@link Identifiers}.
@@ -54,8 +57,15 @@
     /**
      * Creates an identifier with a code space.
      */
-    private static Identifier identifier(final String codeSpace, final String code) {
-        return new DefaultIdentifier(codeSpace, code, null);
+    private static ReferenceIdentifier identifier(final String codeSpace, final String code) {
+        return new Id(codeSpace, code);
+    }
+
+    @SuppressWarnings("serial")
+    private static final class Id extends DefaultIdentifier implements ReferenceIdentifier {
+        Id(String codeSpace, String code) {
+            super(codeSpace, code, null);
+        }
     }
 
     /**
@@ -63,8 +73,8 @@
      */
     @Test
     public void testHasCommonIdentifier() {
-        final List<Identifier> id1 = new ArrayList<>(3);
-        final List<Identifier> id2 = new ArrayList<>(2);
+        final List<ReferenceIdentifier> id1 = new ArrayList<>(3);
+        final List<ReferenceIdentifier> id2 = new ArrayList<>(2);
         assertNull(Identifiers.hasCommonIdentifier(id1, id2));
         /*
          * Add codes for two Operation Methods which are implemented in Apache SIS by the same class:
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/MergerTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/MergerTest.java
index 9320f39..80c3a43 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/MergerTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/privy/MergerTest.java
@@ -40,9 +40,6 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSetEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.CoverageDescription;
-
 
 /**
  * Tests the {@link Merger} class.
@@ -117,7 +114,7 @@
         final Iterator<ContentInformation> it       = target.getContentInfo().iterator();
         final ImageDescription             image    = (ImageDescription)            it.next();
         final FeatureCatalogueDescription  features = (FeatureCatalogueDescription) it.next();
-        final CoverageDescription          coverage = (CoverageDescription)         it.next();
+        final DefaultCoverageDescription   coverage = (DefaultCoverageDescription)  it.next();
         assertFalse(it.hasNext());
 
         assertEquals(ImagingCondition.CLOUD, image   .getImagingCondition());
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
index a1d4ad0..0f2ea4b 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
@@ -29,9 +29,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -121,22 +120,22 @@
             assertNull(actualID, name);
         } else if (actualID != null) {
             assertEquals(expectedID.getCode(),      actualID.getCode(),      name);
-            assertEquals(expectedID.getCodeSpace(), actualID.getCodeSpace(), name);
-            assertEquals(expectedID.getVersion(),   actualID.getVersion(),   name);
+//          assertEquals(expectedID.getCodeSpace(), actualID.getCodeSpace(), name);
+//          assertEquals(expectedID.getVersion(),   actualID.getVersion(),   name);
         }
         /*
          * The fallback may not declare all responsible parties.
          * If it declares a party, the name and role shall be equal.
          */
-        final Responsibility expectedResp = first(fromDB.getCitedResponsibleParties());
-        final Responsibility actualResp   = first(fromFB.getCitedResponsibleParties());
+        final ResponsibleParty expectedResp = first(fromDB.getCitedResponsibleParties());
+        final ResponsibleParty actualResp   = first(fromFB.getCitedResponsibleParties());
         if (expectedResp == null) {
             assertNull(actualResp, name);
         } else if (actualResp != null) {
             assertEquals(expectedResp.getRole(), actualResp.getRole(), name);
-            final Party expectedParty = first(expectedResp.getParties());
-            final Party actualParty = first(actualResp.getParties());
-            assertEquals(expectedParty.getName(), actualParty.getName(), name);
+//          final Party expectedParty = first(expectedResp.getParties());
+//          final Party actualParty = first(actualResp.getParties());
+//          assertEquals(expectedParty.getName(), actualParty.getName(), name);
         }
         assertEquals(first(fromDB.getPresentationForms()),
                      first(fromFB.getPresentationForms()), name);
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataSourceTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataSourceTest.java
index dd4ad99..8a6e143 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataSourceTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataSourceTest.java
@@ -34,9 +34,6 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.TestStep;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.apache.sis.test.TestUtilities.getSingleton;
-
 
 /**
  * Tests {@link MetadataSource}.
@@ -116,10 +113,8 @@
      * @param title         the expected format title.
      */
     private static void verify(final Format format, final String abbreviation, final String title) {
-        final Citation spec = format.getFormatSpecificationCitation();
-        assertNotNull(spec, "formatSpecificationCitation");
-        assertEquals(abbreviation, String.valueOf(getSingleton(spec.getAlternateTitles())), "abbreviation");
-        assertEquals(title, String.valueOf(spec.getTitle()), "title");
+        assertEquals(abbreviation, String.valueOf(format.getName()), "abbreviation");
+        assertEquals(title, String.valueOf(format.getSpecification()), "title");
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataWriterTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataWriterTest.java
index b92e56f..d05dda5 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataWriterTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataWriterTest.java
@@ -34,11 +34,8 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
-import org.apache.sis.util.privy.URLs;
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -86,6 +83,7 @@
      */
     @Test
     @ResourceLock(TestDatabase.POSTGRESQL)
+    @org.junit.jupiter.api.Disabled("Requires GeoAPI 3.1.")
     public void testPostgreSQL() throws Exception {
         try (final TestDatabase db = TestDatabase.createOnPostgreSQL("MetadataWriter", true)) {
             source = new MetadataWriter(MetadataStandard.ISO_19115, db.source, "MetadataWriter", null);
@@ -122,7 +120,7 @@
         assertEquals("EPSG",      source.search(HardCodedCitations.EPSG));
         assertEquals("SIS",       source.search(HardCodedCitations.SIS));
         assertNull  (             source.search(HardCodedCitations.ISO_19111));
-        assertEquals("{rp}EPSG",  source.search(TestUtilities.getSingleton(
+        assertEquals("EPSG",      source.search(TestUtilities.getSingleton(
                 HardCodedCitations.EPSG.getCitedResponsibleParties())));
     }
 
@@ -154,21 +152,13 @@
         /*
          * Ask for dependencies that are known to exist.
          */
-        final Responsibility responsible = TestUtilities.getSingleton(c.getCitedResponsibleParties());
+        final ResponsibleParty responsible = TestUtilities.getSingleton(c.getCitedResponsibleParties());
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, responsible.getRole());
 
-        final Party party = TestUtilities.getSingleton(responsible.getParties());
-        assertEquals("International Association of Oil & Gas Producers", party.getName().toString());
-        final Contact contact = TestUtilities.getSingleton(party.getContactInfo());
-        /*
-         * Invoke the deprecated `getOnlineResource()` method (singular form) before the non-deprecated
-         * `getOnlineResources()` (plural form) replacement. They shall give the same result no matter
-         * which form were stored in the database.
-         */
-        @SuppressWarnings("deprecation")
-        final OnlineResource resource = contact.getOnlineResource();
-        assertSame(resource, TestUtilities.getSingleton(contact.getOnlineResources()));
-        assertEquals(URLs.EPSG, resource.getLinkage().toString());
+        assertEquals("International Association of Oil & Gas Producers", responsible.getOrganisationName().toString());
+
+        OnlineResource resource = responsible.getContactInfo().getOnlineResource();
+        assertEquals("https://epsg.org/", resource.getLinkage().toString());
         assertEquals(OnLineFunction.INFORMATION, resource.getFunction());
         /*
          * Ask columns that are known to not exist.
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
index 172a10d..a06c614 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
@@ -76,9 +76,6 @@
               </gco:TypeName>
             </gco:attributeType>
           </srv:name>
-          <srv:direction>
-            <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-          </srv:direction>
           <srv:optionality>
             <gco:CharacterString>Optional</gco:CharacterString>
           </srv:optionality>
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
index ce54e7b..35b359f 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
@@ -41,7 +41,7 @@
   </srv:serviceTypeVersion>
 
   <srv:couplingType>
-    <srv:SV_CouplingType codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#SV_CouplingType" codeListValue="loose" codeSpace="eng">Loose</srv:SV_CouplingType>
+    <srv:SV_CouplingType codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#SV_CouplingType" codeListValue="loose">Loose</srv:SV_CouplingType>
   </srv:couplingType>
 
   <srv:coupledResource>
@@ -62,7 +62,7 @@
             <gco:CharacterString>Get Map</gco:CharacterString>
           </srv:operationName>
           <srv:distributedComputingPlatform>
-            <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices" codeSpace="eng">Web services</srv:DCPList>
+            <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices">Web services</srv:DCPList>
           </srv:distributedComputingPlatform>
           <srv:connectPoint gco:nilReason="missing"/>
           <srv:parameter>
@@ -81,9 +81,6 @@
                   </gco:attributeType>
                 </gco:MemberName>
               </srv:name>
-              <srv:direction>
-                <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-              </srv:direction>
               <srv:optionality>
                 <gco:Boolean>true</gco:Boolean>
               </srv:optionality>
@@ -103,7 +100,7 @@
         <gco:CharacterString>Get Map</gco:CharacterString>
       </srv:operationName>
       <srv:distributedComputingPlatform>
-        <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices" codeSpace="eng">Web services</srv:DCPList>
+        <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices">Web services</srv:DCPList>
       </srv:distributedComputingPlatform>
       <srv:connectPoint gco:nilReason="missing"/>
       <srv:parameter>
@@ -122,9 +119,6 @@
               </gco:attributeType>
             </gco:MemberName>
           </srv:name>
-          <srv:direction>
-            <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-          </srv:direction>
           <srv:optionality>
             <gco:Boolean>true</gco:Boolean>
           </srv:optionality>
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/SchemaComplianceTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/SchemaComplianceTest.java
deleted file mode 100644
index ae14e73..0000000
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/xml/SchemaComplianceTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.metadata.xml;
-
-import java.nio.file.Path;
-import java.nio.file.Files;
-import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.system.DataDirectory;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assumptions.*;
-import org.apache.sis.test.TestCase;
-import org.apache.sis.test.ProjectDirectories;
-import org.apache.sis.xml.test.SchemaCompliance;
-
-
-/**
- * Tests conformance of JAXB annotations with XML schemas if those schemas are available.
- * This tests requires the {@code $SIS_DATA/Schemas/iso/19115/-3} directory to exists.
- * Those files must be installed manually; they are not distributed with Apache SIS for licensing reasons.
- * Content can be downloaded as ZIP files from <a href="https://schemas.isotc211.org/19115/">ISO portal</a>.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class SchemaComplianceTest extends TestCase {
-    /**
-     * Creates a new test case.
-     */
-    public SchemaComplianceTest() {
-    }
-
-    /**
-     * Verifies compliance with metadata schemas.
-     *
-     * @throws Exception if an error occurred while checking the schema.
-     *
-     * @see <a href="https://schemas.isotc211.org/19115/">ISO schemas for metadata</a>
-     */
-    @Test
-    public void verifyMetadata() throws Exception {
-        Path directory = DataDirectory.SCHEMAS.getDirectory();
-        assumeTrue(directory != null, "Schema directory is not specified.");
-        directory = directory.resolve("iso");
-        assumeTrue(Files.isDirectory(directory.resolve("19115")));
-        /*
-         * Locate the root of metadata class directory. In a Maven build:
-         * "core/sis-metadata/target/classes/org/apache/sis/metadata/iso"
-         */
-        final ProjectDirectories dir = new ProjectDirectories(ISOMetadata.class);
-        final SchemaCompliance checker = new SchemaCompliance(dir.classesRootDirectory, directory);
-        checker.loadDefaultSchemas();
-        checker.verify(dir.classesPackageDirectory);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/CoordinateSystemAxisMock.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
index 9c28157..8b06962 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
@@ -61,6 +61,8 @@
     @Override public int                  getDimension()         {return 1;}
     @Override public CoordinateSystemAxis getAxis(int dimension) {return this;}
     @Override public AxisDirection        getDirection()         {return null;}
+    @Override public double               getMinimumValue()      {return Double.NEGATIVE_INFINITY;}
+    @Override public double               getMaximumValue()      {return Double.POSITIVE_INFINITY;}
     @Override public RangeMeaning         getRangeMeaning()      {return RangeMeaning.EXACT;}
     @Override public Unit<?>              getUnit()              {return null;}
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/GeographicCRSMock.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/GeographicCRSMock.java
index 7a3e43e..c2a0594 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/GeographicCRSMock.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/GeographicCRSMock.java
@@ -29,11 +29,11 @@
 @XmlType(name = "GeodeticCRSType", propOrder = {
     "coordinateSystem",
     "datum",
-    "datumEnsemble",
     "name",
     "alias",
     "identifiers",
-    "domains",
+    "scope",
+    "domainOfValidity",
     "remarks"
 })
 public abstract class GeographicCRSMock implements GeographicCRS {
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/IdentifiedObjectMock.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/IdentifiedObjectMock.java
index 9721162..ed3bdd8 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/IdentifiedObjectMock.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/IdentifiedObjectMock.java
@@ -31,6 +31,11 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
+// Specific to the main branch:
+import java.util.Set;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.citation.Citation;
+
 
 /**
  * A dummy implementation of {@link IdentifiedObject} with minimal XML (un)marshalling capability.
@@ -124,6 +129,26 @@
     }
 
     /**
+     * Returns the namespace version ({@code null} for now).
+     *
+     * @return the namespace version.
+     */
+    @Override
+    public final String getVersion() {
+        return null;
+    }
+
+    /**
+     * Returns the authority that define the object ({@code null} for now).
+     *
+     * @return the defining authority.
+     */
+    @Override
+    public final Citation getAuthority() {
+        return null;
+    }
+
+    /**
      * Returns {@link #alias} in an unmodifiable collection, or an empty collection if the alias is null.
      *
      * @return {@link #alias} singleton or an empty collection.
@@ -134,6 +159,37 @@
     }
 
     /**
+     * Returns the identifiers (currently null).
+     *
+     * @return the identifiers of this object.
+     */
+    @Override
+    public final Set<ReferenceIdentifier> getIdentifiers() {
+        return null;
+    }
+
+    /**
+     * Returns the remarks (currently null).
+     *
+     * @return the remarks associated to this object.
+     */
+    @Override
+    public final InternationalString getRemarks() {
+        return null;
+    }
+
+    /**
+     * Returns the WKT representation (currently none).
+     *
+     * @return the WKT representation of this object.
+     * @throws UnsupportedOperationException if there is no WKT representation.
+     */
+    @Override
+    public final String toWKT() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns a string representation for debugging purpose.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/MetadataMock.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/MetadataMock.java
index b6584fe..345856d 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/MetadataMock.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/MetadataMock.java
@@ -16,9 +16,9 @@
  */
 package org.apache.sis.test.mock;
 
+import java.util.Locale;
 import java.util.Set;
 import java.util.Collection;
-import java.util.Locale;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@@ -27,11 +27,6 @@
 import org.apache.sis.xml.bind.lan.LocaleAdapter;
 import org.apache.sis.metadata.simple.SimpleMetadata;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Map;
-import java.util.Collections;
-import java.nio.charset.Charset;
-
 
 /**
  * A dummy implementation of {@link org.opengis.metadata.Metadata} with minimal XML (un)marshalling capability.
@@ -69,22 +64,11 @@
     }
 
     /**
-     * Returns {@link #language} in a singleton map or an empty map.
-     *
-     * @return {@link #language}
-     */
-    @Override
-    public Map<Locale,Charset> getLocalesAndCharsets() {
-        return (language != null) ? Collections.singletonMap(language, null) : Collections.emptyMap();
-    }
-
-    /**
      * Returns {@link #language} in a singleton set or an empty set.
      *
      * @return {@link #language}
      */
     @Override
-    @Deprecated
     public Collection<Locale> getLanguages() {
         return (language != null) ? Set.of(language) : Set.of();
     }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/VerticalCRSMock.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/VerticalCRSMock.java
index 2ad61c5..e2bf9fc 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/VerticalCRSMock.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/test/mock/VerticalCRSMock.java
@@ -29,9 +29,9 @@
 import org.opengis.metadata.extent.Extent;
 import org.opengis.util.InternationalString;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Optional;
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import java.util.Date;
+import org.opengis.referencing.datum.VerticalDatumType;
 
 
 /**
@@ -39,7 +39,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-@SuppressWarnings({"serial", "deprecation"})
+@SuppressWarnings("serial")
 public final class VerticalCRSMock extends IdentifiedObjectMock
         implements VerticalCRS, VerticalDatum, VerticalCS, CoordinateSystemAxis
 {
@@ -47,36 +47,36 @@
      * Height in metres.
      */
     public static final VerticalCRS HEIGHT = new VerticalCRSMock("Height",
-            RealizationMethod.GEOID, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.METRE, true);
+            VerticalDatumType.GEOIDAL, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.METRE, true);
 
     /**
      * Height in feet.
      */
     public static final VerticalCRS HEIGHT_ft = new VerticalCRSMock("Height",
-            RealizationMethod.GEOID, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.FOOT, true);
+            VerticalDatumType.GEOIDAL, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.FOOT, true);
 
     /**
      * Height estimated from hPa.
      */
     public static final VerticalCRS BAROMETRIC_HEIGHT = new VerticalCRSMock("Barometric height",
-            RealizationMethod.LEVELLING, 0, Double.POSITIVE_INFINITY, Units.HECTOPASCAL, true);
+            VerticalDatumType.BAROMETRIC, 0, Double.POSITIVE_INFINITY, Units.HECTOPASCAL, true);
 
     /**
      * Depth in metres.
      */
     public static final VerticalCRS DEPTH = new VerticalCRSMock("Depth",
-            RealizationMethod.TIDAL, 0, Double.POSITIVE_INFINITY, Units.METRE, false);
+            VerticalDatumType.DEPTH, 0, Double.POSITIVE_INFINITY, Units.METRE, false);
 
     /**
      * Depth as a fraction of the sea floor depth at the location of the point for which the depth is evaluated.
      */
     public static final VerticalCRS SIGMA_LEVEL = new VerticalCRSMock("Sigma level",
-            null, 0, 1, Units.UNITY, false);
+            VerticalDatumType.OTHER_SURFACE, 0, 1, Units.UNITY, false);
 
     /**
-     * The realization method (geoid, tidal, <i>etc.</i>), or {@code null} if unspecified.
+     * The datum type (geoidal, barometric, etc.).
      */
-    private final RealizationMethod method;
+    private final VerticalDatumType type;
 
     /**
      * The minimum and maximum values.
@@ -97,17 +97,16 @@
      * Creates a new vertical CRS for the given name.
      *
      * @param name          the CRS, CS, datum and axis name.
-     * @param method        the realization method (geoid, tidal, <i>etc.</i>).
      * @param minimumValue  the minium value.
      * @param maximumValue  the maximum value.
      * @param unit          the unit of measurement.
      * @param up            {@code true} if the axis direction is up, or {@code false} if down.
      */
-    private VerticalCRSMock(final String name, final RealizationMethod method,
+    private VerticalCRSMock(final String name, VerticalDatumType type,
             final double minimumValue, final double maximumValue, final Unit<?> unit, final boolean up)
     {
         super(name);
-        this.method       = method;
+        this.type         = type;
         this.minimumValue = minimumValue;
         this.maximumValue = maximumValue;
         this.unit         = unit;
@@ -124,8 +123,10 @@
 
     @Override public String                      getAbbreviation()      {return up ? "h" : "d";}
     @Override public InternationalString         getScope()             {return null;}
+    @Override public InternationalString         getAnchorPoint()       {return null;}
+    @Override public Date                        getRealizationEpoch()  {return null;}
     @Override public Extent                      getDomainOfValidity()  {return null;}
-    @Override public Optional<RealizationMethod> getRealizationMethod() {return Optional.ofNullable(method);}
+    @Override public VerticalDatumType           getVerticalDatumType() {return type;}
     @Override public VerticalDatum               getDatum()             {return this;}
     @Override public VerticalCS                  getCoordinateSystem()  {return this;}
     @Override public int                         getDimension()         {return 1;}
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/DefaultRecordSchemaTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/DefaultRecordSchemaTest.java
index 0a61396..966e38c 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/DefaultRecordSchemaTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/DefaultRecordSchemaTest.java
@@ -45,7 +45,7 @@
      * Tests {@link DefaultRecordSchema#createRecordType(CharSequence, Map)}.
      */
     @Test
-    @SuppressWarnings({"deprecation", "removal"})
+    @SuppressWarnings("removal")
     public void testCreateRecordType() {
         final var schema = new DefaultRecordSchema(null, null, "MySchema");
         final var fields = new LinkedHashMap<CharSequence,Class<?>>(8);
@@ -60,7 +60,7 @@
         assertSame(schema, recordType.getContainer());
         assertEquals(Names.createTypeName("MySchema", ":", "MyRecordType"), recordType.getTypeName());
         int count = 0;
-        for (final Map.Entry<MemberName,Type> entry : recordType.getFieldTypes().entrySet()) {
+        for (final Map.Entry<MemberName,Type> entry : recordType.getMemberTypes().entrySet()) {
             final String   expectedName;
             final String   expectedType;
             final Class<?> expectedClass;
@@ -106,7 +106,7 @@
         final var copy = new DefaultRecordType(
                 recordType.getTypeName(),
                 recordType.getContainer(),
-                recordType.getFieldTypes());
+                recordType.getMemberTypes());
         assertEquals(recordType, copy);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypeNamesTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypeNamesTest.java
index 47be06b..7420eb3 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypeNamesTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypeNamesTest.java
@@ -58,7 +58,7 @@
         final DefaultNameFactory factory = DefaultNameFactory.provider();
         final TypeName type = factory.toTypeName(valueClass);
         assertNotNull(type, name);
-        assertSame   (valueClass, type.toJavaType().get(), name);
+        assertSame   (valueClass, ((DefaultTypeName) type).toJavaType().get(), name);
         assertEquals (namespace,  type.scope().name().toString(), name);
         assertEquals (name,       type.toString(), name);
         assertEquals (valueClass, TypeNames.toClass(namespace, name), name);
@@ -111,7 +111,7 @@
     public void testMetadataClasses() throws ClassNotFoundException {
         verifyLookup(OGC, "Geometry",    Geometry.class);
         verifyLookup(OGC, "MD_Metadata", Metadata.class);
-        verifyLookup(OGC, "CRS",         CoordinateReferenceSystem.class);
+        verifyLookup(OGC, "SC_CRS",      CoordinateReferenceSystem.class);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java
index 056bf5d..baa5a79 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java
@@ -28,8 +28,8 @@
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.OnLineFunction;
 import org.opengis.metadata.content.ImagingCondition;
-import org.opengis.metadata.constraint.Restriction;
 import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.cs.AxisDirection;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.DefaultInternationalString;
@@ -39,8 +39,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.parameter.ParameterDirection;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.PENDING_NEXT_GEOAPI_RELEASE;
 
 
 /**
@@ -98,9 +98,9 @@
      */
     @Test
     public void testGetStandardName() {
-        assertEquals("CI_Citation",   Types.getStandardName(Citation     .class));
-        assertEquals("Datum",         Types.getStandardName(Datum        .class));
-        assertEquals("AxisDirection", Types.getStandardName(AxisDirection.class));
+        assertEquals("CI_Citation",      Types.getStandardName(Citation     .class));
+        assertEquals("CD_Datum",         Types.getStandardName(Datum        .class));
+        assertEquals("CS_AxisDirection", Types.getStandardName(AxisDirection.class));
     }
 
     /**
@@ -109,16 +109,16 @@
     @Test
     public void testForStandardName() {
         assertEquals(Citation     .class, Types.forStandardName("CI_Citation"));
-        assertEquals(Datum        .class, Types.forStandardName("Datum"));
+        assertEquals(Datum        .class, Types.forStandardName("CD_Datum"));
         assertEquals(Citation     .class, Types.forStandardName("CI_Citation"));            // Value should be cached.
         assertEquals(Citation     .class, Types.forStandardName("Citation"));
-        assertEquals(AxisDirection.class, Types.forStandardName("AxisDirection"));
+        assertEquals(AxisDirection.class, Types.forStandardName("CS_AxisDirection"));
         assertNull  (                     Types.forStandardName("MD_Dummy"));
     }
 
     /**
      * Tests the {@link Types#forEnumName(Class, String)} method with an enumeration from the JDK.
-     * Such enumerations do not implement the {@link org.opengis.util.ControlledVocabulary} interface.
+     * Such enumerations do not implement the {@code org.opengis.util.ControlledVocabulary} interface.
      */
     @Test
     public void testForStandardEnumName() {
@@ -130,19 +130,6 @@
     }
 
     /**
-     * Tests the {@link Types#forEnumName(Class, String)} method with an enumeration from GeoAPI.
-     * Such enumerations implement the {@link org.opengis.util.ControlledVocabulary} interface.
-     */
-    @Test
-    public void testForGeoapiEnumName() {
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "IN_OUT"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "INOUT"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "in out"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "in/out"));
-        assertNull(Types.forEnumName(ParameterDirection.class, "out/in"));
-    }
-
-    /**
      * Tests the {@link Types#forCodeName(Class, String, boolean)} method.
      */
     @Test
@@ -153,8 +140,10 @@
         assertSame(ImagingCondition.SEMI_DARKNESS, Types.forCodeName(ImagingCondition.class, "semi-darkness", null));
         assertNull(Types.forCodeName(ImagingCondition.class, "darkness", null));
 
-        assertSame(Restriction.LICENCE, Types.forCodeName(Restriction.class, "licence", null));
-        assertSame(Restriction.LICENCE, Types.forCodeName(Restriction.class, "license", null));
+        assertSame(PixelInCell.CELL_CORNER, Types.forCodeName(PixelInCell.class, "cell corner", null));
+        assertSame(PixelInCell.CELL_CORNER, Types.forCodeName(PixelInCell.class, "cellCorner",  null));
+        assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell center", null));
+        assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCenter",  null));
     }
 
     /**
@@ -183,7 +172,7 @@
     }
 
     /**
-     * Tests the {@link Types#getDescription(ControlledVocabulary)} method.
+     * Tests the {@code Types.getDescription(ControlledVocabulary)} method.
      */
     @Test
     public void testGetCodeDescription() {
@@ -192,34 +181,32 @@
                 description.toString(Locale.ROOT));
         assertEquals("Online instructions for transferring data from one storage device or system to another.",
                 description.toString(Locale.ENGLISH));
-        assertEquals("Transfert de la ressource d’un système à un autre.",
+        assertEquals("Transfert de la ressource d'un système à un autre.",
                 description.toString(Locale.FRENCH));
     }
 
     /**
-     * Tests the examples given in {@link Types#getListName(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getListName(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetListName() {
-        assertEquals("SV_ParameterDirection",   Types.getListName(ParameterDirection.IN_OUT));
-        assertEquals("AxisDirection",           Types.getListName(AxisDirection     .NORTH));
+        assertEquals("CS_AxisDirection",        Types.getListName(AxisDirection     .NORTH));
         assertEquals("CI_OnLineFunctionCode",   Types.getListName(OnLineFunction    .DOWNLOAD));
         assertEquals("MD_ImagingConditionCode", Types.getListName(ImagingCondition  .BLURRED_IMAGE));
     }
 
     /**
-     * Tests the examples given in {@link Types#getCodeName(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getCodeName(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetCodeName() {
-        assertEquals("in/out",       Types.getCodeName(ParameterDirection.IN_OUT));
         assertEquals("north",        Types.getCodeName(AxisDirection     .NORTH));
         assertEquals("download",     Types.getCodeName(OnLineFunction    .DOWNLOAD));
         assertEquals("blurredImage", Types.getCodeName(ImagingCondition  .BLURRED_IMAGE));
     }
 
     /**
-     * Tests the examples given in {@link Types#getCodeLabel(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getCodeLabel(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetCodeLabel() {
@@ -229,7 +216,7 @@
     }
 
     /**
-     * Tests {@link Types#getCodeTitle(ControlledVocabulary)}.
+     * Tests {@code Types.getCodeTitle(ControlledVocabulary)}.
      * Also opportunistically tests {@link Types#forCodeTitle(CharSequence)}.
      */
     @Test
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/CharSequenceSubstitutionTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/CharSequenceSubstitutionTest.java
index a77ca6c..887eb34 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/CharSequenceSubstitutionTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/CharSequenceSubstitutionTest.java
@@ -33,8 +33,9 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 
 
 /**
@@ -77,7 +78,7 @@
                 "</gmd:MD_ReferenceSystem>";
 
         final ReferenceSystemMetadata md = unmarshal(ReferenceSystemMetadata.class, expected);
-        final Identifier id = md.getName();
+        final ReferenceIdentifier id = md.getName();
         assertEquals("L101", id.getCodeSpace(), "codespace");
         assertEquals("EPSG:4326", id.getCode(), "code");
     }
@@ -103,7 +104,7 @@
                 "  </mcc:codeSpace>\n" +
                 "</mcc:MD_Identifier>";
 
-        final Identifier id = unmarshal(Identifier.class, expected);
+        final DefaultIdentifier id = unmarshal(DefaultIdentifier.class, expected);
         assertEquals("L101", id.getCodeSpace(), "codespace");
         assertEquals("EPSG:4326", id.getCode(), "code");
     }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java
index 6ad32a7..21bf2f2 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java
@@ -29,8 +29,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -221,7 +221,7 @@
         assertTrue (c.equals(e2, ComparisonMode.DEBUG));
 
         // Following object should alway be different because it does not implement the same interface.
-        final Responsibility r1 = NilReason.TEMPLATE.createNilObject(Responsibility.class);
+        final ResponsibleParty r1 = NilReason.TEMPLATE.createNilObject(ResponsibleParty.class);
         assertFalse(c.equals(r1, ComparisonMode.STRICT));
         assertFalse(c.equals(r1, ComparisonMode.BY_CONTRACT));
         assertFalse(c.equals(r1, ComparisonMode.IGNORE_METADATA));
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java
index 8fa5713..b1f8bd1 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/ReferenceResolverTest.java
@@ -56,11 +56,11 @@
         /*
          * The fragment should reference the exact same object as the one in the citation.
          */
-        final var parent  = getSingleton(citation.getCitedResponsibleParties().iterator().next().getParties());
-        final var reusing = getSingleton(getSingleton(data.getPointOfContacts()).getParties());
-        assertEquals("Little John", reusing.getName().toString());
-        assertSame(getSingleton(parent .getContactInfo()),
-                   getSingleton(reusing.getContactInfo()));
+        final var parent  = citation.getCitedResponsibleParties().iterator().next();
+        final var reusing = getSingleton(data.getPointOfContacts());
+        assertEquals("Little John", reusing.getIndividualName());
+        assertSame(parent .getContactInfo(),
+                   reusing.getContactInfo());
 
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/RenameListGenerator.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/RenameListGenerator.java
deleted file mode 100644
index f5327f5..0000000
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/RenameListGenerator.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.xml;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryIteratorException;
-import java.lang.reflect.Method;
-import jakarta.xml.bind.annotation.XmlSchema;
-import jakarta.xml.bind.annotation.XmlElement;
-import jakarta.xml.bind.annotation.XmlRootElement;
-import org.opengis.geoapi.schema.SchemaException;
-import org.apache.sis.xml.privy.LegacyNamespaces;
-
-
-/**
- * Creates a file in the {@value TransformingReader#FILENAME} format.
- * {@code RenameListGenerator} can be executed if ISO 19115 standards have changed.
- * The format is described in the {@code readme.html} page in source code directory.
- * Output format contains namespaces first, then classes, then properties. Example:
- *
- * <pre class="text">
- * http://standards.iso.org/iso/19115/-3/cit/1.0
- *   CI_Address
- *     administrativeArea
- *     city
- *   CI_Citation
- *     citedResponsibleParty</pre>
- *
- * This class can be used as a starting point for generating a new file from scratch.
- * It should not be used for updating the existing file (unless a lot of things have changed)
- * because some of {@value TransformingReader#FILENAME} content have been edited by hand.
- * In particular:
- *
- * <ul>
- *   <li>Current implementation lists all classes, including classes that should
- *       not be listed because they did not existed in previous standard.</li>
- *   <li>Current implementation repeats properties inherited from parent classes.
- *       It does not use the "<var>Child</var> : <var>Parent</var>" syntax.</li>
- * </ul>
- *
- * For generating a new file:
- *
- * {@snippet lang="java" :
- *     public static void main(String[] args) throws Exception {
- *         RenameListGenerator gen = new RenameListGenerator(Path.of("/home/user/project/build/classes"));
- *         gen.add(Path.of("org/apache/sis/metadata/iso"));
- *         try (final BufferedWriter out = Files.newBufferedWriter(Path.of("MyOutputFile.lst"))) {
- *             gen.print(out);
- *         }
- *     }
- * }
- */
-public final class RenameListGenerator {
-    /**
-     * Properties in those namespaces do not have older namespaces to map from.
-     */
-    private static final Set<String> LEGACY_NAMESPACES = Set.of(
-            LegacyNamespaces.GMD,
-            LegacyNamespaces.GMI,
-            LegacyNamespaces.SRV);
-
-    /**
-     * The {@value} string used in JAXB annotations for default names or namespaces.
-     */
-    private static final String DEFAULT = "##default";
-
-    /**
-     * Root directory from which to search for classes.
-     */
-    private final Path classRootDirectory;
-
-    /**
-     * The content to write. Keys in the first (outer) map are namespaces. Keys in the enclosed maps
-     * are class names. Keys in the enclosed set are property names.
-     */
-    private final Map<String, Map<String, Set<String>>> content;
-
-    /**
-     * Creates a new {@value TransformingReader#FILENAME} generator for classes under the given directory.
-     * The given directory shall be the root of {@code "*.class"} files.
-     *
-     * @param  classRootDirectory   the root of compiled class files.
-     */
-    public RenameListGenerator(final Path classRootDirectory) {
-        this.classRootDirectory = classRootDirectory;
-        content = new TreeMap<>();
-    }
-
-    /**
-     * Gets the namespaces, types and properties for all class files in the given directory and sub-directories.
-     * Those information are memorized for future listing with {@link #print(Appendable)}.
-     *
-     * @param  directory  the directory to scan for classes, relative to class root directory.
-     * @throws IOException if an error occurred while reading files or schemas.
-     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
-     * @throws SchemaException if two properties have the same name in the same class and namespace.
-     */
-    public void add(final Path directory) throws IOException, ClassNotFoundException, SchemaException {
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
-            for (Path path : stream) {
-                final String filename = path.getFileName().toString();
-                if (!filename.startsWith(".")) {
-                    if (Files.isDirectory(path)) {
-                        add(path);
-                    } else if (filename.endsWith(".class")) {
-                        path = classRootDirectory.relativize(path);
-                        String classname = path.toString();
-                        classname = classname.substring(0, classname.length() - 6).replace('/', '.');
-                        add(Class.forName(classname));
-                    }
-                }
-            }
-        } catch (DirectoryIteratorException e) {
-            throw e.getCause();
-        }
-    }
-
-    /**
-     * Gets the namespaces, types and properties for the given class.
-     * Properties defined in super-classes will be copied as if they were declared in-line.
-     * Those information are memorized for future listing with {@link #print(Appendable)}.
-     *
-     * @throws SchemaException if two properties have the same name in the same class and namespace.
-     */
-    private void add(Class<?> classe) throws SchemaException {
-        XmlRootElement root = classe.getDeclaredAnnotation(XmlRootElement.class);
-        if (root != null) {
-            /*
-             * Add the following entry:
-             *
-             *     http://a.namespace
-             *      PX_AClass
-             *       …
-             *
-             * Then list all properties below "PX_AClass". Note that the namespace may change because properties
-             * may be declared in different namespaces, but the class name stay the same. If the same properties
-             * are inherited by many classes, they will be repeated in each subclass.
-             */
-            final String topLevelTypeName = root.name();
-            String classNS = namespace(classe, root.namespace());
-            for (;; classNS = namespace(classe, root.namespace())) {
-                for (final Method method : classe.getDeclaredMethods()) {
-                    if (!method.isBridge()) {
-                        final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
-                        if (xe != null) {
-                            String namespace = xe.namespace();
-                            if (namespace.equals(DEFAULT)) {
-                                namespace = classNS;
-                            }
-                            add(namespace, topLevelTypeName, xe.name());
-                        }
-                    }
-                }
-                classe = classe.getSuperclass();
-                root = classe.getDeclaredAnnotation(XmlRootElement.class);
-                if (root == null) break;
-            }
-        } else {
-            /*
-             * In Apache SIS implementation, classes without JAXB annotation except on a single method are
-             * code lists or enumerations. Those classes have exactly one method annotated with @XmlElement,
-             * and that method actually gives a type, not a property (because of the way OGC/ISO wrap every
-             * properties in a type).
-             */
-            XmlElement singleton = null;
-            for (final Method method : classe.getDeclaredMethods()) {
-                final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
-                if (xe != null) {
-                    if (singleton != null) return;
-                    singleton = xe;
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the namespace declared on {@link XmlSchema} annotation.
-     * May be the namespace inherited from the package.
-     */
-    private static String namespace(final Class<?> classe, String classNS) {
-        if (classNS.equals(DEFAULT)) {
-            classNS = classe.getPackage().getDeclaredAnnotation(XmlSchema.class).namespace();
-        }
-        return classNS;
-    }
-
-    /**
-     * Adds a property in the given class in the given namespace.
-     */
-    private void add(final String namespace, final String typeName, final String property) throws SchemaException {
-        if (!LEGACY_NAMESPACES.contains(namespace)) {
-            if (!content.computeIfAbsent(namespace, (k) -> new TreeMap<>())
-                        .computeIfAbsent(typeName,  (k) -> new TreeSet<>())
-                        .add(property))
-            {
-                if (typeName.equals("Integer")) return;     // Exception because of GO_Integer and GO_Integer64.
-                throw new SchemaException(String.format("Duplicated property %s.%s in:%n%s", typeName, property, namespace));
-            }
-        }
-    }
-
-    /**
-     * Prints the {@value TransformingReader#FILENAME} file.
-     *
-     * @param  out  where to print the content.
-     * @throws IOException if an error occurred while printing the content.
-     */
-    public void print(final Appendable out) throws IOException {
-        for (final Map.Entry<String, Map<String, Set<String>>> e : content.entrySet()) {
-            out.append(e.getKey()).append('\n');                                            // Namespace
-            for (final Map.Entry<String, Set<String>> c : e.getValue().entrySet()) {
-                out.append(' ').append(c.getKey()).append('\n');                            // Class
-                for (final String p : c.getValue()) {
-                    out.append("  ").append(p).append('\n');                                // Property
-                }
-            }
-        }
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/CodeListMarshallingTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/CodeListMarshallingTest.java
index 3f3fb32..2a3c383 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/CodeListMarshallingTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/CodeListMarshallingTest.java
@@ -37,8 +37,8 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -107,7 +107,7 @@
     @Test
     public void testDefaultURL() throws JAXBException {
         final String expected = getResponsiblePartyXML(CodeListUID.METADATA_ROOT_LEGACY);
-        final Responsibility rp = unmarshal(Responsibility.class, expected);
+        final ResponsibleParty rp = unmarshal(ResponsibleParty.class, expected);
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, rp.getRole());
         /*
          * Use the convenience method in order to avoid the effort of creating
@@ -127,7 +127,7 @@
     @Test
     public void testLegacyISO_URL() throws JAXBException {
         final String expected = getResponsiblePartyXML("http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/");
-        final Responsibility rp = unmarshal(Responsibility.class, expected);
+        final ResponsibleParty rp = unmarshal(ResponsibleParty.class, expected);
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, rp.getRole());
 
         final MarshallerPool pool = getMarshallerPool();
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/LanguageCodeTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/LanguageCodeTest.java
index a5737d9..7249e95 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/LanguageCodeTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/LanguageCodeTest.java
@@ -37,9 +37,6 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.apache.sis.test.TestUtilities.getSingleton;
-
 
 /**
  * Tests the XML marshalling of {@code Locale} when used for a language.
@@ -154,7 +151,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(LANGUAGE_CODE);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
     }
 
     /**
@@ -176,7 +173,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(LANGUAGE_CODE_WITHOUT_ATTRIBUTE);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
         pool.recycle(unmarshaller);
     }
 
@@ -218,7 +215,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(CHARACTER_STRING);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
         pool.recycle(unmarshaller);
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java
index 6ea99d0..59d36ec 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java
@@ -28,10 +28,8 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
-import org.opengis.referencing.operation.Matrix;
-import org.opengis.metadata.quality.ValueStructure;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -53,11 +51,10 @@
      */
     public static QualityParameter create() {
         final QualityParameter param = new QualityParameter();
-        param.code           = "some parameter";
-        param.definition     = new SimpleInternationalString("a definition");
-        param.description    = new DefaultMeasureDescription("a description");
-        param.valueStructure = ValueStructure.MATRIX;
-        param.valueType      = Names.createTypeName(Integer.class);
+        param.code        = "some parameter";
+        param.definition  = new SimpleInternationalString("a definition");
+        param.description = new DefaultMeasureDescription("a description");
+        param.valueType   = Names.createTypeName(Integer.class);
         return param;
     }
 
@@ -67,11 +64,9 @@
     @Test
     public void testGetName() {
         final QualityParameter param = create();
-        final Identifier name = param.getName();
+        final ReferenceIdentifier name = param.getName();
         assertNull  (name.getCodeSpace());
         assertEquals("some parameter", name.getCode());
-        assertEquals("a definition",   name.getDescription().toString());
-        assertEquals("a description",  param.getDescription().orElseThrow().toString());
     }
 
     /**
@@ -80,7 +75,6 @@
     @Test
     public void testGetValueType() {
         final QualityParameter param = create();
-        assertEquals(Matrix.class, param.getValueClass());
         assertEquals("OGC:Integer", param.getValueType().toFullyQualifiedName().toString());
     }
 
@@ -115,10 +109,6 @@
                 "      </gco:aName>\n" +
                 "    </gco:TypeName>\n" +
                 "  </dqm:valueType>\n" +
-                "  <dqm:valueStructure>\n" +
-                "    <dqm:DQM_ValueStructure codeList=\"http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DQM_ValueStructure\""
-                                         + " codeListValue=\"matrix\">Matrix</dqm:DQM_ValueStructure>\n" +
-                "  </dqm:valueStructure>\n" +
                 "</dqm:DQM_Parameter>\n", xml, "xmlns:*");
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java
index 4d67ae3..c4eb58a 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java
@@ -27,9 +27,8 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
-import org.opengis.parameter.ParameterDirection;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -56,7 +55,6 @@
         param.memberName    = name;
         param.optionality   = true;
         param.repeatability = false;
-        param.direction     = ParameterDirection.IN;
         return param;
     }
 
@@ -66,7 +64,7 @@
     @Test
     public void testGetName() {
         final ServiceParameter param = create();
-        final Identifier name = param.getName();
+        final ReferenceIdentifier name = param.getName();
         assertEquals("TestSpace", name.getCodeSpace());
         assertEquals("My service parameter", name.getCode());
         assertEquals("TestSpace:My service parameter", name.toString());
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/AnnotationConsistencyCheck.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/AnnotationConsistencyCheck.java
index a80c7c6..3627078 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/AnnotationConsistencyCheck.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/AnnotationConsistencyCheck.java
@@ -46,11 +46,6 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCaseWithLogs;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.annotation.Classifier;
-import org.opengis.annotation.Stereotype;
-import org.opengis.util.ControlledVocabulary;
-
 
 /**
  * Verifies consistency between {@link UML}, {@link XmlElement} and other annotations.
@@ -72,7 +67,6 @@
  * </ul>
  *
  * This class does not verify JAXB annotations against a XSD file.
- * For such verification, see {@link SchemaCompliance}.
  *
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -229,13 +223,12 @@
      * <p>The default implementation recognizes the
      * {@linkplain Specification#ISO_19115   ISO 19115},
      * {@linkplain Specification#ISO_19115_2 ISO 19115-2},
-     * {@linkplain Specification#ISO_19115_3 ISO 19115-3},
      * {@linkplain Specification#ISO_19139   ISO 19139} and
      * {@linkplain Specification#ISO_19108   ISO 19108} specifications,
      * with a hard-coded list of exceptions to the general rule.
      * Subclasses shall override this method if they need to support more namespaces.</p>
      *
-     * <p>Note that a more complete verification is done by {@link SchemaCompliance}.
+     * <p>Note that a more complete verification is done by {@code SchemaCompliance}.
      * But the test done in this {@link AnnotationConsistencyCheck} class can be run without network access.</p>
      *
      * <p>The prefix for the given namespace will be fetched by
@@ -245,8 +238,10 @@
      * @param  uml   the UML associated to the class or the method.
      * @return the expected namespace.
      * @throws IllegalArgumentException if the given UML is unknown to this method.
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    @SuppressWarnings("deprecation")
+    @Deprecated
     protected String getExpectedNamespaceStart(final Class<?> impl, final UML uml) {
         final String identifier = uml.identifier();
         switch (identifier) {
@@ -255,13 +250,11 @@
             case "SV_OperationChainMetadata":
             case "SV_ServiceIdentification": {              // Historical reasons (other standard integrated into ISO 19115)
                 assertEquals(Specification.ISO_19115, uml.specification(), "Unexpected @Specification value.");
-                assertEquals((short) 0, uml.version(), "Specification version should be latest ISO 19115.");
                 return Namespaces.SRV;
             }
             case "DQ_TemporalAccuracy":                     // Renamed DQ_TemporalQuality
             case "DQ_NonQuantitativeAttributeAccuracy": {   // Renamed DQ_NonQuantitativeAttributeCorrectness
                 assertEquals(Specification.ISO_19115, uml.specification(), "Unexpected @Specification value.");
-                assertEquals((short) 2003, uml.version(), "Specification version should be legacy ISO 19115.");
                 return LegacyNamespaces.GMD;
             }
             case "role": {
@@ -307,27 +300,32 @@
             }
         }
         if (identifier.startsWith("DQ_")) {
-            assertEquals(Specification.ISO_19157, uml.specification(), "Unexpected @Specification value.");
-            assertEquals((short) 0, uml.version(), "Specification version should be ISO 19157.");
+            assertEquals(Specification.ISO_19115, uml.specification(), "Unexpected @Specification value.");
             return Namespaces.MDQ;
         }
         if (identifier.startsWith("DQM_")) {
-            assertEquals(Specification.ISO_19157, uml.specification(), "Unexpected @Specification value.");
-            assertEquals((short) 0, uml.version(), "Specification version should be ISO 19157.");
             return Namespaces.DQM;
         }
         if (identifier.startsWith("QE_")) {
             assertEquals(Specification.ISO_19115_2, uml.specification(), "Unexpected @Specification value.");
-            assertEquals((short) 2009, uml.version(), "Specification version should be legacy ISO 19115-2.");
-            return LegacyNamespaces.GMI;
+            switch (/*uml.version()*/ 0) {
+                case 0:    return Namespaces.MDQ;
+                case 2009: return LegacyNamespaces.GMI;
+                default: fail("Unexpected version number in " + uml);
+            }
+        }
+        if (org.opengis.metadata.quality.DataQuality.class.isAssignableFrom(impl) ||    // For properties in those types.
+            org.opengis.metadata.quality.Element.class.isAssignableFrom(impl) ||
+            org.opengis.metadata.quality.Result.class.isAssignableFrom(impl))
+        {
+            return Namespaces.MDQ;
         }
         /*
          * General cases (after we processed all the special cases)
          * based on which standard defines the type or property.
          */
-        final short version = uml.version();
         final Specification specification = uml.specification();
-        if (version != 0 && version < specification.defaultVersion()) {
+        if (/*uml.version()*/ 0 != 0) {
             switch (specification) {
                 case ISO_19115:   return LegacyNamespaces.GMD;
                 case ISO_19115_2: return LegacyNamespaces.GMI;
@@ -335,15 +333,9 @@
         }
         switch (specification) {
             case ISO_19115:
-            case ISO_19115_2:
-            case ISO_19115_3: return CodeListUID.METADATA_ROOT;
+            case ISO_19115_2: return CodeListUID.METADATA_ROOT;
             case ISO_19139:   return LegacyNamespaces.GMX;
             case ISO_19108:   return LegacyNamespaces.GMD;
-            case ISO_19157: {
-                // Case for a method. By contrast, above `identifier.startsWith(…)` checks were for types.
-                final UML parent = TestUtilities.getSingleton(impl.getInterfaces()).getAnnotation(UML.class);
-                return parent.identifier().startsWith("DQM_") ? Namespaces.DQM : Namespaces.MDQ;
-            }
             default: throw new IllegalArgumentException(uml.toString());
         }
     }
@@ -355,18 +347,17 @@
      * and unconditionally with {@code "_Type"} appended.
      * Subclasses shall override this method when mismatches are known to exist between the UML and XML type names.
      *
-     * @param  stereotype  the stereotype of the interface, or {@code null} if none.
-     * @param  uml         the UML of the interface for which to get the corresponding XML type name.
+     * @param  uml  the UML of the interface for which to get the corresponding XML type name.
      * @return the name of the XML type for the given element, or {@code null} if none.
      *
      * @see #testImplementationAnnotations()
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    protected String getExpectedXmlTypeName(final Stereotype stereotype, final UML uml) {
+    @Deprecated
+    protected String getExpectedXmlTypeName(final UML uml) {
         final String rootName = uml.identifier();
         final StringBuilder buffer = new StringBuilder(rootName.length() + 13);
-        if (stereotype == Stereotype.ABSTRACT) {
-            buffer.append("Abstract");
-        }
         return buffer.append(rootName).append("_Type").toString();
     }
 
@@ -375,16 +366,19 @@
      * The default implementation returns {@link UML#identifier()}, possibly with {@code "Abstract"} prepended.
      * Subclasses shall override this method when mismatches are known to exist between the UML and XML element names.
      *
-     * @param  stereotype  the stereotype of the interface, or {@code null} if none.
-     * @param  uml         the UML of the interface for which to get the corresponding XML root element name.
+     * @param  uml  the UML of the interface for which to get the corresponding XML root element name.
      * @return the name of the XML root element for the given UML.
      *
      * @see #testImplementationAnnotations()
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    protected String getExpectedXmlRootElementName(final Stereotype stereotype, final UML uml) {
+    @Deprecated
+    protected String getExpectedXmlRootElementName(final UML uml) {
         String name = uml.identifier();
-        if (stereotype == Stereotype.ABSTRACT) {
-            name = "Abstract".concat(name);
+        switch (name) {
+            // This case can be removed if https://issues.apache.org/jira/browse/SIS-398 is fixed.
+            case "MI_PolarizationOrientationCode": name = "MI_PolarisationOrientationCode"; break;
         }
         return name;
     }
@@ -406,6 +400,12 @@
     protected String getExpectedXmlElementName(final Class<?> enclosing, final UML uml) {
         String name = firstIdentifier(uml);
         switch (name) {
+            case "stepDateTime": {
+                if (org.opengis.metadata.lineage.ProcessStep.class.isAssignableFrom(enclosing)) {
+                    name = "dateTime";
+                }
+                break;
+            }
             case "satisfiedPlan": {
                 if (org.opengis.metadata.acquisition.Requirement.class.isAssignableFrom(enclosing)) {
                     name = "satisifiedPlan";                // Misspelling in ISO 19115-3:2016
@@ -418,6 +418,24 @@
                 }
                 break;
             }
+            case "detectedPolarization": {
+                if (org.opengis.metadata.content.Band.class.isAssignableFrom(enclosing)) {
+                    name = "detectedPolarisation";          // Spelling change in XSD files
+                }
+                break;
+            }
+            case "transmittedPolarization": {
+                if (org.opengis.metadata.content.Band.class.isAssignableFrom(enclosing)) {
+                    name = "transmittedPolarisation";       // Spelling change in XSD files
+                }
+                break;
+            }
+            case "featureType": {
+                if (org.opengis.metadata.distribution.DataFile.class.isAssignableFrom(enclosing)) {
+                    name = "featureTypes";                  // Spelling change in XSD files
+                }
+                break;
+            }
             case "valueType": {
                 if (org.opengis.metadata.quality.Result.class.isAssignableFrom(enclosing)) {
                     return "valueRecordType";
@@ -583,7 +601,7 @@
             case "getNominalSpatialResolution":
             case "getTransferFunctionType": {
                 final Class<?> dc = method.getDeclaringClass();
-                return org.opengis.metadata.content.SampleDimension.class.isAssignableFrom(dc)
+                return org.apache.sis.metadata.iso.content.DefaultSampleDimension.class.isAssignableFrom(dc)
                         && !org.opengis.metadata.content.Band.class.isAssignableFrom(dc);
             }
             /*
@@ -591,7 +609,7 @@
              * We do not duplicate the Java methods only for that.
              */
             case "getDerivedElements": {
-                return org.opengis.metadata.quality.Metaquality.class.isAssignableFrom(method.getDeclaringClass());
+                return true;
             }
             /*
              * - "resultContent" is a property in the ISO 10157 model but not yet in the XML schema.
@@ -636,7 +654,7 @@
             testingClass = type.getCanonicalName();
             UML uml = type.getAnnotation(UML.class);
             assertNotNull(uml, "Missing @UML annotation.");
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 for (final Method method : type.getDeclaredMethods()) {
                     if (isPublic(method)) {
                         testingMethod = method.getName();
@@ -666,7 +684,7 @@
     public void testPackageAnnotations() {
         final Set<Package> packages = new HashSet<>();
         for (final Class<?> type : types) {
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 testingClass = type.getCanonicalName();
                 final Class<?> impl = getImplementation(type);
                 if (impl != null) {
@@ -707,7 +725,7 @@
     @Test
     public void testImplementationAnnotations() {
         for (final Class<?> type : types) {
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (CodeList.class.isAssignableFrom(type)) {
                 // Skip code lists, since they are not the purpose of this test.
                 continue;
             }
@@ -730,14 +748,7 @@
             final XmlRootElement root = impl.getAnnotation(XmlRootElement.class);
             assertNotNull(root, "Missing @XmlRootElement annotation.");
             final UML uml = type.getAnnotation(UML.class);
-            Stereotype stereotype = null;
-            if (uml != null) {
-                final Classifier c = type.getAnnotation(Classifier.class);
-                if (c != null) {
-                    stereotype = c.value();
-                }
-                assertEquals(getExpectedXmlRootElementName(stereotype, uml), root.name(), "Wrong @XmlRootElement.name().");
-            }
+            // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
             /*
              * Check that the namespace is the expected one (according subclass)
              * and is not redundant with the package @XmlSchema annotation.
@@ -748,11 +759,7 @@
              */
             final XmlType xmlType = impl.getAnnotation(XmlType.class);
             assertNotNull(xmlType, "Missing @XmlType annotation.");
-            String expected = getExpectedXmlTypeName(stereotype, uml);
-            if (expected == null) {
-                expected = DEFAULT;
-            }
-            assertEquals(expected, xmlType.name(), "Wrong @XmlType.name().");
+            // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
         }
         loggings.assertNoUnexpectedLog();
     }
@@ -770,7 +777,7 @@
     @Test
     public void testMethodAnnotations() {
         for (final Class<?> type : types) {
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (CodeList.class.isAssignableFrom(type)) {
                 // Skip code lists, since they are not the purpose of this test.
                 continue;
             }
@@ -844,9 +851,7 @@
                  */
                 if (uml != null) {
                     assertEquals(getExpectedXmlElementName(type, uml), element.name(), "Wrong @XmlElement.name().");
-                    if (!method.isAnnotationPresent(Deprecated.class) && uml.version() == 0) {
-                        assertEquals(uml.obligation() == Obligation.MANDATORY, element.required(), "Wrong @XmlElement.required().");
-                    }
+                    // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
                 }
                 /*
                  * Check that the namespace is the expected one (according subclass)
@@ -934,10 +939,10 @@
                 assertFalse(wrapper.isInherited, "Expected @XmlElementRef.");
                 final UML uml = type.getAnnotation(UML.class);
                 if (uml != null) {                  // `assertNotNull` is `testInterfaceAnnotations()` job.
-                    assertEquals(getExpectedXmlRootElementName(null, uml), element.name(), "Wrong @XmlElement.");
+                    assertEquals(getExpectedXmlRootElementName(uml), element.name(), "Wrong @XmlElement.");
                 }
                 final String namespace = assertExpectedNamespace(element.namespace(), wrapper.type, uml);
-                if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+                if (!CodeList.class.isAssignableFrom(type)) {
                     final String expected = getNamespace(getImplementation(type));
                     if (expected != null) {         // `assertNotNull` is `testImplementationAnnotations()` job.
                         assertEquals(expected, namespace, "Inconsistent @XmlRootElement namespace.");
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/PackageVerifier.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/PackageVerifier.java
deleted file mode 100644
index 8650b2a..0000000
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/PackageVerifier.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.xml.test;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.HashMap;
-import java.util.Collection;
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.ParameterizedType;
-import javax.xml.parsers.ParserConfigurationException;
-import jakarta.xml.bind.annotation.XmlNs;
-import jakarta.xml.bind.annotation.XmlType;
-import jakarta.xml.bind.annotation.XmlSchema;
-import jakarta.xml.bind.annotation.XmlElement;
-import jakarta.xml.bind.annotation.XmlRootElement;
-import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
-import org.xml.sax.SAXException;
-import org.opengis.util.CodeList;
-import org.opengis.annotation.UML;
-import org.opengis.geoapi.schema.SchemaException;
-import org.opengis.geoapi.schema.SchemaInformation;
-import static org.opengis.geoapi.schema.SchemaInformation.ROOT_NAMESPACE;
-import static org.opengis.geoapi.schema.SchemaInformation.SCHEMA_ROOT_URL;
-import org.apache.sis.util.Classes;
-import org.apache.sis.system.Modules;
-import org.apache.sis.xml.Namespaces;
-import org.apache.sis.xml.privy.LegacyNamespaces;
-
-// Test dependencies
-import static org.apache.sis.test.TestCase.PENDING_FUTURE_SIS_VERSION;
-
-
-/**
- * Verifies JAXB annotations in a single package. A new instance of this class is created by
- * {@link SchemaCompliance#verify(java.nio.file.Path)} for each Java package to be verified.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-final class PackageVerifier {
-    /**
-     * Sentinel value used in {@link #LEGACY_NAMESPACES} for meaning "all properties in that namespace".
-     */
-    @SuppressWarnings("unchecked")
-    private static final Set<String> ALL = InfiniteSet.INSTANCE;
-
-    /**
-     * Classes or properties having a JAXB annotation in this namespace should be deprecated.
-     * Deprecated namespaces are enumerated as keys. If the associated value is {@link #ALL},
-     * the whole namespace is deprecated. If the value is not ALL, then only the enumerated
-     * properties are deprecated.
-     *
-     * <p>Non-ALL values are rare. They happen in a few cases where a property is legacy despite its namespace.
-     * Those "properties" are errors in the legacy ISO 19139:2007 schema; they were types without their property
-     * wrappers. For example, in {@code SV_CoupledResource}, {@code <gco:ScopedName>} was marshalled without its
-     * {@code <srv:scopedName>} wrapper — note the upper and lower-case "s". Because {@code ScopedName} is a type,
-     * we had to keep the namespace declared in {@link org.apache.sis.util.iso.DefaultScopedName}
-     * (the replacement is performed by {@code org.apache.sis.xml.TransformingWriter}).</p>
-     */
-    private static final Map<String, Set<String>> LEGACY_NAMESPACES = Map.of(
-            LegacyNamespaces.GMD, ALL,
-            LegacyNamespaces.GMI, ALL,
-            LegacyNamespaces.GMX, ALL,
-            LegacyNamespaces.SRV, ALL,
-            Namespaces.GCO, Set.of("ScopedName"));     // Not to be confused with standard <srv:scopedName>
-
-    /**
-     * Types declared in JAXB annotations to be considered as equivalent to types in XML schemas.
-     */
-    private static final Map<String,String> TYPE_EQUIVALENCES = Map.of(
-            "PT_FreeText",             "CharacterString",
-            "Abstract_Citation",       "CI_Citation",
-            "AbstractCI_Party",        "CI_Party",
-            "Abstract_Responsibility", "CI_Responsibility",
-            "Abstract_Extent",         "EX_Extent");
-
-    /**
-     * XML elements that are not yet in the XML schema used by this verifier.
-     * They are XML elements added by corrigendum applied on abstract models,
-     * but not yet (at the time of writing this test) propagated in the XML schema.
-     */
-    private static final Map<Class<?>, String> PENDING_XML_ELEMENTS = Map.of(
-            org.apache.sis.metadata.iso.citation.AbstractParty.class, "partyIdentifier",
-            org.apache.sis.metadata.iso.content.DefaultSampleDimension.class, "rangeElementDescription",
-            org.apache.sis.metadata.iso.spatial.AbstractSpatialRepresentation.class, "scope");
-
-    /**
-     * The schemas to compare with the JAXB annotations.
-     * Additional schemas will be loaded as needed.
-     */
-    private final SchemaCompliance schemas;
-
-    /**
-     * The package name, for reporting error.
-     */
-    private final String packageName;
-
-    /**
-     * The default namespace to use if a class does not define explicitly a namespace.
-     */
-    private final String packageNS;
-
-    /**
-     * The namespace of the class under examination.
-     * This field must be updated for every class found in a package.
-     */
-    private String classNS;
-
-    /**
-     * The class under examination, used in error messages.
-     * This field must be updated for every class found in a package.
-     */
-    private Class<?> currentClass;
-
-    /**
-     * Whether the class under examination is defined in a legacy namespace.
-     * In such case, some checks may be skipped because we didn't loaded schemas for legacy properties.
-     */
-    private boolean isDeprecatedClass;
-
-    /**
-     * The schema definition for the class under examination.
-     *
-     * @see SchemaCompliance#getTypeDefinition(String)
-     */
-    private Map<String, SchemaCompliance.Element> properties;
-
-    /**
-     * Whether a namespace is actually used of not.
-     * We use this map for identifying unnecessary prefix declarations.
-     */
-    private final Map<String,Boolean> namespaceIsUsed;
-
-    /**
-     * Whether adapters declared in {@code package-info.java} are used or not.
-     */
-    private final Map<Class<?>,Boolean> adapterIsUsed;
-
-    /**
-     * Creates a new verifier for the given package.
-     */
-    PackageVerifier(final SchemaCompliance schemas, final Package pkg)
-            throws IOException, ParserConfigurationException, SAXException, SchemaException
-    {
-        this.schemas = schemas;
-        namespaceIsUsed = new HashMap<>();
-        adapterIsUsed = new HashMap<>();
-        String name = "?", namespace = "";
-        if (pkg != null) {
-            name = pkg.getName();
-            final XmlSchema schema = pkg.getAnnotation(XmlSchema.class);
-            if (schema != null) {
-                namespace = schema.namespace();
-                String location = schema.location();
-                if (!XmlSchema.NO_LOCATION.equals(location)) {
-                    String expected = location;
-                    if (expected.startsWith(SCHEMA_ROOT_URL)) {
-                        expected = ROOT_NAMESPACE + expected.substring(SCHEMA_ROOT_URL.length());
-                    }
-                    if (!expected.startsWith(schema.namespace())) {
-                        throw new SchemaException("XML schema location inconsistent with namespace in package " + name);
-                    }
-                    schemas.loadSchema(location);
-                }
-                for (final XmlNs xmlns : schema.xmlns()) {
-                    final String pr = xmlns.prefix();
-                    final String ns = xmlns.namespaceURI();
-                    final String cr = schemas.allXmlNS.put(pr, ns);
-                    if (cr != null && !cr.equals(ns)) {
-                        throw new SchemaException(String.format("Prefix \"%s\" associated to two different namespaces:%n%s%n%s", pr, cr, ns));
-                    }
-                    if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) {
-                        throw new SchemaException(String.format("Duplicated namespace in package %s:%n%s", name, ns));
-                    }
-                }
-            }
-            /*
-             * Lists the type of all values for which an adapter is declared in package-info.
-             * If the type is not explicitly declared, then it is inferred from class signature.
-             */
-            final XmlJavaTypeAdapters adapters = pkg.getAnnotation(XmlJavaTypeAdapters.class);
-            if (adapters != null) {
-                for (final XmlJavaTypeAdapter adapter : adapters.value()) {
-                    Class<?> propertyType = adapter.type();
-                    if (propertyType == XmlJavaTypeAdapter.DEFAULT.class) {
-                        for (Class<?> adapterClass = adapter.value(); ; adapterClass = adapterClass.getSuperclass()) {
-                            final Type adapterType = adapterClass.getGenericSuperclass();
-                            if (adapterType == null) {
-                                throw new SchemaException(String.format(
-                                        "Cannot infer type for %s adapter.", adapter.value().getName()));
-                            }
-                            if (adapterType instanceof ParameterizedType p1) {
-                                final Type[] parameters = p1.getActualTypeArguments();
-                                if (parameters.length == 2) {
-                                    Type typeInAPI = parameters[1];
-                                    if (typeInAPI instanceof ParameterizedType p2) {
-                                        typeInAPI = p2.getRawType();
-                                    }
-                                    if (typeInAPI instanceof Class<?> pc) {
-                                        propertyType = pc;
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    if (adapterIsUsed.put((Class<?>) propertyType, Boolean.FALSE) != null) {
-                        throw new SchemaException(String.format(
-                                "More than one adapter for %s in package %s", propertyType, name));
-                    }
-                }
-            }
-        }
-        packageName = name;
-        packageNS = namespace;
-    }
-
-    /**
-     * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention
-     * (type name should be same as root element name with {@value SchemaCompliance#TYPE_SUFFIX} suffix appended),
-     * ensures that the name exists in the schema, and checks the namespace.
-     *
-     * @param  type  the class on which to verify annotations.
-     */
-    final void verify(final Class<?> type)
-            throws IOException, ParserConfigurationException, SAXException, SchemaException
-    {
-        /*
-         * Reinitialize fields to be updated for each class.
-         */
-        classNS           = null;
-        currentClass      = type;
-        isDeprecatedClass = false;
-        properties        = Map.of();
-
-        final XmlType        xmlType = type.getDeclaredAnnotation(XmlType.class);
-        final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class);
-        XmlElement codeList = null;
-        /*
-         * Get the type name and namespace from the @XmlType or @XmlRootElement annotations.
-         * If both of them are present, verify that they are consistent (same namespace and
-         * same name with "_Type" suffix in @XmlType). If the type name is not declared, we
-         * assume that it is the same as the class name (this is what Apache SIS 1.0 does
-         * in its org.apache.sis.xml.bind.metadata.code package for CodeList adapters).
-         */
-        final String isoName;       // ISO class name (not the same as Java class name).
-        if (xmlRoot != null) {
-            classNS = xmlRoot.namespace();
-            isoName = xmlRoot.name();
-            if (xmlType != null) {
-                if (!classNS.equals(xmlType.namespace())) {
-                    throw new SchemaException(errorInClassMember(null)
-                            .append("Mismatched namespace in @XmlType and @XmlRootElement.").toString());
-                }
-                SchemaCompliance.verifyNamingConvention(type.getName(), isoName, xmlType.name(), SchemaCompliance.TYPE_SUFFIX);
-            }
-        } else if (xmlType != null) {
-            classNS = xmlType.namespace();
-            final String name = xmlType.name();
-            isoName = SchemaCompliance.trim(name, SchemaCompliance.TYPE_SUFFIX);
-        } else {
-            /*
-             * If there is neither @XmlRootElement or @XmlType annotation, it may be a code list as implemented
-             * in the org.apache.sis.xml.bind.metadata.code package. Those adapters have a single @XmlElement which
-             * is to be interpreted as if it was the actual type.
-             */
-            for (final Method method : type.getDeclaredMethods()) {
-                final XmlElement e = method.getDeclaredAnnotation(XmlElement.class);
-                if (e != null) {
-                    if (codeList != null) return;
-                    codeList = e;
-                }
-            }
-            if (codeList == null) return;
-            classNS = codeList.namespace();
-            isoName = codeList.name();
-        }
-        /*
-         * Verify that the namespace declared on the class is not redundant with the namespace
-         * declared in the package. Actually redundant namespaces are not wrong, but we try to
-         * reduce code size.
-         */
-        if (classNS.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            classNS = packageNS;
-        } else if (classNS.equals(packageNS)) {
-            throw new SchemaException(errorInClassMember(null)
-                    .append("Redundant namespace declaration: ").append(classNS).toString());
-        }
-        /*
-         * Verify that the namespace has a prefix associated to it in the package-info file.
-         */
-        if (namespaceIsUsed.put(classNS, Boolean.TRUE) == null) {
-            throw new SchemaException(errorInClassMember(null)
-                    .append("No prefix in package-info for ").append(classNS).toString());
-        }
-        /*
-         * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement
-         * is already available or not. However, properties in other namespaces should not be deprecated.
-         * Some validations of deprecated properties are skipped because we didn't loaded their schema.
-         */
-        isDeprecatedClass = (LEGACY_NAMESPACES.get(classNS) == ALL);
-        if (!isDeprecatedClass) {
-            if (type.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append("Unexpected @Deprecated annotation.").toString());
-            }
-            /*
-             * Verify that class name exists, then verify its namespace (associated to the null key by convention).
-             */
-            properties = schemas.getTypeDefinition(isoName);
-            if (properties == null) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append("Unknown name declared in @XmlRootElement: ").append(isoName).toString());
-            }
-            final String expectedNS = properties.get(null).namespace;
-            if (!classNS.equals(expectedNS)) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append(isoName).append(" shall be associated to namespace ").append(expectedNS).toString());
-            }
-            if (codeList != null) return;                   // If the class was a code list, we are done.
-        }
-        /*
-         * At this point the classNS, className, isDeprecatedClass and properties field have been set.
-         * We can now loop over the XML elements, which may be on fields or on methods (public or private).
-         */
-        for (final Field field : type.getDeclaredFields()) {
-            Class<?> valueType = field.getType();
-            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
-            if (isCollection) {
-                valueType = Classes.boundOfParameterizedProperty(field);
-            }
-            verify(field, field.getName(), valueType, isCollection);
-        }
-        for (final Method method : type.getDeclaredMethods()) {
-            Class<?> valueType = method.getReturnType();
-            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
-            if (isCollection || Classes.isParameterizedProperty(valueType)) {
-                valueType = Classes.boundOfParameterizedProperty(method);
-            }
-            verify(method, method.getName(), valueType, isCollection);
-        }
-    }
-
-    /**
-     * Validate a field or a method against the expected schema.
-     *
-     * @param  property      the field or method to validate.
-     * @param  javaName      the field name or method name in Java code.
-     * @param  valueType     the field type or the method return type, or element type in case of collection.
-     * @param  isCollection  whether the given value type is the element type of a collection.
-     */
-    private void verify(final AnnotatedElement property, final String javaName,
-            final Class<?> valueType, final boolean isCollection) throws SchemaException
-    {
-        final XmlElement element = property.getDeclaredAnnotation(XmlElement.class);
-        if (element == null) {
-            return;                               // No @XmlElement annotation - skip this property.
-        }
-        String name = element.name();
-        if (name.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            name = javaName;
-        }
-        String ns = element.namespace();
-        if (ns.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            ns = classNS;
-        }
-        if (namespaceIsUsed.put(ns, Boolean.TRUE) == null) {
-            throw new SchemaException(errorInClassMember(javaName)
-                    .append("Missing @XmlNs for namespace ").append(ns).toString());
-        }
-        /*
-         * Remember that we need an adapter for this property, unless the method or field defines its own adapter.
-         * In theory we do not need to report missing adapter since JAXB performs its own check, but we do anyway
-         * because JAXB has default adapters for String, Double, Boolean, Date, etc. which do not match the way
-         * OGC/ISO marshal those elements.
-         */
-        if (!property.isAnnotationPresent(XmlJavaTypeAdapter.class) && valueType != null) {
-            /*
-             * Internal classes in Apache SIS "jaxb" subpackages can be marshalled directly.
-             * Apache SIS classes defined in other packages may be code lists, which still need adapters.
-             */
-            if (!valueType.getName().startsWith(Modules.CLASSNAME_PREFIX) || CodeList.class.isAssignableFrom(valueType)) {
-                Class<?> c = valueType;
-                while (adapterIsUsed.replace(c, Boolean.TRUE) == null) {
-                    final Class<?> parent = c.getSuperclass();
-                    if (parent != null) {
-                        c = parent;
-                    } else {
-                        final Class<?>[] p = c.getInterfaces();
-                        if (p.length == 0) {
-                            if (valueType == org.opengis.metadata.Obligation.class)  {
-                                break;
-                            }
-                            throw new SchemaException(errorInClassMember(javaName)
-                                    .append("Missing @XmlJavaTypeAdapter for ").append(valueType).toString());
-                        }
-                        c = p[0];       // Take only the first interface, which should be the "main" parent.
-                    }
-                }
-            }
-        }
-        /*
-         * We do not verify fully the properties in legacy namespaces because we didn't loaded their schemas.
-         * However, we verify at least that those properties are not declared as required.
-         */
-        if (LEGACY_NAMESPACES.getOrDefault(ns, Set.of()).contains(name)) {
-            if (!isDeprecatedClass && element.required()) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Legacy property should not be required.").toString());
-            }
-        } else {
-            /*
-             * Property in non-legacy namespaces should not be deprecated. Verify also their namespace
-             * and whether the property is required or optional, and whether it should be a collection.
-             */
-            if (property.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Unexpected deprecation status.").toString());
-            }
-            final SchemaCompliance.Element info = properties.get(name);
-            if (info == null) {
-                if (name.equals(PENDING_XML_ELEMENTS.get(currentClass))) {
-                    return;
-                }
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Unexpected XML element: ").append(name).toString());
-            }
-            if (info.namespace != null && !ns.equals(info.namespace)) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Declared namespace: ").append(ns).append(System.lineSeparator())
-                        .append("Expected namespace: ").append(info.namespace).toString());
-            }
-            if (element.required() != info.isRequired) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Expected @XmlElement(required = ").append(info.isRequired).append(')').toString());
-            }
-            /*
-             * Following is a continuation of our check for multiplicity, but also the beginning of the check
-             * for return value type. The return type should be an interface with a UML annotation; we check
-             * that this annotation contains the name of the expected type.
-             */
-            if (isCollection) {
-                if (!info.isCollection) {
-                    if (PENDING_FUTURE_SIS_VERSION)  // Temporarily disabled because require GeoAPI modifications.
-                    throw new SchemaException(errorInClassMember(javaName).append("Value should be a singleton.").toString());
-                }
-            } else if (info.isCollection) {
-                if (PENDING_FUTURE_SIS_VERSION)  // Temporarily disabled because require GeoAPI modifications.
-                throw new SchemaException(errorInClassMember(javaName).append("Value should be a collection.").toString());
-            }
-            if (valueType != null) {
-                final UML valueUML = valueType.getAnnotation(UML.class);
-                if (valueUML != null) {
-                    String expected = info.typeName;
-                    String actual   = valueUML.identifier();
-                    expected = TYPE_EQUIVALENCES.getOrDefault(expected, expected);
-                    actual   = TYPE_EQUIVALENCES.getOrDefault(actual,   actual);
-                    if (!expected.equals(actual)) {
-                        if (PENDING_FUTURE_SIS_VERSION)  // Temporarily disabled because require GeoAPI modifications.
-                        throw new SchemaException(errorInClassMember(javaName)
-                                .append("Declared value type: ").append(actual).append(System.lineSeparator())
-                                .append("Expected value type: ").append(expected).toString());
-                    }
-                }
-            }
-            /*
-             * Verify if we have a @XmlNs for the type of the value. This is probably not required, but we
-             * do that as a safety. A common namespace added by this check is Metadata Common Classes (MCC).
-             */
-            final Map<String, SchemaCompliance.Element> valueInfo = schemas.getTypeDefinition(info.typeName);
-            if (valueInfo != null) {
-                final SchemaInformation.Element typeAndNS = valueInfo.get(null);
-                if (typeAndNS != null) {
-                    final String valueNS = typeAndNS.namespace;
-                    if (namespaceIsUsed.put(valueNS, Boolean.TRUE) == null) {
-                        throw new SchemaException(errorInClassMember(javaName)
-                                .append("Missing @XmlNs for property value namespace: ").append(valueNS).toString());
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns a message beginning with "Error in …", to be completed by the caller.
-     * This is an helper method for exception messages.
-     *
-     * @param  name  the property name, or {@code null} if none.
-     */
-    private StringBuilder errorInClassMember(final String name) {
-        final StringBuilder builder = new StringBuilder(80).append("Error in ");
-        if (isDeprecatedClass) {
-            builder.append("legacy ");
-        }
-        builder.append(currentClass.getCanonicalName());
-        if (name != null) {
-            builder.append('.').append(name);
-        }
-        return builder.append(':').append(System.lineSeparator());
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/SchemaCompliance.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/SchemaCompliance.java
deleted file mode 100644
index af1dcc8..0000000
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/SchemaCompliance.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.xml.test;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryIteratorException;
-import java.util.Map;
-import java.util.HashMap;
-import javax.xml.parsers.ParserConfigurationException;
-import jakarta.xml.bind.annotation.XmlNs;
-import jakarta.xml.bind.annotation.XmlElement;
-import org.xml.sax.SAXException;
-import org.opengis.geoapi.schema.Departures;
-import org.opengis.geoapi.schema.DocumentationStyle;
-import org.opengis.geoapi.schema.SchemaInformation;
-import org.opengis.geoapi.schema.SchemaException;
-import org.apache.sis.util.StringBuilders;
-
-
-/**
- * Compares JAXB annotations against the ISO 19115 schemas. This test requires a connection to
- * <a href="https://schemas.isotc211.org/19115/">https://schemas.isotc211.org/19115/</a>.
- * All classes in a given directory are scanned.
- *
- * <h2>Limitations</h2>
- * Current implementation ignores the XML prefix (e.g. {@code "cit:"} in {@code "cit:CI_Citation"}).
- * We assume that there is no name collision, especially given that {@code "CI_"} prefix in front of
- * most OGC/ISO class names have the effect of a namespace. If a collision nevertheless happen, then
- * an exception will be thrown.
- *
- * <p>Current implementation assumes that XML element name, type name, property name and property type
- * name follow some naming convention. For example, type names are suffixed with {@code "_Type"} in OGC
- * schemas, while property type names are suffixed with {@code "_PropertyType"}.  This class throws an
- * exception if a type does not follow the expected naming convention. This requirement makes
- * implementation easier, by reducing the number of {@link Map}s that we need to manage.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class SchemaCompliance extends SchemaInformation {
-    /**
-     * The prefix of XML type names for properties. In ISO/OGC schemas, this prefix does not appear
-     * in the definition of class types but may appear in the definition of property types.
-     */
-    private static final String ABSTRACT_PREFIX = "Abstract_";
-
-    /**
-     * The suffix of XML type names for classes.
-     * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
-     */
-    static final String TYPE_SUFFIX = "_Type";
-
-    /**
-     * Separator between XML prefix and the actual name.
-     */
-    private static final char PREFIX_SEPARATOR = ':';
-
-    /**
-     * Root directory from which to search for classes.
-     */
-    private final Path classRootDirectory;
-
-    /**
-     * The namespaces associated to prefixes, as declared by JAXB {@link XmlNs} annotations.
-     * Used for verifying that no prefix is defined twice for different namespaces.
-     *
-     * <p>This field is not really related to schema loading process. But we keep it in this class for
-     * {@link PackageVerifier} convenience, as a way to share a single map for all verifier instances.</p>
-     */
-    final Map<String,String> allXmlNS;
-
-    /**
-     * Creates a new verifier for classes under the given directory. The given directory shall be the
-     * root of {@code "*.class"} files. For example if the {@code mypackage.MyClass} class is compiled
-     * in the {@code "MyProject/target/classes/mypackage/MyClass.class"} file, then the root directory
-     * shall be {@code "MyProject/target/classes/"}.
-     *
-     * @param  classRootDirectory   the root of compiled class files.
-     * @param  schemaRootDirectory  if the computer contains a local copy of ISO schemas, path to that directory.
-     *                              Otherwise {@code null}. This is only for making tests faster.
-     */
-    public SchemaCompliance(final Path classRootDirectory, final Path schemaRootDirectory) {
-        super(schemaRootDirectory, new Departures(), DocumentationStyle.NONE);
-        this.classRootDirectory = classRootDirectory;
-        allXmlNS = new HashMap<>();
-    }
-
-    /**
-     * Verifies {@link XmlElement} annotations on all {@code *.class} files in the given directory and sub-directories.
-     * The given directory must be a sub-directory of the root directory given at construction time.
-     * This method will invoke itself for scanning sub-directories.
-     *
-     * @param  directory  the directory to scan for classes, relative to class root directory.
-     * @throws IOException if an error occurred while reading files or schemas.
-     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
-     * @throws ParserConfigurationException if {@link javax.xml.parsers.DocumentBuilder} cannot be created.
-     * @throws SAXException if an error occurred while parsing the XSD file.
-     * @throws SchemaException if a XSD file does not comply with our assumptions,
-     *         or a JAXB annotation failed a compliance check.
-     */
-    public void verify(final Path directory)
-            throws IOException, ClassNotFoundException, ParserConfigurationException, SAXException, SchemaException
-    {
-        PackageVerifier verifier = null;
-        final StringBuilder buffer = new StringBuilder();
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
-            for (Path path : stream) {
-                final String filename = path.getFileName().toString();
-                if (!filename.startsWith(".")) {
-                    if (Files.isDirectory(path)) {
-                        verify(path);
-                    } else if (filename.endsWith(".class")) {
-                        path = classRootDirectory.relativize(path);
-                        buffer.setLength(0);
-                        buffer.append(path.toString()).setLength(buffer.length() - 6);      // Remove ".class" suffix.
-                        StringBuilders.replace(buffer, File.separatorChar, '.');
-                        final Class<?> c = Class.forName(buffer.toString());
-                        if (verifier == null) {
-                            verifier = new PackageVerifier(this, c.getPackage());
-                        }
-                        verifier.verify(c);
-                    }
-                }
-            }
-        } catch (DirectoryIteratorException e) {
-            throw e.getCause();
-        }
-    }
-
-    /**
-     * Verifies that the relationship between the name of the given entity and its type are consistent with
-     * OGC/ISO conventions. This method ignores the prefix (e.g. {@code "mdb:"} in {@code "mdb:MD_Metadata"}).
-     *
-     * @param  enclosing  schema or other container where the error happened.
-     * @param  name       the class or property name. Example: {@code "MD_Metadata"}, {@code "citation"}.
-     * @param  type       the type of the above named object. Example: {@code "MD_Metadata_Type"}, {@code "CI_Citation_PropertyType"}.
-     * @param  suffix     the expected suffix at the end of {@code type}.
-     * @throws SchemaException if the given {@code name} and {@code type} are not compliant with expected convention.
-     */
-    static void verifyNamingConvention(final String enclosing,
-            final String name, final String type, final String suffix) throws SchemaException
-    {
-        if (type.endsWith(suffix)) {
-            int nameStart = name.indexOf(PREFIX_SEPARATOR) + 1;        // Skip "mdb:" or similar prefix.
-            int typeStart = type.indexOf(PREFIX_SEPARATOR) + 1;
-            if (name.startsWith(ABSTRACT_PREFIX, nameStart)) nameStart += ABSTRACT_PREFIX.length();
-            if (type.startsWith(ABSTRACT_PREFIX, typeStart)) typeStart += ABSTRACT_PREFIX.length();
-            final int length = name.length() - nameStart;
-            if (type.length() - typeStart - suffix.length() == length &&
-                    type.regionMatches(typeStart, name, nameStart, length))
-            {
-                return;
-            }
-        }
-        throw new SchemaException(String.format("Error in %s:%n" +
-                "The type name should be the name with \"%s\" suffix, but found name=\"%s\" and type=\"%s\">.",
-                enclosing, suffix, name, type));
-    }
-
-    /**
-     * Removes leading and trailing spaces if any, then the prefix and the suffix in the given name.
-     * The prefix is anything before the first {@value #PREFIX_SEPARATOR} character.
-     * The suffix must be the given string, otherwise an exception is thrown.
-     *
-     * @param  name     the name from which to remove prefix and suffix.
-     * @param  suffix   the suffix to remove.
-     * @return the given name without prefix and suffix.
-     * @throws SchemaException if the given name does not end with the given suffix.
-     */
-    static String trim(String name, final String suffix) throws SchemaException {
-        name = name.trim();
-        if (name.endsWith(suffix)) {
-            return name.substring(name.indexOf(PREFIX_SEPARATOR) + 1, name.length() - suffix.length());
-        }
-        throw new SchemaException(String.format("Expected a name ending with \"%s\" but got \"%s\".", suffix, name));
-    }
-}
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/package-info.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/package-info.java
index 5406add..b9bb88c 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/test/package-info.java
@@ -18,7 +18,6 @@
 /**
  * Utility methods for testing XML files or JAXB annotations.
  * {@link org.apache.sis.xml.test.AnnotationConsistencyCheck} and
- * {@link org.apache.sis.xml.test.SchemaCompliance} verifies JAXB annotations.
  * {@link org.apache.sis.xml.test.DocumentComparator} compares an actual XML document with the expected one.
  *
  * <p>Objects defined in this package are only for SIS testing purpose any many change
diff --git a/endorsed/src/org.apache.sis.openoffice/bundle/README.md b/endorsed/src/org.apache.sis.openoffice/bundle/README.md
index e21a698..84cd4dc 100644
--- a/endorsed/src/org.apache.sis.openoffice/bundle/README.md
+++ b/endorsed/src/org.apache.sis.openoffice/bundle/README.md
@@ -83,7 +83,7 @@
 
 ```
 cd target
-unopkg add apache-sis-1.x-SNAPSHOT.oxt --log-file log.txt
+unopkg add apache-sis-1.5-SNAPSHOT.oxt --log-file log.txt
 scalc -env:RTL_LOGFILE=log.txt
 ```
 
diff --git a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
index 3883c2d..c6a2624 100644
--- a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
+++ b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
@@ -42,8 +42,9 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.base.CodeType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.apache.sis.referencing.DefaultObjectDomain;
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -106,7 +107,6 @@
      * @throws FactoryException if an error occurred while creating the object.
      * @throws DataStoreException if an error occurred while reading a data file.
      */
-    @SuppressWarnings("removal")
     private IdentifiedObject getIdentifiedObject(final String codeOrPath, CodeType type)
             throws FactoryException, DataStoreException
     {
@@ -175,7 +175,7 @@
                 return object.getName().getCode();
             }
             // In Apache SIS implementation, `getDescriptionText(…)` returns the identified object name.
-            name = CRS.getAuthorityFactory(null).getDescriptionText(IdentifiedObject.class, codeOrPath).orElse(null);
+            name = CRS.getAuthorityFactory(null).getDescriptionText(codeOrPath);
         } catch (Exception exception) {
             return getLocalizedMessage(exception);
         }
@@ -193,7 +193,7 @@
         final Object value;
         try {
             final IdentifiedObject object = getIdentifiedObject(codeOrPath, null);
-            for (final ObjectDomain domain : object.getDomains()) {
+            for (final DefaultObjectDomain domain : Legacy.getDomains(object)) {
                 InternationalString scope = domain.getScope();
                 if (scope != null) {
                     return scope.toString(getJavaLocale());
@@ -215,7 +215,7 @@
     public String getDomainOfValidity(final String codeOrPath) {
         try {
             final IdentifiedObject object = getIdentifiedObject(codeOrPath, null);
-            for (final ObjectDomain domain : object.getDomains()) {
+            for (final DefaultObjectDomain domain : Legacy.getDomains(object)) {
                 final Extent extent = domain.getDomainOfValidity();
                 if (extent != null) {
                     final InternationalString description = extent.getDescription();
@@ -250,7 +250,7 @@
                 area = handler.peek();
                 if (area == null) try {
                     final IdentifiedObject object = getIdentifiedObject(codeOrPath, null);
-                    for (final ObjectDomain domain : object.getDomains()) {
+                    for (final DefaultObjectDomain domain : Legacy.getDomains(object)) {
                         area = Extents.getGeographicBoundingBox(domain.getDomainOfValidity());
                         if (area != null) {
                             break;
diff --git a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/Transformer.java b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/Transformer.java
index 410e140..89ee4d8 100644
--- a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/Transformer.java
+++ b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/Transformer.java
@@ -161,7 +161,7 @@
                     sourcePt.coordinates[i] = (i < coords.length) ? coords[i] : 0;
                 }
                 try {
-                    result[j] = mt.transform(sourcePt, targetPt).getCoordinates();
+                    result[j] = mt.transform(sourcePt, targetPt).getCoordinate();
                 } catch (TransformException exception) {
                     /*
                      * The coordinate operation failed for this particular point. But maybe it will
@@ -200,8 +200,8 @@
         }
         final GeneralEnvelope result = Envelopes.transform(operation, new GeneralEnvelope(min, max));
         return new double[][] {
-            result.getLowerCorner().getCoordinates(),
-            result.getUpperCorner().getCoordinates()
+            result.getLowerCorner().getCoordinate(),
+            result.getUpperCorner().getCoordinate()
         };
     }
 }
diff --git a/endorsed/src/org.apache.sis.portrayal/main/module-info.java b/endorsed/src/org.apache.sis.portrayal/main/module-info.java
index af2abe2..6be2cb7 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/module-info.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/module-info.java
@@ -33,9 +33,6 @@
     exports org.apache.sis.map.coverage to
             org.apache.sis.gui;                         // In the "optional" sub-project.
 
-    exports org.apache.sis.style.se1 to
-            org.apache.sis.portrayal.map;               // In the "incubator" sub-project.
-
     /*
      * Allow JAXB to use reflection for marshalling and
      * unmarshalling Apache SIS objects in XML documents.
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
index 4511f97..d3a9302 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
@@ -59,9 +59,9 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.apache.sis.geometry.MismatchedReferenceSystemException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
@@ -572,7 +572,7 @@
                      */
                     oldObjectiveToDisplay = getObjectiveToDisplay();
                     final WraparoundApplicator wp = new WraparoundApplicator(null, objectivePOI, oldValue.getCoordinateSystem());
-                    final MathTransform change = orthogonalTangent(wp.forDomainOfUse(newToOld), anchor.getCoordinates());
+                    final MathTransform change = orthogonalTangent(wp.forDomainOfUse(newToOld), anchor.getCoordinate());
                     final MathTransform result = MathTransforms.concatenate(change, oldObjectiveToDisplay);
                     /*
                      * The result is the new `objectiveToTransform` such as the display is unchanged around POI.
@@ -739,7 +739,7 @@
                 return;
             }
         }
-        throw new org.opengis.geometry.MismatchedDimensionException(errors().getString(
+        throw new MismatchedDimensionException(errors().getString(
                 Errors.Keys.MismatchedDimension_3, OBJECTIVE_TO_DISPLAY_PROPERTY, expected, actual));
     }
 
@@ -911,7 +911,7 @@
      * May be {@code null} if the point of interest is unknown.
      */
     final double[] getObjectivePOI() {
-        return (objectivePOI != null) ? objectivePOI.getCoordinates() : null;
+        return (objectivePOI != null) ? objectivePOI.getCoordinate() : null;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasExtent.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasExtent.java
index 1390d8f..1687d1b 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasExtent.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/CanvasExtent.java
@@ -108,7 +108,7 @@
         }
         final double[] c = new double[agmDim];
         for (int i = poi.getDimension(); --i >= 0;) {
-            c[i] = poi.getCoordinate(i);
+            c[i] = poi.getOrdinate(i);
         }
         return new CanvasExtent(axisTypes, lower, upper, c);
     }
@@ -197,7 +197,7 @@
         while (supplementalDimensions != 0) {
             final int n = Long.numberOfTrailingZeros(supplementalDimensions);
             gridToCRS.setElement(j, j, Double.NaN);
-            gridToCRS.setElement(j++, agmDim, pointOfInterest.getCoordinate(n));
+            gridToCRS.setElement(j++, agmDim, pointOfInterest.getOrdinate(n));
             supplementalDimensions &= ~(1L << n);
         }
         return MathTransforms.linear(gridToCRS);
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AbstractStyle.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AbstractStyle.java
index 39f0e39..263294c 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AbstractStyle.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AbstractStyle.java
@@ -27,14 +27,15 @@
 import jakarta.xml.bind.annotation.XmlAttribute;
 import org.opengis.util.GenericName;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.ResourceId;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
 
 
 /**
  * Defines the styling that is to be applied on data of some arbitrary type.
  * The type of data is specified by the {@code <R>} parameterized type and depends on the concrete subclass.
- * The two main types are {@link org.opengis.feature.Feature} and {@link org.apache.sis.coverage.BandedCoverage}.
+ * The two main types are {@link org.apache.sis.feature.AbstractFeature}
+ * and {@link org.apache.sis.coverage.BandedCoverage}.
  *
  * <!-- Following list of authors contains credits to OGC GeoAPI 2 contributors. -->
  * @author  Johann Sorel (Geomatys)
@@ -92,7 +93,7 @@
      * @see #getFeatureInstanceIDs()
      * @see #setFeatureInstanceIDs(ResourceId)
      */
-    protected ResourceId<? super R> featureInstanceIDs;
+    protected Filter<? super R> featureInstanceIDs;
 
     /**
      * Name of the feature type that this style is meant to act upon, or {@code null} if none.
@@ -236,7 +237,7 @@
      *
      * @return identification of the feature instances.
      */
-    public Optional<ResourceId<? super R>> getFeatureInstanceIDs() {
+    public Optional<Filter<? super R>> getFeatureInstanceIDs() {
         return Optional.ofNullable(featureInstanceIDs);
     }
 
@@ -246,7 +247,7 @@
      *
      * @param  value  new identification of feature instances, or {@code null} if none.
      */
-    public void setFeatureInstanceIDs(final ResourceId<? super R> value) {
+    public void setFeatureInstanceIDs(final Filter<? super R> value) {
         featureInstanceIDs = value;
     }
 
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AnchorPoint.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AnchorPoint.java
index f894f27..b89f88a 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AnchorPoint.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/AnchorPoint.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ContrastEnhancement.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ContrastEnhancement.java
index 794f189..25cced3 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ContrastEnhancement.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ContrastEnhancement.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Displacement.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Displacement.java
index 119c8ff..5b32357 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Displacement.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Displacement.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ExpressionAdapter.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ExpressionAdapter.java
index 2dd08f7..f2a3b93 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ExpressionAdapter.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ExpressionAdapter.java
@@ -18,8 +18,8 @@
 
 import jakarta.xml.bind.annotation.adapters.XmlAdapter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/FeatureTypeStyle.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/FeatureTypeStyle.java
index 34c96f2..2c9c7a3 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/FeatureTypeStyle.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/FeatureTypeStyle.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlRootElement;
 import org.apache.sis.filter.DefaultFilterFactory;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -34,11 +34,11 @@
  */
 @XmlType(name = "FeatureTypeStyleType")
 @XmlRootElement(name = "FeatureTypeStyle")
-public class FeatureTypeStyle extends AbstractStyle<Feature> {
+public class FeatureTypeStyle extends AbstractStyle<AbstractFeature> {
     /**
      * The default style factory for features.
      */
-    public static final StyleFactory<Feature> FACTORY =
+    public static final StyleFactory<AbstractFeature> FACTORY =
             new StyleFactory<>(DefaultFilterFactory.forFeatures());
 
     /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Fill.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Fill.java
index a1a69ba..967ab34 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Fill.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Fill.java
@@ -22,8 +22,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Font.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Font.java
index fd5adf9..e822c15 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Font.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Font.java
@@ -21,8 +21,8 @@
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Graphic.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Graphic.java
index 339ceb3..51d08f2 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Graphic.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Graphic.java
@@ -23,8 +23,8 @@
 import jakarta.xml.bind.annotation.XmlElements;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/GraphicStroke.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/GraphicStroke.java
index 89e698f..9152165 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/GraphicStroke.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/GraphicStroke.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Halo.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Halo.java
index e99788d..7dd1bdd 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Halo.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Halo.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LinePlacement.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LinePlacement.java
index ef57751..0a52d58 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LinePlacement.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LinePlacement.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LineSymbolizer.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LineSymbolizer.java
index 71481e4..a742bb5 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LineSymbolizer.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/LineSymbolizer.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Mark.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Mark.java
index bf70e3a..fbea3f9 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Mark.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Mark.java
@@ -22,8 +22,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PointPlacement.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PointPlacement.java
index ffa53f5..f233a0d 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PointPlacement.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PointPlacement.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PolygonSymbolizer.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PolygonSymbolizer.java
index 038221d..cf88922 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PolygonSymbolizer.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/PolygonSymbolizer.java
@@ -22,8 +22,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/RasterSymbolizer.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/RasterSymbolizer.java
index 79d1d2f..442ac82 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/RasterSymbolizer.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/RasterSymbolizer.java
@@ -21,8 +21,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Rule.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Rule.java
index 6abb5ab..4224b6a 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Rule.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Rule.java
@@ -28,8 +28,8 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/SelectedChannel.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/SelectedChannel.java
index efeab4a..fae15f2 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/SelectedChannel.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/SelectedChannel.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ShadedRelief.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ShadedRelief.java
index c33153b..e5bafab 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ShadedRelief.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/ShadedRelief.java
@@ -20,8 +20,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Stroke.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Stroke.java
index be3ac3c..b364d35 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Stroke.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Stroke.java
@@ -22,8 +22,8 @@
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleElement.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleElement.java
index cb20c18..72ce62a 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleElement.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleElement.java
@@ -22,9 +22,8 @@
 import jakarta.xml.bind.annotation.XmlTransient;
 import org.opengis.util.InternationalString;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -86,7 +85,7 @@
      * @param  value  the value for which to return a literal, or {@code null} if none.
      * @return literal for the given value, or {@code null} if the given value was null.
      */
-    public final <E> Literal<R,E> literal(final E value) {
+    public final <E> Expression<R,E> literal(final E value) {
         return (value == null) ? null : factory.filterFactory.literal(value);
     }
 
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleFactory.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleFactory.java
index 90e2f83..40b2e80 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleFactory.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/StyleFactory.java
@@ -22,17 +22,17 @@
 import org.apache.sis.feature.privy.AttributeConvention;
 import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.DefaultFilterFactory;
 
 
 /**
  * Factory of style elements.
  * Style factory uses a {@link FilterFactory} instance that depends on the type of data to be styled.
  * That type of data is specified by the parameterized type {@code <R>}.
- * The two main types are {@link org.opengis.feature.Feature} and {@link org.apache.sis.coverage.BandedCoverage}.
+ * The two main types are {@link org.apache.sis.feature.AbstractFeature}
+ * and {@link org.apache.sis.coverage.BandedCoverage}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
@@ -42,7 +42,7 @@
     /**
      * The factory to use for creating expressions.
      */
-    final FilterFactory<R,?,?> filterFactory;
+    final DefaultFilterFactory<R,?,?> filterFactory;
 
     /**
      * Literal commonly used as a default value.
@@ -50,12 +50,12 @@
      * @see StyleElement#defaultToTrue(Expression)
      * @see StyleElement#defaultToFalse(Expression)
      */
-    final Literal<R,Boolean> enabled, disabled;
+    final Expression<R,Boolean> enabled, disabled;
 
     /**
      * Literal commonly used as a default value.
      */
-    final Literal<R,Integer> zeroAsInt;
+    final Expression<R,Integer> zeroAsInt;
 
     /**
      * Literal commonly used as a default value.
@@ -64,23 +64,23 @@
      * @see StyleElement#defaultToHalf(Expression)
      * @see StyleElement#defaultToOne(Expression)
      */
-    final Literal<R,Double> zero, half, one, six, ten;
+    final Expression<R,Double> zero, half, one, six, ten;
 
     /**
      * Default factor for shaded relief.
      * This is an arbitrary suggested but not standardized by OGC 05-077r4.
      */
-    final Literal<R,Double> relief;
+    final Expression<R,Double> relief;
 
     /**
      * Literal commonly used as a default value.
      */
-    final Literal<R,String> normal, square, bevel;
+    final Expression<R,String> normal, square, bevel;
 
     /**
      * Literal for a predefined color which can be used as fill color.
      */
-    final Literal<R,Color> black, gray, white;
+    final Expression<R,Color> black, gray, white;
 
     /**
      * An expression for fetching the default geometry.
@@ -88,14 +88,14 @@
      * @todo According SE specification, the default expression in the context of some symbolizers should
      *       fetch all geometries, not only a default one. The default seems to depend on the symbolizer type.
      */
-    final ValueReference<R,?> defaultGeometry;
+    final Expression<R,?> defaultGeometry;
 
     /**
      * Creates a new style factory.
      *
      * @param  filterFactory  the factory to use for creating expressions.
      */
-    public StyleFactory(final FilterFactory<R,?,?> filterFactory) {
+    public StyleFactory(final DefaultFilterFactory<R,?,?> filterFactory) {
         this.filterFactory = Objects.requireNonNull(filterFactory);
         enabled   = filterFactory.literal(Boolean.TRUE);
         disabled  = filterFactory.literal(Boolean.FALSE);
@@ -126,21 +126,21 @@
      */
     @SuppressWarnings("unchecked")
     StyleFactory(final StyleFactory<?> source) {
-        enabled   = (Literal<R,Boolean>) source.enabled;
-        disabled  = (Literal<R,Boolean>) source.disabled;
-        zeroAsInt = (Literal<R,Integer>) source.zeroAsInt;
-        zero      = (Literal<R,Double>)  source.zero;
-        half      = (Literal<R,Double>)  source.half;
-        one       = (Literal<R,Double>)  source.one;
-        six       = (Literal<R,Double>)  source.six;
-        ten       = (Literal<R,Double>)  source.ten;
-        relief    = (Literal<R,Double>)  source.relief;
-        normal    = (Literal<R,String>)  source.normal;
-        square    = (Literal<R,String>)  source.square;
-        bevel     = (Literal<R,String>)  source.bevel;
-        black     = (Literal<R,Color>)   source.black;
-        gray      = (Literal<R,Color>)   source.gray;
-        white     = (Literal<R,Color>)   source.white;
+        enabled   = (Expression<R,Boolean>) source.enabled;
+        disabled  = (Expression<R,Boolean>) source.disabled;
+        zeroAsInt = (Expression<R,Integer>) source.zeroAsInt;
+        zero      = (Expression<R,Double>)  source.zero;
+        half      = (Expression<R,Double>)  source.half;
+        one       = (Expression<R,Double>)  source.one;
+        six       = (Expression<R,Double>)  source.six;
+        ten       = (Expression<R,Double>)  source.ten;
+        relief    = (Expression<R,Double>)  source.relief;
+        normal    = (Expression<R,String>)  source.normal;
+        square    = (Expression<R,String>)  source.square;
+        bevel     = (Expression<R,String>)  source.bevel;
+        black     = (Expression<R,Color>)   source.black;
+        gray      = (Expression<R,Color>)   source.gray;
+        white     = (Expression<R,Color>)   source.white;
 
         filterFactory   = null;   // TODO: FilterFactory for coverage is not yet available.
         defaultGeometry = null;   // Idem.
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Symbolizer.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Symbolizer.java
index 297a78d..8f379f5 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Symbolizer.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Symbolizer.java
@@ -28,9 +28,8 @@
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -224,7 +223,6 @@
 
     /**
      * Returns the expression used for fetching the geometry to draw.
-     * This expression is often a {@link ValueReference}.
      * The value in the referenced feature property should be a geometry
      * from a {@linkplain org.apache.sis.setup.GeometryLibrary supported library},
      * expect in the particular case of {@link RasterSymbolizer} where the value
@@ -240,7 +238,7 @@
     /**
      * Sets the expression used for fetching the geometry to draw.
      * If this method is never invoked, then the default value is
-     * a {@link ValueReference} fetching {@code "sis:geometry"}.
+     * a {@code ValueReference} fetching {@code "sis:geometry"}.
      *
      * @todo The default expression may change in a future version.
      *       According SE specification, the default should fetch all geometries.
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/TextSymbolizer.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/TextSymbolizer.java
index fdba948..968c590 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/TextSymbolizer.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/TextSymbolizer.java
@@ -22,8 +22,8 @@
 import jakarta.xml.bind.annotation.XmlElementRef;
 import jakarta.xml.bind.annotation.XmlRootElement;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Translucent.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Translucent.java
index f1f39f4..15735aa 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Translucent.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/style/se1/Translucent.java
@@ -16,8 +16,8 @@
  */
 package org.apache.sis.style.se1;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/HaloTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/HaloTest.java
index c2f7916..0a2369f 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/HaloTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/HaloTest.java
@@ -20,8 +20,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -44,7 +44,7 @@
         final var cdt = factory.createHalo();
 
         // Check default
-        Fill<Feature> value = cdt.getFill();
+        Fill<AbstractFeature> value = cdt.getFill();
         assertEquals(factory.white, value.getColor());
         assertLiteralEquals(1.0, value.getOpacity());
 
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/LineSymbolizerTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/LineSymbolizerTest.java
index 3a0fa5a..4744252 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/LineSymbolizerTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/LineSymbolizerTest.java
@@ -22,8 +22,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -46,7 +46,7 @@
         final var cdt = factory.createLineSymbolizer();
 
         // Check default
-        Stroke<Feature> value = cdt.getStroke();
+        Stroke<AbstractFeature> value = cdt.getStroke();
         assertLiteralEquals(Color.BLACK, value.getColor());
 
         // Check get/set
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointPlacementTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointPlacementTest.java
index 7997c48..089dffc 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointPlacementTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointPlacementTest.java
@@ -20,8 +20,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -44,7 +44,7 @@
         final var cdt = factory.createPointPlacement();
 
         // Check default
-        AnchorPoint<Feature> value = cdt.getAnchorPoint();
+        AnchorPoint<AbstractFeature> value = cdt.getAnchorPoint();
         assertLiteralEquals(0.5, value.getAnchorPointX());
         assertLiteralEquals(0.5, value.getAnchorPointY());
 
@@ -62,7 +62,7 @@
         final var cdt = factory.createPointPlacement();
 
         // Check default
-        Displacement<Feature> value = cdt.getDisplacement();
+        Displacement<AbstractFeature> value = cdt.getDisplacement();
         assertLiteralEquals(0.0, value.getDisplacementX());
         assertLiteralEquals(0.0, value.getDisplacementY());
 
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointSymbolizerTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointSymbolizerTest.java
index fbc3768..364e47a 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointSymbolizerTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PointSymbolizerTest.java
@@ -20,8 +20,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -44,7 +44,7 @@
         final var cdt = factory.createPointSymbolizer();
 
         // Check default
-        Graphic<Feature> value = cdt.getGraphic();
+        Graphic<AbstractFeature> value = cdt.getGraphic();
         assertLiteralEquals(1.0, value.getOpacity());
 
         // Check get/set
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PolygonSymbolizerTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PolygonSymbolizerTest.java
index bfae6de..6fa6f68 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PolygonSymbolizerTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/PolygonSymbolizerTest.java
@@ -22,8 +22,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -64,7 +64,7 @@
         final var cdt = factory.createPolygonSymbolizer();
 
         // Check default
-        Fill<Feature> value = cdt.getFill().orElseThrow();
+        Fill<AbstractFeature> value = cdt.getFill().orElseThrow();
         assertLiteralEquals(Color.GRAY, value.getColor());
 
         // Check get/set
@@ -82,7 +82,7 @@
         final var cdt = factory.createPolygonSymbolizer();
 
         // Check default
-        Displacement<Feature> value = cdt.getDisplacement();
+        Displacement<AbstractFeature> value = cdt.getDisplacement();
         assertLiteralEquals(0.0, value.getDisplacementX());
         assertLiteralEquals(0.0, value.getDisplacementY());
 
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/RuleTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/RuleTest.java
index 9977611..5a7a2a5 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/RuleTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/RuleTest.java
@@ -23,8 +23,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Filter;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/StyleTestCase.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/StyleTestCase.java
index 2654290..a8113c4 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/StyleTestCase.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/StyleTestCase.java
@@ -24,10 +24,10 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.Literal;
 
 
 /**
@@ -39,7 +39,7 @@
     /**
      * The factory to use for creating style elements.
      */
-    final StyleFactory<Feature> factory;
+    final StyleFactory<AbstractFeature> factory;
 
     /**
      * Creates a new test case.
@@ -55,14 +55,14 @@
      * @param  value  the value for which to return a literal.
      * @return literal for the given value.
      */
-    final <E> Literal<Feature,E> literal(final E value) {
+    final <E> Expression<AbstractFeature,E> literal(final E value) {
         return factory.filterFactory.literal(value);
     }
 
     /**
      * Creates a dummy description with arbitrary title and abstract.
      */
-    final Description<Feature> anyDescription() {
+    final Description<AbstractFeature> anyDescription() {
         final var value = factory.createDescription();
         value.setTitle(new SimpleInternationalString("A random title"));
         value.setAbstract(new SimpleInternationalString("A random abstract"));
@@ -73,7 +73,7 @@
      * Returns an expression with a random color.
      * The color is {@link #ANY_COLOR}.
      */
-    final Expression<Feature,Color> anyColor() {
+    final Expression<AbstractFeature,Color> anyColor() {
         return literal(ANY_COLOR);
     }
 
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/SymbolizerTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/SymbolizerTest.java
index 0f9f913..90f8f3b 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/SymbolizerTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/SymbolizerTest.java
@@ -22,8 +22,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/TextSymbolizerTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/TextSymbolizerTest.java
index 3d9c451..ddfa426 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/TextSymbolizerTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/style/se1/TextSymbolizerTest.java
@@ -20,8 +20,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -78,7 +78,7 @@
         final var cdt = factory.createTextSymbolizer();
 
         // Check default
-        LabelPlacement<Feature> value = cdt.getLabelPlacement();
+        LabelPlacement<AbstractFeature> value = cdt.getLabelPlacement();
         assertNotNull(value);
 
         // Check get/set
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocation.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocation.java
index 7f225d0..59a73d2 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocation.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocation.java
@@ -28,10 +28,8 @@
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.GeneralDirectPosition;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -67,7 +65,7 @@
  *
  * @since 0.8
  */
-public abstract class AbstractLocation implements Location {
+public abstract class AbstractLocation {
     /**
      * The description of the nature of this geographic identifier, or {@code null} if unspecified.
      *
@@ -76,7 +74,7 @@
      *
      * @see #getLocationType()
      */
-    private LocationType type;
+    private AbstractLocationType type;
 
     /**
      * The geographic identifier, or {@code null} if unspecified.
@@ -89,10 +87,24 @@
      * Creates a new location for the given geographic identifier.
      * This constructor accepts {@code null} arguments, but this is not recommended.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of {@code type} argument may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param type        the description of the nature of this geographic identifier.
      * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
      */
-    protected AbstractLocation(final LocationType type, final CharSequence identifier) {
+    protected AbstractLocation(final ModifiableLocationType type, final CharSequence identifier) {
+        this.type       = type;
+        this.identifier = identifier;
+    }
+
+    /**
+     * Temporary workaround for the lack of {@code LocationType} interface and {@code AbstractLocationType}
+     * being package-private. We do not want to expose {@code AbstractLocationType} in public API for now.
+     */
+    AbstractLocation(final AbstractLocationType type, final CharSequence identifier) {
         this.type       = type;
         this.identifier = identifier;
     }
@@ -115,7 +127,7 @@
      * for example “Paris, Texas”.
      *
      * <h4>Examples</h4>
-     * If {@link LocationType#getIdentifications()} contain “name”, then geographic identifiers may be country
+     * If {@code LocationType.getIdentifications()} contain “name”, then geographic identifiers may be country
      * names like “Japan” or “France”, or places like “Eiffel Tower”. If location type identifications contain
      * “code”, then geographic identifiers may be “SW1P 3AD” postcode.
      *
@@ -123,7 +135,6 @@
      *
      * @see ModifiableLocationType#getIdentifications()
      */
-    @Override
     public InternationalString getGeographicIdentifier() {
         return Types.toInternationalString(identifier);
     }
@@ -134,7 +145,6 @@
      *
      * @return other identifier(s) for the location instance, or an empty collection if none.
      */
-    @Override
     public Collection<? extends InternationalString> getAlternativeGeographicIdentifiers() {
         return Collections.emptySet();
     }
@@ -145,7 +155,6 @@
      *
      * @return date of creation of this version of the location instance, or {@code null} if none.
      */
-    @Override
     public TemporalExtent getTemporalExtent() {
         return null;
     }
@@ -161,7 +170,6 @@
      * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
      * @see org.apache.sis.metadata.iso.extent.DefaultBoundingPolygon
      */
-    @Override
     public GeographicExtent getGeographicExtent() {
         return null;
     }
@@ -175,7 +183,6 @@
      *
      * @return envelope that encompass the location, or {@code null} if none.
      */
-    @Override
     public Envelope getEnvelope() {
         final GeographicExtent extent = getGeographicExtent();
         return (extent instanceof GeographicBoundingBox) ? new Envelope2D((GeographicBoundingBox) extent) : null;
@@ -189,7 +196,6 @@
      *
      * @return coordinates of a representative point for the location instance, or {@code null} if none.
      */
-    @Override
     public DirectPosition getPosition() {
         final Envelope envelope = getEnvelope();
         if (envelope == null) {
@@ -207,10 +213,24 @@
     /**
      * Returns a description of the nature of this geographic identifier.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.
+     * If applied, this method will be made non-final.</div>
+     *
      * @return the nature of the identifier and its associated geographic location.
      */
-    @Override
-    public LocationType getLocationType() {
+    public final ModifiableLocationType getLocationType() {
+        return ModifiableLocationTypeAdapter.copy(type);
+    }
+
+    /**
+     * Workaround for the lack of {@code LocationType} interface in GeoAPI 3.0.
+     * This workaround will be removed in a future SIS version if the location
+     * type interface is introduced in a future GeoAPI version.
+     */
+    final AbstractLocationType type() {
         return type;
     }
 
@@ -218,14 +238,17 @@
      * Returns the organization responsible for defining the characteristics of the location instance.
      * The default implementation returns the {@linkplain ModifiableLocationType#getOwner() owner}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return organization responsible for defining the characteristics of the location instance, or {@code null}.
      *
      * @see ModifiableLocationType#getOwner()
      * @see ReferencingByIdentifiers#getOverallOwner()
      */
-    @Override
-    public Party getAdministrator() {
-        final LocationType type = getLocationType();
+    public AbstractParty getAdministrator() {
         return (type != null) ? type.getOwner() : null;
     }
 
@@ -233,12 +256,16 @@
      * Returns location instances of a different location type, for which this location instance is a sub-division.
      * The default implementation returns an empty list.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of collection elements may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return parent locations, or an empty collection if none.
      *
      * @see ModifiableLocationType#getParents()
      */
-    @Override
-    public Collection<? extends Location> getParents() {
+    public Collection<? extends AbstractLocation> getParents() {
         return Collections.emptyList();
     }
 
@@ -246,12 +273,16 @@
      * Returns location instances of a different location type which subdivides this location instance.
      * The default implementation returns an empty list.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of collection elements may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return child locations, or an empty collection if none.
      *
      * @see ModifiableLocationType#getChildren()
      */
-    @Override
-    public Collection<? extends Location> getChildren() {
+    public Collection<? extends AbstractLocation> getChildren() {
         return Collections.emptyList();
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocationType.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
index 0878aef..c591389 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
@@ -30,9 +30,11 @@
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTable;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+// Specific to the main branch:
+import java.util.Collection;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.extent.GeographicExtent;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -41,7 +43,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-abstract class AbstractLocationType implements LocationType, LenientComparable {
+abstract class AbstractLocationType implements LenientComparable {
     /**
      * For sub-class constructors.
      */
@@ -62,11 +64,11 @@
      * @param  types  the location types for which to take a snapshot.
      * @return unmodifiable copies of the given location types.
      */
-    public static List<LocationType> snapshot(final ReferenceSystemUsingIdentifiers rs, final LocationType... types) {
+    public static List<AbstractLocationType> snapshot(final ReferencingByIdentifiers rs, final AbstractLocationType... types) {
         ArgumentChecks.ensureNonNull("types", types);
-        final List<LocationType> snapshot = FinalLocationType.snapshot(Arrays.asList(types), rs, new IdentityHashMap<>());
-        final Map<LocationType,Boolean> parents = new IdentityHashMap<>();
-        for (final LocationType type : snapshot) {
+        final List<AbstractLocationType> snapshot = FinalLocationType.snapshot(Arrays.asList(types), rs, new IdentityHashMap<>());
+        final Map<AbstractLocationType,Boolean> parents = new IdentityHashMap<>();
+        for (final AbstractLocationType type : snapshot) {
             checkForCycles(type, parents);
         }
         return snapshot;
@@ -77,11 +79,11 @@
      *
      * @throws IllegalArgumentException if an infinite recursivity is detected.
      */
-    private static void checkForCycles(final LocationType type, final Map<LocationType,Boolean> parents) {
+    private static void checkForCycles(final AbstractLocationType type, final Map<AbstractLocationType,Boolean> parents) {
         if (parents.put(type, Boolean.TRUE) != null) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.LocationTypeCycle_1, type.getName()));
         }
-        for (final LocationType child : type.getChildren()) {
+        for (final AbstractLocationType child : type.getChildren()) {
             checkForCycles(child, parents);
         }
         parents.remove(type);
@@ -98,6 +100,54 @@
     }
 
     /**
+     * Name of the location type.
+     */
+    public abstract InternationalString getName();
+
+    /**
+     * Property used as the defining characteristic of the location type.
+     */
+    public abstract InternationalString getTheme();
+
+    /**
+     * Method(s) of uniquely identifying location instances.
+     */
+    public abstract Collection<? extends InternationalString> getIdentifications();
+
+    /**
+     * The way in which location instances are defined.
+     */
+    public abstract InternationalString getDefinition();
+
+    /**
+     * Geographic area within which the location type occurs.
+     */
+    public abstract GeographicExtent getTerritoryOfUse();
+
+    /**
+     * The reference system that comprises this location type.
+     */
+    public abstract ReferencingByIdentifiers getReferenceSystem();
+
+    /**
+     * Name of organization or class of organization able to create and destroy location instances.
+     */
+    public abstract AbstractParty getOwner();
+
+    /**
+     * Parent location types (location types of which this location type is a sub-division).
+     * A location type can have more than one possible parent. For example the parent of a
+     * location type named <cite>“street”</cite> could be <cite>“locality”</cite>, <cite>“town”</cite>
+     * or <cite>“administrative area”</cite>.
+     */
+    public abstract Collection<? extends AbstractLocationType> getParents();
+
+    /**
+     * Child location types (location types which sub-divides this location type).
+     */
+    public abstract Collection<? extends AbstractLocationType> getChildren();
+
+    /**
      * Compares this location type with the specified object for equality.
      * This method compares the value of {@link #getName()} and {@link #getChildren()} in all modes.
      * At the opposite, values of {@link #getParents()} and {@link #getReferenceSystem()} are never
@@ -126,8 +176,8 @@
                 // Fall through
             }
             case BY_CONTRACT: {
-                if (!(object instanceof LocationType)) break;
-                final LocationType that = (LocationType) object;
+                if (!(object instanceof AbstractLocationType)) break;
+                final AbstractLocationType that = (AbstractLocationType) object;
                 // Do not compare the ReferenceSystem as it may cause an infinite recursion.
                 if (!Utilities.deepEquals(getTheme(),           that.getTheme(),           mode) ||
                     !Utilities.deepEquals(getIdentifications(), that.getIdentifications(), mode) ||
@@ -140,8 +190,8 @@
                 // Fall through
             }
             default: {
-                if (!(object instanceof LocationType)) break;
-                final LocationType that = (LocationType) object;
+                if (!(object instanceof AbstractLocationType)) break;
+                final AbstractLocationType that = (AbstractLocationType) object;
                 if (Objects.equals(getName(), that.getName())) {
                     /*
                      * To be safe, we should apply some check against infinite recursivity here.
@@ -183,7 +233,7 @@
     @Override
     public int hashCode() {
         int code = Objects.hashCode(getName());
-        for (final LocationType child : getChildren()) {
+        for (final AbstractLocationType child : getChildren()) {
             // Take only children name without recursivity over their own children.
             code = code*31 + Objects.hashCode(child.getName());
         }
@@ -218,10 +268,10 @@
      * on the assumption that subclasses verified this constraint by calls
      * to {@link #checkForCycles()}.
      */
-    private static void format(final LocationType type, final TreeTable.Node node) {
+    private static void format(final AbstractLocationType type, final TreeTable.Node node) {
         node.setValue(TableColumn.NAME, type.getName());
         node.setValue(TableColumn.VALUE_AS_TEXT, type.getDefinition());
-        for (final LocationType child : type.getChildren()) {
+        for (final AbstractLocationType child : type.getChildren()) {
             format(child, node.newChild());
         }
     }
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/FinalLocationType.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/FinalLocationType.java
index 887b045..cf97b3a 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/FinalLocationType.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/FinalLocationType.java
@@ -29,15 +29,12 @@
 import org.apache.sis.metadata.ModifiableMetadata;
 import org.apache.sis.metadata.MetadataCopier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.ObjectDomain;
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
- * Unmodifiable description of a location created as a snapshot of another {@link LocationType} instance
+ * Unmodifiable description of a location created as a snapshot of another {@code LocationType} instance
  * at {@link ReferencingByIdentifiers} construction time. This instance will be set a different reference
  * system than the original location type.
  *
@@ -77,8 +74,7 @@
     /**
      * The reference system that comprises this location type.
      */
-    @SuppressWarnings("serial")         // Most Apache SIS implementations are serializable.
-    private final ReferenceSystemUsingIdentifiers referenceSystem;
+    private final ReferencingByIdentifiers referenceSystem;
 
     /**
      * Geographic area within which the location type occurs.
@@ -89,22 +85,21 @@
     /**
      * Name of organization or class of organization able to create and destroy location instances.
      */
-    @SuppressWarnings("serial")         // Most Apache SIS implementations are serializable.
-    private final Party owner;
+    private final AbstractParty owner;
 
     /**
      * Parent location types (location types of which this location type is a sub-division).
      * This list is unmodifiable.
      */
     @SuppressWarnings("serial")
-    private final List<LocationType> parents;
+    private final List<AbstractLocationType> parents;
 
     /**
      * Child location types (location types which sub-divides this location type).
      * This list is unmodifiable.
      */
     @SuppressWarnings("serial")
-    final List<LocationType> children;
+    final List<AbstractLocationType> children;
 
     /**
      * Creates a copy of the given location type with the reference system set to the given value.
@@ -114,8 +109,8 @@
      * @param existing  other {@code FinalLocationType} instances created before this one.
      */
     @SuppressWarnings("LocalVariableHidesMemberVariable")
-    private FinalLocationType(final LocationType source, final ReferenceSystemUsingIdentifiers rs,
-            final Map<LocationType, FinalLocationType> existing)
+    private FinalLocationType(final AbstractLocationType source, final ReferencingByIdentifiers rs,
+            final Map<AbstractLocationType, FinalLocationType> existing)
     {
         /*
          * Put 'this' in the map at the beginning in case the parents and children contain cyclic references.
@@ -133,7 +128,7 @@
          */
         InternationalString theme;
         GeographicExtent    territoryOfUse;
-        Party               owner;
+        AbstractParty       owner;
         /*
          * Copy the value from the source location type, make them unmodifiable,
          * fallback on the ReferenceSystemUsingIdentifiers if necessary.
@@ -142,8 +137,8 @@
         theme           = source.getTheme();
         identifications = snapshot(source.getIdentifications());
         definition      = source.getDefinition();
-        territoryOfUse  = unmodifiable(GeographicExtent.class, source.getTerritoryOfUse());
-        owner           = unmodifiable(Party.class, source.getOwner());
+        territoryOfUse  = (GeographicExtent) unmodifiable(source.getTerritoryOfUse());
+        owner           = (AbstractParty) unmodifiable(source.getOwner());
         parents         = snapshot(source.getParents(),  rs, existing);
         children        = snapshot(source.getChildren(), rs, existing);
         referenceSystem = rs;
@@ -151,11 +146,9 @@
             if (theme == null) theme = rs.getTheme();
             if (owner == null) owner = rs.getOverallOwner();
             if (territoryOfUse == null) {
-                for (ObjectDomain domain : rs.getDomains()) {
-                    Extent domainOfValidity = domain.getDomainOfValidity();
-                    if (domainOfValidity instanceof GeographicExtent) {
-                        territoryOfUse = (GeographicExtent) domainOfValidity;
-                    }
+                final Extent domainOfValidity = rs.getDomainOfValidity();
+                if (domainOfValidity instanceof GeographicExtent) {
+                    territoryOfUse = (GeographicExtent) domainOfValidity;
                 }
             }
         }
@@ -172,12 +165,12 @@
      * @param rs        the reference system to assign to the new location types.
      * @param existing  an initially empty identity hash map for internal usage by this method.
      */
-    static List<LocationType> snapshot(final Collection<? extends LocationType> types,
-            final ReferenceSystemUsingIdentifiers rs, final Map<LocationType, FinalLocationType> existing)
+    static List<AbstractLocationType> snapshot(final Collection<? extends AbstractLocationType> types,
+            final ReferencingByIdentifiers rs, final Map<AbstractLocationType, FinalLocationType> existing)
     {
-        final LocationType[] array = types.toArray(LocationType[]::new);
+        final AbstractLocationType[] array = types.toArray(AbstractLocationType[]::new);
         for (int i=0; i < array.length; i++) {
-            final LocationType source = array[i];
+            final AbstractLocationType source = array[i];
             ArgumentChecks.ensureNonNullElement("types", i, source);
             FinalLocationType copy = existing.get(source);
             if (copy == null) {
@@ -211,14 +204,12 @@
     /**
      * Returns an unmodifiable copy of the given metadata, if necessary and possible.
      *
-     * @param  <T>       compile-time value of the {@code type} argument.
-     * @param  type      the interface of the metadata object to eventually copy.
      * @param  metadata  the metadata object to eventually copy, or {@code null}.
      * @return an unmodifiable copy of the given metadata object, or {@code null} if the given argument is {@code null}.
      */
-    private static <T> T unmodifiable(final Class<T> type, T metadata) {
+    private static Object unmodifiable(Object metadata) {
         if (metadata instanceof ModifiableMetadata) {
-            metadata = MetadataCopier.forModifiable(((ModifiableMetadata) metadata).getStandard()).copy(type, metadata);
+            metadata = MetadataCopier.forModifiable(((ModifiableMetadata) metadata).getStandard()).copy(metadata);
             if (metadata instanceof ModifiableMetadata) {
                 ((ModifiableMetadata) metadata).transitionTo(ModifiableMetadata.State.FINAL);
             }
@@ -301,7 +292,7 @@
      * @return the reference system that comprises this location type.
      */
     @Override
-    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
+    public ReferencingByIdentifiers getReferenceSystem() {
         return referenceSystem;
     }
 
@@ -311,7 +302,7 @@
      * @return organization or class of organization able to create and destroy location instances.
      */
     @Override
-    public Party getOwner() {
+    public AbstractParty getOwner() {
         return owner;
     }
 
@@ -324,7 +315,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because unmodifiable
-    public Collection<LocationType> getParents() {
+    public Collection<AbstractLocationType> getParents() {
         return parents;
     }
 
@@ -335,7 +326,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because unmodifiable
-    public Collection<LocationType> getChildren() {
+    public Collection<AbstractLocationType> getChildren() {
         return children;
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
index 1666a27..8bb6727 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
@@ -45,10 +45,6 @@
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.pending.jdk.JDK18;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Geographic coordinates represented as <i>geohashes</i> strings.
@@ -199,10 +195,10 @@
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.8")
-    private static LocationType[] types() {
+    private static ModifiableLocationType[] types() {
         final ModifiableLocationType gzd = new ModifiableLocationType(IDENTIFIER);
         gzd.addIdentification(Vocabulary.formatInternational(Vocabulary.Keys.Code));
-        return new LocationType[] {gzd};
+        return new ModifiableLocationType[] {gzd};
     }
 
     /**
@@ -321,7 +317,7 @@
             final int lonNumBits = latNumBits + (length & 1);   // Longitude has 1 more bit when length is odd.
             if (position != null) try {
                 position = toGeographic(position);
-                double φ = Math.toRadians(position.getCoordinate(1));
+                double φ = Math.toRadians(position.getOrdinate(1));
                 double a = Math.PI/2 * Formulas.geocentricRadius(ellipsoid, φ);     // Arc length of 90° using radius at φ.
                 double b = Math.cos(φ) * (2*a) / (1 << lonNumBits);                 // Precision along longitude axis.
                 a /= (1 << latNumBits);                                             // Precision along latitude axis.
@@ -357,7 +353,7 @@
                 p = unit.getConverterToAny(ellipsoid.getAxisUnit()).convert(p);
                 if (position != null) try {
                     position = toGeographic(position);
-                    double φ = Math.toRadians(position.getCoordinate(1));
+                    double φ = Math.toRadians(position.getOrdinate(1));
                     numLat   = Math.PI/2 * Formulas.geocentricRadius(ellipsoid, φ) / p;
                     numLon   = Math.cos(φ) * (2*numLat);
                 } catch (FactoryException | TransformException e) {
@@ -472,7 +468,7 @@
             } catch (FactoryException e) {
                 throw new GazetteerException(e.getLocalizedMessage(), e);
             }
-            return encode(position.getCoordinate(1), position.getCoordinate(0));
+            return encode(position.getOrdinate(1), position.getOrdinate(0));
         }
 
         /**
@@ -500,7 +496,7 @@
                 throw new GazetteerException(e.getLocalizedMessage(), e);
             }
             setPrecision(precision, position);
-            return encode(position.getCoordinate(1), position.getCoordinate(0));
+            return encode(position.getOrdinate(1), position.getOrdinate(0));
         }
 
         /**
@@ -525,12 +521,17 @@
          * Decodes the given geohash into a latitude and a longitude.
          * The axis order depends on the coordinate reference system of the enclosing {@link GeohashReferenceSystem}.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  geohash  geohash string to decode.
          * @return a new geographic coordinate for the given geohash.
          * @throws TransformException if an error occurred while parsing the given string.
          */
         @Override
-        public Location decode(final CharSequence geohash) throws TransformException {
+        public AbstractLocation decode(final CharSequence geohash) throws TransformException {
             ArgumentChecks.ensureNonEmpty("geohash", geohash);
             return new Decoder(geohash, coordinates);
         }
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/LocationFormat.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/LocationFormat.java
index e4185ab..974f0f2 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/LocationFormat.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/LocationFormat.java
@@ -59,14 +59,12 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
- * Formats {@link Location} instances in a tabular format.
+ * Formats {@code Location} instances in a tabular format.
  * This format assumes a monospaced font and an encoding supporting drawing box characters (e.g. UTF-8).
  *
  * <h2>Example</h2>
@@ -96,7 +94,7 @@
  * @version 0.8
  * @since   0.8
  */
-public class LocationFormat extends TabularFormat<Location> {
+public class LocationFormat extends TabularFormat<AbstractLocation> {
     /**
      * For cross-version compatibility.
      */
@@ -137,8 +135,8 @@
      * @return the type of values formatted by this {@code Format} instance.
      */
     @Override
-    public Class<Location> getValueType() {
-        return Location.class;
+    public Class<AbstractLocation> getValueType() {
+        return AbstractLocation.class;
     }
 
     /**
@@ -188,13 +186,18 @@
     /**
      * Writes a textual representation of the given location in the given stream or buffer.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of {@code location} parameter may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param  location    the location to format.
      * @param  toAppendTo  where to format the location.
      * @throws IOException if an error occurred while writing to the given appendable.
      */
     @Override
     @SuppressWarnings("fallthrough")
-    public void format(final Location location, final Appendable toAppendTo) throws IOException {
+    public void format(final AbstractLocation location, final Appendable toAppendTo) throws IOException {
         ArgumentChecks.ensureNonNull("location", location);
         final Locale locale = getLocale(Locale.Category.DISPLAY);
         final Vocabulary vocabulary = Vocabulary.forLocale(locale);
@@ -204,7 +207,7 @@
          * Location type.
          */
         table.appendHorizontalSeparator();
-        final LocationType type = location.getLocationType();
+        final AbstractLocationType type = location.type();
         if (type != null) {
             append(table, vocabulary, Vocabulary.Keys.LocationType, toString(type.getName(), locale));
         }
@@ -335,8 +338,8 @@
                             dimension = 1;
                             break;
                     case 4: dimension = 1;                            // Fall through
-                    case 1: if (geopos   != null) g = geopos  .getCoordinate(dimension);
-                            if (position != null) p = position.getCoordinate(dimension);
+                    case 1: if (geopos   != null) g = geopos  .getOrdinate(dimension);
+                            if (position != null) p = position.getOrdinate(dimension);
                             rounding = RoundingMode.HALF_EVEN;
                             break;
                 }
@@ -405,7 +408,7 @@
         /*
          * Organization responsible for defining the characteristics of the location instance.
          */
-        final Party administrator = location.getAdministrator();
+        final AbstractParty administrator = location.getAdministrator();
         if (administrator != null) {
             append(table, vocabulary, Vocabulary.Keys.Administrator, toString(administrator.getName(), locale));
         }
@@ -419,7 +422,7 @@
 
     /**
      * Creates the format to use for formatting a latitude, longitude or projected coordinate.
-     * This method is invoked by {@link #format(Location, Appendable)} when first needed.
+     * This method is invoked by {@code format(Location, Appendable)} when first needed.
      *
      * @param  valueType  {@code Angle.class}. {@code Number.class} or {@code Unit.class}.
      * @return a new {@link AngleFormat}, {@link NumberFormat} or {@link UnitFormat} instance
@@ -472,7 +475,7 @@
      * @throws ParseException if an error occurred while parsing the location.
      */
     @Override
-    public Location parse(CharSequence text, ParsePosition pos) throws ParseException {
+    public AbstractLocation parse(CharSequence text, ParsePosition pos) throws ParseException {
         throw new ParseException(Errors.format(Errors.Keys.UnsupportedOperation_1, "parse"), pos.getIndex());
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
index 8590661..38a0e75 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
@@ -76,13 +76,8 @@
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-import org.apache.sis.metadata.sql.MetadataSource;
-import org.apache.sis.metadata.sql.MetadataStoreException;
-import org.apache.sis.util.logging.Logging;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -295,15 +290,8 @@
      */
     @Workaround(library="JDK", version="1.8")
     private static Map<String,?> properties() {
-        Party party;
-        try {
-            party = MetadataSource.getProvided().lookup(Party.class, "{org}NATO");
-        } catch (MetadataStoreException e) {
-            party = null;
-            Logging.unexpectedException(LOGGER, MilitaryGridReferenceSystem.class, "<init>", e);
-        }
-        NamedIdentifier name = new NamedIdentifier(null, "NATO", Resources.formatInternational(Resources.Keys.MGRS), null, null);
-        return properties(name, IDENTIFIER, party);
+        AbstractParty party = new AbstractParty("North Atlantic Treaty Organization", null);
+        return properties(new NamedIdentifier(null, "NATO", Resources.formatInternational(Resources.Keys.MGRS), null, null), IDENTIFIER, party);
     }
 
     /**
@@ -311,7 +299,7 @@
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.8")
-    private static LocationType[] types() {
+    private static ModifiableLocationType[] types() {
         final ModifiableLocationType gzd    = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.GridZoneDesignator));
         final ModifiableLocationType square = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.SquareIdentifier100));
         final ModifiableLocationType coord  = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.GridCoordinates));
@@ -319,7 +307,7 @@
         coord .addIdentification(Vocabulary.formatInternational(Vocabulary.Keys.Coordinate));
         square.addParent(gzd);
         coord .addParent(square);
-        return new LocationType[] {gzd};
+        return new ModifiableLocationType[] {gzd};
     }
 
     /**
@@ -337,7 +325,7 @@
                     south ? TransverseMercator.Zoner.SOUTH_BOUNDS
                           : TransverseMercator.Zoner.NORTH_BOUNDS, 0);
             double northing = datum.universal(position.x * 1.01, position.y).getConversionFromBase()
-                                   .getMathTransform().transform(position, position).getCoordinate(1);
+                                   .getMathTransform().transform(position, position).getOrdinate(1);
             if (south) {
                 northing = 2*PolarStereographicA.UPS_SHIFT - northing;
             }
@@ -771,12 +759,17 @@
          * Decodes the given MGRS reference into a position and an envelope.
          * The Coordinate Reference System (CRS) associated to the returned position depends on the given reference.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  reference  MGRS string to decode.
          * @return a new position with the longitude at coordinate 0 and latitude at coordinate 1.
          * @throws TransformException if an error occurred while parsing the given string.
          */
         @Override
-        public Location decode(final CharSequence reference) throws TransformException {
+        public AbstractLocation decode(final CharSequence reference) throws TransformException {
             ArgumentChecks.ensureNonEmpty("reference", reference);
             return new Decoder(this, reference);
         }
@@ -854,8 +847,8 @@
                          * Use of lower and upper corners below are not the same as calls to Envelope.getMinimum(0)
                          * or Envelope.getMaximum(0) if the envelope crosses the anti-meridian.
                          */
-                        zoneStart = ZONER.zone(0, geographicArea.getLowerCorner().getCoordinate(0)) - 1;  // Inclusive.
-                        zoneEnd   = ZONER.zone(0, geographicArea.getUpperCorner().getCoordinate(0));      // Exclusive.
+                        zoneStart = ZONER.zone(0, geographicArea.getLowerCorner().getOrdinate(0)) - 1;  // Inclusive.
+                        zoneEnd   = ZONER.zone(0, geographicArea.getUpperCorner().getOrdinate(0));      // Exclusive.
                         if (zoneEnd < zoneStart) {
                             zoneEnd += zoneCount;                              // Envelope crosses the anti-meridian.
                         }
@@ -1352,8 +1345,8 @@
                         int y = gridY;
                         if (x < xCenter) x += step - 1;
                         if (downward)    y += step - 1;
-                        normalized.setCoordinate(0, x);
-                        normalized.setCoordinate(1, y);
+                        normalized.setOrdinate(0, x);
+                        normalized.setOrdinate(1, y);
                         String ref = encoder.encode(this, normalized, false, separator, digits, 0);
                         if (ref != null) {
                             /*
@@ -1365,7 +1358,7 @@
                             latitudeBand = encoder.latitudeBand;
                             if (latitudeBand != previous && previous != 0) {
                                 pending = ref;
-                                normalized.setCoordinate(1, y + (downward ? +1 : -1));
+                                normalized.setOrdinate(1, y + (downward ? +1 : -1));
                                 ref = encoder.encode(this, normalized, false, separator, digits, 0);
                                 if (ref == null || encoder.latitudeBand == previous) {
                                     ref = pending;  // No result or same result as previous iteration - cancel.
@@ -1595,7 +1588,7 @@
                 owner.normalized = position = toNormalized.transform(position, owner.normalized);
             }
             owner.geographic = position = toGeographic.transform(position, owner.geographic);
-            return position.getCoordinate(0);
+            return position.getOrdinate(0);
         }
 
         /**
@@ -1619,8 +1612,8 @@
             }
             final DirectPosition geographic = toGeographic.transform(position, owner.geographic);
             owner.geographic     = geographic;                  // For reuse in next method calls.
-            final double  λ      = geographic.getCoordinate(1);
-            final double  φ      = geographic.getCoordinate(0);
+            final double  λ      = geographic.getOrdinate(1);
+            final double  φ      = geographic.getOrdinate(0);
             final boolean isUTM  = φ >= TransverseMercator.Zoner.SOUTH_BOUNDS &&
                                    φ <  TransverseMercator.Zoner.NORTH_BOUNDS;
             final int zone       = isUTM ? ZONER.zone(φ, λ) : POLE;
@@ -1643,7 +1636,7 @@
                     toActualZone = CRS.findOperation(datum.geographic(), datum.universal(φ, λ), null).getMathTransform();
                     actualZone   = signedZone;
                 }
-                geographic.setCoordinate(1, Longitude.normalize(λ));
+                geographic.setOrdinate(1, Longitude.normalize(λ));
                 owner.normalized = position = toActualZone.transform(geographic, owner.normalized);
             }
             /*
@@ -1674,8 +1667,8 @@
              * 100 kilometres square identification.
              */
             if (digits >= 0) {
-                final double  x = position.getCoordinate(0);
-                final double  y = position.getCoordinate(1);
+                final double  x = position.getOrdinate(0);
+                final double  y = position.getOrdinate(1);
                 final double cx = Math.floor(x / GRID_SQUARE_SIZE);
                 final double cy = Math.floor(y / GRID_SQUARE_SIZE);
                 int col = (int) cx;
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
index 5b6e5bd..264603c 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
@@ -32,9 +32,8 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -141,7 +140,7 @@
     /**
      * Name of organization or class of organization able to create and destroy location instances.
      */
-    private Party owner;
+    private AbstractParty owner;
 
     /**
      * Parent location types (location types of which this location type is a sub-division).
@@ -350,6 +349,11 @@
      * If no organization has been explicitly set, then this method inherits the value from
      * the parents providing that all parents specify the same organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return organization or class of organization able to create and destroy location instances,
      *         or {@code null} if no value has been defined or can be inherited.
      *
@@ -357,7 +361,7 @@
      * @see ReferencingByIdentifiers#getOverallOwner()
      */
     @Override
-    public Party getOwner() {
+    public AbstractParty getOwner() {
         return (owner != null) ? owner : inherit(ModifiableLocationType::getOwner);
     }
 
@@ -366,9 +370,14 @@
      * The given value is typically an instance of {@link DefaultOrganisation}.
      * For an alternative where only the organization name is specified, see {@link #setOwner(CharSequence)}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the argument type may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @param  value  the new owner.
      */
-    public void setOwner(final Party value) {
+    public void setOwner(final AbstractParty value) {
         owner = value;
     }
 
@@ -471,12 +480,17 @@
      * the reference system is always null. The reference system is defined when the location types are
      * given to the {@link ReferencingByIdentifiers} constructor for example.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return {@code null}.
      *
      * @see ReferencingByIdentifiers#getLocationTypes()
      */
     @Override
-    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
+    public ReferencingByIdentifiers getReferenceSystem() {
         return null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java
new file mode 100644
index 0000000..b265215
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.gazetteer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.IdentityHashMap;
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.privy.UnmodifiableArrayList;
+
+
+/**
+ * Workaround for the lack of {@code LocationType} interface in GeoAPI 3.0.
+ * This workaround will be removed if a future GeoAPI version publish that interface,
+ * or if {@link AbstractLocationType} is made public.
+ */
+final class ModifiableLocationTypeAdapter extends ModifiableLocationType {
+    /**
+     * The reference system of the original type.
+     * This is the only information not stored in {@link ModifiableLocationType}.
+     */
+    private final ReferencingByIdentifiers referenceSystem;
+
+    /**
+     * Copies all information from the given type.
+     */
+    private ModifiableLocationTypeAdapter(final AbstractLocationType type,
+            final Map<AbstractLocationType,ModifiableLocationTypeAdapter> previous)
+    {
+        super(type.getName());
+        setTheme(type.getTheme());
+        setDefinition(type.getDefinition());
+        setTerritoryOfUse(type.getTerritoryOfUse());
+        setOwner(type.getOwner());
+        for (final InternationalString s : type.getIdentifications()) {
+            addIdentification(s);
+        }
+        referenceSystem = type.getReferenceSystem();
+        for (final AbstractLocationType c : type.getChildren()) {
+            ModifiableLocationTypeAdapter p = previous.get(c);
+            if (p == null) {
+                p = new ModifiableLocationTypeAdapter(c, previous);
+                previous.put(c, p);
+            }
+            p.addParent(this);
+        }
+    }
+
+    /**
+     * Returns type type as-is if it is already an instance of {@code ModifiableLocationType},
+     * or returns a copy otherwise.
+     */
+    static ModifiableLocationType copy(final AbstractLocationType type) {
+        if (type instanceof ModifiableLocationType) {
+            return (ModifiableLocationType) type;
+        } else {
+            return new ModifiableLocationTypeAdapter(type,
+                    new IdentityHashMap<AbstractLocationType,ModifiableLocationTypeAdapter>());
+        }
+    }
+
+    /**
+     * Copies a list of location types.
+     */
+    static List<ModifiableLocationType> copy(final List<? extends AbstractLocationType> types) {
+        final Map<AbstractLocationType,ModifiableLocationTypeAdapter> previous = new IdentityHashMap<>();
+        final ModifiableLocationType[] nt = new ModifiableLocationType[types.size()];
+        for (int i=0; i<nt.length; i++) {
+            final AbstractLocationType c = types.get(i);
+            ModifiableLocationTypeAdapter p = previous.get(c);
+            if (p == null) {
+                p = new ModifiableLocationTypeAdapter(c, previous);
+                previous.put(c, p);
+            }
+            nt[i] = p;
+        }
+        return UnmodifiableArrayList.wrap(nt);
+    }
+
+    /**
+     * Returns the reference system of the type given to the constructor.
+     */
+    @Override
+    public ReferencingByIdentifiers getReferenceSystem() {
+        return referenceSystem;
+    }
+}
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
index 0d526a2..b348bd7 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
@@ -45,12 +45,8 @@
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.ObjectDomain;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -72,7 +68,25 @@
  * @since 0.8
  */
 @XmlTransient
-public abstract class ReferencingByIdentifiers extends AbstractReferenceSystem implements ReferenceSystemUsingIdentifiers {
+public abstract class ReferencingByIdentifiers extends AbstractReferenceSystem {
+    /**
+     * Key for the <code>{@value}</code> property to be given to the
+     * object factory {@code createFoo(…)} methods.
+     * This is used for setting the value to be returned by {@link #getTheme()}.
+     *
+     * @see #getTheme()
+     */
+    public static final String THEME_KEY = "theme";
+
+    /**
+     * Key for the <code>{@value}</code> property to be given to the
+     * object factory {@code createFoo(…)} methods.
+     * This is used for setting the value to be returned by {@link #getOverallOwner()}.
+     *
+     * @see #getOverallOwner()
+     */
+    public static final String OVERALL_OWNER_KEY = "overallOwner";
+
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -96,8 +110,7 @@
      *
      * @see #getOverallOwner()
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private final Party overallOwner;
+    private final AbstractParty overallOwner;
 
     /**
      * Description of location type(s) in the spatial reference system.
@@ -105,8 +118,8 @@
      *
      * @see #getLocationTypes()
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private final List<LocationType> locationTypes;
+    @SuppressWarnings("serial")
+    final List<AbstractLocationType> locationTypes;
 
     /**
      * Creates a reference system from the given properties.
@@ -121,12 +134,12 @@
      *     <th>Value type</th>
      *     <th>Returned by</th>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#THEME_KEY}</td>
+     *     <td>"theme"</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getTheme()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#OVERALL_OWNER_KEY}</td>
-     *     <td>{@link Party}</td>
+     *     <td>"overallOwner"</td>
+     *     <td>{@code Party}</td>
      *     <td>{@link #getOverallOwner()}</td>
      *   </tr><tr>
      *     <th colspan="3" class="hsep">Defined in parent class (reminder)</th>
@@ -147,28 +160,33 @@
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceSystem#DOMAIN_OF_VALIDITY_KEY}</td>
      *     <td>{@link org.opengis.metadata.extent.Extent}</td>
      *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getDomainOfValidity()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#SCOPE_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceSystem#SCOPE_KEY}</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getScope()}</td>
      *   </tr>
      * </table>
      *
-     * This constructor copies the given {@link LocationType} instances as per
-     * {@link ModifiableLocationType#snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
+     * This constructor copies the given {@code LocationType} instances as per
+     * {@code ModifiableLocationType.snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
      * Changes in the given location types after construction will not affect this {@code ReferencingByIdentifiers}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * In a future SIS version, the type of array elements may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.LocationType} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param properties  the properties to be given to the reference system.
      * @param types       description of location type(s) in the spatial reference system.
      */
     @SuppressWarnings("this-escape")
-    public ReferencingByIdentifiers(final Map<String,?> properties, final LocationType... types) {
+    public ReferencingByIdentifiers(final Map<String,?> properties, final ModifiableLocationType... types) {
         super(properties);
         theme = Types.toInternationalString(properties, THEME_KEY);
-        overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, Party.class);
+        overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, AbstractParty.class);
         /*
          * Having the 'this' reference escaped in object construction should not be an issue here because
          * we invoke package-private method in such a way that if an exception is thrown, the whole tree
@@ -185,27 +203,15 @@
      * @param id     an identifier for the reference system. Use SIS namespace until we find an authority for them.
      * @param party  the overall owner, or {@code null} if none.
      */
-    static Map<String,Object> properties(final Object name, final String id, final Party party) {
+    static Map<String,Object> properties(final Object name, final String id, final AbstractParty party) {
         final Map<String,Object> properties = new HashMap<>(8);
         properties.put(NAME_KEY, name);
         properties.put(IDENTIFIERS_KEY, new ImmutableIdentifier(Citations.SIS, Constants.SIS, id));
-        properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, Extents.WORLD);
+        properties.put(DOMAIN_OF_VALIDITY_KEY, Extents.WORLD);
         properties.put(THEME_KEY, Vocabulary.formatInternational(Vocabulary.Keys.Mapping));
         properties.put(OVERALL_OWNER_KEY, party);
         return properties;
     }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The default implementation returns {@code ReferenceSystemUsingIdentifiers.class}.
-     *
-     * @return the GeoAPI interface implemented by this class.
-     */
-    @Override
-    public Class<? extends ReferenceSystemUsingIdentifiers> getInterface() {
-        return ReferenceSystemUsingIdentifiers.class;
-    }
-
     /**
      * Property used to characterize the spatial reference system.
      *
@@ -213,7 +219,6 @@
      *
      * @see ModifiableLocationType#getTheme()
      */
-    @Override
     public InternationalString getTheme() {
         return theme;
     }
@@ -221,13 +226,17 @@
     /**
      * Authority with overall responsibility for the spatial reference system.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return authority with overall responsibility for the spatial reference system.
      *
      * @see ModifiableLocationType#getOwner()
      * @see AbstractLocation#getAdministrator()
      */
-    @Override
-    public Party getOverallOwner() {
+    public AbstractParty getOverallOwner() {
         return overallOwner;
     }
 
@@ -235,20 +244,24 @@
      * Description of location type(s) in the spatial reference system.
      * The collection returned by this method is unmodifiable.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of elements type may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return description of location type(s) in the spatial reference system.
      *
      * @see ModifiableLocationType#getReferenceSystem()
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because the collection is unmodifiable.
-    public List<? extends LocationType> getLocationTypes() {
-        return locationTypes;
+    public List<? extends ModifiableLocationType> getLocationTypes() {
+        return ModifiableLocationTypeAdapter.copy(locationTypes);
     }
 
     /**
      * Returns the first location type.
      */
-    final LocationType rootType() {
+    final AbstractLocationType rootType() {
         return locationTypes.get(0);
     }
 
@@ -351,11 +364,16 @@
          * Decodes the given identifier into a latitude and a longitude.
          * The axis order depends on the coordinate reference system of the enclosing {@link ReferencingByIdentifiers}.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  identifier  identifier string to decode.
          * @return a new geographic coordinate for the given identifier.
          * @throws TransformException if an error occurred while parsing the given string.
          */
-        public abstract Location decode(CharSequence identifier) throws TransformException;
+        public abstract AbstractLocation decode(CharSequence identifier) throws TransformException;
 
         /**
          * Logs a warning for a recoverable error while transforming a position. This is used for implementations
@@ -397,7 +415,7 @@
                        locationTypes.equals(that.locationTypes);
             }
             case BY_CONTRACT: {
-                final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
+                final ReferencingByIdentifiers that = (ReferencingByIdentifiers) object;
                 if (!Utilities.deepEquals(getTheme(),        that.getTheme(),        mode) ||
                     !Utilities.deepEquals(getOverallOwner(), that.getOverallOwner(), mode))
                 {
@@ -407,8 +425,8 @@
             }
             default: {
                 // Theme and owner are metadata, so they can be ignored.
-                final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
-                return Utilities.deepEquals(getLocationTypes(), that.getLocationTypes(), mode);
+                final ReferencingByIdentifiers that = (ReferencingByIdentifiers) object;
+                return Utilities.deepEquals(locationTypes, that.locationTypes, mode);
             }
         }
     }
@@ -444,7 +462,7 @@
             formatter.newLine();
             formatter.append(new SubElement("Owner", overallOwner.getName()));
         }
-        for (final LocationType type : locationTypes) {
+        for (final AbstractLocationType type : locationTypes) {
             formatter.newLine();
             formatter.append(new SubElement("LocationType", type.getName()));
         }
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/SimpleLocation.java b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/SimpleLocation.java
index 932739f..4672c25 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/SimpleLocation.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/SimpleLocation.java
@@ -27,9 +27,6 @@
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * A location described by an unmodifiable direct position that defines the centroid of an envelope.
@@ -89,7 +86,7 @@
      * @param type        the description of the nature of this geographic identifier.
      * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
      */
-    SimpleLocation(final LocationType type, final CharSequence identifier) {
+    SimpleLocation(final AbstractLocationType type, final CharSequence identifier) {
         super(type, identifier);
     }
 
@@ -121,6 +118,16 @@
     }
 
     /**
+     * Returns this direct position.
+     *
+     * @return {@code this}.
+     */
+    @Override
+    public final DirectPosition getDirectPosition() {
+        return this;
+    }
+
+    /**
      * Returns the coordinate reference system the envelope and the position.
      * Default implementation returns {@link CommonCRS#defaultGeographic()}.
      * Subclasses must override this method if they use another CRS.
@@ -142,15 +149,15 @@
      * Returns the coordinates of the centroid.
      */
     @Override
-    public final double[] getCoordinates() {
-        return new double[] {getCoordinate(0), getCoordinate(1)};
+    public final double[] getCoordinate() {
+        return new double[] {getOrdinate(0), getOrdinate(1)};
     }
 
     /**
      * Returns the centroid coordinate value for the specified dimension.
      */
     @Override
-    public final double getCoordinate(final int dimension) {
+    public final double getOrdinate(final int dimension) {
         return getMedian(dimension);
     }
 
@@ -229,7 +236,7 @@
      * Do not allow modification of the direct position.
      */
     @Override
-    public final void setCoordinate(int dimension, double value) {
+    public final void setOrdinate(int dimension, double value) {
         throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, DirectPosition.class));
     }
 
@@ -326,7 +333,7 @@
          * @param type        the description of the nature of this geographic identifier.
          * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
          */
-        Projected(final LocationType type, final CharSequence identifier) {
+        Projected(final AbstractLocationType type, final CharSequence identifier) {
             super(type, identifier);
         }
 
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
index 596fce1..e22fd23 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
@@ -33,10 +33,6 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests methods from the {@link GeohashReferenceSystem} class.
@@ -253,10 +249,10 @@
      */
     private void testDecode(final GeohashReferenceSystem.Coder coder, final int λi, final int φi) throws TransformException {
         for (final Place place : PLACES) {
-            final Location location = coder.decode(place.geohash);
+            final AbstractLocation location = coder.decode(place.geohash);
             final DirectPosition result = location.getPosition();
-            assertEquals(place.longitude, result.getCoordinate(λi), TOLERANCE, place.name);
-            assertEquals(place.latitude,  result.getCoordinate(φi), TOLERANCE, place.name);
+            assertEquals(place.longitude, result.getOrdinate(λi), TOLERANCE, place.name);
+            assertEquals(place.latitude,  result.getOrdinate(φi), TOLERANCE, place.name);
         }
     }
 
@@ -271,7 +267,7 @@
         assertEquals("Mapping",      rs.getTheme().toString(Locale.ENGLISH));
         assertEquals("Cartographie", rs.getTheme().toString(Locale.FRENCH));
 
-        final LocationType type = TestUtilities.getSingleton(rs.getLocationTypes());
+        final AbstractLocationType type = TestUtilities.getSingleton(rs.getLocationTypes());
         assertEquals("Geohash", type.getName().toString(Locale.ENGLISH));
         assertEquals(0, type.getParents().size());
         assertEquals(0, type.getChildren().size());
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationTypeTest.java b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
index 8fa1657..921b643 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
@@ -27,9 +27,6 @@
 import static org.apache.sis.test.Assertions.assertMultilinesEquals;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests {@link AbstractLocationType}, {@link FinalLocationType} and {@link ModifiableLocationType}.
@@ -120,7 +117,7 @@
     /**
      * Verifies the value of a "administrative area" location type.
      */
-    private static void verify(final LocationType[] type) {
+    private static void verify(final AbstractLocationType[] type) {
         assertEquals(5, type.length);
         verify(type[0], "administrative area",
                         "local administration",
@@ -152,8 +149,8 @@
     /**
      * Verifies the value of a location type created by or copied from {@link #create(boolean)}.
      */
-    private static void verify(final LocationType type, final String name, final String theme, final String definition,
-            final String identification, final String owner)
+    private static void verify(final AbstractLocationType type, final String name, final String theme,
+            final String definition, final String identification, final String owner)
     {
         assertEquals(name,           String.valueOf(type.getName()));
         assertEquals(theme,          String.valueOf(type.getTheme()));
@@ -186,7 +183,7 @@
      */
     @Test
     public void testSnapshot() {
-        verify(ModifiableLocationType.snapshot(null, create(true)).toArray(LocationType[]::new));
+        verify(ModifiableLocationType.snapshot(null, create(true)).toArray(AbstractLocationType[]::new));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationViewer.java b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationViewer.java
index 211711b..a9bf548 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationViewer.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/LocationViewer.java
@@ -41,12 +41,9 @@
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.util.Debug;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.gazetteer.Location;
-
 
 /**
- * A Swing panel drawing {@link Location} instances.
+ * A Swing panel drawing {@code Location} instances.
  * This is used for debugging purpose only.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -212,7 +209,7 @@
      * @throws FactoryException if a transformation to the display CRS cannot be obtained.
      * @throws TransformException if an error occurred while transforming an envelope.
      */
-    public void addLocation(final String label, final Location location) throws FactoryException, TransformException {
+    public void addLocation(final String label, final AbstractLocation location) throws FactoryException, TransformException {
         final Envelope envelope = location.getEnvelope();
         final MathTransform2D tr = (MathTransform2D) CRS.findOperation(
                 envelope.getCoordinateReferenceSystem(), displayCRS, null).getMathTransform();
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
index 03a81be..50b3000 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
@@ -47,10 +47,6 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests {@link MilitaryGridReferenceSystem}.
@@ -73,15 +69,15 @@
         assertEquals("Mapping",      rs.getTheme().toString(Locale.ENGLISH));
         assertEquals("Cartographie", rs.getTheme().toString(Locale.FRENCH));
 
-        final LocationType gzd = TestUtilities.getSingleton(rs.getLocationTypes());
+        final AbstractLocationType gzd = TestUtilities.getSingleton(rs.getLocationTypes());
         assertEquals("Grid zone designator", gzd.getName().toString(Locale.ENGLISH));
         assertEquals(0, gzd.getParents().size());
 
-        final LocationType sid = TestUtilities.getSingleton(gzd.getChildren());
+        final AbstractLocationType sid = TestUtilities.getSingleton(gzd.getChildren());
         assertEquals("100 km square identifier", sid.getName().toString(Locale.ENGLISH));
         assertSame(gzd, TestUtilities.getSingleton(sid.getParents()));
 
-        final LocationType gc = TestUtilities.getSingleton(sid.getChildren());
+        final AbstractLocationType gc = TestUtilities.getSingleton(sid.getChildren());
         assertEquals("Grid coordinate", gc.getName().toString(Locale.ENGLISH));
         assertSame(sid, TestUtilities.getSingleton(gc.getParents()));
     }
@@ -164,7 +160,7 @@
              */
             geographic.x = φ;
             geographic.y = isSouth ? zoneBorder : zoneCentre;
-            final double ymin = projection.transform(geographic, projected).getCoordinate(1);
+            final double ymin = projection.transform(geographic, projected).getOrdinate(1);
             /*
              * Computes the largest possible northing value. This is not only the value of the next latitude band;
              * we also need to interchange the "zone centre" and "zone border" logic described in previous comment.
@@ -172,7 +168,7 @@
              */
             geographic.y = isSouth ? zoneCentre : zoneBorder;
             geographic.x = MilitaryGridReferenceSystem.Decoder.upperBound(φ);
-            final double ymax = projection.transform(geographic, projected).getCoordinate(1);
+            final double ymax = projection.transform(geographic, projected).getOrdinate(1);
             /*
              * Computes the value that we will encode in the MilitaryGridReferenceSystem.Decoder.ROW_RESOLVER table.
              * The lowest 4 bits are the number of the row cycle (a cycle of 2000 km). The remaining bits tell which
@@ -216,7 +212,7 @@
     private static DirectPosition decode(final MilitaryGridReferenceSystem.Coder coder, final String reference)
             throws TransformException
     {
-        final Location loc = coder.decode(reference);
+        final AbstractLocation loc = coder.decode(reference);
         final Envelope2D envelope = new Envelope2D(loc.getEnvelope());
         final DirectPosition2D pos = new DirectPosition2D(loc.getPosition());
         assertTrue(envelope.contains(pos), reference);
@@ -286,28 +282,28 @@
 
         position = decode(coder, "32TNL8410239239");
         assertSame(CommonCRS.WGS84.universal(41, 10), position.getCoordinateReferenceSystem());
-        assertEquals( 584102.5, position.getCoordinate(0));
-        assertEquals(4539239.5, position.getCoordinate(1));
+        assertEquals( 584102.5, position.getOrdinate(0));
+        assertEquals(4539239.5, position.getOrdinate(1));
 
         position = decode(coder, "29XMM8446304963");
         assertSame(CommonCRS.WGS84.universal(82, -10), position.getCoordinateReferenceSystem());
-        assertEquals( 484463.5, position.getCoordinate(0));
-        assertEquals(9104963.5, position.getCoordinate(1));
+        assertEquals( 484463.5, position.getOrdinate(0));
+        assertEquals(9104963.5, position.getOrdinate(1));
 
         position = decode(coder, "32GNV8410260761");
         assertSame(CommonCRS.WGS84.universal(-41, 10), position.getCoordinateReferenceSystem());
-        assertEquals( 584102.5, position.getCoordinate(0));
-        assertEquals(5460761.5, position.getCoordinate(1));
+        assertEquals( 584102.5, position.getOrdinate(0));
+        assertEquals(5460761.5, position.getOrdinate(1));
 
         position = decode(coder, "33XVM2240708183");
         assertSame(CommonCRS.WGS84.universal(82, 10), position.getCoordinateReferenceSystem());
-        assertEquals( 422407.5, position.getCoordinate(0));
-        assertEquals(9108183.5, position.getCoordinate(1));
+        assertEquals( 422407.5, position.getOrdinate(0));
+        assertEquals(9108183.5, position.getOrdinate(1));
 
         position = decode(coder, "32FNL9360826322");
         assertSame(CommonCRS.WGS84.universal(-49.4, 10.3), position.getCoordinateReferenceSystem());
-        assertEquals( 593608.5, position.getCoordinate(0));
-        assertEquals(4526322.5, position.getCoordinate(1));
+        assertEquals( 593608.5, position.getOrdinate(0));
+        assertEquals(4526322.5, position.getOrdinate(1));
     }
 
     /**
@@ -331,14 +327,14 @@
         position = decode(coder, "19JBK");                                            // South hemisphere
         crs = CommonCRS.WGS84.universal(-10, -69);
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 250000, position.getCoordinate(0), 1);
-        assertEquals(6950000, position.getCoordinate(1));
+        assertEquals( 250000, position.getOrdinate(0), 1);
+        assertEquals(6950000, position.getOrdinate(1));
 
         coder.setClipToValidArea(true);
         position = decode(coder, "19JBK");
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 251256, position.getCoordinate(0), 1);
-        assertEquals(6950000, position.getCoordinate(1));
+        assertEquals( 251256, position.getOrdinate(0), 1);
+        assertEquals(6950000, position.getOrdinate(1));
         /*
          * Easting range before clipping is [300000 … 400000] metres.
          * The east bound become 343828 metres after clipping.
@@ -349,14 +345,14 @@
         position = decode(coder, "1VCK");                                // North of Norway latitude band
         crs = CommonCRS.WGS84.universal(62, -180);
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 350000, position.getCoordinate(0), 1);
-        assertEquals(6950000, position.getCoordinate(1));
+        assertEquals( 350000, position.getOrdinate(0), 1);
+        assertEquals(6950000, position.getOrdinate(1));
 
         coder.setClipToValidArea(true);
         position = decode(coder, "1VCK");
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 371914, position.getCoordinate(0), 1);
-        assertEquals(6950000, position.getCoordinate(1));
+        assertEquals( 371914, position.getOrdinate(0), 1);
+        assertEquals(6950000, position.getOrdinate(1));
         /*
          * Northing value before clipping: 7350000
          * Northing value after  clipping: 7371306
@@ -365,14 +361,14 @@
         position = decode(coder, "57KTP");
         crs = CommonCRS.WGS84.universal(-24, 156);
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 250000, position.getCoordinate(0));
-        assertEquals(7350000, position.getCoordinate(1), 1);
+        assertEquals( 250000, position.getOrdinate(0));
+        assertEquals(7350000, position.getOrdinate(1), 1);
 
         coder.setClipToValidArea(true);
         position = decode(coder, "57KTP");
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 250000, position.getCoordinate(0));
-        assertEquals(7371306, position.getCoordinate(1), 1);
+        assertEquals( 250000, position.getOrdinate(0));
+        assertEquals(7371306, position.getOrdinate(1), 1);
         /*
          * Easting and northing values before clipping:  650000   6250000
          * Easting and northing values after  clipping:  643536   6253618
@@ -381,14 +377,14 @@
         position = decode(coder, "56VPH");
         crs = CommonCRS.WGS84.universal(55, 154);
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 650000, position.getCoordinate(0), 1);
-        assertEquals(6250000, position.getCoordinate(1), 1);
+        assertEquals( 650000, position.getOrdinate(0), 1);
+        assertEquals(6250000, position.getOrdinate(1), 1);
 
         coder.setClipToValidArea(true);
         position = decode(coder, "56VPH");
         assertSame(crs, position.getCoordinateReferenceSystem());
-        assertEquals( 643536, position.getCoordinate(0), 1);
-        assertEquals(6253618, position.getCoordinate(1), 1);
+        assertEquals( 643536, position.getOrdinate(0), 1);
+        assertEquals(6253618, position.getOrdinate(1), 1);
     }
 
     /**
@@ -443,35 +439,35 @@
          */
         position = decode(coder, "BAN0001000010");
         assertSame(CommonCRS.WGS84.universal(-90, 0), position.getCoordinateReferenceSystem());
-        assertEquals(2000010.5, position.getCoordinate(0));
-        assertEquals(2000010.5, position.getCoordinate(1));
+        assertEquals(2000010.5, position.getOrdinate(0));
+        assertEquals(2000010.5, position.getOrdinate(1));
 
         position = decode(coder, "AZM9999099990");
         assertSame(CommonCRS.WGS84.universal(-90, 0), position.getCoordinateReferenceSystem());
-        assertEquals(1999990.5, position.getCoordinate(0));
-        assertEquals(1999990.5, position.getCoordinate(1));
+        assertEquals(1999990.5, position.getOrdinate(0));
+        assertEquals(1999990.5, position.getOrdinate(1));
 
         position = decode(coder, "BLJ0672702814");
         assertSame(CommonCRS.WGS84.universal(-90, 0), position.getCoordinateReferenceSystem());
-        assertEquals(2806727.5, position.getCoordinate(0));
-        assertEquals(1602814.5, position.getCoordinate(1));
+        assertEquals(2806727.5, position.getOrdinate(0));
+        assertEquals(1602814.5, position.getOrdinate(1));
         /*
          * North case.
          */
         position = decode(coder, "ZAH0001000010");
         assertSame(CommonCRS.WGS84.universal(90, 0), position.getCoordinateReferenceSystem());
-        assertEquals(2000010.5, position.getCoordinate(0));
-        assertEquals(2000010.5, position.getCoordinate(1));
+        assertEquals(2000010.5, position.getOrdinate(0));
+        assertEquals(2000010.5, position.getOrdinate(1));
 
         position = decode(coder, "YZG9999099990");
         assertSame(CommonCRS.WGS84.universal(90, 0), position.getCoordinateReferenceSystem());
-        assertEquals(1999990.5, position.getCoordinate(0));
-        assertEquals(1999990.5, position.getCoordinate(1));
+        assertEquals(1999990.5, position.getOrdinate(0));
+        assertEquals(1999990.5, position.getOrdinate(1));
 
         position = decode(coder, "YRK8672702814");
         assertSame(CommonCRS.WGS84.universal(90, 0), position.getCoordinateReferenceSystem());
-        assertEquals(1386727.5, position.getCoordinate(0));
-        assertEquals(2202814.5, position.getCoordinate(1));
+        assertEquals(1386727.5, position.getOrdinate(0));
+        assertEquals(2202814.5, position.getOrdinate(1));
     }
 
     /**
@@ -569,28 +565,28 @@
 
         position = decode(coder, "32TNL8410239239");
         assertEquals(position, decode(coder, "32/T/NL/84102/39239"));
-        assertEquals( 584102.5, position.getCoordinate(0));
-        assertEquals(4539239.5, position.getCoordinate(1));
+        assertEquals( 584102.5, position.getOrdinate(0));
+        assertEquals(4539239.5, position.getOrdinate(1));
 
         position = decode(coder, "32TNL8439");
         assertEquals(position, decode(coder, "32/T/NL/84/39"));
-        assertEquals( 584500.0, position.getCoordinate(0));
-        assertEquals(4539500.0, position.getCoordinate(1));
+        assertEquals( 584500.0, position.getOrdinate(0));
+        assertEquals(4539500.0, position.getOrdinate(1));
 
         position = decode(coder, "32TNL83");
         assertEquals(position, decode(coder, "32/T/NL/8/3"));
-        assertEquals( 585000.0, position.getCoordinate(0));
-        assertEquals(4535000.0, position.getCoordinate(1));
+        assertEquals( 585000.0, position.getOrdinate(0));
+        assertEquals(4535000.0, position.getOrdinate(1));
 
         position = decode(coder, "32TNL");
         assertEquals(position, decode(coder, "32/T/NL"));
-        assertEquals( 550000.0, position.getCoordinate(0));
-        assertEquals(4550000.0, position.getCoordinate(1));
+        assertEquals( 550000.0, position.getOrdinate(0));
+        assertEquals(4550000.0, position.getOrdinate(1));
 
         position = decode(coder, "32T");
         assertEquals(position, decode(coder, "32/T"));
-        assertEquals( 500000.0, position.getCoordinate(0));
-        assertEquals(9000000.0, position.getCoordinate(1));
+        assertEquals( 500000.0, position.getOrdinate(0));
+        assertEquals(9000000.0, position.getOrdinate(1));
     }
 
     /**
@@ -635,7 +631,7 @@
             final DirectPosition r = decode(coder, reference);
             final ProjectedCRS crs = (ProjectedCRS) r.getCoordinateReferenceSystem();
             assertSame(expected, crs.getConversionFromBase().getMathTransform().transform(position, expected));
-            final double distance = expected.distance(r.getCoordinate(0), r.getCoordinate(1));
+            final double distance = expected.distance(r.getOrdinate(0), r.getOrdinate(1));
             if (!(distance < 1.5)) {    // Use '!' for catching NaN.
                 final String lineSeparator = System.lineSeparator();
                 fail("Consistency check failed for φ = " + position.x + " and λ = " + position.y + lineSeparator
diff --git a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiersTest.java b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiersTest.java
index f7cc6fb..756f05d 100644
--- a/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiersTest.java
+++ b/endorsed/src/org.apache.sis.referencing.gazetteer/test/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiersTest.java
@@ -27,9 +27,6 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-
 
 /**
  * Tests {@link ReferencingByIdentifiers}.
@@ -54,7 +51,7 @@
         assertNull(properties.put(ReferencingByIdentifiers.NAME_KEY, "UK property addressing"));
         assertNull(properties.put(ReferencingByIdentifiers.THEME_KEY, "property"));
         assertNull(properties.put(ReferencingByIdentifiers.OVERALL_OWNER_KEY, new DefaultOrganisation("Office for National Statistics", null, null, null)));
-        assertNull(properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, new DefaultExtent("UK", null, null, null)));
+        assertNull(properties.put(ReferencingByIdentifiers.DOMAIN_OF_VALIDITY_KEY, new DefaultExtent("UK", null, null, null)));
         return new ReferencingByIdentifiers(properties, LocationTypeTest.create(inherit)) {
             @Override public ReferencingByIdentifiers.Coder createCoder() {
                 throw new UnsupportedOperationException();
diff --git a/endorsed/src/org.apache.sis.referencing/main/module-info.java b/endorsed/src/org.apache.sis.referencing/main/module-info.java
index c19d16b..a78fcb1 100644
--- a/endorsed/src/org.apache.sis.referencing/main/module-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/module-info.java
@@ -195,6 +195,10 @@
             org.glassfish.jaxb.core,                        // For access to various classes.
             jakarta.xml.bind;                               // Seems ignored.
 
+    exports org.apache.sis.referencing.internal to          // On main branch only, for transition from GeoAPI 3.0.
+            org.apache.sis.console,
+            org.apache.sis.openoffice;
+
     /*
      * Allow JAXB to use reflection for marshalling and
      * unmarshalling Apache SIS objects in XML documents.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/AbstractCoordinateSet.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/AbstractCoordinateSet.java
index 212b77f..bea3303 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/AbstractCoordinateSet.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/AbstractCoordinateSet.java
@@ -20,51 +20,93 @@
 import java.io.Serializable;
 import org.apache.sis.referencing.IdentifiedObjects;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.CoordinateSet;
-import org.opengis.coordinate.CoordinateMetadata;
+// Specific to the main branch:
+import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19111;
+import org.opengis.annotation.UML;
+import org.opengis.geometry.DirectPosition;
 
 
 /**
  * Skeletal implementation of a collection of coordinate tuples referenced to the same <abbr>CRS</abbr> and epoch.
  * This implementation is serializable if the coordinate metadata given at construction time is also serializable.
  *
+ * <h2>Future evolution</h2>
+ * This class is expected to implement a {@code CoordinateSet} interface after the next GeoAPI release.
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.5
  * @since   1.5
  */
-public abstract class AbstractCoordinateSet implements CoordinateSet, Serializable {
+public abstract class AbstractCoordinateSet implements Iterable<DirectPosition>, Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 3656426153519035462L;
+    private static final long serialVersionUID = 3656426153519035461L;
 
     /**
      * Coordinate reference system and epoch (if dynamic) of this coordinate set.
      */
-    @SuppressWarnings("serial")     // Apache SIS implementations of this interface are serializable.
-    private final CoordinateMetadata metadata;
+    private final DefaultCoordinateMetadata metadata;
 
     /**
      * Creates a new set of coordinate tuples.
      *
      * @param metadata  coordinate reference system and epoch (if dynamic) of this coordinate set.
      */
-    protected AbstractCoordinateSet(final CoordinateMetadata metadata) {
+    protected AbstractCoordinateSet(final DefaultCoordinateMetadata metadata) {
         this.metadata = Objects.requireNonNull(metadata);
     }
 
     /**
      * Returns the coordinate metadata to which this coordinate set is referenced.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * {@code DefaultCoordinateMetadata} class may be replaced by {@code CoordinateMetadata} interface
+     * after upgrade to GeoAPI 3.1.
+     * </div>
+     *
      * @return coordinate metadata to which this coordinate set is referenced.
      */
-    @Override
-    public CoordinateMetadata getCoordinateMetadata() {
+    public DefaultCoordinateMetadata getCoordinateMetadata() {
         return metadata;
     }
 
     /**
+     * Returns the number of dimensions of coordinate tuples. This is determined by the
+     * {@linkplain DefaultCoordinateMetadata#getCoordinateReferenceSystem() coordinate reference system}.
+     *
+     * @return the number of dimensions of coordinate tuples.
+     */
+    public int getDimension() {
+        // All methods invoked below are for attributes declared as mandatory. Values shall not be null.
+        return getCoordinateMetadata().getCoordinateReferenceSystem().getCoordinateSystem().getDimension();
+    }
+
+    /**
+     * Returns the positions described by coordinate tuples.
+     *
+     * @return position described by coordinate tuples.
+     */
+    @Override
+    @UML(identifier="coordinateTuple", obligation=MANDATORY, specification=ISO_19111)
+    public abstract Iterator<DirectPosition> iterator();
+
+    /**
+     * Returns a stream of coordinate tuples.
+     * Whether the stream is sequential or parallel is implementation dependent.
+     * The default implementation creates a sequential stream.
+     *
+     * @return a sequential or parallel stream of coordinate tuples.
+     */
+    public Stream<DirectPosition> stream() {
+        return StreamSupport.stream(spliterator(), false);
+    }
+
+    /**
      * Returns a string representation of this coordinate set for debugging purposes.
      * This string representation may change in any future version.
      *
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/DefaultCoordinateMetadata.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/DefaultCoordinateMetadata.java
index a5ee561..39a7426 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/DefaultCoordinateMetadata.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/DefaultCoordinateMetadata.java
@@ -33,9 +33,6 @@
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.Utilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.CoordinateMetadata;
-
 
 /**
  * Default implementation of metadata required to reference coordinates.
@@ -43,12 +40,15 @@
  * This default implementation provides <i>Well-Known Text</i> support.
  * It is immutable and serializable if the CRS and epoch are also serializable.
  *
+ * <h2>Future evolution</h2>
+ * This class is expected to implement a {@code CoordinateMetadata} interface after the next GeoAPI release.
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.5
  * @since   1.5
  */
 public class DefaultCoordinateMetadata extends FormattableObject
-        implements CoordinateMetadata, LenientComparable, Serializable
+        implements LenientComparable, Serializable
 {
     /**
      * Serial number for inter-operability with different versions.
@@ -88,45 +88,11 @@
     }
 
     /**
-     * Creates a new coordinate metadata with the same values as the specified one.
-     * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
-     * or a user-defined one (as a subclass), usually in order to leverage some implementation-specific API.
-     *
-     * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
-     *
-     * @param  metadata  the coordinate metadata to copy.
-     *
-     * @see #castOrCopy(CoordinateMetadata)
-     */
-    protected DefaultCoordinateMetadata(final CoordinateMetadata metadata) {
-        this(metadata.getCoordinateReferenceSystem(), metadata.getCoordinateEpoch().orElse(null));
-    }
-
-    /**
-     * Returns a SIS datum implementation with the same values as the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultCoordinateMetadata castOrCopy(final CoordinateMetadata object) {
-        if (object == null || object instanceof DefaultCoordinateMetadata) {
-            return (DefaultCoordinateMetadata) object;
-        } else {
-            return new DefaultCoordinateMetadata(object);
-        }
-    }
-
-    /**
      * Returns the <abbr>CRS</abbr> in which the coordinate tuples are given.
      * Should never be null in principle, however this implementation does not enforce this restriction.
      *
      * @return the coordinate reference system (CRS) of coordinate tuples.
      */
-    @Override
     public CoordinateReferenceSystem getCoordinateReferenceSystem() {
         return crs;
     }
@@ -136,7 +102,6 @@
      *
      * @return epoch at which coordinate tuples are valid.
      */
-    @Override
     public Optional<Temporal> getCoordinateEpoch() {
         return Optional.ofNullable(epoch);
     }
@@ -182,10 +147,6 @@
                     final var other = (DefaultCoordinateMetadata) obj;
                     return crs.equals(other.crs) && Objects.equals(epoch, other.epoch);
                 }
-            } else if (obj instanceof CoordinateMetadata) {
-                final var other = (CoordinateMetadata) obj;
-                return Utilities.deepEquals(getCoordinateReferenceSystem(), other.getCoordinateReferenceSystem(), mode)
-                        && Objects.equals(getCoordinateEpoch(), other.getCoordinateEpoch());
             }
         }
         return false;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
index 3e7b7c4..28d89e3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
@@ -25,8 +25,7 @@
 import java.util.Arrays;
 import java.util.Objects;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -87,6 +86,102 @@
     }
 
     /**
+     * Returns this direct position.
+     *
+     * @return {@code this}.
+     */
+    @Override
+    public final DirectPosition getDirectPosition() {
+        return this;
+    }
+
+    /**
+     * Returns the coordinate reference system in which the coordinate tuple is given.
+     * May be {@code null} if this particular {@code DirectPosition} is included in a larger object
+     * with such a reference to a {@linkplain CoordinateReferenceSystem coordinate reference system}.
+     *
+     * <p>The default implementation returns {@code null}.
+     * Subclasses should override this method if the CRS can be provided.</p>
+     *
+     * @return the coordinate reference system, or {@code null}.
+     */
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return null;
+    }
+
+    /**
+     * Returns a sequence of numbers that hold the coordinate of this position in its reference system.
+     *
+     * @return the coordinates.
+     *
+     * @deprecated Renamed {@link #getCoordinates()} for consistency with ISO 19111 terminology.
+     */
+    @Override
+    @Deprecated(since="1.5")
+    public double[] getCoordinate() {
+        return getCoordinates();
+    }
+
+    /**
+     * Returns a sequence of numbers that hold the coordinate of this position in its reference system.
+     *
+     * @return the coordinates.
+     */
+    public double[] getCoordinates() {
+        final double[] coordinates = new double[getDimension()];
+        for (int i=0; i<coordinates.length; i++) {
+            coordinates[i] = getCoordinate(i);
+        }
+        return coordinates;
+    }
+
+    /**
+     * Returns the coordinate at the specified dimension.
+     *
+     * @param  dimension  the dimension in the range 0 to {@linkplain #getDimension dimension}-1.
+     * @return the coordinate at the specified dimension.
+     * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater
+     *         than the {@linkplain #getDimension() number of dimensions}.
+     *
+     * @deprecated Renamed {@link #getCoordinate(int)} for consistency with ISO 19111 terminology.
+     */
+    @Deprecated(since="1.5")
+    public double getOrdinate(int dimension) throws IndexOutOfBoundsException {
+        return getCoordinate(dimension);
+    }
+
+    /**
+     * Returns the coordinate at the specified dimension.
+     *
+     * @param  dimension  the dimension in the range 0 to {@linkplain #getDimension dimension}-1.
+     * @return the coordinate at the specified dimension.
+     * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater
+     *         than the {@linkplain #getDimension() number of dimensions}.
+     *
+     * @since 1.5
+     */
+    public abstract double getCoordinate(int dimension) throws IndexOutOfBoundsException;
+
+    /**
+     * Sets the coordinate value along the specified dimension.
+     *
+     * @param  dimension  the dimension for the coordinate of interest.
+     * @param  value      the coordinate value of interest.
+     * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater
+     *         than the {@linkplain #getDimension() position dimension}.
+     * @throws UnsupportedOperationException if this direct position is immutable.
+     *
+     * @deprecated Renamed {@link #setCoordinate(int, double)} for consistency with ISO 19111 terminology.
+     */
+    @Deprecated(since="1.5")
+    public void setOrdinate(int dimension, double value)
+            throws IndexOutOfBoundsException, UnsupportedOperationException
+    {
+        setCoordinate(dimension, value);
+    }
+
+    /**
      * Sets the coordinate value along the specified dimension.
      *
      * <p>The default implementation throws {@link UnsupportedOperationException}.
@@ -100,7 +195,6 @@
      *
      * @since 1.5
      */
-    @Override
     public void setCoordinate(int dimension, double value) {
         throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, getClass()));
     }
@@ -111,15 +205,15 @@
      *
      * <p>If this position and the given position have a non-null CRS, then the default implementation
      * requires the CRS to be {@linkplain Utilities#equalsIgnoreMetadata equals (ignoring metadata)},
-     * otherwise a {@code MismatchedCoordinateMetadataException} is thrown. However, subclass may choose
+     * otherwise a {@code MismatchedReferenceSystemException} is thrown. However, subclass may choose
      * to assign the CRS of this position to the CRS of the given position.</p>
      *
      * @param  position  the new position, or {@code null}.
      * @throws MismatchedDimensionException if the given position doesn't have the expected dimension.
-     * @throws MismatchedCoordinateMetadataException if the given position doesn't use the expected CRS.
+     * @throws MismatchedReferenceSystemException if the given position doesn't use the expected CRS.
      */
     public void setLocation(final DirectPosition position)
-            throws MismatchedDimensionException, MismatchedCoordinateMetadataException
+            throws MismatchedDimensionException, MismatchedReferenceSystemException
     {
         final int dimension = getDimension();
         if (position != null) {
@@ -132,7 +226,7 @@
                 }
             }
             for (int i=0; i<dimension; i++) {
-                setCoordinate(i, position.getCoordinate(i));
+                setCoordinate(i, position.getOrdinate(i));
             }
         } else {
             for (int i=0; i<dimension; i++) {
@@ -262,7 +356,7 @@
             char separator = '(';
             for (int i=0; i<dimension; i++) {
                 buffer.append(separator);
-                final double coordinate = position.getCoordinate(i);
+                final double coordinate = position.getOrdinate(i);
                 if (isSinglePrecision) {
                     buffer.append((float) coordinate);
                 } else {
@@ -414,7 +508,7 @@
             final int dimension = getDimension();
             if (dimension == that.getDimension()) {
                 for (int i=0; i<dimension; i++) {
-                    if (!Numerics.equals(getCoordinate(i), that.getCoordinate(i))) {
+                    if (!Numerics.equals(getCoordinate(i), that.getOrdinate(i))) {
                         return false;
                     }
                 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java
index 89f1a85..5225cb9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractEnvelope.java
@@ -31,8 +31,7 @@
 import jakarta.xml.bind.annotation.XmlTransient;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -185,11 +184,11 @@
      * @param  lowerCorner  the first position.
      * @param  upperCorner  the second position.
      * @return their common CRS, or {@code null} if none.
-     * @throws MismatchedCoordinateMetadataException if the two positions don't use equal CRS.
+     * @throws MismatchedReferenceSystemException if the two positions don't use equal CRS.
      */
     static CoordinateReferenceSystem getCommonCRS(final DirectPosition lowerCorner,
                                                   final DirectPosition upperCorner)
-            throws MismatchedCoordinateMetadataException
+            throws MismatchedReferenceSystemException
     {
         final CoordinateReferenceSystem crs1 = lowerCorner.getCoordinateReferenceSystem();
         final CoordinateReferenceSystem crs2 = upperCorner.getCoordinateReferenceSystem();
@@ -283,7 +282,7 @@
      * The default implementation returns a view over the {@link #getLower(int)} method,
      * so changes in this envelope will be immediately reflected in the returned direct position.
      * If the particular case of the {@code GeneralEnvelope} subclass, the returned position
-     * supports also {@linkplain DirectPosition#setCoordinate(int, double) write operations},
+     * supports also {@linkplain DirectPosition#setOrdinate(int, double) write operations},
      * so changes in the position are reflected back in the envelope.
      *
      * <h4>Note on wraparound</h4>
@@ -311,7 +310,7 @@
      * The default implementation returns a view over the {@link #getUpper(int)} method,
      * so changes in this envelope will be immediately reflected in the returned direct position.
      * If the particular case of the {@code GeneralEnvelope} subclass, the returned position
-     * supports also {@linkplain DirectPosition#setCoordinate(int, double) write operations},
+     * supports also {@linkplain DirectPosition#setOrdinate(int, double) write operations},
      * so changes in the position are reflected back in the envelope.
      *
      * <h4>Note on wraparound</h4>
@@ -787,7 +786,7 @@
         ensureDimensionMatches("point", dimension, position);
         assert assertEquals(getCoordinateReferenceSystem(), position.getCoordinateReferenceSystem()) : position;
         for (int i=0; i<dimension; i++) {
-            final double value = position.getCoordinate(i);
+            final double value = position.getOrdinate(i);
             final double lower = getLower(i);
             final double upper = getUpper(i);
             final boolean c1   = (value >= lower);
@@ -870,8 +869,8 @@
         for (int i=0; i<dimension; i++) {
             final double lower0 = getLower(i);
             final double upper0 = getUpper(i);
-            final double lower1 = lowerCorner.getCoordinate(i);
-            final double upper1 = upperCorner.getCoordinate(i);
+            final double lower1 = lowerCorner.getOrdinate(i);
+            final double upper1 = upperCorner.getOrdinate(i);
             final boolean lowerCondition, upperCondition;
             if (edgesInclusive) {
                 lowerCondition = (lower1 >= lower0);
@@ -1006,8 +1005,8 @@
         for (int i=0; i<dimension; i++) {
             final double lower0 = getLower(i);
             final double upper0 = getUpper(i);
-            final double lower1 = lowerCorner.getCoordinate(i);
-            final double upper1 = upperCorner.getCoordinate(i);
+            final double lower1 = lowerCorner.getOrdinate(i);
+            final double upper1 = upperCorner.getOrdinate(i);
             final boolean lowerCondition, upperCondition;
             if (touch) {
                 lowerCondition = (lower1 <= upper0);
@@ -1061,7 +1060,7 @@
      */
     static boolean hasNaN(final DirectPosition position) {
         for (int i=position.getDimension(); --i>=0;) {
-            if (Double.isNaN(position.getCoordinate(i))) {
+            if (Double.isNaN(position.getOrdinate(i))) {
                 return true;
             }
         }
@@ -1123,8 +1122,8 @@
                     ε *= span;
                 }
             }
-            if (!epsilonEqual(getLower(i), lowerCorner.getCoordinate(i), ε) ||
-                !epsilonEqual(getUpper(i), upperCorner.getCoordinate(i), ε))
+            if (!epsilonEqual(getLower(i), lowerCorner.getOrdinate(i), ε) ||
+                !epsilonEqual(getUpper(i), upperCorner.getOrdinate(i), ε))
             {
                 return false;
             }
@@ -1248,7 +1247,7 @@
             do {                                                        // Executed exactly twice.
                 for (int i=0; i<dimension; i++) {
                     buffer.append(i == 0 && !isUpper ? '(' : ' ');
-                    final double coordinate = (isUpper ? upperCorner : lowerCorner).getCoordinate(i);
+                    final double coordinate = (isUpper ? upperCorner : lowerCorner).getOrdinate(i);
                     if (isSinglePrecision) {
                         buffer.append((float) coordinate);
                     } else {
@@ -1285,8 +1284,8 @@
     @Override
     protected String formatTo(final Formatter formatter) {
         final Vector[] points = {
-            Vector.create(getLowerCorner().getCoordinates()),
-            Vector.create(getUpperCorner().getCoordinates())
+            Vector.create(getLowerCorner().getCoordinate()),
+            Vector.create(getUpperCorner().getCoordinate())
         };
         formatter.append(points, WKTUtilities.suggestFractionDigits(getCoordinateReferenceSystem(), points));
         final int dimension = getDimension();
@@ -1300,7 +1299,7 @@
 
     /**
      * Base class for unmodifiable direct positions backed by the enclosing envelope.
-     * Subclasses must override the {@link #getCoordinate(int)} method in order to delegate
+     * Subclasses must override the {@link #getOrdinate(int)} method in order to delegate
      * the work to the appropriate {@link AbstractEnvelope} method.
      *
      * <p>Instance of this class are serializable if the enclosing envelope is serializable.</p>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ArrayEnvelope.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ArrayEnvelope.java
index c7b46a7..a6156e2 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ArrayEnvelope.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ArrayEnvelope.java
@@ -27,8 +27,7 @@
 import java.io.Serializable;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.cs.RangeMeaning;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
@@ -101,10 +100,10 @@
      * @param  lowerCorner  the limits in the direction of decreasing coordinate values for each dimension.
      * @param  upperCorner  the limits in the direction of increasing coordinate values for each dimension.
      * @throws MismatchedDimensionException if the two positions do not have the same dimension.
-     * @throws MismatchedCoordinateMetadataException if the CRS of the two position are not equal.
+     * @throws MismatchedReferenceSystemException if the CRS of the two position are not equal.
      */
     public ArrayEnvelope(final DirectPosition lowerCorner, final DirectPosition upperCorner)
-            throws MismatchedDimensionException, MismatchedCoordinateMetadataException
+            throws MismatchedDimensionException, MismatchedReferenceSystemException
     {
         crs = getCommonCRS(lowerCorner, upperCorner);           // This performs also an argument check.
         final int dimension = lowerCorner.getDimension();
@@ -112,8 +111,8 @@
         ensureSameDimension(dimension, upperCorner.getDimension());
         coordinates = new double[dimension * 2];
         for (int i=0; i<dimension; i++) {
-            coordinates[i            ] = lowerCorner.getCoordinate(i);
-            coordinates[i + dimension] = upperCorner.getCoordinate(i);
+            coordinates[i            ] = lowerCorner.getOrdinate(i);
+            coordinates[i + dimension] = upperCorner.getOrdinate(i);
         }
         verifyRanges(crs, coordinates);
     }
@@ -170,8 +169,8 @@
         final DirectPosition lowerCorner = envelope.getLowerCorner();
         final DirectPosition upperCorner = envelope.getUpperCorner();
         for (int i=0; i<dimension; i++) {
-            coordinates[i]           = lowerCorner.getCoordinate(i);
-            coordinates[i+dimension] = upperCorner.getCoordinate(i);
+            coordinates[i]           = lowerCorner.getOrdinate(i);
+            coordinates[i+dimension] = upperCorner.getOrdinate(i);
         }
         verifyRanges(crs, coordinates);
     }
@@ -312,7 +311,7 @@
      */
     static void ensureSameDimension(final int dim1, final int dim2) throws MismatchedDimensionException {
         if (dim1 != dim2) {
-            throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+            throw new MismatchedDimensionException(Errors.format(
                     Errors.Keys.MismatchedDimension_2, dim1, dim2));
         }
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
index 8b353f2..ade1c28 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
@@ -1459,7 +1459,7 @@
          */
         final int dimension = position.getDimension();
         for (int i=0; i < dimension; i++) {
-            double value = position.getCoordinate(i);
+            double value = position.getOrdinate(i);
             final Object valueObject;
             final String unit, direction;
             final Format f;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition1D.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition1D.java
index 29809ad..d67980a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition1D.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition1D.java
@@ -26,7 +26,7 @@
 import java.io.Serializable;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.apache.sis.util.resources.Errors;
 
 import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
@@ -212,7 +212,7 @@
     public void setLocation(final DirectPosition position) throws MismatchedDimensionException {
         ensureDimensionMatches("position", 1, position);
         setCoordinateReferenceSystem(position.getCoordinateReferenceSystem());
-        coordinate = position.getCoordinate(0);
+        coordinate = position.getOrdinate(0);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition2D.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition2D.java
index b091bdf..93c57c1 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition2D.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/DirectPosition2D.java
@@ -26,8 +26,8 @@
 import org.apache.sis.util.resources.Errors;
 import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -150,8 +150,8 @@
      */
     public DirectPosition2D(final DirectPosition position) throws MismatchedDimensionException {
         ensureDimensionMatches("position", 2, position);
-        x   = position.getCoordinate(0);
-        y   = position.getCoordinate(1);
+        x   = position.getOrdinate(0);
+        y   = position.getOrdinate(1);
         crs = position.getCoordinateReferenceSystem();
     }
 
@@ -183,6 +183,16 @@
     }
 
     /**
+     * Returns this direct position.
+     *
+     * @return {@code this}.
+     */
+    @Override
+    public final DirectPosition getDirectPosition() {
+        return this;
+    }
+
+    /**
      * The length of coordinate sequence (the number of entries).
      * This is always 2 for {@code DirectPosition2D} objects.
      *
@@ -222,10 +232,24 @@
      * This method is final for ensuring consistency with the {@code x} and {@code y} fields, which are public.</div>
      *
      * @return the coordinate.
+     * @deprecated Renamed {@link #getCoordinates()} for consistency with ISO 19111 terminology.
+     */
+    @Override
+    @Deprecated(since="1.5")
+    public final double[] getCoordinate() {
+        return getCoordinates();
+    }
+
+    /**
+     * Returns a sequence of numbers that hold the coordinate of this position in its reference system.
+     *
+     * <div class="note"><b>API note:</b>
+     * This method is final for ensuring consistency with the {@code x} and {@code y} fields, which are public.</div>
+     *
+     * @return the coordinate.
      *
      * @since 1.5
      */
-    @Override
     public final double[] getCoordinates() {
         return new double[] {x,y};
     }
@@ -239,10 +263,26 @@
      * @param  dimension  the dimension in the range 0 to 1 inclusive.
      * @return the coordinate at the specified dimension.
      * @throws IndexOutOfBoundsException if the specified dimension is out of bounds.
+     * @deprecated Renamed {@link #getCoordinate(int)} for consistency with ISO 19111 terminology.
+     */
+    @Override
+    @Deprecated(since="1.5")
+    public final double getOrdinate(final int dimension) throws IndexOutOfBoundsException {
+        return getCoordinate(dimension);
+    }
+
+    /**
+     * Returns the coordinate at the specified dimension.
+     *
+     * <div class="note"><b>API note:</b>
+     * This method is final for ensuring consistency with the {@code x} and {@code y} fields, which are public.</div>
+     *
+     * @param  dimension  the dimension in the range 0 to 1 inclusive.
+     * @return the coordinate at the specified dimension.
+     * @throws IndexOutOfBoundsException if the specified dimension is out of bounds.
      *
      * @since 1.5
      */
-    @Override
     public final double getCoordinate(final int dimension) throws IndexOutOfBoundsException {
         switch (dimension) {
             case 0:  return x;
@@ -257,10 +297,23 @@
      * @param  dimension  the dimension for the coordinate of interest.
      * @param  value      the coordinate value of interest.
      * @throws IndexOutOfBoundsException if the specified dimension is out of bounds.
+     * @deprecated Renamed {@link #setCoordinate(int, double)} for consistency with ISO 19111 terminology.
+     */
+    @Override
+    @Deprecated(since="1.5")
+    public void setOrdinate(int dimension, double value) throws IndexOutOfBoundsException {
+        setCoordinate(dimension, value);
+    }
+
+    /**
+     * Sets the coordinate value along the specified dimension.
+     *
+     * @param  dimension  the dimension for the coordinate of interest.
+     * @param  value      the coordinate value of interest.
+     * @throws IndexOutOfBoundsException if the specified dimension is out of bounds.
      *
      * @since 1.5
      */
-    @Override
     public void setCoordinate(int dimension, double value) throws IndexOutOfBoundsException {
         switch (dimension) {
             case 0:  x = value; break;
@@ -334,8 +387,8 @@
         if (object instanceof DirectPosition) {
             final DirectPosition other = (DirectPosition) object;
             if (other.getDimension() == 2 &&
-                doubleToLongBits(other.getCoordinate(0)) == doubleToLongBits(x) &&
-                doubleToLongBits(other.getCoordinate(1)) == doubleToLongBits(y) &&
+                doubleToLongBits(other.getOrdinate(0)) == doubleToLongBits(x) &&
+                doubleToLongBits(other.getOrdinate(1)) == doubleToLongBits(y) &&
                 Objects.equals(other.getCoordinateReferenceSystem(), crs))
             {
                 assert hashCode() == other.hashCode() : this;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelope2D.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelope2D.java
index e676c4e..f159622 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelope2D.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelope2D.java
@@ -40,9 +40,8 @@
 import static org.apache.sis.geometry.AbstractEnvelope.isWrapAround;
 import static org.apache.sis.geometry.AbstractEnvelope.isNegativeUnsafe;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -157,8 +156,8 @@
          * JDK constraint: The call to ensureDimensionMatch(…) should have been first if Sun/Oracle
          * fixed RFE #4093999 (Relax constraint on placement of this()/super() call in constructors).
          */
-        this(lowerCorner.getCoordinate(0), lowerCorner.getCoordinate(1),
-             upperCorner.getCoordinate(0), upperCorner.getCoordinate(1));
+        this(lowerCorner.getOrdinate(0), lowerCorner.getOrdinate(1),
+             upperCorner.getOrdinate(0), upperCorner.getOrdinate(1));
         ensureDimensionMatches("crs", DIMENSION, crs);
         this.crs = crs;
     }
@@ -171,11 +170,11 @@
      *
      * @param  lowerCorner  the first position.
      * @param  upperCorner  the second position.
-     * @throws MismatchedCoordinateMetadataException if the two positions don't use the same CRS.
+     * @throws MismatchedReferenceSystemException if the two positions don't use the same CRS.
      * @throws MismatchedDimensionException if the two positions are not two-dimensional.
      */
     public Envelope2D(final DirectPosition lowerCorner, final DirectPosition upperCorner)
-            throws MismatchedCoordinateMetadataException, MismatchedDimensionException
+            throws MismatchedReferenceSystemException, MismatchedDimensionException
     {
         this(AbstractEnvelope.getCommonCRS(lowerCorner, upperCorner), lowerCorner, upperCorner);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
index 9d4d853..a8019ff 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
@@ -678,18 +678,18 @@
                     if (sourcePt == null) {
                         sourcePt = new GeneralDirectPosition(dimension);
                         for (int j=0; j<dimension; j++) {
-                            sourcePt.setCoordinate(j, envelope.getMedian(j));
+                            sourcePt.setOrdinate(j, envelope.getMedian(j));
                         }
                     }
                     if (b1) {
-                        sourcePt.setCoordinate(i, v1);
+                        sourcePt.setOrdinate(i, v1);
                         transformed.add(targetPt = mt.transform(sourcePt, targetPt));
                     }
                     if (b2) {
-                        sourcePt.setCoordinate(i, v2);
+                        sourcePt.setOrdinate(i, v2);
                         transformed.add(targetPt = mt.transform(sourcePt, targetPt));
                     }
-                    sourcePt.setCoordinate(i, envelope.getMedian(i));
+                    sourcePt.setOrdinate(i, envelope.getMedian(i));
                 }
             }
         }
@@ -792,7 +792,7 @@
                     targetPt  = new GeneralDirectPosition(centerPt.clone());
                     sourceBox = AbstractEnvelope.castOrCopy(envelope);
                 }
-                targetPt.setCoordinate(i, extremum);
+                targetPt.setOrdinate(i, extremum);
                 try {
                     sourcePt = inverse.transform(targetPt, sourcePt);
                     if (sourceBox.contains(sourcePt)) {
@@ -806,7 +806,7 @@
                          */
                         if (CoordinateOperations.isWrapAround(axis)) {
                             revertPt = mt.transform(sourcePt, revertPt);
-                            final double delta = Math.abs(revertPt.getCoordinate(i) - extremum);
+                            final double delta = Math.abs(revertPt.getOrdinate(i) - extremum);
                             if (!(delta < SPAN_FRACTION_AS_BOUND * (axis.getMaximumValue() - axis.getMinimumValue()))) {
                                 continue;
                             }
@@ -838,7 +838,7 @@
             }
             // Restore `targetPt` to its initial state, which is equal to `centerPt`.
             if (targetPt != null) {
-                targetPt.setCoordinate(i, centerPt[i]);
+                targetPt.setOrdinate(i, centerPt[i]);
             }
         }
         /*
@@ -886,12 +886,12 @@
                                 c++;                // Skip also the case for "wrapAroundMax".
                                 continue;
                             }
-                            targetPt.setCoordinate(axisIndex, (c == 0) ? axis.getMinimumValue() : axis.getMaximumValue());
+                            targetPt.setOrdinate(axisIndex, (c == 0) ? axis.getMinimumValue() : axis.getMaximumValue());
                             value = min;
                         } else {
                             value = max;
                         }
-                        targetPt.setCoordinate(wrapAroundDimension, value);
+                        targetPt.setOrdinate(wrapAroundDimension, value);
                         try {
                             sourcePt = inverse.transform(targetPt, sourcePt);
                             if (sourceBox.contains(sourcePt)) {
@@ -921,9 +921,9 @@
                             }
                         }
                     }
-                    targetPt.setCoordinate(axisIndex, centerPt[axisIndex]);
+                    targetPt.setOrdinate(axisIndex, centerPt[axisIndex]);
                 }
-                targetPt.setCoordinate(wrapAroundDimension, centerPt[wrapAroundDimension]);
+                targetPt.setOrdinate(wrapAroundDimension, centerPt[wrapAroundDimension]);
             }
         }
         /*
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java
index bbeb485..bfcd3e8 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java
@@ -27,7 +27,7 @@
 import java.io.Serializable;
 import java.lang.reflect.Field;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArraysExt;
@@ -135,7 +135,7 @@
      * @param point  the position to copy.
      */
     public GeneralDirectPosition(final DirectPosition point) {
-        coordinates = point.getCoordinates();                            // Should already be cloned.
+        coordinates = point.getCoordinate();                            // Should already be cloned.
         crs = point.getCoordinateReferenceSystem();
         ensureDimensionMatches("crs", coordinates.length, crs);
     }
@@ -292,7 +292,7 @@
             ensureDimensionMatches("position", coordinates.length, position);
             setCoordinateReferenceSystem(position.getCoordinateReferenceSystem());
             for (int i=0; i<coordinates.length; i++) {
-                coordinates[i] = position.getCoordinate(i);
+                coordinates[i] = position.getOrdinate(i);
             }
         }
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java
index 0d3086f..13be0b5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralEnvelope.java
@@ -34,8 +34,7 @@
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.referencing.privy.TemporalAccessor;
 import org.apache.sis.referencing.privy.AxisDirections;
@@ -159,10 +158,10 @@
      * @param  lowerCorner  the limits in the direction of decreasing coordinate values for each dimension.
      * @param  upperCorner  the limits in the direction of increasing coordinate values for each dimension.
      * @throws MismatchedDimensionException if the two positions do not have the same dimension.
-     * @throws MismatchedCoordinateMetadataException if the CRS of the two position are not equal.
+     * @throws MismatchedReferenceSystemException if the CRS of the two position are not equal.
      */
     public GeneralEnvelope(final DirectPosition lowerCorner, final DirectPosition upperCorner)
-            throws MismatchedDimensionException, MismatchedCoordinateMetadataException
+            throws MismatchedDimensionException, MismatchedReferenceSystemException
     {
         super(lowerCorner, upperCorner);
     }
@@ -386,7 +385,7 @@
         }
         final int d = corners.length >>> 1;
         if (d != dimension) {
-            throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+            throw new MismatchedDimensionException(Errors.format(
                     Errors.Keys.MismatchedDimension_3, "coordinates", dimension, d));
         }
     }
@@ -424,8 +423,8 @@
             for (int i=0; i<dimension; i++) {
                 final int iLower = beginIndex + i;
                 final int iUpper = iLower + d;
-                coordinates[iLower] = lower.getCoordinate(i);
-                coordinates[iUpper] = upper.getCoordinate(i);
+                coordinates[iLower] = lower.getOrdinate(i);
+                coordinates[iUpper] = upper.getOrdinate(i);
             }
         }
         final CoordinateReferenceSystem envelopeCRS = envelope.getCoordinateReferenceSystem();
@@ -563,7 +562,7 @@
         for (int i=0; i<dimension; i++) {
             final int iLower = beginIndex + i;
             final int iUpper = iLower + d;
-            final double value = position.getCoordinate(i);
+            final double value = position.getOrdinate(i);
             final double min = coordinates[iLower];
             final double max = coordinates[iUpper];
             if (!isNegative(max - min)) {                       // Standard case, or NaN.
@@ -670,8 +669,8 @@
             final int iUpper = iLower + d;
             final double min0 = coordinates[iLower];
             final double max0 = coordinates[iUpper];
-            final double min1 = lower.getCoordinate(i);
-            final double max1 = upper.getCoordinate(i);
+            final double min1 = lower.getOrdinate(i);
+            final double max1 = upper.getOrdinate(i);
             final boolean sp0 = isNegative(max0 - min0);
             final boolean sp1 = isNegative(max1 - min1);
             if (sp0 == sp1) {
@@ -827,8 +826,8 @@
             final int iUpper = iLower + d;
             final double min0  = coordinates[iLower];
             final double max0  = coordinates[iUpper];
-            final double min1  = lower.getCoordinate(i);
-            final double max1  = upper.getCoordinate(i);
+            final double min1  = lower.getOrdinate(i);
+            final double max1  = upper.getOrdinate(i);
             final double span0 = max0 - min0;
             final double span1 = max1 - min1;
             if (isSameSign(span0, span1)) {                 // Always `false` if any value is NaN.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java
index a47b29e..64fdfee 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java
@@ -25,8 +25,7 @@
 import java.io.Serializable;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 
@@ -60,10 +59,10 @@
      * @param  lowerCorner  the limits in the direction of decreasing coordinate values for each dimension.
      * @param  upperCorner  the limits in the direction of increasing coordinate values for each dimension.
      * @throws MismatchedDimensionException if the two positions do not have the same dimension.
-     * @throws MismatchedCoordinateMetadataException if the CRS of the two position are not equal.
+     * @throws MismatchedReferenceSystemException if the CRS of the two position are not equal.
      */
     public ImmutableEnvelope(final DirectPosition lowerCorner, final DirectPosition upperCorner)
-            throws MismatchedDimensionException, MismatchedCoordinateMetadataException
+            throws MismatchedDimensionException, MismatchedReferenceSystemException
     {
         super(lowerCorner, upperCorner);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/MismatchedReferenceSystemException.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/MismatchedReferenceSystemException.java
index 5bfd193..76b034d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/MismatchedReferenceSystemException.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/MismatchedReferenceSystemException.java
@@ -16,9 +16,6 @@
  */
 package org.apache.sis.geometry;
 
-// Specific to the geoapi-3.1 branch:
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
-
 
 /**
  * Indicates that an object cannot be constructed because of a mismatch in the
@@ -27,11 +24,8 @@
  * @author  Martin Desruisseaux (IRD)
  * @since   0.3
  * @version 0.3
- *
- * @deprecated Replaced by {@link MismatchedCoordinateMetadataException}.
  */
-@Deprecated(since = "2.0")  // Temporary version number until this branch is released.
-public class MismatchedReferenceSystemException extends MismatchedCoordinateMetadataException {
+public class MismatchedReferenceSystemException extends IllegalArgumentException {
     /**
      * Serial number for inter-operability with different versions.
      */
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/UnmodifiableGeometryException.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/UnmodifiableGeometryException.java
new file mode 100644
index 0000000..d27549a
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/UnmodifiableGeometryException.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.geometry;
+
+
+/**
+ * Indicates that an operation is not allowed on a geometry object
+ * because it is unmodifiable. Note that unmodifiable geometries are not necessarily immutable;
+ * they are just not allowed to be modified through the {@code setFoo(...)} method that
+ * raised this exception. Whatever an unmodifiable geometry is immutable or not is
+ * implementation dependent.
+ *
+ * @author  Martin Desruisseaux (IRD)
+ * @since   0.3
+ * @version 0.3
+ */
+public class UnmodifiableGeometryException extends UnsupportedOperationException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 8679047625299612669L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public UnmodifiableGeometryException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public UnmodifiableGeometryException(final String message) {
+        super(message);
+    }
+}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java
index c4cbca0..b593574 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java
@@ -193,7 +193,7 @@
         try {
             return CRS.findOperation(source, target, geographicDomain);
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
     }
 
@@ -369,8 +369,8 @@
                      * how many periods we need to add to those values. We adjust the side which results in the value closest
                      * to zero, in order to reduce rounding error if no more adjustment is done in the next block.
                      */
-                    final double lower = lowerCorner.getCoordinate(i);
-                    final double upper = upperCorner.getCoordinate(i);
+                    final double lower = lowerCorner.getOrdinate(i);
+                    final double upper = upperCorner.getOrdinate(i);
                     double lowerCycles = 0;                             // In number of periods.
                     double upperCycles = 0;
                     double delta = upper - lower;
@@ -531,7 +531,7 @@
             for (int i=0; i<periods.length; i++) {
                 final double period = periods[i];
                 if (period > 0) {
-                    final double x = shifted.getCoordinate(i);
+                    final double x = shifted.getOrdinate(i);
                     double delta = shiftableDomain.getMinimum(i) - x;
                     if (delta > 0) {                                        // Test for point before domain of validity.
                         delta = Math.ceil(delta / period);
@@ -549,7 +549,7 @@
                             shifted = new GeneralDirectPosition(pointOfInterest);
                         }
                         pointOfInterest = shifted;                         // `shifted` may have been set before the loop.
-                        shifted.setCoordinate(i, Math.fma(period, delta, x));
+                        shifted.setOrdinate(i, Math.fma(period, delta, x));
                     }
                 }
             }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
index bd16e42..8106282 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
@@ -87,9 +87,11 @@
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.xml.NilObject;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
+import org.opengis.referencing.ReferenceIdentifier;
+import org.apache.sis.referencing.DefaultObjectDomain;
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -854,17 +856,17 @@
             appendForSubtypes(object);
         }
         if (showIDs) {
-            Collection<? extends Identifier> identifiers = object.getIdentifiers();
+            Collection<ReferenceIdentifier> identifiers = object.getIdentifiers();
             if (identifiers != null) {                                                  // Paranoiac check
                 if (filterID) {
-                    for (final Identifier id : identifiers) {
+                    for (final ReferenceIdentifier id : identifiers) {
                         if (Citations.identifierMatches(authority, id.getAuthority())) {
                             identifiers = Set.of(id);
                             break;
                         }
                     }
                 }
-                for (Identifier id : identifiers) {
+                for (ReferenceIdentifier id : identifiers) {
                     if (!(id instanceof FormattableObject)) {
                         id = ImmutableIdentifier.castOrCopy(id);
                     }
@@ -888,11 +890,11 @@
         InternationalString anchor = null, scope = null;
         Extent area = null;
         if (object instanceof Datum) {
-            anchor = ((Datum) object).getAnchorDefinition().orElse(null);
+            anchor = ((Datum) object).getAnchorPoint();
         } else if (!(object instanceof ReferenceSystem || object instanceof CoordinateOperation)) {
             return;
         }
-        for (final ObjectDomain domain : object.getDomains()) {
+        for (final DefaultObjectDomain domain : Legacy.getDomains(object)) {
             scope = domain.getScope();
             area = domain.getDomainOfValidity();
             if (area != null) break;
@@ -904,7 +906,7 @@
 
     /**
      * Appends the usage (scope and domain of validity) of an object.
-     * The arguments are the components of an {@link ObjectDomain}.
+     * The arguments are the components of an {@link DefaultObjectDomain}.
      * The given extent is decomposed in horizontal, vertical and temporal components.
      * The horizontal component uses the default number of fraction digits recommended by ISO 19162.
      *
@@ -1167,7 +1169,7 @@
      *
      * @param  code  the code list to append to the WKT, or {@code null} if none.
      */
-    public void append(final ControlledVocabulary code) {
+    public void append(final CodeList<?> code) {
         if (code != null) {
             appendSeparator();
             final String name = convention.majorVersion() == 1 ? code.name() : Types.getCodeName(code);
@@ -1594,15 +1596,12 @@
             } else {
                 append(number.doubleValue());
             }
-        } else if (value instanceof ControlledVocabulary) {
-            append((ControlledVocabulary) value);
-        } else if (value instanceof Date) {
-            append((Date) value);
-        } else if (value instanceof Temporal) {
-            append((Temporal) value);
-        } else if (value instanceof Boolean) {
-            append((Boolean) value);
-        } else if (value instanceof CharSequence) {
+        }
+        else if (value instanceof CodeList<?>) append((CodeList<?>) value);
+        else if (value instanceof Date)        append((Date)        value);
+        else if (value instanceof Temporal)    append((Temporal)    value);
+        else if (value instanceof Boolean)     append((Boolean)     value);
+        else if (value instanceof CharSequence) {
             append((value instanceof InternationalString) ?
                     ((InternationalString) value).toString(locale) : value.toString(), null);
         } else if (value.getClass().isArray()) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 433f389..4c8acdc 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -87,8 +87,11 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceSystem;
+import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.referencing.internal.ServicesForMetadata;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 
 
 /**
@@ -103,6 +106,14 @@
  */
 @SuppressWarnings("LocalVariableHidesMemberVariable")       // We hide with the same value made final.
 class GeodeticObjectParser extends MathTransformParser implements Comparator<CoordinateSystemAxis> {
+    /*
+     * Force class initialization of `AxisDirections` in order to have
+     * its constants added to the list of know `AxisDirection` values.
+     */
+    static {
+        AxisDirections.AWAY_FROM.toString();
+    }
+
     /**
      * The names of the 7 parameters in a {@code TOWGS84[…]} element.
      * Those names are derived from the <cite>Well Known Text</cite> (WKT) version 1 specification.
@@ -476,7 +487,7 @@
              */
             element = parent.pullElement(OPTIONAL, WKTKeywords.Scope);
             if (element != null) {
-                properties.put(ObjectDomain.SCOPE_KEY, element.pullString("scope"));
+                properties.put(ReferenceSystem.SCOPE_KEY, element.pullString("scope"));  // Other types like Datum use the same key.
                 element.close(ignoredElements);
             }
             /*
@@ -541,7 +552,7 @@
                 }
             }
             if (extent != null) {
-                properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, extent);
+                properties.put(ReferenceSystem.DOMAIN_OF_VALIDITY_KEY, extent);
             }
             /*
              * Example: REMARK["Замечание на русском языке"]
@@ -567,7 +578,7 @@
         final Element anchor = element.pullElement(OPTIONAL, WKTKeywords.Anchor);
         final Map<String,Object> properties = parseMetadataAndClose(element, name, null);
         if (anchor != null) {
-            properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor.pullString("anchorDefinition"));
+            properties.put(Datum.ANCHOR_POINT_KEY, anchor.pullString("anchorDefinition"));
             anchor.close(ignoredElements);
         }
         return properties;
@@ -829,11 +840,11 @@
                     nz        = "Height";
                     direction = AxisDirection.UP;
                     if (datum instanceof VerticalDatum) {
-                        final RealizationMethod vt = ((VerticalDatum) datum).getRealizationMethod().orElse(null);
-                        if (vt == RealizationMethod.GEOID) {
+                        final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType();
+                        if (vt == VerticalDatumType.GEOIDAL) {
                             nz = AxisNames.GRAVITY_RELATED_HEIGHT;
                             z  = "H";
-                        } else if (vt == RealizationMethod.TIDAL) {
+                        } else if (vt == VerticalDatumType.DEPTH) {
                             direction = AxisDirection.DOWN;
                             nz = AxisNames.DEPTH;
                             z  = "D";
@@ -864,7 +875,7 @@
                     if (defaultUnit == null) {
                         throw parent.missingComponent(WKTKeywords.ParametricUnit);
                     }
-                    direction = AxisDirection.UNSPECIFIED;
+                    direction = AxisDirections.UNSPECIFIED;
                     nz = "Parametric";
                     z = "p";
                     break;
@@ -924,7 +935,10 @@
             }
             case WKTKeywords.spherical: {
                 switch (axes.length) {
-                    case 2: return csFactory.createSphericalCS(csProperties, axes[0], axes[1]);
+                    case 2: if (csFactory instanceof GeodeticObjectFactory) {
+                                return ((GeodeticObjectFactory) csFactory).createSphericalCS(csProperties, axes[0], axes[1]);
+                            }
+                            break;
                     case 3: return csFactory.createSphericalCS(csProperties, axes[0], axes[1], axes[2]);
                 }
                 dimension = (axes.length < 2) ? 2 : 3;                      // For error message.
@@ -968,7 +982,7 @@
             }
             case WKTKeywords.parametric: {
                 if (axes.length != (dimension = 1)) break;
-                return csFactory.createParametricCS(csProperties, axes[0]);
+                return ServicesForMetadata.createParametricCS(csProperties, axes[0], csFactory);
             }
             default: {
                 warning(parent, WKTKeywords.CS, Errors.formatInternational(Errors.Keys.UnknownType_1, type), null);
@@ -1286,14 +1300,16 @@
          */
         FactoryException suppressed = null;
         final CoordinateOperationFactory opFactory = factories.getCoordinateOperationFactory();
-        if (id != null) try {
+        final MathTransformFactory mtFactory = factories.getMathTransformFactory();
+        if (id instanceof ReferenceIdentifier) try {
             // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values.
-            return opFactory.getOperationMethod(id.getCodeSpace() + Constants.DEFAULT_SEPARATOR + id.getCode());
+            return ServicesForMetadata.getOperationMethod(opFactory, mtFactory,
+                    ((ReferenceIdentifier) id).getCodeSpace() + Constants.DEFAULT_SEPARATOR + id.getCode());
         } catch (FactoryException e) {
             suppressed = e;
         }
         try {
-            return opFactory.getOperationMethod(name);
+            return ServicesForMetadata.getOperationMethod(opFactory, mtFactory, name);
         } catch (FactoryException e) {
             if (suppressed != null) {
                 e.addSuppressed(suppressed);
@@ -1445,7 +1461,7 @@
             return null;
         }
         final String name = element.pullString("name");
-        RealizationMethod method = null;
+        VerticalDatumType method = null;
         if (isWKT1) {
             method = VerticalDatumTypes.fromLegacy(element.pullInteger("datum"));
         }
@@ -1483,7 +1499,7 @@
         origin.close(ignoredElements);
         final DatumFactory datumFactory = factories.getDatumFactory();
         try {
-            return datumFactory.createTemporalDatum(parseAnchorAndClose(element, name), epoch);
+            return datumFactory.createTemporalDatum(parseAnchorAndClose(element, name), TemporalDate.toDate(epoch));
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -1498,10 +1514,10 @@
      *
      * @param  mode    {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent  the parent element.
-     * @return the {@code "ParametricDatum"} element as a {@link ParametricDatum} object.
+     * @return the {@code "ParametricDatum"} element as a {@code ParametricDatum} object.
      * @throws ParseException if the {@code "ParametricDatum"} element cannot be parsed.
      */
-    private ParametricDatum parseParametricDatum(final int mode, final Element parent) throws ParseException {
+    private Datum parseParametricDatum(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.ParametricDatum, WKTKeywords.PDatum);
         if (element == null) {
             return null;
@@ -1509,7 +1525,7 @@
         final String name = element.pullString("name");
         final DatumFactory datumFactory = factories.getDatumFactory();
         try {
-            return datumFactory.createParametricDatum(parseAnchorAndClose(element, name));
+            return ServicesForMetadata.createParametricDatum(parseAnchorAndClose(element, name), datumFactory);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -1562,7 +1578,6 @@
      * @return the {@code "ImageDatum"} element.
      * @throws ParseException if the {@code "ImageDatum"} element cannot be parsed.
      */
-    @SuppressWarnings("removal")
     private ImageDatum parseImageDatum(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.ImageDatum, WKTKeywords.IDatum);
         if (element == null) {
@@ -1669,7 +1684,6 @@
      * @return the {@code "ImageCRS"} element as an {@link ImageCRS} object.
      * @throws ParseException if the {@code "ImageCRS"} element cannot be parsed.
      */
-    @SuppressWarnings("removal")
     private ImageCRS parseImageCRS(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.ImageCRS);
         if (element == null) {
@@ -1849,17 +1863,16 @@
             }
             final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, longitudeUnit);
             final GeodeticDatum datum = parseDatum(MANDATORY, element, meridian);
-            final DatumEnsemble<GeodeticDatum> datumEnsemble = null;    // TODO
             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
             if (cs instanceof EllipsoidalCS) {                                  // By far the most frequent case.
-                return crsFactory.createGeographicCRS(properties, datum, datumEnsemble, (EllipsoidalCS) cs);
+                return crsFactory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs);
             }
             if (cs instanceof CartesianCS) {                                    // The second most frequent case.
-                return crsFactory.createGeodeticCRS(properties, datum, datumEnsemble,
+                return crsFactory.createGeocentricCRS(properties, datum,
                         Legacy.forGeocentricCRS((CartesianCS) cs, false));
             }
             if (cs instanceof SphericalCS) {                                    // Not very common case.
-                return crsFactory.createGeodeticCRS(properties, datum, datumEnsemble, (SphericalCS) cs);
+                return crsFactory.createGeocentricCRS(properties, datum, (SphericalCS) cs);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -1937,9 +1950,9 @@
                  * But sometimes the axis (which was not available when we created the datum) provides
                  * more information. Verify if we can have a better type now, and if so rebuild the datum.
                  */
-                if (datum.getRealizationMethod().isEmpty()) {
+                if (datum.getVerticalDatumType() == VerticalDatumType.OTHER_SURFACE) {
                     var type = VerticalDatumTypes.guess(datum.getName().getCode(), datum.getAlias(), cs.getAxis(0));
-                    if (type != null) {
+                    if (type != VerticalDatumType.OTHER_SURFACE) {
                         final DatumFactory datumFactory = factories.getDatumFactory();
                         datum = datumFactory.createVerticalDatum(IdentifiedObjects.getProperties(datum), type);
                     }
@@ -2043,8 +2056,7 @@
          * A ParametricCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind ParametricCRS.
          * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseParametricCRS.
          */
-        ParametricDatum datum = null;
-        DatumEnsemble<ParametricDatum> datumEnsemble = null;    // TODO
+        Datum datum = null;
         SingleCRS baseCRS = null;
         Conversion fromBase = null;
         if (!isBaseCRS) {
@@ -2070,12 +2082,12 @@
         try {
             cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, false, unit, datum);
             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
-            if (cs instanceof ParametricCS) {
+            if (cs != null) {
                 final CRSFactory crsFactory = factories.getCRSFactory();
                 if (baseCRS != null) {
                     return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs);
                 }
-                return crsFactory.createParametricCRS(properties, datum, datumEnsemble, (ParametricCS) cs);
+                return ServicesForMetadata.createParametricCRS(properties, datum, cs, crsFactory);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -2246,7 +2258,7 @@
                 buffer.append(number);
                 axes[i] = csFactory.createCoordinateSystemAxis(
                         singletonMap(CoordinateSystemAxis.NAME_KEY, buffer.toString()),
-                        number, AxisDirection.UNSPECIFIED, Units.UNITY);
+                        number, AxisDirections.UNSPECIFIED, Units.UNITY);
             }
             final Map<String,Object> properties = parseMetadataAndClose(element, name, baseCRS);
             final Map<String,Object> axisName = singletonMap(CoordinateSystem.NAME_KEY, AxisDirections.appendTo(new StringBuilder("CS"), axes));
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/MathTransformParser.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/MathTransformParser.java
index 0e2c757..9885072 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/MathTransformParser.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/MathTransformParser.java
@@ -58,7 +58,7 @@
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Rueben Schulz (UBC)
  *
- * @see <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
+ * @see <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
  */
 class MathTransformParser extends AbstractParser {
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
index 37275e7..017d807 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/VerticalInfo.java
@@ -31,8 +31,8 @@
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import org.opengis.referencing.datum.VerticalDatumType;
 
 
 /**
@@ -96,7 +96,7 @@
      * This method invokes {@link DefaultVerticalExtent#setVerticalCRS(VerticalCRS)} with the given CRS if:
      *
      * <ul>
-     *   <li>realization method is {@link RealizationMethod#GEOID},</li>
+     *   <li>datum type is {@link VerticalDatumType#GEOIDAL},</li>
      *   <li>increasing height values are up, and</li>
      *   <li>axis unit of measurement is the given linear unit.</li>
      * </ul>
@@ -107,7 +107,7 @@
      *         became empty as a result of this operation.
      */
     final VerticalInfo resolve(final VerticalCRS crs) {
-        if (crs != null && crs.getDatum().getRealizationMethod().orElse(null) == RealizationMethod.GEOID) {
+        if (crs != null && crs.getDatum().getVerticalDatumType() == VerticalDatumType.GEOIDAL) {
             return resolve(crs, crs.getCoordinateSystem().getAxis(0));
         }
         return this;
@@ -136,7 +136,7 @@
      * The CRS created by this method is implementation-dependent. The only guarantees are:
      *
      * <ul>
-     *   <li>realization method is {@link RealizationMethod#GEOID},</li>
+     *   <li>datum type is {@link VerticalDatumType#GEOIDAL},</li>
      *   <li>increasing height values are up, and</li>
      *   <li>axis unit of measurement is the given linear unit.</li>
      * </ul>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java
index fdbf241..950fc0e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java
@@ -58,8 +58,8 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.FrequencySortedSet;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -255,7 +255,7 @@
              * Identifier should never be null because `WKTDictionary` accepts only definitions having
              * an `ID[…]` or `AUTHORITY[…]` element. A WKT can contain at most one of those elements.
              */
-            final Identifier id = CollectionsExt.first(object.getIdentifiers());
+            final ReferenceIdentifier id = CollectionsExt.first(object.getIdentifiers());
             codespace = id.getCodeSpace();
             version   = id.getVersion();
             value     = object;
@@ -755,7 +755,7 @@
      * @throws FactoryException if parsing failed.
      */
     private IdentifiedObject parseAndAdd(final String codespace, final String version,
-            final String code, final String wkt, final Identifier defaultIdentifier) throws FactoryException
+            final String code, final String wkt, final DefaultIdentifier defaultIdentifier) throws FactoryException
     {
         ArgumentChecks.ensureNonEmpty("code", code);
         ArgumentChecks.ensureNonEmpty("wkt",  wkt);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java
index f95fd49..01a1e8d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java
@@ -60,8 +60,8 @@
 import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 
 
 /**
@@ -225,9 +225,9 @@
      * <p>This field is transient because this is not yet a public API. The {@code transient}
      * keyword may be removed in a future version if we commit to this API.</p>
      *
-     * @see #setDefaultIdentifier(Identifier)
+     * @see #setDefaultIdentifier(DefaultIdentifier)
      */
-    private transient Identifier defaultIdentifier;
+    private transient DefaultIdentifier defaultIdentifier;
 
     /**
      * WKT fragments that can be inserted in longer WKT strings, or {@code null} if none. Keys are short identifiers
@@ -700,7 +700,7 @@
      *
      * @param  identifier  the default identifier, or {@code null} if none.
      */
-    final void setDefaultIdentifier(final Identifier identifier) {
+    final void setDefaultIdentifier(final DefaultIdentifier identifier) {
         defaultIdentifier = identifier;
     }
 
@@ -1045,7 +1045,10 @@
         @Override String getFacadeMethod() {return "parse";}
 
         /** Invoked when an identifier need to be supplied to root {@link IdentifiedObject}. */
-        @Override public Object apply(Object key) {return new ImmutableIdentifier(defaultIdentifier);}
+        @Override public Object apply(Object key) {
+            return new ImmutableIdentifier(defaultIdentifier.getAuthority(), defaultIdentifier.getCodeSpace(),
+                    defaultIdentifier.getCode(), defaultIdentifier.getVersion(), defaultIdentifier.getDescription());
+        }
 
         /** Invoked when a root {@link IdentifiedObject} is about to be created. */
         @Override void completeRoot(final Map<String,Object> properties) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/AbstractParameterDescriptor.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/AbstractParameterDescriptor.java
index 8af3850..777b2f5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/AbstractParameterDescriptor.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/AbstractParameterDescriptor.java
@@ -33,10 +33,6 @@
 import org.apache.sis.util.resources.Errors;
 import static org.apache.sis.xml.bind.referencing.CC_GeneralOperationParameter.DEFAULT_OCCURRENCE;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Objects;
-import static org.apache.sis.util.Utilities.deepEquals;
-
 
 /**
  * Abstract definition of a parameter or group of parameters used by a coordinate operation or a process.
@@ -67,7 +63,7 @@
  *     <td class="sep">{@code description}</td>
  *     <td class="sep">Also known as “definition”.</td>
  *   </tr><tr>
- *     <td>{@link #getDirection()}</td>
+ *     <td>{@code getDirection()}</td>
  *     <td class="sep"></td>
  *     <td class="sep"></td>
  *     <td class="sep">{@code direction}</td>
@@ -141,7 +137,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -151,7 +147,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -261,9 +257,7 @@
                 default: {
                     final GeneralParameterDescriptor that = (GeneralParameterDescriptor) object;
                     return getMinimumOccurs() == that.getMinimumOccurs() &&
-                           getMaximumOccurs() == that.getMaximumOccurs() &&
-                           Objects.equals(getDirection(), that.getDirection()) &&
-                           deepEquals(getDescription(), that.getDescription(), mode);
+                           getMaximumOccurs() == that.getMaximumOccurs();
                 }
             }
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptor.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptor.java
index 49c412f..2efbe2f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptor.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptor.java
@@ -138,7 +138,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -146,10 +146,10 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr><tr>
@@ -326,7 +326,6 @@
      *
      * @since 1.3
      */
-    @Override
     public TypeName getValueType() {
         return QualityParameter.getValueType(valueClass);
     }
@@ -507,7 +506,6 @@
                     return getMinimumOccurs() == that.getMinimumOccurs() &&
                            getMaximumOccurs() == that.getMaximumOccurs() &&
                            getValueClass()    == that.getValueClass()    &&
-                           Utilities.deepEquals(getValueType(),    that.getValueType(), mode) &&
                            Objects.      equals(getValidValues(),  that.getValidValues())  &&
                            Objects.      equals(getMinimumValue(), that.getMinimumValue()) &&
                            Objects.      equals(getMaximumValue(), that.getMaximumValue()) &&
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
index 6223101..bce9a2d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
@@ -39,9 +39,6 @@
 import org.apache.sis.util.resources.Errors;
 import static org.apache.sis.util.Utilities.deepEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.parameter.ParameterDirection;
-
 
 /**
  * The definition of a group of related parameters used by an operation method.
@@ -124,7 +121,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -132,10 +129,10 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr><tr>
@@ -318,31 +315,6 @@
     }
 
     /**
-     * Returns an indication if all parameters in this group are inputs to the service, outputs or both.
-     * If this group contains parameters with different direction, then this method returns {@code null}.
-     *
-     * @return indication if all parameters are inputs or outputs to the service, or {@code null} if undetermined.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        ParameterDirection dir = null;
-        for (final GeneralParameterDescriptor param : descriptors) {
-            final ParameterDirection c = param.getDirection();
-            if (c == null) {
-                return null;
-            }
-            if (c != dir) {
-                if (dir == null) {
-                    dir = c;
-                } else {
-                    return null;
-                }
-            }
-        }
-        return dir;
-    }
-
-    /**
      * Returns all parameters in this group.
      *
      * @return the parameter descriptors in this group.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterFormat.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterFormat.java
index eeafaee..5154d27 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterFormat.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterFormat.java
@@ -62,8 +62,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -341,7 +341,7 @@
 
     /**
      * Filters names, aliases and identifiers by their code spaces. If the given array is non-null, then the only names,
-     * aliases and identifiers to be formatted are those having a {@link Identifier#getCodeSpace()},
+     * aliases and identifiers to be formatted are those having a {@link ReferenceIdentifier#getCodeSpace()},
      * {@link ScopedName#head()} or {@link GenericName#scope()} value in the given list, unless no name or alias
      * matches this criterion.
      *
@@ -719,8 +719,8 @@
                                 configure((NumberFormat) format, Math.abs(((Number) value).doubleValue()));
                             }
                             text = format.format(value, buffer, dummyFP);
-                        } else if (value instanceof ControlledVocabulary) {
-                            text = Types.getCodeTitle((ControlledVocabulary) value).toString(getLocale());
+                        } else if (value instanceof CodeList<?>) {
+                            text = Types.getCodeTitle((CodeList<?>) value).toString(getLocale());
                         } else if (value instanceof InternationalString) {
                             text = ((InternationalString) value).toString(getLocale());
                         } else {
@@ -845,7 +845,7 @@
             final Set<ReferenceIdentifier> identifiers = object.getIdentifiers();
             if (identifiers != null) {                                              // Paranoiac check.
                 Identifier identifier = null;
-                for (final Identifier candidate : identifiers) {
+                for (final ReferenceIdentifier candidate : identifiers) {
                     if (candidate != null) {                                        // Paranoiac check.
                         if (isPreferredCodespace(candidate.getCodeSpace())) {
                             identifier = candidate;
@@ -866,7 +866,7 @@
              * in the current row and clear the 'name' locale variable. Otherwise, keep the 'name'
              * locale variable in case we found no alias to format.
              */
-            Identifier name = object.getName();
+            ReferenceIdentifier name = object.getName();
             if (name != null) { // Paranoiac check.
                 final String codespace = name.getCodeSpace();
                 if (isPreferredCodespace(codespace)) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterTableRow.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterTableRow.java
index a98e17d..8cacca3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterTableRow.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/ParameterTableRow.java
@@ -48,6 +48,9 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.util.InternationalString;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * A row in the table to be formatted by {@link ParameterFormat}.
@@ -127,7 +130,7 @@
         values = new ArrayList<>(2);            // In the vast majority of cases, we will have only one value.
         units  = new ArrayList<>(2);
         identifiers = new LinkedHashMap<>();
-        Identifier name = object.getName();
+        ReferenceIdentifier name = object.getName();
         if (name != null) {                                             // Paranoiac check.
             final String codespace = name.getCodeSpace();
             if (preferredCodespaces == null || preferredCodespaces.contains(codespace)) {
@@ -170,9 +173,9 @@
          * Add identifiers (detailed mode only).
          */
         if (!isBrief) {
-            final Collection<? extends Identifier> ids = object.getIdentifiers();
+            final Collection<? extends ReferenceIdentifier> ids = object.getIdentifiers();
             if (ids != null) {                                          // Paranoiac check.
-                for (final Identifier id : ids) {
+                for (final ReferenceIdentifier id : ids) {
                     if (!isDeprecated(id)) {
                         final String codespace = id.getCodeSpace();
                         if (preferredCodespaces == null || preferredCodespaces.contains(codespace)) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Parameters.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Parameters.java
index 9b6be7e..9ab9078 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Parameters.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Parameters.java
@@ -920,8 +920,8 @@
                     try {
                         target = destination.parameter(name);
                     } catch (ParameterNotFoundException cause) {
-                        throw new InvalidParameterNameException(Errors.format(
-                                    Errors.Keys.UnexpectedParameter_1, name), cause, name);
+                        throw (InvalidParameterNameException) new InvalidParameterNameException(Errors.format(
+                                    Errors.Keys.UnexpectedParameter_1, name), name).initCause(cause);
                     }
                 } else {
                     target = (ParameterValue<?>) getOrCreate(destination, name, occurrence);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/TensorParameters.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/TensorParameters.java
index 91249d4..ad75d7e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/TensorParameters.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/TensorParameters.java
@@ -702,7 +702,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link DefaultParameterDescriptorGroup#getName()}</td>
      *   </tr>
      *   <tr>
@@ -712,7 +712,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link DefaultParameterDescriptorGroup#getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -785,8 +785,8 @@
                     cause = e;
                 }
                 if (indices == null) {
-                    throw new InvalidParameterNameException(Errors.format(
-                                Errors.Keys.UnexpectedParameter_1, name), cause, name);
+                    throw (InvalidParameterNameException) new InvalidParameterNameException(Errors.format(
+                                Errors.Keys.UnexpectedParameter_1, name), name).initCause(cause);
                 }
                 matrix.setElement(indices[0], indices[1], ((ParameterValue<?>) param).doubleValue());
             }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/DynamicReferenceFrame.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/DynamicReferenceFrame.java
new file mode 100644
index 0000000..e22df3a
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/DynamicReferenceFrame.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.referencing;
+
+import java.time.temporal.Temporal;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.*;
+import static org.opengis.annotation.Specification.*;
+
+
+/**
+ * Placeholder for an interface that may be added in GeoAPI 3.1.
+ */
+public interface DynamicReferenceFrame extends Datum {
+    /**
+     * {@return the epoch to which the coordinates of stations defining the dynamic datum are referenced}.
+     */
+    @UML(identifier="frameReferenceEpoch", obligation=MANDATORY, specification=ISO_19111)
+    Temporal getFrameReferenceEpoch();
+}
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/package-info.java
similarity index 79%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/package-info.java
index 4307e9c..3172e99 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/package-info.java
@@ -16,12 +16,6 @@
  */
 
 /**
- * CQL parser.
- *
- * @author  Johann Sorel (Geomatys)
+ * Place-holder for some interfaces not present in GeoAPI 3.0 but proposed for addition in GeoAPI 3.1.
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
-}
+package org.apache.sis.pending.geoapi.referencing;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
index 8b4b5d2..9af53cb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@ -77,8 +77,10 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceSystem;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -182,6 +184,13 @@
     public static final String DEPRECATED_KEY = "deprecated";
 
     /**
+     * Key for the <code>{@value}</code> property to be given to the
+     * {@code ObjectFactory.createFoo(Map, ...)} methods.
+     * This is used for setting the value to be returned by {@link #getDomains()}.
+     */
+    static final String DOMAINS_KEY = "domains";
+
+    /**
      * The name for this object or code. Shall never be {@code null}.
      *
      * <p><b>Consider this field as final!</b>
@@ -229,7 +238,7 @@
      * @see #getDomains()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private Collection<ObjectDomain> domains;
+    private Collection<DefaultObjectDomain> domains;
 
     /**
      * Comments on or information about this object, or {@code null} if none.
@@ -291,7 +300,7 @@
      *     <td>{@link String}</td>
      *     <td>{@link NamedIdentifier#getVersion()} on the {@linkplain #getName() name}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link String}</td>
      *     <td>{@link NamedIdentifier#getDescription()} on the {@linkplain #getName() name}</td>
      *   </tr><tr>
@@ -303,15 +312,15 @@
      *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#SCOPE_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceSystem#SCOPE_KEY}</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link DefaultObjectDomain#getScope()} on the {@linkplain #getDomains() domain}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceSystem#DOMAIN_OF_VALIDITY_KEY}</td>
      *     <td>{@link Extent}</td>
      *     <td>{@link DefaultObjectDomain#getDomainOfValidity()} on the {@linkplain #getDomains() domain}</td>
      *   </tr><tr>
@@ -402,16 +411,16 @@
         // "domains": ObjectDomain or ObjectDomain[]
         // -----------------------------------------
         value = properties.get(DOMAINS_KEY);
-        if (value instanceof ObjectDomain) {
-            domains = Collections.singleton((ObjectDomain) value);
-        } else if (value instanceof ObjectDomain[]) {
-            domains = immutableSet(true, (ObjectDomain[]) value);
+        if (value instanceof DefaultObjectDomain) {
+            domains = Collections.singleton((DefaultObjectDomain) value);
+        } else if (value instanceof DefaultObjectDomain[]) {
+            domains = immutableSet(true, (DefaultObjectDomain[]) value);
         } else if (value != null) {
             throw illegalPropertyType(properties, DOMAINS_KEY, value);
         } else {
             // Compatibility with previous way to specify domain.
-            final InternationalString scope = Types.toInternationalString(properties, ObjectDomain.SCOPE_KEY);
-            final Extent domainOfValidity = Containers.property(properties, ObjectDomain.DOMAIN_OF_VALIDITY_KEY, Extent.class);
+            final InternationalString scope = Types.toInternationalString(properties, ReferenceSystem.SCOPE_KEY);
+            final Extent domainOfValidity = Containers.property(properties, ReferenceSystem.DOMAIN_OF_VALIDITY_KEY, Extent.class);
             if (scope != null || domainOfValidity != null) {
                 domains = Collections.singleton(new DefaultObjectDomain(scope, domainOfValidity));
             }
@@ -458,7 +467,7 @@
         name        =          object.getName();
         alias       = nonEmpty(object.getAlias()); // Favor null for empty set in case it is not Collections.EMPTY_SET
         identifiers = nonEmpty(object.getIdentifiers());
-        domains     = nonEmpty(object.getDomains());
+        domains     = nonEmpty(Legacy.getDomains(object));
         remarks     =          object.getRemarks();
         deprecated  = (object instanceof Deprecable) ? ((Deprecable) object).isDeprecated() : false;
     }
@@ -568,8 +577,7 @@
      *
      * @since 1.4
      */
-    @Override
-    public Collection<ObjectDomain> getDomains() {
+    public Collection<DefaultObjectDomain> getDomains() {
         return nonNull(domains);
     }
 
@@ -587,7 +595,14 @@
      * @since 0.6
      */
     public Optional<InternationalString> getDescription() {
-        return Optional.ofNullable((name != null) ? name.getDescription() : null);
+        final ReferenceIdentifier name = getName();
+        if (name instanceof ImmutableIdentifier) {
+            return Optional.ofNullable(((ImmutableIdentifier) name).getDescription());
+        }
+        if (name instanceof DefaultIdentifier) {
+            return Optional.ofNullable(((DefaultIdentifier) name).getDescription());
+        }
+        return Optional.empty();
     }
 
     /**
@@ -782,7 +797,7 @@
                 return deepEquals(getName(),        that.getName(),        mode) &&
                        deepEquals(getAlias(),       that.getAlias(),       mode) &&
                        deepEquals(getIdentifiers(), that.getIdentifiers(), mode) &&
-                       deepEquals(getDomains(),     that.getDomains(),     mode) &&
+                       deepEquals(getDomains(),     Legacy.getDomains(that), mode) &&
                        deepEquals(getRemarks(),     that.getRemarks(),     mode);
             }
             case IGNORE_METADATA:
@@ -1194,10 +1209,10 @@
      * Finds the first non-null domain element.
      *
      * @param  <T>     type of domain element to get.
-     * @param  getter  {@link ObjectDomain} getter method to invoke.
+     * @param  getter  {@code ObjectDomain} getter method to invoke.
      * @return first non-null value, or {@code null} if none.
      */
-    private <T> T findFirst(final Function<ObjectDomain,T> getter) {
+    private <T> T findFirst(final Function<DefaultObjectDomain,T> getter) {
         if (domains == null) return null;
         return domains.stream().map(getter).filter(ImplementationHelper::nonNil).findFirst().orElse(null);
     }
@@ -1213,7 +1228,7 @@
     @Workaround(library = "JDK", version = "1.8")
     @XmlJavaTypeAdapter(EX_Extent.class)
     private Extent getDomainExtent() {
-        return findFirst(ObjectDomain::getDomainOfValidity);
+        return findFirst(DefaultObjectDomain::getDomainOfValidity);
     }
 
     /**
@@ -1226,7 +1241,7 @@
      */
     @XmlElement(name ="scope")
     private InternationalString getDomainScope() {
-        return findFirst(ObjectDomain::getScope);
+        return findFirst(DefaultObjectDomain::getScope);
     }
 
     /**
@@ -1234,7 +1249,7 @@
      */
     private void setDomainExtent(final Extent value) {
         InternationalString scope = null;
-        final DefaultObjectDomain domain = DefaultObjectDomain.castOrCopy(CollectionsExt.first(domains));
+        final DefaultObjectDomain domain = CollectionsExt.first(domains);
         if (domain != null) {
             if (domain.domainOfValidity != null) {
                 propertyAlreadySet("setDomain", "domainOfValidity");
@@ -1250,7 +1265,7 @@
      */
     private void setDomainScope(final InternationalString value) {
         Extent area = null;
-        final DefaultObjectDomain domain = DefaultObjectDomain.castOrCopy(CollectionsExt.first(domains));
+        final DefaultObjectDomain domain = CollectionsExt.first(domains);
         if (domain != null) {
             if (domain.scope != null) {
                 propertyAlreadySet("setDomainScope", "scope");
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractReferenceSystem.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractReferenceSystem.java
index 8cb50ff..baf4a29 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractReferenceSystem.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractReferenceSystem.java
@@ -22,8 +22,10 @@
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.ReferenceSystem;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.metadata.extent.Extent;
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -72,7 +74,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -80,11 +82,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -124,6 +126,33 @@
         return ReferenceSystem.class;
     }
 
+    /**
+     * Returns the region or timeframe in which this reference system is valid, or {@code null} if unspecified.
+     *
+     * @return area or region or timeframe in which this (coordinate) reference system is valid, or {@code null}.
+     *
+     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.4")
+    public Extent getDomainOfValidity() {
+        return Legacy.getDomainOfValidity(getDomains());
+    }
+
+    /**
+     * Returns the domain or limitations of usage, or {@code null} if unspecified.
+     *
+     * @return description of domain of usage, or limitations of usage, for which this
+     *         (coordinate) reference system object is valid, or {@code null}.
+     *
+     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.4")
+    public InternationalString getScope() {
+        return Legacy.getScope(getDomains());
+    }
+
 
 
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
index c9433e3..2c37be0 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
@@ -186,8 +186,8 @@
      * {@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY} and
      * {@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY} keys.
      * Subclasses may add other entries like
-     * {@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY} and
-     * {@value org.opengis.referencing.ObjectDomain#SCOPE_KEY} keys.
+     * {@value org.opengis.referencing.ReferenceSystem#DOMAIN_OF_VALIDITY_KEY} and
+     * {@value org.opengis.referencing.ReferenceSystem#SCOPE_KEY} keys.
      *
      * <p>See <cite>Notes for subclass implementers</cite> in class javadoc for usage conditions.</p>
      *
@@ -207,7 +207,7 @@
 
     /**
      * The codespace as a {@code NameSpace} object, or {@code null} if not yet created.
-     * This object is built from the {@value org.opengis.metadata.Identifier#CODESPACE_KEY} value when first needed.
+     * This object is built from the "codespace" value when first needed.
      */
     private transient NameSpace namespace;
 
@@ -404,7 +404,7 @@
      * @return the string specified by the last call to {@code setCodeSpace(…)}, or {@code null} if none.
      */
     private String getCodeSpace() {
-        return (String) properties.get(Identifier.CODESPACE_KEY);
+        return (String) properties.get(ReferenceIdentifier.CODESPACE_KEY);
     }
 
     /**
@@ -436,7 +436,7 @@
      * @see ImmutableIdentifier#getCodeSpace()
      */
     public B setCodeSpace(final Citation authority, final String codespace) {
-        if (!setProperty(Identifier.CODESPACE_KEY, codespace)) {
+        if (!setProperty(ReferenceIdentifier.CODESPACE_KEY, codespace)) {
             namespace = null;
         }
         setProperty(Identifier.AUTHORITY_KEY, authority);
@@ -450,7 +450,7 @@
      * @return the value specified by the last call to {@code setVersion(…)}, or {@code null} if none.
      */
     private String getVersion() {
-        return (String) properties.get(Identifier.VERSION_KEY);
+        return (String) properties.get(ReferenceIdentifier.VERSION_KEY);
     }
 
     /**
@@ -471,7 +471,7 @@
      *         once since builder construction or since the last call to a {@code createXXX(…)} method.
      */
     public B setVersion(final String version) {
-        setProperty(Identifier.VERSION_KEY, version);
+        setProperty(ReferenceIdentifier.VERSION_KEY, version);
         return self();
     }
 
@@ -910,7 +910,7 @@
      * or {@code null} if none.
      */
     private InternationalString getDescription() {
-        return (InternationalString) properties.get(Identifier.DESCRIPTION_KEY);
+        return (InternationalString) properties.get("description");
     }
 
     /**
@@ -944,7 +944,7 @@
          * Convert to InternationalString now in order to share the same instance if
          * the same description is used both for an Identifier and an IdentifiedObject.
          */
-        properties.put(Identifier.DESCRIPTION_KEY, Types.toInternationalString(description));
+        properties.put("description", Types.toInternationalString(description));
         return self();
     }
 
@@ -1038,7 +1038,7 @@
         if (cleanup) {
             properties .put(IdentifiedObject.NAME_KEY, null);
             properties .remove(IdentifiedObject.REMARKS_KEY);
-            properties .remove(Identifier.DESCRIPTION_KEY);
+            properties .remove("description");
             properties .remove(AbstractIdentifiedObject.DEPRECATED_KEY);
             aliases    .clear();
             identifiers.clear();
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index bba206a..81630da 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -93,14 +93,9 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.geometry.Geometry;
-import org.opengis.referencing.ObjectDomain;
-import org.opengis.referencing.crs.DerivedCRS;
-import org.opengis.referencing.datum.DynamicReferenceFrame;
-import org.opengis.metadata.extent.BoundingPolygon;
-import org.opengis.metadata.extent.GeographicExtent;
-import org.opengis.coordinate.CoordinateMetadata;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.referencing.DynamicReferenceFrame;
+import org.apache.sis.coordinate.DefaultCoordinateMetadata;
 
 
 /**
@@ -482,7 +477,7 @@
      * Suggests a coordinate reference system which could be a common target for coordinate operations having the
      * given sources. This method compares the {@linkplain #getGeographicBoundingBox(CoordinateReferenceSystem)
      * domain of validity} of all given CRSs. If a CRS has a domain of validity that contains the domain of all other
-     * CRS, then that CRS is returned. Otherwise this method verifies if a {@linkplain DerivedCRS#getBaseCRS()
+     * CRS, then that CRS is returned. Otherwise this method verifies if a {@linkplain GeneralDerivedCRS#getBaseCRS()
      * base CRS} (usually a {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS} instance)
      * would be suitable. If no suitable CRS is found, then this method returns {@code null}.
      *
@@ -641,8 +636,8 @@
      *
      * @since 1.5
      */
-    public static CoordinateOperation findOperation(final CoordinateMetadata source,
-                                                    final CoordinateMetadata target,
+    public static CoordinateOperation findOperation(final DefaultCoordinateMetadata source,
+                                                    final DefaultCoordinateMetadata target,
                                                     final GeographicBoundingBox areaOfInterest)
             throws FactoryException
     {
@@ -864,16 +859,6 @@
      * If non-null, then the returned envelope will use the same coordinate reference system than the given CRS
      * argument.
      *
-     * <p>This method looks in two places:</p>
-     * <ol>
-     *   <li>First, it checks the {@linkplain DefaultObjectDomain#getDomainOfValidity() domain of validity}
-     *       associated with the given CRS. Only geographic extents that are instances of
-     *       {@link BoundingPolygon} associated to the given CRS are taken in account for this first step.</li>
-     *   <li>If the above step did not found found any bounding polygon, then the
-     *       {@linkplain #getGeographicBoundingBox(CoordinateReferenceSystem) geographic bounding boxes}
-     *       are used as a fallback and transformed to the given CRS.</li>
-     * </ol>
-     *
      * @param  crs  the coordinate reference system, or {@code null}.
      * @return the envelope with coordinates in the given CRS, or {@code null} if none.
      *
@@ -886,39 +871,7 @@
     public static Envelope getDomainOfValidity(final CoordinateReferenceSystem crs) {
         Envelope envelope = null;
         GeneralEnvelope merged = null;
-        if (crs != null) {
-            for (final ObjectDomain domain : crs.getDomains()) {
-                final Extent domainOfValidity = domain.getDomainOfValidity();
-                if (domainOfValidity != null) {
-                    for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) {
-                        if (extent instanceof BoundingPolygon && !Boolean.FALSE.equals(extent.getInclusion())) {
-                            for (final Geometry geometry : ((BoundingPolygon) extent).getPolygons()) {
-                                final Envelope candidate = geometry.getEnvelope();
-                                if (candidate != null) {
-                                    final CoordinateReferenceSystem sourceCRS = candidate.getCoordinateReferenceSystem();
-                                    if (sourceCRS == null || Utilities.equalsIgnoreMetadata(sourceCRS, crs)) {
-                                        if (envelope == null) {
-                                            envelope = candidate;
-                                        } else {
-                                            if (merged == null) {
-                                                envelope = merged = new GeneralEnvelope(envelope);
-                                            }
-                                            merged.add(envelope);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        /*
-         * If no envelope was found, uses the geographic bounding box as a fallback. We will
-         * need to transform it from WGS84 to the supplied CRS. This step was not required in
-         * the previous block because the latter selected only envelopes in the right CRS.
-         */
-        if (envelope == null) {
+        /* if (envelope == null) */ {   // Condition needed on other branches but not on trunk.
             final GeographicBoundingBox bounds = getGeographicBoundingBox(crs);
             if (bounds != null && !Boolean.FALSE.equals(bounds.getInclusion())) {
                 /*
@@ -953,7 +906,7 @@
 
     /**
      * Returns the epoch to which the coordinates of stations defining the dynamic CRS are referenced.
-     * If the CRS is associated to a {@linkplain DynamicReferenceFrame dynamic datum}, then the epoch
+     * If the CRS is associated to a dynamic datum, then the epoch
      * of that datum is returned. Otherwise if the CRS is {@linkplain CompoundCRS compound}, then this
      * method requires that all dynamic components have the same epoch.
      *
@@ -971,7 +924,7 @@
                 epoch = ((DynamicReferenceFrame) datum).getFrameReferenceEpoch();
             }
         } else if (crs instanceof CompoundCRS) {
-            for (SingleCRS component : ((CompoundCRS) crs).getSingleComponents()) {
+            for (SingleCRS component : getSingleComponents(crs)) {
                 final Datum datum = component.getDatum();
                 if (datum instanceof DynamicReferenceFrame) {
                     final Temporal t = ((DynamicReferenceFrame) datum).getFrameReferenceEpoch();
@@ -995,7 +948,7 @@
      *
      * <h4>Ellipsoidal height</h4>
      * If a two-dimensional geographic or projected CRS is followed or preceded by a vertical CRS with ellipsoidal
-     * {@linkplain org.apache.sis.referencing.datum.DefaultVerticalDatum#getRealizationMethod() realization method},
+     * {@linkplain org.apache.sis.referencing.datum.DefaultVerticalDatum#getVerticalDatumType() datum type},
      * this method combines them in a single three-dimensional geographic or projected CRS.  Note that standalone
      * ellipsoidal heights are not allowed according ISO 19111. But if such situation is nevertheless found, then
      * the action described here fixes the issue. This is the reverse of <code>{@linkplain #getVerticalComponent
@@ -1469,7 +1422,13 @@
         if (crs == null) {
             singles = List.of();
         } else if (crs instanceof CompoundCRS) {
-            singles = ((CompoundCRS) crs).getSingleComponents();
+            if (crs instanceof DefaultCompoundCRS) {
+                singles = ((DefaultCompoundCRS) crs).getSingleComponents();
+            } else {
+                final List<CoordinateReferenceSystem> elements = ((CompoundCRS) crs).getComponents();
+                singles = new ArrayList<>(elements.size());
+                ReferencingUtilities.getSingleComponents(elements, singles);
+            }
         } else {
             // Intentional CassCastException here if the crs is not a SingleCRS.
             singles = List.of((SingleCRS) crs);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index f9f1937..62c37c6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -86,8 +86,9 @@
 import org.apache.sis.measure.Units;
 import static org.apache.sis.util.privy.Constants.SECONDS_PER_DAY;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import org.opengis.referencing.crs.GeocentricCRS;
+import org.opengis.referencing.datum.VerticalDatumType;
 
 
 /**
@@ -378,14 +379,14 @@
      *
      * @see #geocentric()
      */
-    private transient volatile GeodeticCRS cachedGeocentric;
+    private transient volatile GeocentricCRS cachedGeocentric;
 
     /**
      * The geocentric CRS using spherical coordinate system, created when first needed.
      *
      * @see #spherical()
      */
-    private transient volatile GeodeticCRS cachedSpherical;
+    private transient volatile GeocentricCRS cachedSpherical;
 
     /**
      * The Universal Transverse Mercator (UTM) or Universal Polar Stereographic (UPS) projections,
@@ -742,19 +743,23 @@
      *   <tr><td>WGS 84</td>                   <td>{@link #WGS84}</td>  <td>4978</td></tr>
      * </table></blockquote>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface
+     * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div>
+     *
      * @return the geocentric CRS associated to this enum.
      *
      * @see CRS#forCode(String)
      * @see DefaultGeocentricCRS
      */
-    public GeodeticCRS geocentric() {
-        GeodeticCRS object = cachedGeocentric;
+    public GeocentricCRS geocentric() {
+        GeocentricCRS object = cachedGeocentric;
         if (object == null) {
             if (geocentric != 0) {
                 final GeodeticAuthorityFactory factory = factory();
                 if (factory != null) try {
                     // Synchronization provided by the cache of the factory.
-                    cachedGeocentric = object = factory.createGeodeticCRS(String.valueOf(geocentric));
+                    cachedGeocentric = object = factory.createGeocentricCRS(String.valueOf(geocentric));
                     return object;
                 } catch (FactoryException e) {
                     failure(this, "geocentric", e, geocentric);
@@ -794,14 +799,18 @@
      *   <li>Geocentric radius in metres oriented toward {@linkplain AxisDirection#UP up}.</li>
      * </ol>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface
+     * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div>
+     *
      * @return the geocentric CRS associated to this enum.
      *
      * @see DefaultGeocentricCRS
      *
      * @since 0.7
      */
-    public GeodeticCRS spherical() {
-        GeodeticCRS object = cachedSpherical;
+    public GeocentricCRS spherical() {
+        GeocentricCRS object = cachedSpherical;
         if (object == null) {
             /*
              * All constants defined in this enumeration use the same coordinate system, EPSG:6404.
@@ -1249,8 +1258,6 @@
          *   <tr><th>Direction:</th>            <td>{@link AxisDirection#UP}</td></tr>
          *   <tr><th>Unit:</th>                 <td>{@link Units#METRE}</td></tr>
          * </table></blockquote>
-         *
-         * @see RealizationMethod#TIDAL
          */
         MEAN_SEA_LEVEL(true, (short) 5714, (short) 5100),
 
@@ -1264,8 +1271,6 @@
          *   <tr><th>Direction:</th>            <td>{@link AxisDirection#DOWN}</td></tr>
          *   <tr><th>Unit:</th>                 <td>{@link Units#METRE}</td></tr>
          * </table></blockquote>
-         *
-         * @see RealizationMethod#TIDAL
          */
         DEPTH(true, (short) 5715, (short) 5100),
 
@@ -1334,7 +1339,7 @@
          * Creates a new enumeration value of the given name.
          *
          * <h4>API design note</h4>
-         * This constructor does not expect {@link RealizationMethod} constant in order to avoid
+         * This constructor does not expect {@link VerticalDatumType} constant in order to avoid
          * the creation of non-standard code list value before they are actually needed.
          */
         private Vertical(final boolean isEPSG, final short crs, final short datum) {
@@ -1474,11 +1479,7 @@
                              * ELLIPSOIDAL. The way to construct the ellipsoidal pseudo-method shall be equivalent
                              * to a call to `VerticalDatumTypes.ellipsoidal()`.
                              */
-                            RealizationMethod method = null;
-                            if (this != OTHER_SURFACE) {
-                                method = RealizationMethod.valueOf(name());
-                            }
-                            object = new DefaultVerticalDatum(properties(datum), method);
+                            object = new DefaultVerticalDatum(properties(datum), VerticalDatumType.valueOf(name()));
                         }
                         cached = object;
                     }
@@ -1912,7 +1913,7 @@
          */
         GEODISPLAY(new DefaultEngineeringDatum(Map.of(
                 EngineeringDatum.NAME_KEY, "Computer display",
-                EngineeringDatum.ANCHOR_DEFINITION_KEY, "Origin is in upper left."))),
+                EngineeringDatum.ANCHOR_POINT_KEY, "Origin is in upper left."))),
 
         /**
          * Cartesian coordinate system with (right, down) oriented axes in pixel units.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java
index 3241d94..7e8a78c 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java
@@ -32,9 +32,6 @@
 import org.apache.sis.xml.NilReason;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-
 
 /**
  * Scope and domain of validity of a CRS-related object.
@@ -44,6 +41,14 @@
  * object implementing the {@link NilObject} interface with {@link NilReason#UNKNOWN}.
  * The use of <i>"not known"</i> text is an ISO 19111 recommendation.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19111 international standard published in 2019,
+ * while GeoAPI 3.0 is based on the version published in 2007. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code ObjectDomain} interface.
+ * </div>
+ *
  * <h2>Immutability and thread safety</h2>
  * This class is immutable and thus thread-safe if the property values
  * given to the constructor are also immutable.
@@ -52,7 +57,7 @@
  * @version 1.4
  * @since   1.4
  */
-public class DefaultObjectDomain extends FormattableObject implements ObjectDomain, LenientComparable, Serializable {
+public class DefaultObjectDomain extends FormattableObject implements LenientComparable, Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -163,44 +168,12 @@
     }
 
     /**
-     * Creates a new domain with the same values as the specified one.
-     * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
-     * or a user-defined one (as a subclass), usually in order to leverage some implementation-specific API.
-     *
-     * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
-     *
-     * @param  domain  the domain to copy.
-     *
-     * @see #castOrCopy(ObjectDomain)
-     */
-    public DefaultObjectDomain(final ObjectDomain domain) {
-        scope = domain.getScope();
-        domainOfValidity = domain.getDomainOfValidity();
-    }
-
-    /**
-     * Returns a SIS datum implementation with the same values as the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultObjectDomain castOrCopy(final ObjectDomain object) {
-        return (object == null) || (object instanceof DefaultObjectDomain)
-                ? (DefaultObjectDomain) object : new DefaultObjectDomain(object);
-    }
-
-    /**
      * Returns a description of usage, or limitations of usage, for which this object is valid.
      * If no scope was specified to the constructor, then this method returns <i>"not known"</i>
      * in an instance implementing the {@link NilObject} interface with {@link NilReason#UNKNOWN}.
      *
      * @return the domain of usage.
      */
-    @Override
     public InternationalString getScope() {
         return (scope != null) ? scope : UnknownScope.INSTANCE;
     }
@@ -212,7 +185,6 @@
      *
      * @return the area or time frame of usage.
      */
-    @Override
     public Extent getDomainOfValidity() {
         return (domainOfValidity != null) ? domainOfValidity : UnknownExtent.INSTANCE;
     }
@@ -255,8 +227,8 @@
                        Objects.equals(domainOfValidity, that.domainOfValidity);
             }
         } else {
-            if (object instanceof ObjectDomain) {
-                final var that = (ObjectDomain) object;
+            if (object instanceof DefaultObjectDomain) {
+                final var that = (DefaultObjectDomain) object;
                 return Utilities.deepEquals(getScope(), that.getScope(), mode) &&
                        Utilities.deepEquals(getDomainOfValidity(), that.getDomainOfValidity(), mode);
             }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
index fdcf1d4..dab6091 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
@@ -133,7 +133,6 @@
         final boolean ellipsoid  = type.isAssignableFrom(Ellipsoid    .class);
         final boolean datum      = type.isAssignableFrom(GeodeticDatum.class);
         final boolean geographic = type.isAssignableFrom(GeographicCRS.class);
-        @SuppressWarnings("deprecation")
         final boolean geocentric = type.isAssignableFrom(GeocentricCRS.class);
         final boolean projected  = type.isAssignableFrom(ProjectedCRS .class);
         final Set<String> codes = new LinkedHashSet<>();
@@ -266,7 +265,6 @@
      * Returns a coordinate reference system, datum or ellipsoid for the given EPSG code.
      */
     @Override
-    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws NoSuchAuthorityCodeException {
         return (IdentifiedObject) predefined(code, -1 & ~UNIT);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
index 1662fe8..a490213 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
@@ -409,7 +409,7 @@
         } catch (TransformException e) {
             throw new IllegalArgumentException(transformError(false), e);
         }
-        setStartGeographicPoint(p.getCoordinate(0), p.getCoordinate(1));
+        setStartGeographicPoint(p.getOrdinate(0), p.getOrdinate(1));
     }
 
     /**
@@ -476,7 +476,7 @@
         } catch (TransformException e) {
             throw new IllegalArgumentException(transformError(false), e);
         }
-        setEndGeographicPoint(p.getCoordinate(0), p.getCoordinate(1));
+        setEndGeographicPoint(p.getOrdinate(0), p.getOrdinate(1));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
index c8b2c0c..38b8410 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
@@ -59,8 +59,9 @@
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
 import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -96,14 +97,14 @@
      *       <td>{@link IdentifiedObject#getAlias()}</td></tr>
      *   <tr><td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
      *       <td>{@link IdentifiedObject#getIdentifiers()}</td></tr>
-     *   <tr><td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *       <td>{@link IdentifiedObject#getDomains()}</td></tr>
+     *   <tr><td>"domains"</td>
+     *       <td>{@link AbstractIdentifiedObject#getDomains()}</td></tr>
      *   <tr><td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *       <td>{@link IdentifiedObject#getRemarks()}</td></tr>
-     *   <tr><td>{@value org.opengis.referencing.ObjectDomain#SCOPE_KEY}</td>
-     *       <td>{@link ObjectDomain#getScope()}</td></tr>
-     *   <tr><td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
-     *       <td>{@link ObjectDomain#getDomainOfValidity()}</td></tr>
+     *   <tr><td>{@value org.opengis.referencing.ReferenceSystem#SCOPE_KEY}</td>
+     *       <td>{@link DefaultObjectDomain#getScope()}</td></tr>
+     *   <tr><td>{@value org.opengis.referencing.ReferenceSystem#DOMAIN_OF_VALIDITY_KEY}</td>
+     *       <td>{@link DefaultObjectDomain#getDomainOfValidity()}</td></tr>
      *   <tr><td>{@value org.opengis.referencing.operation.CoordinateOperation#OPERATION_VERSION_KEY}</td>
      *       <td>{@link CoordinateOperation#getOperationVersion()}</td></tr>
      *   <tr><td>{@value org.opengis.referencing.operation.CoordinateOperation#COORDINATE_OPERATION_ACCURACY_KEY}</td>
@@ -271,7 +272,7 @@
             if (authority instanceof IdentifierSpace<?>) {
                 cs = ((IdentifierSpace<?>) authority).getName();
             }
-            for (final Identifier identifier : nonNull(object.getIdentifiers())) {
+            for (final ReferenceIdentifier identifier : nonNull(object.getIdentifiers())) {
                 if (identifier != null) {                       // Paranoiac check.
                     if (cs != null && cs.equalsIgnoreCase(identifier.getCodeSpace())) {
                         return identifier;      // Match based on codespace.
@@ -455,7 +456,7 @@
     public static Optional<Extent> getDomainOfValidity(final IdentifiedObject object) {
         Extent domain = null;
         if (object != null) {
-            for (ObjectDomain obj : object.getDomains()) {
+            for (DefaultObjectDomain obj : Legacy.getDomains(object)) {
                 domain = Extents.intersection(domain, obj.getDomainOfValidity());
             }
         }
@@ -480,7 +481,7 @@
         if (object == null) {
             return Optional.empty();
         }
-        return Extents.getGeographicBoundingBox(object.getDomains().stream().map(ObjectDomain::getDomainOfValidity));
+        return Extents.getGeographicBoundingBox(Legacy.getDomains(object).stream().map(DefaultObjectDomain::getDomainOfValidity));
     }
 
     /**
@@ -544,7 +545,7 @@
          */
         final List<? extends IdentifiedObject> components;
         if (object instanceof CompoundCRS) {
-            components = ((CompoundCRS) object).getSingleComponents();
+            components = CRS.getSingleComponents((CompoundCRS) object);
         } else if (object instanceof ConcatenatedOperation) {
             final var cop = (ConcatenatedOperation) object;
             final List<? extends CoordinateOperation> steps = cop.getOperations();
@@ -836,11 +837,16 @@
         if (identifier == null) {
             return null;
         }
-        String cs = identifier.getCodeSpace();
+        String cs = null;
+        if (identifier instanceof ReferenceIdentifier) {
+            cs = ((ReferenceIdentifier) identifier).getCodeSpace();
+        }
         if (Strings.isNullOrEmpty(cs)) {
             cs = Identifiers.getIdentifier(identifier.getAuthority(), true);
         }
-        return NameMeaning.toURN(type, cs, identifier.getVersion(), identifier.getCode());
+        return NameMeaning.toURN(type, cs,
+                (identifier instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) identifier).getVersion() : null,
+                identifier.getCode());
     }
 
     /**
@@ -850,7 +856,7 @@
      * <ul>
      *   <li>If the given identifier implements the {@link GenericName} interface,
      *       then this method delegates to the {@link GenericName#toString()} method.</li>
-     *   <li>Otherwise if the given identifier has a {@linkplain Identifier#getCodeSpace() code space},
+     *   <li>Otherwise if the given identifier has a {@linkplain ReferenceIdentifier#getCodeSpace() code space},
      *       then formats the identifier as "{@code codespace:code}".</li>
      *   <li>Otherwise if the given identifier has an {@linkplain Identifier#getAuthority() authority},
      *       then formats the identifier as "{@code authority:code}".</li>
@@ -877,7 +883,10 @@
             return identifier.toString();
         }
         final String code = identifier.getCode();
-        String cs = identifier.getCodeSpace();
+        String cs = null;
+        if (identifier instanceof ReferenceIdentifier) {
+            cs = ((ReferenceIdentifier) identifier).getCodeSpace();
+        }
         if (Strings.isNullOrEmpty(cs)) {
             cs = Citations.toCodeSpace(identifier.getAuthority());
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java
index 18016e8..ea3a7d6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/ImmutableIdentifier.java
@@ -41,6 +41,9 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.DefaultIdentifier;
+
 
 /**
  * Immutable value uniquely identifying an object within a namespace, together with a version.
@@ -118,6 +121,12 @@
     private static final long serialVersionUID = 1804606250548055829L;
 
     /**
+     * Key for the {@value} property in the map to be given to the constructor.
+     * This can be used for setting the value to be returned by {@link #getDescription()}.
+     */
+    public static final String DESCRIPTION_KEY = "description";
+
+    /**
      * The person or party responsible for maintenance of the namespace, or {@code null} if not available.
      *
      * @see #getAuthority()
@@ -161,14 +170,18 @@
      *
      * @param identifier  the identifier to copy.
      *
-     * @see #castOrCopy(Identifier)
+     * @see #castOrCopy(ReferenceIdentifier)
      */
-    public ImmutableIdentifier(final Identifier identifier) {
+    public ImmutableIdentifier(final ReferenceIdentifier identifier) {
         code        = identifier.getCode();
         codeSpace   = identifier.getCodeSpace();
         authority   = identifier.getAuthority();
         version     = identifier.getVersion();
-        description = identifier.getDescription();
+        if (identifier instanceof DefaultIdentifier) {
+            description = ((DefaultIdentifier) identifier).getDescription();
+        } else {
+            description = null;
+        }
         validate(null);
     }
 
@@ -225,22 +238,22 @@
      *     <td>{@link #getCode()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#CODESPACE_KEY}</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getCodeSpace()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#AUTHORITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#AUTHORITY_KEY}</td>
      *     <td>{@link String} or {@link Citation}</td>
      *     <td>{@link #getAuthority()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#VERSION_KEY}</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getVersion()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>{@value #DESCRIPTION_KEY}</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -327,7 +340,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code ImmutableIdentifier}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code ImmutableIdentifier} instance is created using the
-     *       {@linkplain #ImmutableIdentifier(Identifier) copy constructor} and returned.
+     *       {@linkplain #ImmutableIdentifier(ReferenceIdentifier) copy constructor} and returned.
      *       Note that this is a <em>shallow</em> copy operation, because the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -336,7 +349,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static ImmutableIdentifier castOrCopy(final Identifier object) {
+    public static ImmutableIdentifier castOrCopy(final ReferenceIdentifier object) {
         if (object == null || object instanceof ImmutableIdentifier) {
             return (ImmutableIdentifier) object;
         }
@@ -412,7 +425,6 @@
      *
      * @since 0.5
      */
-    @Override
     public InternationalString getDescription() {
         return description;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NamedIdentifier.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NamedIdentifier.java
index a59869d..5f9460b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NamedIdentifier.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NamedIdentifier.java
@@ -94,7 +94,7 @@
  *
  * @since 0.4
  */
-public class NamedIdentifier extends ImmutableIdentifier implements GenericName, ReferenceIdentifier {
+public class NamedIdentifier extends ImmutableIdentifier implements GenericName {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -122,9 +122,9 @@
      *
      * @param  identifier  the identifier to copy.
      *
-     * @see #castOrCopy(Identifier)
+     * @see #castOrCopy(ReferenceIdentifier)
      */
-    public NamedIdentifier(final Identifier identifier) {
+    public NamedIdentifier(final ReferenceIdentifier identifier) {
         super(identifier);
         if (identifier instanceof GenericName) {
             name = (GenericName) identifier;
@@ -142,7 +142,7 @@
      * @see #castOrCopy(GenericName)
      */
     public NamedIdentifier(final GenericName name) {
-        super(name instanceof Identifier ? (Identifier) name : new NameToIdentifier(name));
+        super(name instanceof ReferenceIdentifier ? (ReferenceIdentifier) name : new NameToIdentifier(name));
         this.name = name;
         isNameSupplied = true;
     }
@@ -173,7 +173,7 @@
      *     <td>{@link #getCode()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+     *     <td>"codespace"</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getCodeSpace()}</td>
      *   </tr>
@@ -183,12 +183,12 @@
      *     <td>{@link #getAuthority()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+     *     <td>"version"</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getVersion()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -340,7 +340,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code NamedIdentifier}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code NamedIdentifier} instance is created using the
-     *       {@linkplain #NamedIdentifier(Identifier) copy constructor} and returned.
+     *       {@linkplain #NamedIdentifier(ReferenceIdentifier) copy constructor} and returned.
      *       Note that this is a <em>shallow</em> copy operation, because the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -351,7 +351,7 @@
      *
      * @since 1.0
      */
-    public static NamedIdentifier castOrCopy(final Identifier object) {
+    public static NamedIdentifier castOrCopy(final ReferenceIdentifier object) {
         if (object == null || object instanceof NamedIdentifier) {
             return (NamedIdentifier) object;
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Properties.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Properties.java
index 5a3d432..66f1979 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Properties.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Properties.java
@@ -36,8 +36,8 @@
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.Datum;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -64,10 +64,10 @@
         /*[ 0]*/ IdentifiedObject        .NAME_KEY,
         /*[ 1]*/ IdentifiedObject        .IDENTIFIERS_KEY,
         /*[ 2]*/ IdentifiedObject        .ALIAS_KEY,
-        /*[ 3]*/ IdentifiedObject        .DOMAINS_KEY,
+        /*[ 3]*/ AbstractIdentifiedObject.DOMAINS_KEY,
         /*[ 4]*/ IdentifiedObject        .REMARKS_KEY,
-        /*[ 5]*/ ObjectDomain            .SCOPE_KEY,
-        /*[ 6]*/ ObjectDomain            .DOMAIN_OF_VALIDITY_KEY,
+        /*[ 5]*/ CoordinateOperation     .SCOPE_KEY,                    // same in Datum and ReferenceSystem
+        /*[ 6]*/ CoordinateOperation     .DOMAIN_OF_VALIDITY_KEY,       // same in Datum and ReferenceSystem
         /*[ 7]*/ CoordinateOperation     .OPERATION_VERSION_KEY,
         /*[ 8]*/ CoordinateOperation     .COORDINATE_OPERATION_ACCURACY_KEY,
         /*[ 9]*/ OperationMethod         .FORMULA_KEY,
@@ -130,11 +130,11 @@
     final Object getAt(final int key) {
         if ((excludeMask & (1 << key)) == 0) {
             switch (key) {
-                case 0: return         object.getName();                                // NAME_KEY
-                case 1: return toArray(object.getIdentifiers(), ReferenceIdentifier[]::new);     // IDENTIFIERS_KEY
-                case 2: return toArray(object.getAlias(),      GenericName[]::new);     // ALIAS_KEY
-                case 3: return toArray(object.getDomains(),   ObjectDomain[]::new);     // DOMAINS_KEY
-                case 4: return         object.getRemarks();                             // REMARKS_KEY
+                case 0: return         object.getName();                                        // NAME_KEY
+                case 1: return toArray(object.getIdentifiers(),   ReferenceIdentifier[]::new);  // IDENTIFIERS_KEY
+                case 2: return toArray(object.getAlias(),         GenericName[]::new);          // ALIAS_KEY
+                case 3: return toArray(Legacy.getDomains(object), DefaultObjectDomain[]::new);  // DOMAINS_KEY
+                case 4: return         object.getRemarks();                                     // REMARKS_KEY
                 case 5: {   // SCOPE_KEY
                     if (object instanceof ReferenceSystem) {
                         return ((ReferenceSystem) object).getScope();
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/StandardDefinitions.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/StandardDefinitions.java
index 6af80ae..1f4c3aa 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/StandardDefinitions.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/StandardDefinitions.java
@@ -73,11 +73,9 @@
 import org.apache.sis.measure.Units;
 import static org.apache.sis.metadata.privy.ReferencingServices.AUTHALIC_RADIUS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.referencing.ObjectDomain.DOMAIN_OF_VALIDITY_KEY;
-
-// Specific to the geoapi-3.1 branch:
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import org.opengis.referencing.datum.VerticalDatumType;
+import static org.opengis.referencing.datum.Datum.DOMAIN_OF_VALIDITY_KEY;
 
 
 /**
@@ -350,7 +348,7 @@
             case 5103: name = "North American Vertical Datum 1988"; alias = "NAVD88"; break;
             default:   throw new AssertionError(code);
         }
-        return new DefaultVerticalDatum(properties(code, name, alias, true), (RealizationMethod) null);
+        return new DefaultVerticalDatum(properties(code, name, alias, true), VerticalDatumType.GEOIDAL);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index 2e03edc..1edcd58 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
@@ -46,8 +46,8 @@
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -154,7 +154,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -162,11 +162,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -216,7 +216,7 @@
      *
      * @see #createSameType(AbstractCS)
      */
-    AbstractCRS(final AbstractCRS original, final Identifier id, final AbstractCS cs) {
+    AbstractCRS(final AbstractCRS original, final ReferenceIdentifier id, final AbstractCS cs) {
         super(ReferencingUtilities.getPropertiesWithoutIdentifiers(original, (id == null) ? null : Map.of(IDENTIFIERS_KEY, id)));
         coordinateSystem = cs;
         forConvention = cs.hasSameAxes(original.coordinateSystem) ? original.forConvention : forConvention(original);
@@ -248,7 +248,8 @@
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
      *   <li>Otherwise if the given object is an instance of
      *       {@link org.opengis.referencing.crs.GeodeticCRS} (including the
-     *       {@link org.opengis.referencing.crs.GeographicCRS subtype}),
+     *       {@link org.opengis.referencing.crs.GeographicCRS} and
+     *       {@link org.opengis.referencing.crs.GeocentricCRS} subtypes),
      *       {@link org.opengis.referencing.crs.VerticalCRS},
      *       {@link org.opengis.referencing.crs.TemporalCRS},
      *       {@link org.opengis.referencing.crs.EngineeringCRS},
@@ -482,7 +483,6 @@
      *
      * <p>This method should be invoked for WKT 2 formatting only.</p>
      */
-    @SuppressWarnings("deprecation")
     static boolean isBaseCRS(final Formatter formatter) {
         return formatter.getEnclosingElement(1) instanceof GeneralDerivedCRS;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
index fb1ab0c..19f8409 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
@@ -47,8 +47,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -64,7 +64,6 @@
     DefaultDerivedCRS.class,
     DefaultProjectedCRS.class
 })
-@SuppressWarnings("deprecation")
 abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS implements GeneralDerivedCRS {
     /**
      * Serial number for inter-operability with different versions.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index 5350bd8..9fe9658 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -56,13 +56,6 @@
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.Convention;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Collection;
-import java.util.NoSuchElementException;
-import org.opengis.referencing.crs.ParametricCRS;
-import org.apache.sis.xml.NilObject;
-import org.apache.sis.metadata.privy.Identifiers;
-
 
 /**
  * A CRS describing the position of points through two or more independent coordinate reference systems.
@@ -153,7 +146,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -161,11 +154,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -362,7 +355,6 @@
      *
      * @see org.apache.sis.referencing.CRS#getSingleComponents(CoordinateReferenceSystem)
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public List<SingleCRS> getSingleComponents() {
         return singles;
@@ -381,57 +373,12 @@
      */
     private boolean setSingleComponents(final List<? extends CoordinateReferenceSystem> elements) {
         final List<SingleCRS> flattened = new ArrayList<>(elements.size());
-        final boolean identical = getSingleComponents(elements, flattened);
+        final boolean identical = ReferencingUtilities.getSingleComponents(elements, flattened);
         singles = UnmodifiableArrayList.wrap(flattened.toArray(SingleCRS[]::new));
         return identical;
     }
 
     /**
-     * Copies all {@link SingleCRS} components from the given source to the given collection.
-     * For each {@link CompoundCRS} element found in the iteration, this method replaces the
-     * {@code CompoundCRS} by its {@linkplain CompoundCRS#getComponents() components}, which
-     * may themselves have other {@code CompoundCRS}. Those replacements are performed recursively
-     * until we obtain a flat view of CRS components.
-     *
-     * @param  source  the collection of single or compound CRS.
-     * @param  addTo   where to add the single CRS in order to obtain a flat view of {@code source}.
-     * @return {@code true} if this method found only single CRS in {@code source}, in which case {@code addTo}
-     *         got the same content (assuming that {@code addTo} was empty prior this method call).
-     * @throws NoSuchElementException if a CRS component is missing.
-     * @throws ClassCastException if a CRS is neither a {@link SingleCRS} or a {@link CompoundCRS}.
-     *
-     * @see org.apache.sis.referencing.CRS#getSingleComponents(CoordinateReferenceSystem)
-     */
-    private static boolean getSingleComponents(final Iterable<? extends CoordinateReferenceSystem> source,
-            final Collection<? super SingleCRS> addTo) throws ClassCastException
-    {
-        boolean sameContent = true;
-        for (final CoordinateReferenceSystem candidate : source) {
-            if (candidate instanceof CompoundCRS) {
-                getSingleComponents(((CompoundCRS) candidate).getComponents(), addTo);
-                sameContent = false;
-            } else if (candidate instanceof SingleCRS) {
-                addTo.add((SingleCRS) candidate);
-            } else {
-                /*
-                 * Illegal class. Try to provide a better error message, in particular when the CRS component
-                 * is nil because it is an unresolved xlink in a GML document. Nil objects are proxies, which
-                 * have hard to understand class names.
-                 */
-                final String message;
-                if (candidate instanceof NilObject) {
-                    message = Errors.format(Errors.Keys.NilObject_1, Identifiers.getNilReason((NilObject) candidate));
-                    throw new NoSuchElementException(message);
-                } else {
-                    message = Errors.format(Errors.Keys.NestedElementNotAllowed_1, ReferencingUtilities.getInterface(candidate));
-                    throw new ClassCastException(message);
-                }
-            }
-        }
-        return sameContent;
-    }
-
-    /**
      * Computes the single CRS list on deserialization.
      *
      * @param  in  the input stream from which to deserialize a compound CRS.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index 1894051..7551b8a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@ -59,12 +59,10 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.collection.Containers;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.DatumEnsemble;
-import org.opengis.referencing.datum.ParametricDatum;
-import org.opengis.referencing.crs.ParametricCRS;
-import org.opengis.referencing.cs.ParametricCS;
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
 
 /**
@@ -136,8 +134,8 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -212,7 +210,7 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@code this.getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}</td>
      *     <td>{@link org.opengis.metadata.extent.Extent}</td>
      *     <td>{@code domain.getDomainOfValidity()}</td>
      *   </tr>
@@ -287,7 +285,7 @@
                 case WKTKeywords.GeodeticCRS:   return new Geodetic  (properties, (GeodeticCRS)   baseCRS, conversion,                derivedCS);
                 case WKTKeywords.VerticalCRS:   return new Vertical  (properties, (VerticalCRS)   baseCRS, conversion,   (VerticalCS) derivedCS);
                 case WKTKeywords.TimeCRS:       return new Temporal  (properties, (TemporalCRS)   baseCRS, conversion,       (TimeCS) derivedCS);
-                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, conversion, (ParametricCS) derivedCS);
+                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, conversion, (DefaultParametricCS) derivedCS);
                 case WKTKeywords.EngineeringCRS: {
                     /*
                      * This case may happen for baseCRS of kind GeodeticCRS, ProjectedCRS or EngineeringCRS.
@@ -341,7 +339,7 @@
                 case WKTKeywords.GeodeticCRS:   return new Geodetic  (properties, (GeodeticCRS)   baseCRS, interpolationCRS, method, baseToDerived,                derivedCS);
                 case WKTKeywords.VerticalCRS:   return new Vertical  (properties, (VerticalCRS)   baseCRS, interpolationCRS, method, baseToDerived,  (VerticalCS)  derivedCS);
                 case WKTKeywords.TimeCRS:       return new Temporal  (properties, (TemporalCRS)   baseCRS, interpolationCRS, method, baseToDerived,      (TimeCS)  derivedCS);
-                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, interpolationCRS, method, baseToDerived, (ParametricCS) derivedCS);
+                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, interpolationCRS, method, baseToDerived, (DefaultParametricCS) derivedCS);
                 case WKTKeywords.EngineeringCRS: {
                     if (baseCRS instanceof EngineeringCRS) {
                         // See the comment in create(Map, SingleCRS, Conversion, CoordinateSystem)
@@ -616,7 +614,7 @@
             return WKTKeywords.VerticalCRS;
         } else if (TemporalCRS.class.isAssignableFrom(type) && derivedCS instanceof TimeCS) {
             return WKTKeywords.TimeCRS;
-        } else if (ParametricCRS.class.isAssignableFrom(type) && derivedCS instanceof ParametricCS) {
+        } else if (ParametricCRS.class.isAssignableFrom(type) && derivedCS instanceof DefaultParametricCS) {
             return WKTKeywords.ParametricCRS;
         } else if (ProjectedCRS.class.isAssignableFrom(type) || EngineeringCRS.class.isAssignableFrom(type)) {
             return WKTKeywords.EngineeringCRS;
@@ -665,11 +663,6 @@
             return (GeodeticDatum) super.getDatum();
         }
 
-        /** Returns the datum ensemble of the base geodetic CRS. */
-        @Override public DatumEnsemble<GeodeticDatum> getDatumEnsemble() {
-            return ((GeodeticCRS) getBaseCRS()).getDatumEnsemble();
-        }
-
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
         @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
             return new Geodetic(this, derivedCS);
@@ -721,11 +714,6 @@
             return (VerticalDatum) super.getDatum();
         }
 
-        /** Returns the datum ensemble of the base vertical CRS. */
-        @Override public DatumEnsemble<VerticalDatum> getDatumEnsemble() {
-            return ((VerticalCRS) getBaseCRS()).getDatumEnsemble();
-        }
-
         /** Returns the coordinate system given at construction time. */
         @Override public VerticalCS getCoordinateSystem() {
             return (VerticalCS) super.getCoordinateSystem();
@@ -782,11 +770,6 @@
             return (TemporalDatum) super.getDatum();
         }
 
-        /** Returns the datum ensemble of the base temporal CRS. */
-        @Override public DatumEnsemble<TemporalDatum> getDatumEnsemble() {
-            return ((TemporalCRS) getBaseCRS()).getDatumEnsemble();
-        }
-
         /** Returns the coordinate system given at construction time. */
         @Override public TimeCS getCoordinateSystem() {
             return (TimeCS) super.getCoordinateSystem();
@@ -827,30 +810,25 @@
         }
 
         /** Creates a new parametric CRS from the given properties. */
-        Parametric(Map<String,?> properties, ParametricCRS baseCRS, Conversion conversion, ParametricCS derivedCS) {
+        Parametric(Map<String,?> properties, ParametricCRS baseCRS, Conversion conversion, DefaultParametricCS derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
         }
 
         /** Creates a new parametric CRS from the given properties. */
         Parametric(Map<String,?> properties, ParametricCRS baseCRS, CoordinateReferenceSystem interpolationCRS,
-                OperationMethod method, MathTransform baseToDerived, ParametricCS derivedCS)
+                OperationMethod method, MathTransform baseToDerived, DefaultParametricCS derivedCS)
         {
             super(properties, baseCRS, interpolationCRS, method, baseToDerived, derivedCS);
         }
 
         /** Returns the datum of the base parametric CRS. */
-        @Override public ParametricDatum getDatum() {
-            return (ParametricDatum) super.getDatum();
-        }
-
-        /** Returns the datum ensemble of the base parametric CRS. */
-        @Override public DatumEnsemble<ParametricDatum> getDatumEnsemble() {
-            return ((ParametricCRS) getBaseCRS()).getDatumEnsemble();
+        @Override public DefaultParametricDatum getDatum() {
+            return (DefaultParametricDatum) super.getDatum();
         }
 
         /** Returns the coordinate system given at construction time. */
-        @Override public ParametricCS getCoordinateSystem() {
-            return (ParametricCS) super.getCoordinateSystem();
+        @Override public DefaultParametricCS getCoordinateSystem() {
+            return (DefaultParametricCS) super.getCoordinateSystem();
         }
 
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
@@ -907,11 +885,6 @@
             return (EngineeringDatum) super.getDatum();
         }
 
-        /** Returns the datum ensemble of the base engineering CRS. */
-        @Override public DatumEnsemble<EngineeringDatum> getDatumEnsemble() {
-            return ((EngineeringCRS) getBaseCRS()).getDatumEnsemble();
-        }
-
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
         @Override AbstractCRS createSameType(final AbstractCS derivedCS) {
             return new Engineering(this, derivedCS);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
index 940daa1..bb2cc27 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
@@ -110,7 +110,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -118,11 +118,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -348,7 +348,6 @@
      * The types for which a specialized method exists.
      * Not including {@link CartesianCS}, because this case is already covered by {@link AffineCS}.
      */
-    @SuppressWarnings("deprecation")
     private static final Class<?>[] SPECIALIZED_TYPES = {
         AffineCS.class, SphericalCS.class, CylindricalCS.class, PolarCS.class, LinearCS.class, UserDefinedCS.class
     };
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
index 6e3c3cb..96abe6b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
@@ -55,8 +55,8 @@
  *   <li>Create a {@code GeodeticCRS} from one of the static convenience shortcuts listed in
  *       {@link org.apache.sis.referencing.CommonCRS#geocentric()}.</li>
  *   <li>Create a {@code GeodeticCRS} from an identifier in a database by invoking
- *       {@link org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeodeticCRS(String)}.</li>
- *   <li>Create a {@code GeodeticCRS} by invoking the {@code CRSFactory.createGeodeticCRS(…)} method
+ *       {@link org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeocentricCRS(String)}.</li>
+ *   <li>Create a {@code GeodeticCRS} by invoking the {@code CRSFactory.createGeocentricCRS(…)} method
  *       (implemented for example by {@link org.apache.sis.referencing.factory.GeodeticObjectFactory}).</li>
  *   <li>Create a {@code GeodeticCRS} by invoking the
  *       {@linkplain #DefaultGeocentricCRS(Map, GeodeticDatum, CartesianCS) constructor}.</li>
@@ -76,12 +76,11 @@
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 1.5
  *
- * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeodeticCRS(String)
+ * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeocentricCRS(String)
  *
  * @since 0.4
  */
 @XmlTransient
-@SuppressWarnings("deprecation")
 public class DefaultGeocentricCRS extends DefaultGeodeticCRS implements GeocentricCRS {
     /**
      * Serial number for inter-operability with different versions.
@@ -102,7 +101,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -110,11 +109,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -127,7 +126,7 @@
      * @param  datum       the datum.
      * @param  cs          the coordinate system, which must be three-dimensional.
      *
-     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeodeticCRS(Map, GeodeticDatum, CartesianCS)
+     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeocentricCRS(Map, GeodeticDatum, CartesianCS)
      */
     public DefaultGeocentricCRS(final Map<String,?> properties,
                                 final GeodeticDatum datum,
@@ -146,7 +145,7 @@
      * @param  datum       the datum.
      * @param  cs          the coordinate system.
      *
-     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeodeticCRS(Map, GeodeticDatum, SphericalCS)
+     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createGeocentricCRS(Map, GeodeticDatum, SphericalCS)
      */
     public DefaultGeocentricCRS(final Map<String,?> properties,
                                 final GeodeticDatum datum,
@@ -208,6 +207,11 @@
      * Returns the GeoAPI interface implemented by this class.
      * The SIS implementation returns {@code GeocentricCRS.class}.
      *
+     * <h4>Note for implementers</h4>
+     * Subclasses usually do not need to override this method since GeoAPI does not define {@code GeocentricCRS}
+     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
+     * own set of interfaces.
+     *
      * @return {@code GeocentricCRS.class} or a user-defined sub-interface.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
index 24d1bed..ae28b16 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
@@ -46,6 +46,9 @@
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.measure.Units;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * A 2- or 3-dimensional coordinate reference system based on a geodetic reference frame.
@@ -109,7 +112,7 @@
      * Creates a new CRS derived from the specified one, but with different axis order or unit.
      * This is for implementing the {@link #createSameType(AbstractCS)} method only.
      */
-    DefaultGeodeticCRS(final DefaultGeodeticCRS original, final Identifier id, final AbstractCS cs) {
+    DefaultGeodeticCRS(final DefaultGeodeticCRS original, final ReferenceIdentifier id, final AbstractCS cs) {
         super(original, id, cs);
         datum = original.datum;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
index 6e0fdf0..98c88f6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
@@ -38,8 +38,8 @@
 import static org.apache.sis.util.privy.Constants.CRS83;
 import static org.apache.sis.util.privy.Constants.CRS84;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -122,7 +122,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -130,11 +130,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -161,7 +161,7 @@
      * Creates a new CRS derived from the specified one, but with different axis order or unit.
      * This is for implementing the {@link #createSameType(AbstractCS)} method only.
      */
-    private DefaultGeographicCRS(final DefaultGeographicCRS original, final Identifier id, final AbstractCS cs) {
+    private DefaultGeographicCRS(final DefaultGeographicCRS original, final ReferenceIdentifier id, final AbstractCS cs) {
         super(original, id, cs);
     }
 
@@ -271,12 +271,12 @@
      */
     @Override
     final AbstractCRS createSameType(final AbstractCS cs) {
-        Identifier id = null;
+        ReferenceIdentifier id = null;
         final CoordinateSystemAxis axis = cs.getAxis(0);
         if (axis.getMinimumValue() == Longitude.MIN_VALUE &&
             axis.getMaximumValue() == Longitude.MAX_VALUE)    // For excluding the AxesConvention.POSITIVE_RANGE case.
         {
-            for (final Identifier identifier : super.getIdentifiers()) {
+            for (final ReferenceIdentifier identifier : super.getIdentifiers()) {
                 if (EPSG.equals(identifier.getCodeSpace())) try {
                     final int i = Arrays.binarySearch(EPSG_CODES, Short.parseShort(identifier.getCode()));
                     if (i >= 0) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
index 1a46661..540f967 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
@@ -101,7 +101,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -109,11 +109,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index b8f7596..b0da7ed 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@ -27,10 +27,9 @@
 import org.apache.sis.referencing.cs.AbstractCS;
 import org.apache.sis.io.wkt.Formatter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.cs.ParametricCS;
-import org.opengis.referencing.crs.ParametricCRS;
-import org.opengis.referencing.datum.ParametricDatum;
+// Specific to the main branch:
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
 
 /**
@@ -73,12 +72,11 @@
      * The datum.
      *
      * <p><b>Consider this field as final!</b>
-     * This field is modified only at unmarshalling time by {@link #setDatum(ParametricDatum)}</p>
+     * This field is modified only at unmarshalling time by {@code setDatum(ParametricDatum)}</p>
      *
      * @see #getDatum()
      */
-    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private ParametricDatum datum;
+    private DefaultParametricDatum datum;
 
     /**
      * Creates a coordinate reference system from the given properties, datum and coordinate system.
@@ -105,8 +103,8 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -115,15 +113,17 @@
      *   </tr>
      * </table>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter types may be changed to
+     * {@code org.opengis.referencing.datum.ParametricDatum} and {@code org.opengis.referencing.cs.ParametricCS}
+     * Those change are pending GeoAPI revision.</div>
+     *
      * @param  properties  the properties to be given to the coordinate reference system.
      * @param  datum       the datum.
      * @param  cs          the coordinate system.
-     *
-     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createParametricCRS(Map, ParametricDatum, ParametricCS)
      */
     public DefaultParametricCRS(final Map<String,?> properties,
-                                final ParametricDatum datum,
-                                final ParametricCS cs)
+                                final DefaultParametricDatum datum,
+                                final DefaultParametricCS cs)
     {
         super(properties, cs);
         this.datum = Objects.requireNonNull(datum);
@@ -146,54 +146,24 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  crs  the coordinate reference system to copy.
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is pending GeoAPI revision.</div>
      *
-     * @see #castOrCopy(ParametricCRS)
+     * @param  crs  the coordinate reference system to copy.
      */
-    protected DefaultParametricCRS(final ParametricCRS crs) {
+    protected DefaultParametricCRS(final DefaultParametricCRS crs) {
         super(crs);
         datum = crs.getDatum();
     }
 
     /**
-     * Returns a SIS coordinate reference system implementation with the same values as the given
-     * arbitrary implementation. If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricCRS castOrCopy(final ParametricCRS object) {
-        return (object == null || object instanceof DefaultParametricCRS)
-                ? (DefaultParametricCRS) object : new DefaultParametricCRS(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricCRS.class}.
-     *
-     * <h4>Note for implementers</h4>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ParametricCRS}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
-     * own set of interfaces.
-     *
-     * @return {@code ParametricCRS.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricCRS> getInterface() {
-        return ParametricCRS.class;
-    }
-
-    /**
      * Returns the datum.
      *
      * @return the datum.
      */
     @Override
     @XmlElement(name = "parametricDatum", required = true)
-    public ParametricDatum getDatum() {
+    public DefaultParametricDatum getDatum() {
         return datum;
     }
 
@@ -204,8 +174,8 @@
      */
     @Override
     @XmlElement(name = "parametricCS", required = true)
-    public ParametricCS getCoordinateSystem() {
-        return (ParametricCS) super.getCoordinateSystem();
+    public DefaultParametricCS getCoordinateSystem() {
+        return (DefaultParametricCS) super.getCoordinateSystem();
     }
 
     /**
@@ -284,7 +254,7 @@
      *
      * @see #getDatum()
      */
-    private void setDatum(final ParametricDatum value) {
+    private void setDatum(final DefaultParametricDatum value) {
         if (datum == null) {
             datum = value;
         } else {
@@ -297,7 +267,7 @@
      *
      * @see #getCoordinateSystem()
      */
-    private void setCoordinateSystem(final ParametricCS cs) {
+    private void setCoordinateSystem(final DefaultParametricCS cs) {
         setCoordinateSystem("parametricCS", cs);
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index df474db..c0946ff 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
@@ -44,8 +44,8 @@
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.operation.Projection;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -108,8 +108,8 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index 5381ff1..b9c9390 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -130,7 +130,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -138,11 +138,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
index e061c40..3442f71 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
@@ -93,7 +93,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -101,11 +101,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ParametricCRS.java
similarity index 76%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ParametricCRS.java
index 4307e9c..a1d7d8a 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ParametricCRS.java
@@ -14,14 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.referencing.crs;
+
+import org.opengis.referencing.crs.SingleCRS;
+
 
 /**
- * CQL parser.
+ * Place-holder for an interface not yet present in GeoAPI 3.0.
  *
- * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
+interface ParametricCRS extends SingleCRS {
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
index 3ef74dd..e6d1b97 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
@@ -163,7 +163,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -171,7 +171,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -199,7 +199,6 @@
      * @param  properties  properties given at construction time, or {@code null} if none.
      * @throws IllegalArgumentException if an axis has an illegal direction or an illegal unit of measurement.
      */
-    @SuppressWarnings("deprecation")
     void validate(final Map<String,?> properties) {
         for (int i=0; i<axes.length; i++) {
             final CoordinateSystemAxis axis = axes[i];
@@ -231,7 +230,7 @@
              * more than one time axis. Such case happen in meteorological models.
              */
             final AxisDirection dir = AxisDirections.absolute(direction);
-            if (dir != AxisDirection.UNSPECIFIED && dir != AxisDirection.OTHER) {
+            if (dir != AxisDirections.UNSPECIFIED && dir != AxisDirection.OTHER) {
                 for (int j=i; --j>=0;) {
                     final AxisDirection other = axes[j].getDirection();
                     final AxisDirection abs = AxisDirections.absolute(other);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java
index debcf1f..12c32d4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java
@@ -73,7 +73,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -81,7 +81,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java
index 29055ae..28e97a3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java
@@ -81,7 +81,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -89,7 +89,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
index 5ccec08..6f0cb81 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
@@ -80,7 +80,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -88,7 +88,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
index 67e697d..4c66e04 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
@@ -255,7 +255,7 @@
      *     <th colspan="3" class="hsep">Defined in parent class (reminder)</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -263,7 +263,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
index 1339cae..3878337 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
@@ -77,7 +77,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -85,7 +85,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
index 526bcf8..0695130 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
@@ -76,7 +76,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -84,7 +84,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java
index b5a5934..ad4077f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java
@@ -74,7 +74,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -82,7 +82,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java
index be8df74..9742a55 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java
@@ -21,9 +21,6 @@
 import jakarta.xml.bind.annotation.XmlType;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.cs.ParametricCS;
-
 
 /**
  * A 1-dimensional coordinate system for parametric values or functions.
@@ -55,7 +52,7 @@
  */
 @XmlType(name = "ParametricCSType")
 @XmlRootElement(name = "ParametricCS")
-public class DefaultParametricCS extends AbstractCS implements ParametricCS {
+public class DefaultParametricCS extends AbstractCS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -117,46 +114,16 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  original  the coordinate system to copy.
-     *
-     * @see #castOrCopy(ParametricCS)
      */
-    protected DefaultParametricCS(final ParametricCS original) {
+    protected DefaultParametricCS(final DefaultParametricCS original) {
         super(original);
     }
 
     /**
-     * Returns a SIS coordinate system implementation with the same values as the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricCS castOrCopy(final ParametricCS object) {
-        return (object == null) || (object instanceof DefaultParametricCS)
-                ? (DefaultParametricCS) object : new DefaultParametricCS(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricCS.class}.
-     *
-     * <h4>Note for implementers</h4>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ParametricCS}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with
-     * their own set of interfaces.
-     *
-     * @return {@code ParametricCS.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricCS> getInterface() {
-        return ParametricCS.class;
-    }
-
-    /**
      * {@inheritDoc}
      *
      * @return {@inheritDoc}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java
index 94ff57e..9889dd5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java
@@ -77,7 +77,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -85,7 +85,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java
index 86bbdae..d1f7ccb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java
@@ -81,7 +81,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -89,7 +89,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java
index 7c187e6..91d9b5f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java
@@ -26,9 +26,6 @@
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.cs.CoordinateDataType;
-
 
 /**
  * A 1-dimensional coordinate system for time elapsed in the specified time units from a specified time origin.
@@ -80,7 +77,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -88,7 +85,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -179,19 +176,6 @@
     }
 
     /**
-     * Returns the type (measure, integer or data-time) of coordinate values.
-     * The current implementation supports only {@link CoordinateDataType#MEASURE}.
-     *
-     * @return the type of coordinate values.
-     *
-     * @since 1.5
-     */
-    @Override
-    public CoordinateDataType getCoordinateType() {
-        return CoordinateDataType.MEASURE;
-    }
-
-    /**
      * {@inheritDoc}
      *
      * @return {@inheritDoc}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
index 525b514..9132237 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
@@ -73,7 +73,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -81,7 +81,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java
index 5a240bf..3edc1fb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java
@@ -88,7 +88,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -96,7 +96,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java
index 543439e..2454d31 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java
@@ -114,11 +114,11 @@
         // Get ordinal of last compass direction defined by GeoAPI. We will continue on the horizontal plane.
         int code = (AxisDirection.NORTH.ordinal() + (AxisDirections.COMPASS_COUNT - 1)) << SHIFT;
         for (final AxisDirection d : new AxisDirection[] {
-            AxisDirection.FORWARD,
-            AxisDirection.STARBOARD,
-            AxisDirection.COUNTER_CLOCKWISE,
-            AxisDirection.CLOCKWISE,
-            AxisDirection.AWAY_FROM
+            AxisDirections.FORWARD,
+            AxisDirections.STARBOARD,
+            AxisDirections.COUNTER_CLOCKWISE,
+            AxisDirections.CLOCKWISE,
+            AxisDirections.AWAY_FROM
         }) ORDER.put(d, ++code);
         // Set the time coordinate as the last coordinate in all cases.
         ORDER.put(AxisDirection.PAST,   (Integer.MAX_VALUE >>> 1) - 1);
@@ -332,7 +332,7 @@
                  * If we were not allowed to normalize the axis direction, we may have a
                  * left-handed coordinate system here. If so, make it right-handed.
                  */
-                if (newAxes[1].getDirection() == AxisDirection.CLOCKWISE && isLengthAndAngle(newAxes, 0)) {
+                if (newAxes[1].getDirection() == AxisDirections.CLOCKWISE && isLengthAndAngle(newAxes, 0)) {
                     ArraysExt.swap(newAxes, 0, 1);
                 }
             }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
index ed1c54c..08a6532 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
@@ -45,8 +45,12 @@
 import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.util.collection.Containers.property;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-4.0 branches:
+import org.apache.sis.referencing.internal.Legacy;
+
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.metadata.extent.Extent;
 
 
 /**
@@ -130,18 +134,18 @@
      *     <th>Value type</th>
      *     <th>Returned by</th>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorDefinition()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+     *     <td>{@code "anchorEpoch"}</td>
      *     <td>{@link Temporal}</td>
      *     <td>{@link #getAnchorEpoch()}</td>
      *   </tr><tr>
      *     <th colspan="3" class="hsep">Defined in parent class (reminder)</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -149,11 +153,11 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -164,14 +168,13 @@
      *
      * @param  properties  the properties to be given to the identified object.
      */
-    @SuppressWarnings("deprecation")
     public AbstractDatum(final Map<String,?> properties) {
         super(properties);
-        anchorDefinition = Types.toInternationalString(properties, ANCHOR_DEFINITION_KEY);
+        anchorDefinition = Types.toInternationalString(properties, "anchorDefinition");
         if (anchorDefinition == null) {
             anchorDefinition = Types.toInternationalString(properties, ANCHOR_POINT_KEY);
         }
-        anchorEpoch = property(properties, ANCHOR_EPOCH_KEY, Temporal.class);
+        anchorEpoch = property(properties, "anchorEpoch", Temporal.class);
         if (anchorEpoch == null) {
             Date date = property(properties, REALIZATION_EPOCH_KEY, Date.class);
             if (date != null) {
@@ -191,8 +194,11 @@
      */
     protected AbstractDatum(final Datum datum) {
         super(datum);
-        anchorEpoch = datum.getAnchorEpoch().orElse(null);
-        anchorDefinition = datum.getAnchorDefinition().orElse(null);
+        Date date = datum.getRealizationEpoch();
+        if (date != null) {
+            anchorEpoch = date.toInstant();
+        }
+        anchorDefinition = datum.getAnchorPoint();
     }
 
     /**
@@ -257,7 +263,6 @@
      *
      * @since 1.5
      */
-    @Override
     public Optional<InternationalString> getAnchorDefinition() {
         return Optional.ofNullable(anchorDefinition);
     }
@@ -288,7 +293,6 @@
      *
      * @since 1.5
      */
-    @Override
     public Optional<Temporal> getAnchorEpoch() {
         return Optional.ofNullable(anchorEpoch);
     }
@@ -305,7 +309,33 @@
     @XmlSchemaType(name = "date")
     @XmlElement(name = "realizationEpoch")
     public Date getRealizationEpoch() {
-        return Datum.super.getRealizationEpoch();
+        return getAnchorEpoch().map(Legacy::toDate).orElse(null);
+    }
+
+    /**
+     * Returns the region or timeframe in which this datum is valid, or {@code null} if unspecified.
+     *
+     * @return area or region or timeframe in which this datum is valid, or {@code null}.
+     *
+     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.4")
+    public Extent getDomainOfValidity() {
+        return Legacy.getDomainOfValidity(getDomains());
+    }
+
+    /**
+     * Returns the domain or limitations of usage, or {@code null} if unspecified.
+     *
+     * @return description of domain of usage, or limitations of usage, for which this datum object is valid.
+     *
+     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.4")
+    public InternationalString getScope() {
+        return Legacy.getScope(getDomains());
     }
 
     /**
@@ -385,8 +415,8 @@
             }
             case BY_CONTRACT: {
                 final Datum that = (Datum) object;
-                return deepEquals(getAnchorEpoch(),      that.getAnchorEpoch(), mode) &&
-                       deepEquals(getAnchorDefinition(), that.getAnchorDefinition(), mode);
+                return deepEquals(getRealizationEpoch(), that.getRealizationEpoch(), mode) &&
+                       deepEquals(getAnchorPoint(),      that.getAnchorPoint(),      mode);
             }
             default: {
                 /*
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/BursaWolfParameters.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/BursaWolfParameters.java
index af6adc7..759856a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/BursaWolfParameters.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/BursaWolfParameters.java
@@ -616,7 +616,7 @@
     @OptionalCandidate
     public Extent getDomainOfValidity() {
         if (domainOfValidity == null && targetDatum != null) {
-            return IdentifiedObjects.getDomainOfValidity(targetDatum).orElse(null);
+            return targetDatum.getDomainOfValidity();
         }
         return domainOfValidity;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEllipsoid.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEllipsoid.java
index f85e3f7..930e4c1 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEllipsoid.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEllipsoid.java
@@ -45,8 +45,8 @@
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -174,7 +174,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -182,7 +182,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
index 5e161c9..6cb7955 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
@@ -25,8 +25,8 @@
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -69,7 +69,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -77,22 +77,22 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorDefinition()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+     *     <td>{@code "anchorEpoch"}</td>
      *     <td>{@link java.time.temporal.Temporal}</td>
      *     <td>{@link #getAnchorEpoch()}</td>
      *   </tr>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index c2e72fb..d01ff32 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@ -54,8 +54,8 @@
 import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
 import static org.apache.sis.referencing.privy.WKTUtilities.toFormattable;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -214,7 +214,7 @@
      *     <th colspan="3" class="hsep">Defined in parent classes (reminder)</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -222,22 +222,22 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorDefinition()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+     *     <td>{@code "anchorEpoch"}</td>
      *     <td>{@link java.time.temporal.Temporal}</td>
      *     <td>{@link #getAnchorEpoch()}</td>
      *   </tr>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java
index 958903d..2c28035 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultImageDatum.java
@@ -33,8 +33,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.datum.ImageDatum;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -91,7 +91,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -99,22 +99,22 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorDefinition()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+     *     <td>{@code "anchorEpoch"}</td>
      *     <td>{@link java.time.temporal.Temporal}</td>
      *     <td>{@link #getAnchorEpoch()}</td>
      *   </tr>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
index 1037b5b..cc63fdb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
@@ -22,9 +22,6 @@
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.ParametricDatum;
-
 
 /**
  * Defines the origin of a parametric coordinate reference system.
@@ -35,7 +32,7 @@
  *
  * <ol>
  *   <li>Create a {@code ParametricDatum} from an identifier in a database by invoking
- *       {@link org.opengis.referencing.datum.DatumAuthorityFactory#createParametricDatum(String)}.</li>
+ *       {@code DatumAuthorityFactory.createParametricDatum(String)}.</li>
  *   <li>Create a {@code ParametricDatum} by invoking the {@code DatumFactory.createParametricDatum(…)} method,
  *       (implemented for example by {@link org.apache.sis.referencing.factory.GeodeticObjectFactory}).</li>
  *   <li>Create a {@code DefaultParametricDatum} by invoking the
@@ -58,7 +55,7 @@
  */
 @XmlType(name = "ParametricDatumType")
 @XmlRootElement(name = "ParametricDatum")
-public class DefaultParametricDatum extends AbstractDatum implements ParametricDatum {
+public class DefaultParametricDatum extends AbstractDatum {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -88,19 +85,19 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorDefinition()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+     *     <td>{@code "anchorEpoch"}</td>
      *     <td>{@link java.time.temporal.Temporal}</td>
      *     <td>{@link #getAnchorEpoch()}</td>
      *   </tr>
@@ -121,47 +118,17 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  datum  the datum to copy.
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
      *
-     * @see #castOrCopy(ParametricDatum)
+     * @param  datum  the datum to copy.
      */
-    protected DefaultParametricDatum(final ParametricDatum datum) {
+    protected DefaultParametricDatum(final DefaultParametricDatum datum) {
         super(datum);
     }
 
     /**
-     * Returns a SIS datum implementation with the same values as the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricDatum castOrCopy(final ParametricDatum object) {
-        return (object == null) || (object instanceof DefaultParametricDatum) ?
-                (DefaultParametricDatum) object : new DefaultParametricDatum(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricDatum.class}.
-     *
-     * <h4>Note for implementers</h4>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code TemporalDatum}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
-     * own set of interfaces.
-     *
-     * @return {@code ParametricDatum.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricDatum> getInterface() {
-        return ParametricDatum.class;
-    }
-
-    /**
-     * Formats this datum as a <i>Well Known Text</i> {@code ParametricDatum[…]} element.
+     * Formats this datum as a <cite>Well Known Text</cite> {@code ParametricDatum[…]} element.
      *
      * <h4>Compatibility note</h4>
      * {@code ParametricDatum} is defined in the WKT 2 specification only.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
index f60c234..8724a54 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
@@ -43,8 +43,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -129,7 +129,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -137,7 +137,7 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -328,7 +328,6 @@
      *
      * @see org.apache.sis.referencing.crs.AbstractCRS#isBaseCRS(Formatter)
      */
-    @SuppressWarnings("deprecation")
     private static boolean isElementOfBaseCRS(final Formatter formatter) {
         return formatter.getEnclosingElement(2) instanceof GeneralDerivedCRS;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
index f1e678b..5f95872 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
@@ -34,12 +34,12 @@
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.FormattableObject;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 // Specific to the main and geoapi-3.1 branches:
 import org.apache.sis.util.privy.TemporalDate;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
-
 
 /**
  * Defines the origin of a temporal coordinate reference system.
@@ -117,7 +117,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -125,22 +125,22 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorDefinition()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+     *     <td>{@code "anchorEpoch"}</td>
      *     <td>{@link java.time.temporal.Temporal}</td>
      *     <td>{@link #getAnchorEpoch()}</td>
      *   </tr>
@@ -149,7 +149,7 @@
      * @param  properties  the properties to be given to the identified object.
      * @param  origin      the date and time origin of this temporal datum.
      *
-     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalDatum(Map, Temporal)
+     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalDatum(Map, Date)
      *
      * @since 1.5
      */
@@ -159,6 +159,19 @@
     }
 
     /**
+     * Creates a temporal datum from the given properties.
+     *
+     * @param  properties  the properties to be given to the identified object.
+     * @param  origin      the date and time origin of this temporal datum.
+     *
+     * @deprecated Use {@link #DefaultTemporalDatum(Map, Temporal)} instead.
+     */
+    @Deprecated(since="1.5")
+    public DefaultTemporalDatum(final Map<String,?> properties, final Date origin) {
+        this(properties, TemporalDate.toTemporal(origin));
+    }
+
+    /**
      * Creates a new datum with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
      * or a user-defined one (as a subclass), usually in order to leverage some implementation-specific API.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index 7c3ccf4..fd46ba3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@ -35,10 +35,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.datum.VerticalDatumType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Optional;
-import org.opengis.referencing.datum.RealizationMethod;
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -61,7 +59,7 @@
  *   <li>Create a {@code VerticalDatum} by invoking the {@code DatumFactory.createVerticalDatum(…)} method
  *       (implemented for example by {@link org.apache.sis.referencing.factory.GeodeticObjectFactory}).</li>
  *   <li>Create a {@code DefaultVerticalDatum} by invoking the
- *       {@linkplain #DefaultVerticalDatum(Map, RealizationMethod) constructor}.</li>
+ *       {@linkplain #DefaultVerticalDatum(Map, VerticalDatumType) constructor}.</li>
  * </ol>
  *
  * <b>Example:</b> the following code gets a vertical datum for height above the geoid:
@@ -94,16 +92,12 @@
     private static final long serialVersionUID = 380347456670516572L;
 
     /**
-     * The realization method (geoid, tidal, <i>etc.</i>), or {@code null} if unspecified.
-     */
-    private RealizationMethod method;
-
-    /**
      * The type of this vertical datum.
+     * If {@code null}, a value will be inferred from the name by {@link #type()}.
      *
+     * @see #type()
      * @see #getVerticalDatumType()
      */
-    @SuppressWarnings("deprecation")
     private VerticalDatumType type;
 
     /**
@@ -119,7 +113,7 @@
      *     <th>Returned by</th>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
@@ -127,52 +121,35 @@
      *     <td>{@link #getAlias()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorDefinition()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+     *     <td>{@code "anchorEpoch"}</td>
      *     <td>{@link java.time.temporal.Temporal}</td>
      *     <td>{@link #getAnchorEpoch()}</td>
      *   </tr>
      * </table>
      *
      * @param  properties  the properties to be given to the identified object.
-     * @param  method      the realization method (geoid, tidal, <i>etc.</i>), or {@code null} if unspecified.
-     *
-     * @since 2.0 (temporary version number until this branch is released)
-     */
-    @SuppressWarnings("this-escape")
-    public DefaultVerticalDatum(final Map<String,?> properties, final RealizationMethod method) {
-        super(properties);
-        this.method = method;
-        type = VerticalDatumTypes.fromMethod(method);
-    }
-
-    /**
-     * Creates a vertical datum from the given properties.
-     *
-     * @param  properties  the properties to be given to the identified object.
      * @param  type        the type of this vertical datum.
      *
-     * @deprecated As of ISO 19111:2019, the {@code VerticalDatumType} argument is replaced by {@code RealizationMethod}.
+     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createVerticalDatum(Map, VerticalDatumType)
      */
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public DefaultVerticalDatum(final Map<String,?> properties, final VerticalDatumType type) {
         super(properties);
         this.type = Objects.requireNonNull(type);
-        method = VerticalDatumTypes.toMethod(type);
     }
 
     /**
@@ -186,10 +163,8 @@
      *
      * @see #castOrCopy(VerticalDatum)
      */
-    @SuppressWarnings("deprecation")
     protected DefaultVerticalDatum(final VerticalDatum datum) {
         super(datum);
-        method = datum.getRealizationMethod().orElse(null);
         type = datum.getVerticalDatumType();
     }
 
@@ -225,15 +200,25 @@
     }
 
     /**
-     * Returns the method through which this vertical reference frame is realized.
+     * Returns the type of this datum, or infers the type from the datum name if no type were specified.
+     * The latter case occurs after unmarshalling, since GML 3.2 does not contain any attribute for the datum type.
+     * It may also happen if the datum were created using reflection.
      *
-     * @return method through which this vertical reference frame is realized.
+     * <p>This method uses heuristic rules and may be changed in any future SIS version.</p>
      *
-     * @since 2.0 (temporary version number until this branch is released)
+     * <p>No synchronization needed; this is not a problem if this value is computed twice.
+     * This method returns only existing immutable instances.</p>
+     *
+     * @see #getVerticalDatumType()
+     * @see #getTypeElement()
      */
-    @Override
-    public Optional<RealizationMethod> getRealizationMethod() {
-        return Optional.ofNullable(method);
+    private VerticalDatumType type() {
+        VerticalDatumType t = type;
+        if (t == null) {
+            final ReferenceIdentifier name = super.getName();
+            type = t = VerticalDatumTypes.guess(name != null ? name.getCode() : null, super.getAlias(), null);
+        }
+        return t;
     }
 
     /**
@@ -246,13 +231,10 @@
      * but in a programmatic way more suitable to coordinate transformation engines.
      *
      * @return the type of this vertical datum.
-     *
-     * @deprecated As of ISO 19111:2019, the {@code VerticalDatumType} argument is replaced by {@code RealizationMethod}.
      */
     @Override
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public VerticalDatumType getVerticalDatumType() {
-        return type;
+        return type();
     }
 
     /**
@@ -265,7 +247,6 @@
      * @return {@code true} if both objects are equal.
      */
     @Override
-    @SuppressWarnings("deprecation")
     public boolean equals(final Object object, final ComparisonMode mode) {
         if (object == this) {
             return true;                                                    // Slight optimization.
@@ -276,16 +257,15 @@
         switch (mode) {
             case STRICT: {
                 final var other = (DefaultVerticalDatum) object;
-                return Objects.equals(method, other.method) && Objects.equals(type, other.type);
+                return type().equals(other.type());
             }
             case BY_CONTRACT: {
                 final var other = (VerticalDatum) object;
-                return Objects.equals(getRealizationMethod(), other.getRealizationMethod()) &&
-                       Objects.equals(getVerticalDatumType(), other.getVerticalDatumType());
+                return Objects.equals(getVerticalDatumType(), other.getVerticalDatumType());
             }
             default: {
                 /*
-                 * RealizationMethod is considered as metadata because it is related to the anchor definition,
+                 * VerticalDatumType is considered as metadata because it is related to the anchor definition,
                  * which is itself considered as metadata. Furthermore, GeodeticObjectParser and EPSGDataAccess
                  * do not always set this property to the same value, because of historical changes in the WKT.
                  */
@@ -303,7 +283,7 @@
      */
     @Override
     protected long computeHashCode() {
-        return super.computeHashCode() + 37 * Objects.hashCode(method);
+        return super.computeHashCode() + type().hashCode();
     }
 
     /**
@@ -313,7 +293,7 @@
      * OGC 01-009 defined numerical codes for various vertical datum types, for example 2005 for geoidal height.
      * Such codes were formatted for all {@code Datum} subtypes in WKT 1. Datum types became specified only for
      * vertical datum in the ISO 19111:2003 standard, then removed completely in the ISO 19111:2007 standard.
-     * They were reintroduced in a different form ({@link RealizationMethod}) in the ISO 19111:2019 standard.
+     * They were reintroduced in a different form ({@code RealizationMethod}) in the ISO 19111:2019 standard.
      *
      * @return {@code "VerticalDatum"} (WKT 2) or {@code "Vert_Datum"} (WKT 1).
      *
@@ -323,7 +303,7 @@
     protected String formatTo(final Formatter formatter) {
         super.formatTo(formatter);
         if (formatter.getConvention().majorVersion() == 1) {
-            formatter.append(VerticalDatumTypes.toLegacy(getVerticalDatumType()));
+            formatter.append(VerticalDatumTypes.toLegacy(type()));
             return WKTKeywords.Vert_Datum;
         }
         return formatter.shortOrLong(WKTKeywords.VDatum, WKTKeywords.VerticalDatum);
@@ -359,7 +339,6 @@
      *
      * @see <a href="http://issues.apache.org/jira/browse/SIS-160">SIS-160: Need XSLT between GML 3.1 and 3.2</a>
      */
-    @SuppressWarnings("deprecation")
     @XmlElement(name = "verticalDatumType")
     private VerticalDatumType getTypeElement() {
         return Context.isGMLVersion(Context.current(), LegacyNamespaces.VERSION_3_2) ? null : getVerticalDatumType();
@@ -368,11 +347,9 @@
     /**
      * Invoked by JAXB only. The vertical datum type is set only if it has not already been specified.
      */
-    @SuppressWarnings("deprecation")
-    private void setTypeElement(final VerticalDatumType value) {
+    private void setTypeElement(final VerticalDatumType t) {
         if (type == null) {
-            type = value;
-            method = VerticalDatumTypes.toMethod(value);
+            type = t;
         } else {
             ImplementationHelper.propertyAlreadySet(DefaultVerticalDatum.class, "setTypeElement", "verticalDatumType");
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
index dd8756e..d75513b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
@@ -178,9 +178,12 @@
      */
     static final AuthorityFactoryProxy<InternationalString> description(final Class<? extends IdentifiedObject> classe) {
         return new AuthorityFactoryProxy<InternationalString>(InternationalString.class, AuthorityFactoryIdentifier.ANY) {
-            @Override InternationalString createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
+            @Override InternationalString create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
                 return factory.getDescriptionText(classe, code).orElse(null);
             }
+            @Override InternationalString createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
+                return factory.getDescriptionText(code);
+            }
             @Override AuthorityFactoryProxy<InternationalString> specialize(String typeName) {
                 return this;
             }
@@ -190,7 +193,6 @@
     /**
      * The proxy for the {@link GeodeticAuthorityFactory#createObject(String)} method.
      */
-    @SuppressWarnings("removal")
     static final AuthorityFactoryProxy<IdentifiedObject> OBJECT =
         new AuthorityFactoryProxy<IdentifiedObject>(IdentifiedObject.class, AuthorityFactoryIdentifier.ANY) {
             @Override IdentifiedObject createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
@@ -229,16 +231,6 @@
             }
     };
 
-    static final AuthorityFactoryProxy<ParametricDatum> PARAMETRIC_DATUM =
-        new AuthorityFactoryProxy<ParametricDatum>(ParametricDatum.class, AuthorityFactoryIdentifier.DATUM) {
-            @Override ParametricDatum create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
-                return factory.createParametricDatum(code);
-            }
-            @Override ParametricDatum createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
-                return datumFactory(factory).createParametricDatum(code);
-            }
-    };
-
     static final AuthorityFactoryProxy<VerticalDatum> VERTICAL_DATUM =
         new AuthorityFactoryProxy<VerticalDatum>(VerticalDatum.class, AuthorityFactoryIdentifier.DATUM) {
             @Override VerticalDatum create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
@@ -379,16 +371,6 @@
             }
     };
 
-    static final AuthorityFactoryProxy<ParametricCS> PARAMETRIC_CS =
-        new AuthorityFactoryProxy<ParametricCS>(ParametricCS.class, AuthorityFactoryIdentifier.CS) {
-            @Override ParametricCS create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
-                return factory.createParametricCS(code);
-            }
-            @Override ParametricCS createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
-                return csFactory(factory).createParametricCS(code);
-            }
-    };
-
     static final AuthorityFactoryProxy<CoordinateSystemAxis> AXIS =
         new AuthorityFactoryProxy<CoordinateSystemAxis>(CoordinateSystemAxis.class, AuthorityFactoryIdentifier.CS) {
             @Override CoordinateSystemAxis create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
@@ -460,7 +442,6 @@
             }
     };
 
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     static final AuthorityFactoryProxy<GeocentricCRS> GEOCENTRIC_CRS =
         new AuthorityFactoryProxy<GeocentricCRS>(GeocentricCRS.class, AuthorityFactoryIdentifier.CRS) {
             @Override GeocentricCRS create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
@@ -471,16 +452,6 @@
             }
     };
 
-    static final AuthorityFactoryProxy<GeodeticCRS> GEODETIC_CRS =
-        new AuthorityFactoryProxy<GeodeticCRS>(GeodeticCRS.class, AuthorityFactoryIdentifier.CRS) {
-            @Override GeodeticCRS create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
-                return factory.createGeodeticCRS(code);
-            }
-            @Override GeodeticCRS createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
-                return crsFactory(factory).createGeodeticCRS(code);
-            }
-    };
-
     @SuppressWarnings("deprecation")
     static final AuthorityFactoryProxy<ImageCRS> IMAGE_CRS =
         new AuthorityFactoryProxy<ImageCRS>(ImageCRS.class, AuthorityFactoryIdentifier.CRS) {
@@ -522,16 +493,6 @@
             }
     };
 
-    static final AuthorityFactoryProxy<ParametricCRS> PARAMETRIC_CRS =
-        new AuthorityFactoryProxy<ParametricCRS>(ParametricCRS.class, AuthorityFactoryIdentifier.CRS) {
-            @Override ParametricCRS create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
-                return factory.createParametricCRS(code);
-            }
-            @Override ParametricCRS createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
-                return crsFactory(factory).createParametricCRS(code);
-            }
-    };
-
     @SuppressWarnings("rawtypes")
     static final AuthorityFactoryProxy<ParameterDescriptor> PARAMETER =
         new AuthorityFactoryProxy<ParameterDescriptor>(ParameterDescriptor.class, AuthorityFactoryIdentifier.GEODETIC) {
@@ -568,12 +529,10 @@
      * with a preference for those who are more likely to be requested.
      * This field can be declared only after all the above constants.
      */
-    @SuppressWarnings("deprecation")
     static final AuthorityFactoryProxy<?>[] PROXIES = new AuthorityFactoryProxy<?>[] {
         PROJECTED_CRS,      // Special kind of GeneralDerivedCRS.
         GEOGRAPHIC_CRS,     // Special kind of GeodeticCRS.
         GEOCENTRIC_CRS,     // Special kind of GeodeticCRS.
-        GEODETIC_CRS,
         VERTICAL_CRS,
         TEMPORAL_CRS,
         IMAGE_CRS,          // Can be seen as a special kind of EngineeringCRS (even if not shown in hierarchy).
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/CommonAuthorityFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
index 089d027..6650570 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
@@ -456,7 +456,6 @@
      * @throws FactoryException if the object creation failed.
      */
     @Override
-    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws FactoryException {
         return createCoordinateReferenceSystem(code);
     }
@@ -686,8 +685,8 @@
      * @return an exception initialized with an error message built from the specified information.
      */
     private static NoSuchAuthorityCodeException noSuchAuthorityCode(String localCode, String code, Exception cause) {
-        return new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.NoSuchAuthorityCode_3,
+        return (NoSuchAuthorityCodeException) new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.NoSuchAuthorityCode_3,
                 Constants.OGC, CoordinateReferenceSystem.class, localCode),
-                Constants.OGC, localCode, code, cause);
+                Constants.OGC, localCode, code).initCause(cause);
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
index dbf400f..dfd35e7 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
@@ -873,7 +873,6 @@
      * @throws FactoryException if the object creation failed.
      */
     @Override
-    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws FactoryException {
         return create(AuthorityFactoryProxy.OBJECT, code);
     }
@@ -929,37 +928,21 @@
      * The default implementation performs the following steps:
      * <ul>
      *   <li>Return the cached instance for the given code if such instance already exists.</li>
-     *   <li>Otherwise if the Data Access Object (DAO) overrides the {@code createGeodeticCRS(String)}
+     *   <li>Otherwise if the Data Access Object (DAO) overrides the {@code createGeocentricCRS(String)}
      *       method, invoke that method and cache the result for future use.</li>
-     *   <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createGeodeticCRS(String)}
+     *   <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createGeocentricCRS(String)}
      *       method in the parent class. This allows to check if the more generic
      *       {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
      * </ul>
      *
-     * @return the coordinate reference system for the given code.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @since 1.5
-     */
-    @Override
-    public GeodeticCRS createGeodeticCRS(final String code) throws FactoryException {
-        if (isDefault(GeodeticCRS.class)) {
-            return super.createGeodeticCRS(code);
-        }
-        return create(AuthorityFactoryProxy.GEODETIC_CRS, code);
-    }
-
-    /**
-     * Returns a 3-dimensional coordinate reference system with the origin at the approximate centre of mass of the earth.
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface
+     * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div>
      *
      * @return the coordinate reference system for the given code.
      * @throws FactoryException if the object creation failed.
-     *
-     * @deprecated ISO 19111:2019 does not define an explicit class for geocentric CRS.
-     * Use {@link #createGeodeticCRS(String)} instead.
      */
     @Override
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public GeocentricCRS createGeocentricCRS(final String code) throws FactoryException {
         if (isDefault(GeocentricCRS.class)) {
             return super.createGeocentricCRS(code);
@@ -1037,31 +1020,6 @@
     }
 
     /**
-     * Returns a 1-dimensional coordinate reference system which uses parameter values or functions.
-     * The default implementation performs the following steps:
-     * <ul>
-     *   <li>Return the cached instance for the given code if such instance already exists.</li>
-     *   <li>Otherwise if the Data Access Object (DAO) overrides the {@code createParametricCRS(String)}
-     *       method, invoke that method and cache the result for future use.</li>
-     *   <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createParametricCRS(String)}
-     *       method in the parent class. This allows to check if the more generic
-     *       {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
-     * </ul>
-     *
-     * @return the coordinate reference system for the given code.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @since 1.4
-     */
-    @Override
-    public ParametricCRS createParametricCRS(final String code) throws FactoryException {
-        if (isDefault(ParametricCRS.class)) {
-            return super.createParametricCRS(code);
-        }
-        return create(AuthorityFactoryProxy.PARAMETRIC_CRS, code);
-    }
-
-    /**
      * Returns a CRS describing the position of points through two or more independent coordinate reference systems.
      * The default implementation performs the following steps:
      * <ul>
@@ -1250,31 +1208,6 @@
     }
 
     /**
-     * Returns an identification of a reference surface used as the origin of a parametric coordinate system.
-     * The default implementation performs the following steps:
-     * <ul>
-     *   <li>Return the cached instance for the given code if such instance already exists.</li>
-     *   <li>Otherwise if the Data Access Object (DAO) overrides the {@code createParametricDatum(String)}
-     *       method, invoke that method and cache the result for future use.</li>
-     *   <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createParametricDatum(String)}
-     *       method in the parent class. This allows to check if the more generic
-     *       {@link #createDatum(String)} method cached a value before to try that method.</li>
-     * </ul>
-     *
-     * @return the datum for the given code.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @since 1.4
-     */
-    @Override
-    public ParametricDatum createParametricDatum(final String code) throws FactoryException {
-        if (isDefault(ParametricDatum.class)) {
-            return super.createParametricDatum(code);
-        }
-        return create(AuthorityFactoryProxy.PARAMETRIC_DATUM, code);
-    }
-
-    /**
      * Returns a datum defining the origin of an engineering coordinate reference system.
      * The default implementation performs the following steps:
      * <ul>
@@ -1485,31 +1418,6 @@
     }
 
     /**
-     * Returns a 1-dimensional coordinate system containing a single axis.
-     * The default implementation performs the following steps:
-     * <ul>
-     *   <li>Return the cached instance for the given code if such instance already exists.</li>
-     *   <li>Otherwise if the Data Access Object (DAO) overrides the {@code createParametricCS(String)}
-     *       method, invoke that method and cache the result for future use.</li>
-     *   <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createParametricCS(String)}
-     *       method in the parent class. This allows to check if the more generic
-     *       {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
-     * </ul>
-     *
-     * @return the coordinate system for the given code.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @since 1.4
-     */
-    @Override
-    public ParametricCS createParametricCS(final String code) throws FactoryException {
-        if (isDefault(ParametricCS.class)) {
-            return super.createParametricCS(code);
-        }
-        return create(AuthorityFactoryProxy.PARAMETRIC_CS, code);
-    }
-
-    /**
      * Returns a 2- or 3-dimensional Cartesian coordinate system made of straight orthogonal axes.
      * The default implementation performs the following steps:
      * <ul>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index 450c464..11f2246 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -42,6 +42,11 @@
 import org.apache.sis.util.iso.AbstractFactory;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the main branch:
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+
 
 /**
  * Creates geodetic objects from codes defined by an authority.
@@ -165,7 +170,6 @@
      *
      * @since 1.5
      */
-    @Override
     public Optional<InternationalString> getDescriptionText(Class<? extends IdentifiedObject> type, String code)
             throws FactoryException
     {
@@ -173,6 +177,24 @@
     }
 
     /**
+     * Returns a description of the object corresponding to a code.
+     *
+     * @param  code  value allocated by authority.
+     * @return a description of the object, or {@code null} if the object
+     *         corresponding to the specified {@code code} has no description.
+     * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
+     * @throws FactoryException if the query failed for some other reason.
+     *
+     * @deprecated This method is ambiguous because the EPSG geodetic registry may allocate
+     *             the same code to different kinds of object.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public InternationalString getDescriptionText(final String code) throws FactoryException {
+        return getDescriptionText(IdentifiedObject.class, code).orElse(null);
+    }
+
+    /**
      * Returns an object of the specified type from a code. This implementation forwards
      * the method call to the most specialized methods determined by the given type.
      *
@@ -212,7 +234,6 @@
      * @see org.apache.sis.referencing.AbstractIdentifiedObject
      */
     @Override
-    @SuppressWarnings("removal")
     public abstract IdentifiedObject createObject(String code) throws NoSuchAuthorityCodeException, FactoryException;
 
     /**
@@ -323,6 +344,10 @@
      *   <tr><td>EPSG:4984</td> <td>World Geodetic System 1972</td></tr>
      * </table>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface
+     * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div>
+     *
      * <h4>Default implementation</h4>
      * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)} and casts the result.
      * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
@@ -334,25 +359,7 @@
      *
      * @see org.apache.sis.referencing.crs.DefaultGeocentricCRS
      * @see org.apache.sis.referencing.CommonCRS#geocentric()
-     *
-     * @since 1.5
      */
-    public GeodeticCRS createGeodeticCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(GeodeticCRS.class, createCoordinateReferenceSystem(code), code);
-    }
-
-    /**
-     * Creates a 3-dimensional coordinate reference system with the origin at the approximate centre of mass of the earth.
-     *
-     * @param  code  value allocated by authority.
-     * @return the coordinate reference system for the given code.
-     * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @deprecated ISO 19111:2019 does not define an explicit class for geocentric CRS.
-     * Use {@link #createGeodeticCRS(String)} instead.
-     */
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public GeocentricCRS createGeocentricCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
         return cast(GeocentricCRS.class, createCoordinateReferenceSystem(code), code);
     }
@@ -451,6 +458,9 @@
      * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)} and casts the result.
      * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the coordinate reference system for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -458,8 +468,8 @@
      *
      * @see org.apache.sis.referencing.crs.DefaultParametricCRS
      */
-    public ParametricCRS createParametricCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricCRS.class, createCoordinateReferenceSystem(code), code);
+    public DefaultParametricCRS createParametricCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricCRS.class, createCoordinateReferenceSystem(code), code);
     }
 
     /**
@@ -695,6 +705,9 @@
      * The default implementation delegates to {@link #createDatum(String)} and casts the result.
      * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the datum for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -702,8 +715,8 @@
      *
      * @see org.apache.sis.referencing.datum.DefaultParametricDatum
      */
-    public ParametricDatum createParametricDatum(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricDatum.class, createDatum(code), code);
+    public DefaultParametricDatum createParametricDatum(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricDatum.class, createDatum(code), code);
     }
 
     /**
@@ -985,6 +998,9 @@
      * The default implementation delegates to {@link #createCoordinateSystem(String)} and casts the result.
      * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the coordinate system for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -992,8 +1008,8 @@
      *
      * @see org.apache.sis.referencing.cs.DefaultParametricCS
      */
-    public ParametricCS createParametricCS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricCS.class, createCoordinateSystem(code), code);
+    public DefaultParametricCS createParametricCS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricCS.class, createCoordinateSystem(code), code);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index 20e1cc7..10d288d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@ -24,7 +24,6 @@
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
 import java.util.concurrent.atomic.AtomicReference;
-import java.time.temporal.Temporal;
 import java.lang.reflect.Constructor;
 import jakarta.xml.bind.JAXBException;
 import javax.measure.Unit;
@@ -111,15 +110,15 @@
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getCode()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+ *     <td>"codespace"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getCodeSpace()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+ *     <td>"version"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getVersion()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+ *     <td>"description"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getDescription()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr><tr>
@@ -131,23 +130,23 @@
  *     <td>{@link Identifier} (optionally as array)</td>
  *     <td>{@link AbstractIdentifiedObject#getIdentifiers()}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
- *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+ *     <td>"domains"</td>
+ *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
  *     <td>{@link AbstractIdentifiedObject#getDomains()}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+ *     <td>{@value org.opengis.referencing.ReferenceSystem#DOMAIN_OF_VALIDITY_KEY}</td>
  *     <td>{@link Extent}</td>
  *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getDomainOfValidity()}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.referencing.ObjectDomain#SCOPE_KEY}</td>
+ *     <td>{@value org.opengis.referencing.ReferenceSystem#SCOPE_KEY}</td>
  *     <td>{@link String} or {@link InternationalString}</td>
- *     <td>{@link DefaultObjectDomain#getScope()}</td>
+ *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getScope()}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_DEFINITION_KEY}</td>
+ *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
  *     <td>{@link InternationalString} or {@link String}</td>
  *     <td>{@link AbstractDatum#getAnchorDefinition()}</td>
  *   </tr><tr>
- *     <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td>
+ *     <td>{@value "anchorEpoch"}</td>
  *     <td>{@link java.time.temporal.Temporal}</td>
  *     <td>{@link AbstractDatum#getAnchorEpoch()}</td>
  *   </tr><tr>
@@ -318,9 +317,13 @@
     /**
      * Creates a geocentric coordinate reference system from a {@linkplain CartesianCS Cartesian coordinate system}.
      * Geocentric CRS have their origin at the approximate centre of mass of the earth.
-     * An {@linkplain #createGeodeticCRS(Map, GeodeticDatum, SphericalCS) alternate method} allows creation of the
+     * An {@linkplain #createGeocentricCRS(Map, GeodeticDatum, SphericalCS) alternate method} allows creation of the
      * same kind of CRS with spherical coordinate system instead of a Cartesian one.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface
+     * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div>
+     *
      * <h4>Dependencies</h4>
      * The components needed by this method can be created by the following methods:
      * <ol>
@@ -337,46 +340,14 @@
      * The default implementation creates a {@link DefaultGeocentricCRS} instance.
      *
      * @param  properties  name and other properties to give to the new object.
-     * @param  datum  geodetic reference frame, or {@code null} if the CRS is associated only to a datum ensemble.
-     * @param  datumEnsemble  collection of reference frames which for low accuracy requirements may be considered
-     *         to be insignificantly different from each other, or {@code null} if there is no such ensemble.
-     * @param  cs  the three-dimensional Cartesian coordinate system for the created CRS.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @see GeodeticAuthorityFactory#createGeodeticCRS(String)
-     * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, CartesianCS)
-     *
-     * @since 1.5
-     */
-    @Override
-    public GeodeticCRS createGeodeticCRS(
-            final Map<String,?> properties,
-            final GeodeticDatum datum,
-            final DatumEnsemble<GeodeticDatum> datumEnsemble,
-            final CartesianCS cs) throws FactoryException
-    {
-        final DefaultGeocentricCRS crs;
-        try {
-            crs = new DefaultGeocentricCRS(complete(properties), datum, cs);
-        } catch (IllegalArgumentException exception) {
-            throw new InvalidGeodeticParameterException(exception);
-        }
-        return unique("createGeodeticCRS", crs);
-    }
-
-    /**
-     * Creates a geocentric coordinate reference system from a Cartesian coordinate system.
-     *
-     * @param  properties  name and other properties to give to the new object.
-     * @param  datum       the geodetic datum to use in created CRS.
+     * @param  datum       the geodetic reference frame to use in created CRS.
      * @param  cs          the three-dimensional Cartesian coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
-     * @deprecated ISO 19111:2019 does not define an explicit class for geocentric CRS.
-     * Use {@link #createGeodeticCRS(Map, GeodeticDatum, CartesianCS)} instead.
+     * @see GeodeticAuthorityFactory#createGeocentricCRS(String)
+     * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, CartesianCS)
      */
     @Override
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public GeocentricCRS createGeocentricCRS(final Map<String,?> properties,
             final GeodeticDatum datum, final CartesianCS cs) throws FactoryException
     {
@@ -428,9 +399,13 @@
     /**
      * Creates a geocentric coordinate reference system from a {@linkplain SphericalCS spherical coordinate system}.
      * Geocentric CRS have their origin at the approximate centre of mass of the earth.
-     * An {@linkplain #createGeodeticCRS(Map, GeodeticDatum, CartesianCS) alternate method} allows creation of the
+     * An {@linkplain #createGeocentricCRS(Map, GeodeticDatum, CartesianCS) alternate method} allows creation of the
      * same kind of CRS with Cartesian coordinate system instead of a spherical one.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface
+     * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div>
+     *
      * <h4>Dependencies</h4>
      * The components needed by this method can be created by the following methods:
      * <ol>
@@ -447,46 +422,14 @@
      * The default implementation creates a {@link DefaultGeocentricCRS} instance.
      *
      * @param  properties  name and other properties to give to the new object.
-     * @param  datum  geodetic reference frame, or {@code null} if the CRS is associated only to a datum ensemble.
-     * @param  datumEnsemble  collection of reference frames which for low accuracy requirements may be considered
-     *         to be insignificantly different from each other, or {@code null} if there is no such ensemble.
-     * @param  cs  the spherical coordinate system for the created CRS.
+     * @param  datum       geodetic reference frame to use in created CRS.
+     * @param  cs          the spherical coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, SphericalCS)
-     * @see GeodeticAuthorityFactory#createGeodeticCRS(String)
-     *
-     * @since 1.5
+     * @see GeodeticAuthorityFactory#createGeocentricCRS(String)
      */
     @Override
-    public GeodeticCRS createGeodeticCRS(
-            final Map<String,?> properties,
-            final GeodeticDatum datum,
-            final DatumEnsemble<GeodeticDatum> datumEnsemble,
-            final SphericalCS cs) throws FactoryException
-    {
-        final DefaultGeocentricCRS crs;
-        try {
-            crs = new DefaultGeocentricCRS(complete(properties), datum, cs);
-        } catch (IllegalArgumentException exception) {
-            throw new InvalidGeodeticParameterException(exception);
-        }
-        return unique("createGeodeticCRS", crs);
-    }
-
-    /**
-     * Creates a geocentric coordinate reference system from a spherical coordinate system.
-     *
-     * @param  properties  name and other properties to give to the new object.
-     * @param  datum       the geodetic datum to use in created CRS.
-     * @param  cs          the three-dimensional Cartesian coordinate system for the created CRS.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @deprecated ISO 19111:2019 does not define an explicit class for geocentric CRS.
-     * Use {@link #createGeodeticCRS(Map, GeodeticDatum, SphericalCS)} instead.
-     */
-    @Override
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public GeocentricCRS createGeocentricCRS(final Map<String,?> properties,
             final GeodeticDatum datum, final SphericalCS cs) throws FactoryException
     {
@@ -556,7 +499,6 @@
      *
      * @since 1.4
      */
-    @Override
     public SphericalCS createSphericalCS(final Map<String,?> properties,
             final CoordinateSystemAxis axis0,
             final CoordinateSystemAxis axis1) throws FactoryException
@@ -593,21 +535,16 @@
      * The default implementation creates a {@link DefaultGeographicCRS} instance.
      *
      * @param  properties  name and other properties to give to the new object.
-     * @param  datum  geodetic reference frame, or {@code null} if the CRS is associated only to a datum ensemble.
-     * @param  datumEnsemble  collection of reference frames which for low accuracy requirements may be considered
-     *         to be insignificantly different from each other, or {@code null} if there is no such ensemble.
-     * @param  cs  the two- or three-dimensional ellipsoidal coordinate system for the created CRS.
+     * @param  datum       geodetic reference frame to use in created CRS.
+     * @param  cs          the two- or three-dimensional ellipsoidal coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultGeographicCRS#DefaultGeographicCRS(Map, GeodeticDatum, EllipsoidalCS)
      * @see GeodeticAuthorityFactory#createGeographicCRS(String)
      */
     @Override
-    public GeographicCRS createGeographicCRS(
-            final Map<String,?> properties,
-            final GeodeticDatum datum,
-            final DatumEnsemble<GeodeticDatum> datumEnsemble,
-            final EllipsoidalCS cs) throws FactoryException
+    public GeographicCRS createGeographicCRS(final Map<String,?> properties,
+            final GeodeticDatum datum, final EllipsoidalCS cs) throws FactoryException
     {
         final DefaultGeographicCRS crs;
         try {
@@ -957,27 +894,22 @@
      * <ol>
      *   <li>{@link #createCoordinateSystemAxis(Map, String, AxisDirection, Unit)}</li>
      *   <li>{@link #createVerticalCS(Map, CoordinateSystemAxis)}</li>
-     *   <li>{@link #createVerticalDatum(Map, RealizationMethod)}</li>
+     *   <li>{@link #createVerticalDatum(Map, VerticalDatumType)}</li>
      * </ol>
      *
      * The default implementation creates a {@link DefaultVerticalCRS} instance.
      *
      * @param  properties  name and other properties to give to the new object.
-     * @param  datum  vertical reference frame, or {@code null} if the CRS is associated only to a datum ensemble.
-     * @param  datumEnsemble  collection of reference frames which for low accuracy requirements may be considered
-     *         to be insignificantly different from each other, or {@code null} if there is no such ensemble.
-     * @param  cs  the vertical coordinate system for the created CRS.
+     * @param  datum       the vertical datum to use in created CRS.
+     * @param  cs          the vertical coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultVerticalCRS#DefaultVerticalCRS(Map, VerticalDatum, VerticalCS)
      * @see GeodeticAuthorityFactory#createVerticalCRS(String)
      */
     @Override
-    public VerticalCRS createVerticalCRS(
-            final Map<String,?> properties,
-            final VerticalDatum datum,
-            final DatumEnsemble<VerticalDatum> datumEnsemble,
-            final VerticalCS cs) throws FactoryException
+    public VerticalCRS createVerticalCRS(final Map<String,?> properties,
+            final VerticalDatum datum, final VerticalCS cs) throws FactoryException
     {
         final DefaultVerticalCRS crs;
         try {
@@ -989,32 +921,6 @@
     }
 
     /**
-     * Creates a vertical datum from a realization method.
-     * The default implementation creates a {@link DefaultVerticalDatum} instance.
-     *
-     * @param  properties  name and other properties to give to the new object.
-     * @param  method      the realization method of the vertical datum, or {@code null} if none.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @see DefaultVerticalDatum#DefaultVerticalDatum(Map, RealizationMethod)
-     * @see GeodeticAuthorityFactory#createVerticalDatum(String)
-     *
-     * @since 2.0 (temporary version number until this branch is released)
-     */
-    @Override
-    public VerticalDatum createVerticalDatum(final Map<String,?> properties,
-            final RealizationMethod method) throws FactoryException
-    {
-        final DefaultVerticalDatum datum;
-        try {
-            datum = new DefaultVerticalDatum(complete(properties), method);
-        } catch (IllegalArgumentException exception) {
-            throw new InvalidGeodeticParameterException(exception);
-        }
-        return unique("createVerticalDatum", datum);
-    }
-
-    /**
      * Creates a vertical datum from an enumerated type value.
      * The default implementation creates a {@link DefaultVerticalDatum} instance.
      *
@@ -1024,11 +930,8 @@
      *
      * @see DefaultVerticalDatum#DefaultVerticalDatum(Map, VerticalDatumType)
      * @see GeodeticAuthorityFactory#createVerticalDatum(String)
-     *
-     * @deprecated As of ISO 19111:2019, the {@code VerticalDatumType} argument is replaced by {@code RealizationMethod}.
      */
     @Override
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public VerticalDatum createVerticalDatum(final Map<String,?> properties,
             final VerticalDatumType type) throws FactoryException
     {
@@ -1087,21 +990,16 @@
      * The default implementation creates a {@link DefaultTemporalCRS} instance.
      *
      * @param  properties  name and other properties to give to the new object.
-     * @param  datum  temporal datum, or {@code null} if the CRS is associated only to a datum ensemble.
-     * @param  datumEnsemble  collection of datum which for low accuracy requirements may be considered
-     *         to be insignificantly different from each other, or {@code null} if there is no such ensemble.
-     * @param  cs  the temporal coordinate system for the created CRS.
+     * @param  datum       the temporal datum to use in created CRS.
+     * @param  cs          the temporal coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultTemporalCRS#DefaultTemporalCRS(Map, TemporalDatum, TimeCS)
      * @see GeodeticAuthorityFactory#createTemporalCRS(String)
      */
     @Override
-    public TemporalCRS createTemporalCRS(
-            final Map<String,?> properties,
-            final TemporalDatum datum,
-            final DatumEnsemble<TemporalDatum> datumEnsemble,
-            final TimeCS cs) throws FactoryException
+    public TemporalCRS createTemporalCRS(final Map<String,?> properties,
+            final TemporalDatum datum, final TimeCS cs) throws FactoryException
     {
         final DefaultTemporalCRS crs;
         try {
@@ -1125,7 +1023,7 @@
      */
     @Override
     public TemporalDatum createTemporalDatum(final Map<String,?> properties,
-            final Temporal origin) throws FactoryException
+            final Date origin) throws FactoryException
     {
         final DefaultTemporalDatum datum;
         try {
@@ -1183,22 +1081,21 @@
      *
      * The default implementation creates a {@link DefaultParametricCRS} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter types may be changed to
+     * {@code org.opengis.referencing.datum.ParametricDatum} and {@code org.opengis.referencing.cs.ParametricCS},
+     * and the return type may be changed to {@code org.opengis.referencing.crs.ParametricCRS}.
+     * Those change are pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
-     * @param  datum  parametric datum, or {@code null} if the CRS is associated only to a datum ensemble.
-     * @param  datumEnsemble  collection of datum which for low accuracy requirements may be considered
-     *         to be insignificantly different from each other, or {@code null} if there is no such ensemble.
-     * @param  cs  the parametric coordinate system for the created CRS.
+     * @param  datum       the parametric datum to use in created CRS.
+     * @param  cs          the parametric coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
-     * @see DefaultParametricCRS#DefaultParametricCRS(Map, ParametricDatum, ParametricCS)
+     * @see DefaultParametricCRS#DefaultParametricCRS(Map, DefaultParametricDatum, DefaultParametricCS)
      * @see GeodeticAuthorityFactory#createParametricCRS(String)
      */
-    @Override
-    public ParametricCRS createParametricCRS(
-            final Map<String,?> properties,
-            final ParametricDatum datum,
-            final DatumEnsemble<ParametricDatum> datumEnsemble,
-            final ParametricCS cs) throws FactoryException
+    public DefaultParametricCRS createParametricCRS(final Map<String,?> properties,
+            final DefaultParametricDatum datum, final DefaultParametricCS cs) throws FactoryException
     {
         final DefaultParametricCRS crs;
         try {
@@ -1213,14 +1110,16 @@
      * Creates a parametric datum.
      * The default implementation creates a {@link DefaultParametricDatum} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultParametricDatum#DefaultParametricDatum(Map)
      * @see GeodeticAuthorityFactory#createParametricDatum(String)
      */
-    @Override
-    public ParametricDatum createParametricDatum(final Map<String,?> properties)
+    public DefaultParametricDatum createParametricDatum(final Map<String,?> properties)
             throws FactoryException
     {
         final DefaultParametricDatum datum;
@@ -1244,6 +1143,9 @@
      *
      * The default implementation creates a {@link DefaultParametricCS} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @param  axis        the single axis.
      * @throws FactoryException if the object creation failed.
@@ -1251,8 +1153,7 @@
      * @see DefaultParametricCS#DefaultParametricCS(Map, CoordinateSystemAxis)
      * @see GeodeticAuthorityFactory#createParametricCS(String)
      */
-    @Override
-    public ParametricCS createParametricCS(Map<String, ?> properties, CoordinateSystemAxis axis) throws FactoryException {
+    public DefaultParametricCS createParametricCS(Map<String, ?> properties, CoordinateSystemAxis axis) throws FactoryException {
         final DefaultParametricCS cs;
         try {
             cs = new DefaultParametricCS(complete(properties), axis);
@@ -1406,21 +1307,16 @@
      * The default implementation creates a {@link DefaultEngineeringCRS} instance.
      *
      * @param  properties  name and other properties to give to the new object.
-     * @param  datum  engineering datum, or {@code null} if the CRS is associated only to a datum ensemble.
-     * @param  datumEnsemble  collection of datum which for low accuracy requirements may be considered
-     *         to be insignificantly different from each other, or {@code null} if there is no such ensemble.
-     * @param  cs  the coordinate system for the created CRS.
+     * @param  datum       the engineering datum to use in created CRS.
+     * @param  cs          the coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultEngineeringCRS#DefaultEngineeringCRS(Map, EngineeringDatum, CoordinateSystem)
      * @see GeodeticAuthorityFactory#createEngineeringCRS(String)
      */
     @Override
-    public EngineeringCRS createEngineeringCRS(
-            final Map<String,?> properties,
-            final EngineeringDatum datum,
-            final DatumEnsemble<EngineeringDatum> datumEnsemble,
-            final CoordinateSystem cs) throws FactoryException
+    public EngineeringCRS createEngineeringCRS(final Map<String,?> properties,
+            final EngineeringDatum datum, final CoordinateSystem cs) throws FactoryException
     {
         final DefaultEngineeringCRS crs;
         try {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
index e68b77c..f988b0e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
@@ -901,7 +901,6 @@
      * @throws FactoryException if the object creation failed.
      */
     @Override
-    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws FactoryException {
         return create(AuthorityFactoryProxy.OBJECT, code);
     }
@@ -955,27 +954,14 @@
      *   <li>{@code http://www.opengis.net/gml/srs/}<var>authority</var>{@code .xml#}<var>code</var></li>
      * </ul>
      *
-     * @return the coordinate reference system for the given code.
-     * @throws FactoryException if the object creation failed.
-     *
-     * @since 1.5
-     */
-    @Override
-    public GeodeticCRS createGeodeticCRS(final String code) throws FactoryException {
-        return create(AuthorityFactoryProxy.GEODETIC_CRS, code);
-    }
-
-    /**
-     * Creates a 3-dimensional coordinate reference system with the origin at the approximate centre of mass of the earth.
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface
+     * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div>
      *
      * @return the coordinate reference system for the given code.
      * @throws FactoryException if the object creation failed.
-     *
-     * @deprecated ISO 19111:2019 does not define an explicit class for geocentric CRS.
-     * Use {@link #createGeodeticCRS(String)} instead.
      */
     @Override
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
     public GeocentricCRS createGeocentricCRS(final String code) throws FactoryException {
         return create(AuthorityFactoryProxy.GEOCENTRIC_CRS, code);
     }
@@ -1727,7 +1713,7 @@
             if (cs instanceof EllipsoidalCS) {
                 return factory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs);
             } else if (cs instanceof SphericalCS) {
-                return factory.createGeodeticCRS(properties, datum, null, (SphericalCS) cs);
+                return factory.createGeocentricCRS(properties, datum, (SphericalCS) cs);
             }
         }
         return null;
@@ -1743,7 +1729,7 @@
      * @return the combined CRS, or {@code null} if the given information are not sufficient.
      * @throws FactoryException if an error occurred while creating the combined CRS.
      */
-    private static DerivedCRS combine(final CoordinateReferenceSystem baseCRS, final Conversion fromBase,
+    private static GeneralDerivedCRS combine(final CoordinateReferenceSystem baseCRS, final Conversion fromBase,
             final CoordinateSystem cs) throws FactoryException
     {
         if (baseCRS != null && fromBase.getSourceCRS() == null && fromBase.getTargetCRS() == null) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
index a67aad7..f7005d0 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
@@ -112,7 +112,6 @@
      * Creates a coordinate operation for the specified EPSG code.
      */
     @Override
-    @SuppressWarnings("deprecation")
     protected CoordinateOperation createObject(final String code) throws FactoryException {
         final Integer base = projections.get(code);
         if (base != null) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index d0ca972..e64088b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -121,9 +121,12 @@
 import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
 import static org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.referencing.internal.ServicesForMetadata;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 
 
 /**
@@ -1199,9 +1202,9 @@
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         final Map<String,Object> properties = createProperties(table, name, code, remarks, deprecated);
         if (domainCode != null) {
-            properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, owner.createExtent(domainCode));
+            properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, owner.createExtent(domainCode));
         }
-        properties.put(ObjectDomain.SCOPE_KEY, scope);
+        properties.put(Datum.SCOPE_KEY, scope);
         return properties;
     }
 
@@ -1235,7 +1238,6 @@
      * @see #createCoordinateSystem(String)
      */
     @Override
-    @SuppressWarnings("removal")
     public synchronized IdentifiedObject createObject(final String code)
             throws NoSuchAuthorityCodeException, FactoryException
     {
@@ -1545,14 +1547,13 @@
                     case "geocentric": {
                         final CoordinateSystem cs = owner.createCoordinateSystem(getString(code, result, 8));
                         final GeodeticDatum datum = owner.createGeodeticDatum   (getString(code, result, 9));
-                        final DatumEnsemble<GeodeticDatum> datumEnsemble = null;  // TODO
                         @SuppressWarnings("LocalVariableHidesMemberVariable")
                         final Map<String,Object> properties = createProperties("Coordinate Reference System",
                                                                 name, epsg, area, scope, remarks, deprecated);
                         if (cs instanceof CartesianCS) {
-                            crs = crsFactory.createGeodeticCRS(properties, datum, datumEnsemble, (CartesianCS) cs);
+                            crs = crsFactory.createGeocentricCRS(properties, datum, (CartesianCS) cs);
                         } else if (cs instanceof SphericalCS) {
-                            crs = crsFactory.createGeodeticCRS(properties, datum, datumEnsemble, (SphericalCS) cs);
+                            crs = crsFactory.createGeocentricCRS(properties, datum, (SphericalCS) cs);
                         } else {
                             throw new FactoryDataException(error().getString(
                                     Errors.Keys.IllegalCoordinateSystem_1, cs.getName()));
@@ -1573,11 +1574,10 @@
                      *   PARAMETRIC CRS
                      * ---------------------------------------------------------------------- */
                     case "parametric": {
-                        final ParametricCS    cs    = owner.createParametricCS   (getString(code, result, 8));
-                        final ParametricDatum datum = owner.createParametricDatum(getString(code, result, 9));
-                        final DatumEnsemble<ParametricDatum> datumEnsemble = null;  // TODO
-                        crs = crsFactory.createParametricCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), datum, datumEnsemble, cs);
+                        final DefaultParametricCS    cs    = owner.createParametricCS   (getString(code, result, 8));
+                        final DefaultParametricDatum datum = owner.createParametricDatum(getString(code, result, 9));
+                        crs = ServicesForMetadata.createParametricCRS(createProperties("Coordinate Reference System",
+                                name, epsg, area, scope, remarks, deprecated), datum, cs, crsFactory);
                         break;
                     }
                     /* ----------------------------------------------------------------------
@@ -1655,7 +1655,7 @@
                 @SuppressWarnings("LocalVariableHidesMemberVariable")
                 Map<String,Object> properties = createProperties("Datum", name, epsg, area, scope, remarks, deprecated);
                 if (anchor != null) {
-                    properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
+                    properties.put(Datum.ANCHOR_POINT_KEY, anchor);
                 }
                 if (epoch != null) try {
                     /*
@@ -1679,7 +1679,7 @@
                         @SuppressWarnings("LocalVariableHidesMemberVariable")
                         final Calendar calendar = getCalendar();
                         calendar.set(year, month, day);
-                        properties.put(Datum.ANCHOR_EPOCH_KEY, calendar.getTime().toInstant());
+                        properties.put(Datum.REALIZATION_EPOCH_KEY, calendar.getTime());
                     }
                 } catch (NumberFormatException exception) {
                     unexpectedException("createDatum", exception);          // Not a fatal error.
@@ -1708,7 +1708,7 @@
                         break;
                     }
                     case "vertical": {
-                        datum = datumFactory.createVerticalDatum(properties, (RealizationMethod) null);
+                        datum = datumFactory.createVerticalDatum(properties, VerticalDatumType.GEOIDAL);
                         break;
                     }
                     /*
@@ -1725,7 +1725,7 @@
                         } catch (RuntimeException e) {
                             throw new FactoryDataException(resources().getString(Resources.Keys.DatumOriginShallBeDate), e);
                         }
-                        datum = datumFactory.createTemporalDatum(properties, originDate);
+                        datum = datumFactory.createTemporalDatum(properties, TemporalDate.toDate(originDate));
                         break;
                     }
                     /*
@@ -1736,7 +1736,7 @@
                         break;
                     }
                     case "parametric": {
-                        datum = datumFactory.createParametricDatum(properties);
+                        datum = ServicesForMetadata.createParametricDatum(properties, datumFactory);
                         break;
                     }
                     default: {
@@ -2175,7 +2175,10 @@
                     }
                     case WKTKeywords.spherical: {
                         switch (dimension) {
-                            case 2: cs = csFactory.createSphericalCS(properties, axes[0], axes[1]); break;
+                            case 2: if (csFactory instanceof GeodeticObjectFactory) {
+                                        cs = ((GeodeticObjectFactory) csFactory).createSphericalCS(properties, axes[0], axes[1]);
+                                    }
+                                    break;
                             case 3: cs = csFactory.createSphericalCS(properties, axes[0], axes[1], axes[2]); break;
                         }
                         break;
@@ -2196,7 +2199,7 @@
                     }
                     case WKTKeywords.parametric: {
                         switch (dimension) {
-                            case 1: cs = csFactory.createParametricCS(properties, axes[0]); break;
+                            case 1: cs = ServicesForMetadata.createParametricCS(properties, axes[0], csFactory); break;
                         }
                         break;
                     }
@@ -2597,7 +2600,7 @@
                 @SuppressWarnings("LocalVariableHidesMemberVariable")
                 final Map<String,Object> properties =
                         createProperties("Coordinate_Operation Parameter", name, epsg, isReversible, deprecated);
-                properties.put(Identifier.DESCRIPTION_KEY, description);
+                properties.put(ImmutableIdentifier.DESCRIPTION_KEY, description);
                 final ParameterDescriptor<?> descriptor = new DefaultParameterDescriptor<>(properties,
                         1, 1, type, valueDomain, null, null);
                 returnValue = ensureSingleton(descriptor, returnValue, code);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
index 2e5cbb0..8e922a8 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
@@ -26,6 +26,11 @@
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.util.CharSequences;
 
+// Specific to the main branch:
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+
 
 /**
  * Information about a specific table. The MS-Access dialect of SQL is assumed;
@@ -62,7 +67,6 @@
      *
      * The order is significant: it is the key for a {@code switch} statement.
      */
-    @SuppressWarnings("deprecation")
     static final TableInfo[] EPSG = {
         CRS = new TableInfo(CoordinateReferenceSystem.class,
                 "[Coordinate Reference System]",
@@ -71,7 +75,7 @@
                 "COORD_REF_SYS_KIND",
                 new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   GeocentricCRS.class,
                                  VerticalCRS.class,    CompoundCRS.class,     EngineeringCRS.class,
-                                 DerivedCRS.class,     TemporalCRS.class,     ParametricCRS.class},     // See comment below
+                                 DerivedCRS.class,     TemporalCRS.class,     DefaultParametricCRS.class},     // See comment below
                 new String[]   {"projected",          "geographic",          "geocentric",
                                 "vertical",           "compound",            "engineering",
                                 "derived",            "temporal",            "parametric"},             // See comment below
@@ -90,7 +94,7 @@
                 "COORD_SYS_TYPE",
                 new Class<?>[] {CartesianCS.class,      EllipsoidalCS.class,      VerticalCS.class,      LinearCS.class,
                                 SphericalCS.class,      PolarCS.class,            CylindricalCS.class,
-                                TimeCS.class,           ParametricCS.class,       AffineCS.class},
+                                TimeCS.class,           DefaultParametricCS.class, AffineCS.class},
                 new String[]   {WKTKeywords.Cartesian,  WKTKeywords.ellipsoidal,  WKTKeywords.vertical,  WKTKeywords.linear,
                                 WKTKeywords.spherical,  WKTKeywords.polar,        WKTKeywords.cylindrical,
                                 WKTKeywords.temporal,   WKTKeywords.parametric,   WKTKeywords.affine},      // Same comment as in the CRS case above.
@@ -109,7 +113,7 @@
                 "DATUM_NAME",
                 "DATUM_TYPE",
                 new Class<?>[] { GeodeticDatum.class,  VerticalDatum.class,   EngineeringDatum.class,
-                                 TemporalDatum.class,  ParametricDatum.class},
+                                 TemporalDatum.class,  DefaultParametricDatum.class},
                 new String[]   {"geodetic",           "vertical",            "engineering",
                                 "temporal",           "parametric"},         // Same comment as in the CRS case above.
                 null),
@@ -258,7 +262,6 @@
      * @param  object  the object to search in the database.
      * @param  buffer  where to append the {@code WHERE} clause.
      */
-    @SuppressWarnings("deprecation")
     final void where(final IdentifiedObject object, final StringBuilder buffer) {
         Class<?> userType = object.getClass();
         if (object instanceof GeodeticCRS) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxy.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxy.java
index 84ab46c..b4419ca 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxy.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxy.java
@@ -24,9 +24,6 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.referencing.GeodeticException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Optional;
-
 
 /**
  * A factory that redirect all method to another factory. This factory is normally useless and not used by Apache SIS.
@@ -61,14 +58,11 @@
     }
 
     @Override
-    public final Optional<InternationalString> getDescriptionText(Class<? extends IdentifiedObject> type, String code)
-            throws FactoryException
-    {
-        return factory().getDescriptionText(type, code);
+    public final InternationalString getDescriptionText(String code) throws FactoryException {
+        return factory().getDescriptionText(code);
     }
 
     @Override
-    @SuppressWarnings("removal")
     public final IdentifiedObject createObject(String code) throws FactoryException {
         return factory().createObject(code);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCRS.java
index a4498bb..9d3962d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCRS.java
@@ -32,10 +32,6 @@
 import org.opengis.referencing.crs.GeocentricCRS;
 import org.opengis.referencing.crs.ImageCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.crs.GeodeticCRS;
-import org.opengis.referencing.crs.ParametricCRS;
-
 
 /**
  * A factory that redirect all method to another factory. This factory is normally useless and not used by Apache SIS.
@@ -82,17 +78,12 @@
         return factory().createGeographicCRS(code);
     }
 
-    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
+    @Override
     public GeocentricCRS createGeocentricCRS(String code) throws FactoryException {
         return factory().createGeocentricCRS(code);
     }
 
     @Override
-    public GeodeticCRS createGeodeticCRS(String code) throws FactoryException {
-        return factory().createGeodeticCRS(code);
-    }
-
-    @Override
     @Deprecated(since = "1.5")
     public ImageCRS createImageCRS(String code) throws FactoryException {
         return factory().createImageCRS(code);
@@ -112,9 +103,4 @@
     public VerticalCRS createVerticalCRS(String code) throws FactoryException {
         return factory().createVerticalCRS(code);
     }
-
-    @Override
-    public ParametricCRS createParametricCRS(String code) throws FactoryException {
-        return factory().createParametricCRS(code);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCS.java
index d9ce915..3f76550 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyCS.java
@@ -30,9 +30,6 @@
 import org.opengis.referencing.cs.VerticalCS;
 import org.apache.sis.referencing.CRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.cs.ParametricCS;
-
 
 /**
  * Same as {@link EPSGFactoryProxyCRS} but for coordinate systems.
@@ -93,11 +90,6 @@
     }
 
     @Override
-    public ParametricCS createParametricCS(String code) throws FactoryException {
-        return factory().createParametricCS(code);
-    }
-
-    @Override
     public CoordinateSystemAxis createCoordinateSystemAxis(String code) throws FactoryException {
         return factory().createCoordinateSystemAxis(code);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyDatum.java
index 33dc34e..fba7aef 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGFactoryProxyDatum.java
@@ -30,9 +30,6 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.datum.ImageDatum;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.ParametricDatum;
-
 
 /**
  * Same as {@link EPSGFactoryProxyCRS} but for datum.
@@ -92,9 +89,4 @@
     public VerticalDatum createVerticalDatum(String code) throws FactoryException {
         return factory().createVerticalDatum(code);
     }
-
-    @Override
-    public ParametricDatum createParametricDatum(String code) throws FactoryException {
-        return factory().createParametricDatum(code);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Legacy.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Legacy.java
index ca37336..ff134ec 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Legacy.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Legacy.java
@@ -29,6 +29,33 @@
 import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 
+// Specific to the main and geoapi-4.0 branches:
+import java.util.Date;
+import java.time.Instant;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
+import java.time.ZoneOffset;
+import java.time.LocalTime;
+import java.time.OffsetTime;
+import java.time.temporal.Temporal;
+
+// Specific to the main branch:
+import java.util.List;
+import java.util.Objects;
+import java.util.Collection;
+import org.opengis.referencing.ReferenceSystem;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.extent.Extent;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.apache.sis.referencing.DefaultObjectDomain;
+import org.apache.sis.referencing.AbstractIdentifiedObject;
+
 
 /**
  * Utilities related to version 1 of Well Known Text format, or to ISO 19111:2007.
@@ -63,7 +90,6 @@
      * the ISO 19111's ones (ISO names are "Geocentric X", "Geocentric Y" and "Geocentric Z"). This constant uses
      * the invalid names and directions for WKT 1 parsing/formatting purposes.
      */
-    @SuppressWarnings("deprecation")
     private static final CartesianCS GEOCENTRIC = new DefaultCartesianCS(Map.of(NAME_KEY, "Legacy geocentric"),
             new DefaultCoordinateSystemAxis(Map.of(NAME_KEY, "X"), "X", AxisDirection.OTHER, Units.METRE),
             new DefaultCoordinateSystemAxis(Map.of(NAME_KEY, "Y"), "Y", AxisDirection.EAST,  Units.METRE),
@@ -124,4 +150,96 @@
         }
         return cs;
     }
+
+    /**
+     * Temporary getter method used as a workaround for the lack of this method in GeoAPI 3.0 interface.
+     *
+     * @param  object  the object from which to get the domain.
+     * @return domain of the given object.
+     */
+    public static Collection<DefaultObjectDomain> getDomains(final IdentifiedObject object) {
+        if (object instanceof AbstractIdentifiedObject) {
+            return ((AbstractIdentifiedObject) object).getDomains();
+        }
+        final Extent domainOfValidity;
+        final InternationalString scope;
+        if (object instanceof ReferenceSystem) {
+            final var c = (ReferenceSystem) object;
+            scope = c.getScope();
+            domainOfValidity = c.getDomainOfValidity();
+        } else if (object instanceof Datum) {
+            final var c = (Datum) object;
+            scope = c.getScope();
+            domainOfValidity = c.getDomainOfValidity();
+        } else if (object instanceof CoordinateOperation) {
+            final var c = (CoordinateOperation) object;
+            scope = c.getScope();
+            domainOfValidity = c.getDomainOfValidity();
+        } else {
+            return null;
+        }
+        if (scope == null && domainOfValidity == null) {
+            return null;
+        }
+        return List.of(new DefaultObjectDomain(scope, domainOfValidity));
+    }
+
+    /**
+     * Returns the first non-null scope found in the given collection.
+     *
+     * @param  domains  the value of {@link AbstractIdentifiedObject#getDomains()}.
+     * @return a description of domain of usage, or {@code null} if none.
+     */
+    public static InternationalString getScope(final Collection<DefaultObjectDomain> domains) {
+        return domains.stream().map(DefaultObjectDomain::getScope).filter(Objects::nonNull).findFirst().orElse(null);
+    }
+
+    /**
+     * Returns the first non-null domain of validity found in the given collection.
+     *
+     * @param  domains  the value of {@link AbstractIdentifiedObject#getDomains()}.
+     * @return the valid domain, or {@code null} if not available.
+     */
+    public static Extent getDomainOfValidity(final Collection<DefaultObjectDomain> domains) {
+        return domains.stream().map(DefaultObjectDomain::getDomainOfValidity).filter(Objects::nonNull).findFirst().orElse(null);
+    }
+
+    /**
+     * Converts a {@link java.time} object to a legacy {@link Date} object.
+     * If the time zone is not specified, UTC is assumed.
+     *
+     * @param  t  the date to convert.
+     * @return the given temporal object as a date, or {@code null} if the method doesn't know how to convert.
+     * @throws ArithmeticException if numeric overflow occurs.
+     */
+    public static Date toDate(final Temporal t) {
+        final Instant instant;
+        if (t instanceof Instant) {
+            instant = (Instant) t;
+        } else {
+            final OffsetDateTime odt;
+            if (t instanceof OffsetDateTime) {
+                odt = (OffsetDateTime) t;
+            } else if (t instanceof ZonedDateTime) {
+                odt = ((ZonedDateTime) t).toOffsetDateTime();
+            } else if (t instanceof LocalDateTime) {
+                odt = ((LocalDateTime) t).atOffset(ZoneOffset.UTC);
+            } else {
+                final LocalDate date;
+                if (t instanceof LocalDate) {
+                    date = (LocalDate) t;
+                } else if (t instanceof YearMonth) {
+                    date = ((YearMonth) t).atDay(1);
+                } else if (t instanceof Year) {
+                    date = ((Year) t).atDay(1);
+                } else {
+                    return null;
+                }
+                odt = date.atTime(OffsetTime.of(LocalTime.MIDNIGHT, ZoneOffset.UTC));
+            }
+            instant = odt.toInstant();
+        }
+        // Do not use `Date.from(Instant)` because we want the `ArithmeticException` in case of overflow.
+        return new Date(instant.toEpochMilli());
+    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
index 5604b18..af0bbe8 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
@@ -30,8 +30,8 @@
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.util.Utilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
index 4a6635c..e4d5b83 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ServicesForMetadata.java
@@ -46,6 +46,7 @@
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.CoordinateFormat;
+import org.apache.sis.metadata.privy.ReferencingServices;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.IdentifiedObjects;
@@ -59,7 +60,6 @@
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
-import org.apache.sis.metadata.privy.ReferencingServices;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.system.Modules;
@@ -69,8 +69,26 @@
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.logging.Logging;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import java.util.Map;
+import org.opengis.util.NoSuchIdentifierException;
+import org.opengis.util.TypeName;
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.referencing.crs.CRSFactory;
+import org.opengis.referencing.cs.CSFactory;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.SingleOperation;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.datum.DatumFactory;
+import org.apache.sis.metadata.privy.NameToIdentifier;
+import org.apache.sis.util.Deprecable;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
 
 
 /**
@@ -430,7 +448,7 @@
          * If above code did not found an EPSG code, discard EPSG codes that
          * we may find in the loop below because they are probably invalid.
          */
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             if (!Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 return IdentifiedObjects.toString(id);
             }
@@ -448,6 +466,15 @@
     ///////////////////////////////////////////////////////////////////////////////////////
 
     /**
+     * Returns the name of the type of values.
+     */
+    @Override
+    public TypeName getValueType(final ParameterDescriptor<?> parameter) {
+        return (parameter instanceof DefaultParameterDescriptor<?>)
+                ? ((DefaultParameterDescriptor<?>) parameter).getValueType() : null;
+    }
+
+    /**
      * Returns a fully implemented parameter descriptor.
      *
      * @param  <T>        the type of values.
@@ -460,6 +487,80 @@
     }
 
     /**
+     * Creates a parametric CS. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the coordinate system name, and optionally other properties.
+     * @param  axis        the axis of the parametric coordinate system.
+     * @param  factory     the factory to use for creating the coordinate system.
+     * @return a parametric coordinate system using the given axes.
+     * @throws FactoryException if the parametric object creation failed.
+     */
+    public static CoordinateSystem createParametricCS(final Map<String,?> properties, final CoordinateSystemAxis axis,
+            CSFactory factory) throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = GeodeticObjectFactory.provider();
+        }
+        return ((GeodeticObjectFactory) factory).createParametricCS(properties, axis);
+    }
+
+    /**
+     * Creates a parametric datum. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the datum name, and optionally other properties.
+     * @param  factory     the factory to use for creating the datum.
+     * @return a parametric datum using the given name.
+     * @throws FactoryException if the parametric object creation failed.
+     */
+    public static Datum createParametricDatum(final Map<String,?> properties, DatumFactory factory)
+            throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = GeodeticObjectFactory.provider();
+        }
+        return ((GeodeticObjectFactory) factory).createParametricDatum(properties);
+    }
+
+    /**
+     * Creates a parametric CRS. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the coordinate reference system name, and optionally other properties.
+     * @param  datum       the parametric datum.
+     * @param  cs          the parametric coordinate system.
+     * @param  factory     the factory to use for creating the coordinate reference system.
+     * @return a parametric coordinate system using the given axes.
+     * @throws FactoryException if the parametric object creation failed.
+     */
+    public static SingleCRS createParametricCRS(final Map<String,?> properties, final Datum datum,
+            final CoordinateSystem cs, CRSFactory factory) throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = GeodeticObjectFactory.provider();
+        }
+        try {
+            return ((GeodeticObjectFactory) factory).createParametricCRS(properties,
+                    (DefaultParametricDatum) datum, (DefaultParametricCS) cs);
+        } catch (ClassCastException e) {
+            throw new InvalidGeodeticParameterException(e.toString(), e);
+        }
+    }
+
+    /**
      * Creates a format for {@link DirectPosition} instances.
      *
      * @param  locale    the locale for the new {@code Format}, or {@code null} for {@code Locale.ROOT}.
@@ -482,6 +583,66 @@
     }
 
     /**
+     * Returns the coordinate operation method for the given classification.
+     * This method checks if the given {@code opFactory} is a SIS implementation
+     * before to fallback on a slower fallback.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  opFactory  The coordinate operation factory to use if it is a SIS implementation.
+     * @param  mtFactory  The math transform factory to use as a fallback.
+     * @param  identifier The name or identifier of the operation method to search.
+     * @return The coordinate operation method for the given name or identifier.
+     * @throws FactoryException if an error occurred which searching for the given method.
+     */
+    public static OperationMethod getOperationMethod(final CoordinateOperationFactory opFactory,
+            final MathTransformFactory mtFactory, final String identifier) throws FactoryException
+    {
+        if (opFactory instanceof DefaultCoordinateOperationFactory) {
+            ((DefaultCoordinateOperationFactory) opFactory).getOperationMethod(identifier);
+        }
+        final OperationMethod method = getOperationMethod(mtFactory.getAvailableMethods(SingleOperation.class), identifier);
+        if (method != null) {
+            return method;
+        }
+        throw new NoSuchIdentifierException("No such operation method: " + identifier, identifier);
+    }
+
+    /**
+     * Returns the operation method for the specified name or identifier. The given argument shall be either a
+     * method name (e.g. <cite>"Transverse Mercator"</cite>) or one of its identifiers (e.g. {@code "EPSG:9807"}).
+     *
+     * @param  methods     the method candidates.
+     * @param  identifier  the name or identifier of the operation method to search.
+     * @return the coordinate operation method for the given name or identifier, or {@code null} if none.
+     *
+     * @see org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory#getOperationMethod(String)
+     * @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#getOperationMethod(String)
+     */
+    private static OperationMethod getOperationMethod(final Iterable<? extends OperationMethod> methods, final String identifier) {
+        OperationMethod fallback = null;
+        for (final OperationMethod method : methods) {
+            if (IdentifiedObjects.isHeuristicMatchForName(method, identifier) ||
+                    NameToIdentifier.isHeuristicMatchForIdentifier(method.getIdentifiers(), identifier))
+            {
+                /*
+                 * Stop the iteration at the first non-deprecated method.
+                 * If we find only deprecated methods, take the first one.
+                 */
+                if (!(method instanceof Deprecable) || !((Deprecable) method).isDeprecated()) {
+                    return method;
+                }
+                if (fallback == null) {
+                    fallback = method;
+                }
+            }
+        }
+        return fallback;
+    }
+
+    /**
      * Returns information about the Apache SIS configuration.
      * See super-class for a list of keys.
      *
@@ -504,9 +665,9 @@
                     final String msg = Exceptions.getLocalizedMessage(e, locale);
                     return (msg != null) ? msg : e.toString();
                 }
-                if (authority != null) {
+                if (authority instanceof DefaultCitation) {
                     final OnLineFunction f = OnLineFunction.valueOf(CONNECTION);
-                    for (final OnlineResource res : authority.getOnlineResources()) {
+                    for (final OnlineResource res : ((DefaultCitation) authority).getOnlineResources()) {
                         if (f.equals(res.getFunction())) {
                             final InternationalString i18n = res.getDescription();
                             if (i18n != null) return i18n.toString(locale);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
index b28513b..a78390b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
@@ -29,19 +29,24 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.datum.VerticalDatumType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import java.util.function.Predicate;
+import org.opengis.util.CodeList;
+import org.apache.sis.util.StringBuilders;
+import org.apache.sis.util.privy.CodeLists;
 
 
 /**
- * Extensions to the standard set of {@link RealizationEpoch}.
- * Some of those constants are derived from a legacy {@link VerticalDatumType} code list.
+ * Extensions to the standard set of {@link VerticalDatumType}.
  * Those constants are not in public API because they were intentionally omitted from ISO 19111,
  * and the ISO experts said that they should really not be public.
  *
+ * <p>This class implements {@link Predicate} for opportunist reasons.
+ * This implementation convenience may change in any future SIS version.</p>
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  */
-public final class VerticalDatumTypes {
+public final class VerticalDatumTypes implements Predicate<CodeList<?>> {
     /**
      * A pseudo-realization method for ellipsoidal heights that are measured along
      * the normal to the ellipsoid used in the definition of horizontal datum.
@@ -77,25 +82,19 @@
     static final String BAROMETRIC = "BAROMETRIC";
 
     /**
-     * Do not allow instantiation of this class.
-     */
-    private VerticalDatumTypes() {
-    }
-
-    /**
      * Returns a pseudo-realization method for ellipsoidal heights.
      * <strong>The use of this method is deprecated</strong> as ellipsoidal height
      * should never be separated from the horizontal components according ISO 19111.
      *
      * <h4>Maintenance note</h4>
-     * If the implementation of this method is modified, search for {@code RealizationMethod.valueOf}
+     * If the implementation of this method is modified, search for {@code VerticalDatumType.valueOf}
      * at least in {@link org.apache.sis.referencing.CommonCRS.Vertical#datum()} and make sure that
      * the code is equivalent.
      *
      * @return the ellipsoidal pseudo-realization method.
      */
-    public static RealizationMethod ellipsoidal() {
-        return RealizationMethod.valueOf(ELLIPSOIDAL);
+    public static VerticalDatumType ellipsoidal() {
+        return VerticalDatumType.valueOf(ELLIPSOIDAL);
     }
 
     /**
@@ -104,7 +103,7 @@
      * @param  method  the method to test, or {@code null}.
      * @return whether the given method is the ellipsoidal pseudo-realization method.
      */
-    public static boolean ellipsoidal(final RealizationMethod method) {
+    public static boolean ellipsoidal(final VerticalDatumType method) {
         return (method != null) && ELLIPSOIDAL.equalsIgnoreCase(method.name());
     }
 
@@ -116,14 +115,14 @@
      * @param  code  the legacy vertical datum code.
      * @return the vertical datum type, or {@code null} if none.
      */
-    public static RealizationMethod fromLegacy(final int code) {
+    public static VerticalDatumType fromLegacy(final int code) {
         switch (code) {
         //  case 2000: return null;                                     // CS_VD_Other
-            case 2001: return RealizationMethod.valueOf(ORTHOMETRIC);   // CS_VD_Orthometric
+            case 2001: return VerticalDatumType.valueOf(ORTHOMETRIC);   // CS_VD_Orthometric
             case 2002: return ellipsoidal();                            // CS_VD_Ellipsoidal
-            case 2003: return RealizationMethod.valueOf(BAROMETRIC);    // CS_VD_AltitudeBarometric
-            case 2005: return RealizationMethod.GEOID;                  // CS_VD_GeoidModelDerived
-            case 2006: return RealizationMethod.TIDAL;                  // CS_VD_Depth
+            case 2003: return VerticalDatumType.BAROMETRIC;             // CS_VD_AltitudeBarometric
+            case 2005: return VerticalDatumType.GEOIDAL;                // CS_VD_GeoidModelDerived
+            case 2006: return VerticalDatumType.DEPTH;                  // CS_VD_Depth
             default:   return null;
         }
     }
@@ -150,44 +149,6 @@
     }
 
     /**
-     * Returns the vertical datum type from a realization method.
-     * If the given method cannot be mapped to a legacy type, then this method returns "other surface".
-     * This is because the vertical datum type was a mandatory property in legacy OGC/ISO standards.
-     * This method is used for writing GML documents older than GML 3.2.
-     *
-     * @param  method  the realization method, or {@code null}.
-     * @return the vertical datum type (never null).
-     */
-    @SuppressWarnings("deprecation")
-    public static VerticalDatumType fromMethod(final RealizationMethod method) {
-        if (method == RealizationMethod.GEOID) return VerticalDatumType.GEOIDAL;
-        if (method == RealizationMethod.TIDAL) return VerticalDatumType.DEPTH;
-        if (method != null) {
-            return VerticalDatumType.valueOf(method.name().toUpperCase(Locale.US));
-        }
-        return VerticalDatumType.OTHER_SURFACE;
-    }
-
-    /**
-     * Returns the realization method from a vertical datum type.
-     * This method is used for reading GML documents older than GML 3.2.
-     *
-     * @param  type  the vertical datum type, or {@code null}.
-     * @return the realization method, or {@code null} if none.
-     */
-    @SuppressWarnings("deprecation")
-    public static RealizationMethod toMethod(final VerticalDatumType type) {
-        if (type != null) {
-            if (type == VerticalDatumType.GEOIDAL)         return RealizationMethod.GEOID;
-            if (type == VerticalDatumType.DEPTH)           return RealizationMethod.TIDAL;
-            if (type == VerticalDatumType.BAROMETRIC)      return RealizationMethod.valueOf(BAROMETRIC);
-            if (ORTHOMETRIC.equalsIgnoreCase(type.name())) return RealizationMethod.valueOf(ORTHOMETRIC);
-            if (ELLIPSOIDAL.equalsIgnoreCase(type.name())) return ellipsoidal();
-        }
-        return null;
-    }
-
-    /**
      * Guesses the realization method of a datum from its name, aliases or a given vertical axis.
      * This is sometimes needed after XML unmarshalling or WKT parsing, because GML 3.2 and ISO 19162
      * do not contain any attribute for the datum type.
@@ -197,12 +158,12 @@
      * @param  name     the name of the datum for which to guess a type, or {@code null} if unknown.
      * @param  aliases  the aliases of the datum for which to guess a type, or {@code null} if unknown.
      * @param  axis     the vertical axis for which to guess a type, or {@code null} if unknown.
-     * @return a datum type, or {@code null} if none can be guessed.
+     * @return a datum type, or {@link VerticalDatumType#OTHER_SURFACE} if none can be guessed.
      */
-    public static RealizationMethod guess(final String name, final Collection<? extends GenericName> aliases,
+    public static VerticalDatumType guess(final String name, final Collection<? extends GenericName> aliases,
             final CoordinateSystemAxis axis)
     {
-        RealizationMethod method = guess(name);
+        VerticalDatumType method = guess(name);
         if (method != null) {
             return method;
         }
@@ -222,19 +183,19 @@
                     AxisDirection dir = AxisDirection.UP;               // Expected direction for accepting the type.
                     switch (abbreviation.charAt(0)) {
                         case 'h': method = ellipsoidal(); break;
-                        case 'H': method = RealizationMethod.GEOID; break;
-                        case 'D': method = RealizationMethod.TIDAL; dir = AxisDirection.DOWN; break;
-                        default:  return null;
+                        case 'H': method = VerticalDatumType.GEOIDAL; break;
+                        case 'D': method = VerticalDatumType.DEPTH; dir = AxisDirection.DOWN; break;
+                        default:  return VerticalDatumType.OTHER_SURFACE;
                     }
                     if (dir.equals(axis.getDirection())) {
                         return method;
                     }
                 }
             } else if (Units.isPressure(unit)) {
-                return RealizationMethod.valueOf(BAROMETRIC);
+                return VerticalDatumType.BAROMETRIC;
             }
         }
-        return null;
+        return VerticalDatumType.OTHER_SURFACE;
     }
 
     /**
@@ -244,15 +205,59 @@
      * @param  name  name of the datum for which to guess a realization method, or {@code null}.
      * @return a realization method, or {@code null} if none can be guessed.
      */
-    private static RealizationMethod guess(final String name) {
+    private static VerticalDatumType guess(final String name) {
         if (name != null) {
             if (CharSequences.equalsFiltered("Mean Sea Level", name, Characters.Filter.LETTERS_AND_DIGITS, true)) {
-                return RealizationMethod.TIDAL;
+                return VerticalDatumType.GEOIDAL;
             }
-            if (name.contains("geoid")) {
-                return RealizationMethod.GEOID;
+            for (int i=0; i<name.length();) {
+                final int c = name.codePointAt(i);
+                if (Character.isLetter(c)) {
+                    return CodeLists.find(VerticalDatumType.class, new VerticalDatumTypes(name));
+                }
+                i += Character.charCount(c);
             }
         }
         return null;
     }
+
+    /**
+     * The name of a datum to compare against the list of datum types,
+     * in upper-case and (if possible) using US-ASCII characters.
+     */
+    private final StringBuilder datum;
+
+    /**
+     * Creates a new {@code CodeList.Filter} which will compare the given datum name against the list of datum types.
+     * The comparison is case-insensitive and ignores some non-ASCII characters. The exact algorithm applied here is
+     * implementation dependent and may change in any future version.
+     *
+     * @param  name  the datum name.
+     */
+    private VerticalDatumTypes(final String name) {
+        final int length = name.length();
+        datum = new StringBuilder(length);
+        for (int i=0; i<length;) {
+            final int c = name.codePointAt(i);
+            datum.appendCodePoint(Character.toUpperCase(c));
+            i += Character.charCount(c);
+        }
+        StringBuilders.toASCII(datum);
+    }
+
+    /**
+     * Returns {@code true} if the name of the given code is the prefix of a word in the datum name.
+     * We do not test the characters following the prefix because the word may be incomplete
+     * (e.g. {@code "geoid"} versus {@code "geoidal"}).
+     *
+     * <p>This method is public as an implementation side-effect and should be ignored.</p>
+     *
+     * @param  code  the code to test.
+     * @return {@code true} if the code matches the criterion.
+      */
+    @Override
+    public boolean test(final CodeList<?> code) {
+        final int i = datum.indexOf(code.name());
+        return (i == 0) || (i >= 0 && Character.isWhitespace(datum.codePointBefore(i)));
+    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 4acbf9f..c9d5f3c 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -77,9 +77,14 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.crs.DerivedCRS;
-import org.opengis.coordinate.CoordinateSet;
+// Specific to the main branch:
+import java.time.temporal.Temporal;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19111;
+import org.opengis.metadata.extent.Extent;
+import org.apache.sis.referencing.internal.Legacy;
+import org.apache.sis.coordinate.AbstractCoordinateSet;
 
 
 /**
@@ -291,8 +296,8 @@
      *     <td>{@link Identifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
-     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td>
+     *     <td>"domains"</td>
+     *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain} (optionally as array)</td>
      *     <td>{@link #getDomains()}</td>
      *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
@@ -422,7 +427,7 @@
         super(operation);
         sourceCRS                   = operation.getSourceCRS();
         targetCRS                   = operation.getTargetCRS();
-        interpolationCRS            = operation.getInterpolationCRS().orElse(null);
+        interpolationCRS            = getInterpolationCRS(operation);
         operationVersion            = operation.getOperationVersion();
         coordinateOperationAccuracy = operation.getCoordinateOperationAccuracy();
         transform                   = operation.getMathTransform();
@@ -486,20 +491,19 @@
      * But SIS supports also defining conversions with non-null CRS provided that:
      *
      * <ul>
-     *   <li>{@link DerivedCRS#getBaseCRS()} is the {@linkplain #getSourceCRS() source CRS} of this operation, and</li>
-     *   <li>{@link DerivedCRS#getConversionFromBase()} is this operation instance.</li>
+     *   <li>{@link GeneralDerivedCRS#getBaseCRS()} is the {@linkplain #getSourceCRS() source CRS} of this operation, and</li>
+     *   <li>{@link GeneralDerivedCRS#getConversionFromBase()} is this operation instance.</li>
      * </ul>
      *
      * When this method returns {@code true}, the source and target CRS are not marshalled in XML documents.
      *
      * @return {@code true} if this coordinate operation is for the definition of a derived or projected CRS.
      */
-    @SuppressWarnings("deprecation")
     public boolean isDefiningConversion() {
         /*
          * Trick: we do not need to verify if (this instanceof Conversion) because:
          *   - Only DefaultConversion constructor accepts null source and target CRS.
-         *   - DerivedCRS.getConversionFromBase() return type is Conversion.
+         *   - GeneralDerivedCRS.getConversionFromBase() return type is Conversion.
          */
         return (sourceCRS == null && targetCRS == null)
                || ((targetCRS instanceof GeneralDerivedCRS)
@@ -546,12 +550,45 @@
      *
      * @return the <abbr>CRS</abbr> required for interpolating the values.
      */
-    @Override
     public Optional<CoordinateReferenceSystem> getInterpolationCRS() {
         return Optional.ofNullable(interpolationCRS);
     }
 
     /**
+     * Returns the interpolation CRS of the given coordinate operation, or {@code null} if none.
+     */
+    static CoordinateReferenceSystem getInterpolationCRS(final CoordinateOperation operation) {
+        return (operation instanceof AbstractCoordinateOperation)
+               ? ((AbstractCoordinateOperation) operation).getInterpolationCRS().orElse(null) : null;
+    }
+
+    /**
+     * Returns the date at which source coordinate tuples are valid.
+     * This is mandatory if the <abbr>CRS</abbr> is dynamic.
+     *
+     * @return epoch at which source coordinate tuples are valid.
+     *
+     * @since 1.5
+     */
+    @UML(identifier="sourceCoordinateEpoch", obligation=CONDITIONAL, specification=ISO_19111)
+    public Optional<Temporal> getSourceEpoch() {
+        return Optional.empty();
+    }
+
+    /**
+     * Returns the date at which target coordinate tuples are valid.
+     * This is mandatory if the <abbr>CRS</abbr> is dynamic.
+     *
+     * @return epoch at which target coordinate tuples are valid.
+     *
+     * @since 1.5
+     */
+    @UML(identifier="targetCoordinateEpoch", obligation=CONDITIONAL, specification=ISO_19111)
+    public Optional<Temporal> getTargetEpoch() {
+        return Optional.empty();
+    }
+
+    /**
      * Returns the version of the coordinate operation. Different versions of a coordinate
      * {@linkplain DefaultTransformation transformation} may exist because of the stochastic
      * nature of the parameters. In principle this property is irrelevant to coordinate
@@ -622,6 +659,32 @@
     }
 
     /**
+     * Returns the area or region or timeframe in which this coordinate operation is valid.
+     *
+     * @return the coordinate operation valid domain, or {@code null} if not available.
+     *
+     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.4")
+    public Extent getDomainOfValidity() {
+        return Legacy.getDomainOfValidity(getDomains());
+    }
+
+    /**
+     * Returns a description of domain of usage, or limitations of usage, for which this operation is valid.
+     *
+     * @return a description of domain of usage, or {@code null} if none.
+     *
+     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.4")
+    public InternationalString getScope() {
+        return Legacy.getScope(getDomains());
+    }
+
+    /**
      * Returns the object for transforming coordinates in the source CRS to coordinates in the target CRS.
      * The transform may be {@code null} if this coordinate operation is a defining conversion.
      *
@@ -645,7 +708,7 @@
      *   <li>If the <abbr>CRS</abbr> and/or epoch of the given data are not equivalent to the source <abbr>CRS</abbr> and/or
      *       epoch of this coordinate operation, this method will automatically prepend an additional operation step.</li>
      *   <li>The coordinate tuples are not transformed immediately, but instead will be computed on-the-fly
-     *       in the stream returned by {@link CoordinateSet#stream()}.</li>
+     *       in the stream returned by {@link AbstractCoordinateSet#stream()}.</li>
      *   <li>If a {@link TransformException} occurs during on-the-fly coordinate operation, it will be wrapped
      *       in an unchecked {@link org.apache.sis.util.collection.BackingStoreException}.</li>
      *   <li>The returned coordinate set is serializable if the given data and the math transform are also serializable.</li>
@@ -659,8 +722,7 @@
      *
      * @since 1.5
      */
-    @Override
-    public CoordinateSet transform(final CoordinateSet data) throws TransformException {
+    public AbstractCoordinateSet transform(final AbstractCoordinateSet data) throws TransformException {
         return new TransformedCoordinateSet(this, data);
     }
 
@@ -768,8 +830,8 @@
      * corresponding wraparound axis in the source CRS because the <em>easting</em> axis in projected CRS does not
      * have a wraparound range meaning. We could argue that
      * {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getDirection() axis directions} match,
-     * but such matching is not guaranteed to exist since {@code ProjectedCRS} is a special case of {@code DerivedCRS}
-     * and derived CRS can have rotations.
+     * but such matching is not guaranteed to exist since {@code ProjectedCRS} is a special case of
+     * {@code GeneralDerivedCRS} and derived CRS can have rotations.
      *
      * <h4>Default implementation</h4>
      * The default implementation infers this set by inspecting the source and target coordinate system axes.
@@ -861,7 +923,7 @@
                 final CoordinateOperation that = (CoordinateOperation) object;
                 if ((mode.isIgnoringMetadata() ||
                     (deepEquals(getCoordinateOperationAccuracy(), that.getCoordinateOperationAccuracy(), mode))) &&
-                     deepEquals(getInterpolationCRS(),            that.getInterpolationCRS(), mode))
+                     deepEquals(getInterpolationCRS().orElse(null), getInterpolationCRS(that), mode))
                 {
                     /*
                      * At this point all metadata match or can be ignored. First, compare the targetCRS.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index 92beac6..b9a1991 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
@@ -230,7 +230,6 @@
      * @since 1.0
      */
     @Override
-    @SuppressWarnings("deprecation")
     public List<CoordinateOperation> createOperations(final CoordinateReferenceSystem sourceCRS,
                                                       final CoordinateReferenceSystem targetCRS)
             throws FactoryException
@@ -372,7 +371,7 @@
     /**
      * Creates operations from an arbitrary single CRS to a derived coordinate reference system.
      * Conversions from {@code GeographicCRS} to {@code ProjectedCRS} are also handled by this method,
-     * since projected CRS are a special kind of {@code DerivedCRS}.
+     * since projected CRS are a special kind of {@code GeneralDerivedCRS}.
      *
      * <p>The default implementation constructs the following operation chain:</p>
      * <blockquote><code>sourceCRS  →  {@linkplain DerivedCRS#getBaseCRS() baseCRS}  →  targetCRS</code></blockquote>
@@ -389,7 +388,6 @@
      * @return coordinate operations from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation cannot be constructed.
      */
-    @SuppressWarnings("deprecation")
     protected List<CoordinateOperation> createOperationStep(final SingleCRS sourceCRS,
                                                             final GeneralDerivedCRS targetCRS)
             throws FactoryException
@@ -409,7 +407,7 @@
     /**
      * Creates an operation from a derived CRS to an arbitrary single coordinate reference system.
      * Conversions from {@code ProjectedCRS} to {@code GeographicCRS} are also handled by this method,
-     * since projected CRS are a special kind of {@code DerivedCRS}.
+     * since projected CRS are a special kind of {@code GeneralDerivedCRS}.
      *
      * <p>The default implementation constructs the following operation chain:</p>
      * <blockquote><code>sourceCRS  →  {@linkplain DerivedCRS#getBaseCRS() baseCRS}  →  targetCRS</code></blockquote>
@@ -426,7 +424,6 @@
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation cannot be constructed.
      */
-    @SuppressWarnings("deprecation")
     protected List<CoordinateOperation> createOperationStep(final GeneralDerivedCRS sourceCRS,
                                                             final SingleCRS targetCRS)
             throws FactoryException
@@ -469,7 +466,6 @@
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation cannot be constructed.
      */
-    @SuppressWarnings("deprecation")
     protected List<CoordinateOperation> createOperationStep(final GeneralDerivedCRS sourceCRS,
                                                             final GeneralDerivedCRS targetCRS)
             throws FactoryException
@@ -1291,7 +1287,6 @@
      * @param  crs  the CRS having a conversion that cannot be inverted.
      * @return a default error message.
      */
-    @SuppressWarnings("deprecation")
     private String canNotInvert(final GeneralDerivedCRS crs) {
         return resources().getString(Resources.Keys.NonInvertibleOperation_1, label(crs.getConversionFromBase()));
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index f2c0519..72912b3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -426,7 +426,6 @@
      * axis order, we want to check also the coordinate operations using (latitude, longitude) axis order
      * because they may be the only ones available.</p>
      */
-    @SuppressWarnings("deprecation")
     private static boolean isEasySearch(final CoordinateReferenceSystem crs) {
         if (crs instanceof GeneralDerivedCRS) {
             return false;
@@ -999,7 +998,7 @@
             }
         }
         return factorySIS.createSingleOperation(properties, sourceCRS, targetCRS,
-                operation.getInterpolationCRS().orElse(null), method, transform);
+                AbstractCoordinateOperation.getInterpolationCRS(operation), method, transform);
     }
 
     /**
@@ -1338,13 +1337,10 @@
                 if (descriptor != null) {
                     final Identifier name = descriptor.getName();
                     if (name != null) {
-                        method = factory.getOperationMethod(name.getCode());
+                        method = factorySIS.getOperationMethod(name.getCode());
                     }
                     if (method == null) {
-                        method = factory.createOperationMethod(properties,
-                                sourceCRS.getCoordinateSystem().getDimension(),
-                                targetCRS.getCoordinateSystem().getDimension(),
-                                descriptor);
+                        method = factorySIS.createOperationMethod(properties, descriptor);
                     }
                 }
             }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
index 8e5f601..ab87dbe 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
@@ -114,7 +114,7 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}</td>
      *     <td>{@link org.opengis.metadata.extent.Extent}</td>
      *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getDomainOfValidity()}</td>
      *   </tr>
@@ -223,7 +223,6 @@
      * @param target      the new target CRS.
      * @param factory     the factory to use for creating a transform from the parameters or for performing axis changes.
      */
-    @SuppressWarnings("deprecation")
     DefaultConversion(final Conversion definition,
                       final CoordinateReferenceSystem source,
                       final CoordinateReferenceSystem target,
@@ -372,7 +371,6 @@
      *
      * @since 1.5
      */
-    @SuppressWarnings("deprecation")
     public Conversion specialize(final CoordinateReferenceSystem sourceCRS,
                                  final CoordinateReferenceSystem targetCRS,
                                  MathTransformFactory factory) throws FactoryException
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index d5b622b..4a31808 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -280,7 +280,6 @@
      *
      * @see DefaultMathTransformFactory#getOperationMethod(String)
      */
-    @Override
     public OperationMethod getOperationMethod(String name) throws FactoryException {
         ArgumentChecks.ensureNonEmpty("name", name = name.strip());
         @SuppressWarnings("LocalVariableHidesMemberVariable")
@@ -353,18 +352,6 @@
     }
 
     /**
-     * @deprecated The dimensions attributes have been removed in ISO 19111:2019 revision.
-     */
-    @Override
-    @Deprecated(since = "1.4")
-    public OperationMethod createOperationMethod(final Map<String,?> properties,
-            final Integer sourceDimensions, final Integer targetDimensions,
-            ParameterDescriptorGroup parameters) throws FactoryException
-    {
-        return createOperationMethod(properties, parameters);
-    }
-
-    /**
      * Creates a defining conversion from the given operation parameters.
      * This conversion has no source and target CRS since those elements are usually unknown at this stage.
      * The source and target CRS will become known later, at the
@@ -392,7 +379,7 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@link DefaultConversion#getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}</td>
      *     <td>{@link org.opengis.metadata.extent.Extent}</td>
      *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getDomainOfValidity()}</td>
      *   </tr>
@@ -481,7 +468,7 @@
      *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
      *     <td>{@link DefaultConversion#getIdentifiers()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}</td>
      *     <td>{@link org.opengis.metadata.extent.Extent}</td>
      *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getDomainOfValidity()}</td>
      *   </tr>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
index 0980d5a..36c9d91 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
@@ -515,7 +515,6 @@
      * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#118">WKT 2 specification §17.2.3</a>
      */
     @Override
-    @SuppressWarnings("deprecation")
     protected String formatTo(final Formatter formatter) {
         final boolean isWKT1 = formatter.getConvention().majorVersion() == 1;
         /*
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultTransformation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultTransformation.java
index d3bdca7..058f194 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultTransformation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultTransformation.java
@@ -87,7 +87,7 @@
      *     <td>{@link org.opengis.metadata.quality.PositionalAccuracy} (optionally as array)</td>
      *     <td>{@link #getCoordinateOperationAccuracy()}</td>
      *   </tr><tr>
-     *     <td>{@value org.opengis.referencing.ObjectDomain#DOMAIN_OF_VALIDITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}</td>
      *     <td>{@link org.opengis.metadata.extent.Extent}</td>
      *     <td>{@link org.apache.sis.referencing.DefaultObjectDomain#getDomainOfValidity()}</td>
      *   </tr>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java
index b0a7591..ba7d05f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/InverseOperationMethod.java
@@ -40,8 +40,9 @@
 import org.apache.sis.util.Deprecable;
 import org.apache.sis.util.collection.Containers;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.apache.sis.referencing.DefaultObjectDomain;
+import org.apache.sis.referencing.internal.Legacy;
 
 
 /**
@@ -138,7 +139,7 @@
      * @param target  where to store the properties of the inverse operation.
      */
     static void properties(final SingleOperation source, final Map<String,Object> target) {
-        target.put(SingleOperation.DOMAINS_KEY, source.getDomains().toArray(ObjectDomain[]::new));
+        target.put("domains", Legacy.getDomains(source).toArray(DefaultObjectDomain[]::new));
         final Collection<PositionalAccuracy> accuracy = source.getCoordinateOperationAccuracy();
         if (!Containers.isNullOrEmpty(accuracy)) {
             target.put(SingleOperation.COORDINATE_OPERATION_ACCURACY_KEY,
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MismatchedDatumException.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MismatchedDatumException.java
index 6e8666c..bc5abc2 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MismatchedDatumException.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MismatchedDatumException.java
@@ -36,8 +36,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.6
  *
- * @see org.opengis.coordinate.MismatchedCoordinateMetadataException
- * @see org.opengis.coordinate.MismatchedDimensionException
+ * @see org.opengis.geometry.MismatchedDimensionException
  * @see org.apache.sis.referencing.operation.matrix.MismatchedMatrixSizeException
  *
  * @since 0.6
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
index 2f0075b..e845b6e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
@@ -25,6 +25,9 @@
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 
+// Specific to the main branch:
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+
 
 /**
  * Information about the operation from a source component to a target component in {@code CompoundCRS} instances.
@@ -51,12 +54,11 @@
      * {@link ProjectedCRS} and {@link DerivedCRS} are not in this list because we rather use their base CRS
      * as the criterion for determining their type.
      */
-    @SuppressWarnings("deprecation")
     private static final Class<?>[][] COMPATIBLE_TYPES = {
         {GeodeticCRS.class},
         {VerticalCRS.class, GeodeticCRS.class},
         {TemporalCRS.class},
-        {ParametricCRS.class},
+        {DefaultParametricCRS.class},
         {EngineeringCRS.class},
         {ImageCRS.class}
     };
@@ -65,7 +67,6 @@
      * Returns the class of the given CRS after unwrapping derived and projected CRS.
      * The returned type is for use with {@link #COMPATIBLE_TYPES}.
      */
-    @SuppressWarnings("deprecation")
     private static Class<?> type(SingleCRS crs) {
         while (crs instanceof GeneralDerivedCRS) {
             crs = (SingleCRS) ((GeneralDerivedCRS) crs).getBaseCRS();
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java
index e51b222..68ff144 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java
@@ -33,10 +33,8 @@
 import org.apache.sis.coordinate.DefaultCoordinateMetadata;
 import org.apache.sis.coordinate.AbstractCoordinateSet;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coordinate.CoordinateSet;
-import org.opengis.coordinate.CoordinateMetadata;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -50,13 +48,12 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -6533100977777070895L;
+    private static final long serialVersionUID = -6533100977777070894L;
 
     /**
      * The data to transform.
      */
-    @SuppressWarnings("serial")     // Apache SIS implementations of this interface are serializable.
-    private final CoordinateSet data;
+    private final AbstractCoordinateSet data;
 
     /**
      * The transform to apply on coordinate tuples.
@@ -74,14 +71,16 @@
      * @param  data  the coordinate tuples to transform.
      * @throws TransformException if the transform cannot be prepared.
      */
-    TransformedCoordinateSet(final AbstractCoordinateOperation op, final CoordinateSet data) throws TransformException {
+    TransformedCoordinateSet(final AbstractCoordinateOperation op, final AbstractCoordinateSet data)
+            throws TransformException
+    {
         super(new DefaultCoordinateMetadata(op.getTargetCRS(), op.getTargetEpoch().orElse(null)));
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         MathTransform transform = op.getMathTransform();
         if (transform == null) {
             throw new TransformException(Resources.format(Resources.Keys.OperationHasNoTransform_2, op.getClass(), op.getName()));
         }
-        final CoordinateMetadata metadata = data.getCoordinateMetadata();
+        final DefaultCoordinateMetadata metadata = data.getCoordinateMetadata();
         if (metadata != null) try {
             GeographicBoundingBox aoi = CRS.getGeographicBoundingBox(op);
             final var step = new DefaultCoordinateMetadata(op.getSourceCRS(), op.getSourceEpoch().orElse(null));
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
index 38c0cfb..4c2b865 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
@@ -32,6 +32,7 @@
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.coordinate.Position;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
@@ -61,8 +62,8 @@
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -368,7 +369,7 @@
         int offset = 0;
         for (int i = gridSize.length; i != 0;) {
             final int size = gridSize[--i];
-            final double coordinate = source.getCoordinate(i);
+            final double coordinate = source.getOrdinate(i);
             final int index = (int) coordinate;
             if (index != coordinate) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.NotAnInteger_1, coordinate));
@@ -411,8 +412,7 @@
             return;
         }
         if (actual != expected) {
-            throw new org.opengis.geometry.MismatchedDimensionException(
-                    Errors.format(Errors.Keys.MismatchedDimension_3, "source", expected, actual));
+            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "source", expected, actual));
         }
     }
 
@@ -422,9 +422,8 @@
      * with {@link #numPoints} the index of the next point that we failed to add.
      */
     private MismatchedDimensionException mismatchedDimension(final String name, final int expected, final int actual) {
-        return new org.opengis.geometry.MismatchedDimensionException(
-                Errors.format(Errors.Keys.MismatchedDimension_3,
-                Strings.toIndexed(name, numPoints), expected, actual));
+        return new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
+                    Strings.toIndexed(name, numPoints), expected, actual));
     }
 
     /**
@@ -554,6 +553,13 @@
     }
 
     /**
+     * Returns the direct position of the given position, or {@code null} if none.
+     */
+    private static DirectPosition position(final Position p) {
+        return (p != null) ? p.getDirectPosition() : null;
+    }
+
+    /**
      * Sets all control point (source, target) pairs, overwriting any previous setting.
      * The source positions are all integer coordinates in a rectangle from (0, 0, …) inclusive
      * to {@code gridSize} exclusive where {@code gridSize} is an {@code int[]} array specified
@@ -573,7 +579,7 @@
         }
         final int srcDim = gridToCRS.getSourceDimensions();
         if (srcDim != gridSize.length) {
-            throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+            throw new MismatchedDimensionException(Errors.format(
                     Errors.Keys.MismatchedTransformDimension_4, "gridToCRS", 0, gridSize.length, srcDim));
         }
         final int tgtDim = gridToCRS.getTargetDimensions();
@@ -635,7 +641,7 @@
      *
      * @since 0.8
      */
-    public void setControlPoints(final Map<? extends DirectPosition, ? extends DirectPosition> sourceToTarget)
+    public void setControlPoints(final Map<? extends Position, ? extends Position> sourceToTarget)
             throws MismatchedDimensionException
     {
         ensureModifiable();
@@ -644,9 +650,9 @@
         numPoints  = 0;
         int srcDim = 0;
         int tgtDim = 0;
-        for (final Map.Entry<? extends DirectPosition, ? extends DirectPosition> entry : sourceToTarget.entrySet()) {
-            final DirectPosition src = entry.getKey();   if (src == null) continue;
-            final DirectPosition tgt = entry.getValue(); if (tgt == null) continue;
+        for (final Map.Entry<? extends Position, ? extends Position> entry : sourceToTarget.entrySet()) {
+            final DirectPosition src = position(entry.getKey());   if (src == null) continue;
+            final DirectPosition tgt = position(entry.getValue()); if (tgt == null) continue;
             /*
              * The first time that we get a non-null source and target coordinate, allocate the arrays.
              * The sources arrays are allocated only if the source coordinates are randomly distributed.
@@ -684,11 +690,11 @@
             } else {
                 index = numPoints;
                 for (int i=0; i<srcDim; i++) {
-                    isValid &= Double.isFinite(sources[i][index] = src.getCoordinate(i));
+                    isValid &= Double.isFinite(sources[i][index] = src.getOrdinate(i));
                 }
             }
             for (int i=0; i<tgtDim; i++) {
-                isValid &= Double.isFinite(targets[i][index] = tgt.getCoordinate(i));
+                isValid &= Double.isFinite(targets[i][index] = tgt.getOrdinate(i));
             }
             /*
              * If the point contains some NaN or infinite coordinate values, it is okay to leave it as-is
@@ -789,7 +795,7 @@
          */
         @Override
         public final boolean containsValue(final Object value) {
-            return (value instanceof DirectPosition) && search(targets, ((DirectPosition) value).getCoordinates()) >= 0;
+            return (value instanceof Position) && search(targets, ((Position) value).getDirectPosition().getCoordinate()) >= 0;
         }
 
         /**
@@ -798,7 +804,7 @@
          */
         @Override
         public final boolean containsKey(final Object key) {
-            return (key instanceof DirectPosition) && flatIndex((DirectPosition) key) >= 0;
+            return (key instanceof Position) && flatIndex(((Position) key).getDirectPosition()) >= 0;
         }
 
         /**
@@ -807,8 +813,8 @@
          */
         @Override
         public final DirectPosition get(final Object key) {
-            if (key instanceof DirectPosition) {
-                final int index = flatIndex((DirectPosition) key);
+            if (key instanceof Position) {
+                final int index = flatIndex(((Position) key).getDirectPosition());
                 if (index >= 0) return position(targets, index);
             }
             return null;
@@ -833,7 +839,7 @@
                     int offset = 0;
                     while (i != 0) {
                         final int size = gridSize[--i];
-                        final double coordinate = source.getCoordinate(i);
+                        final double coordinate = source.getOrdinate(i);
                         final int index = (int) coordinate;
                         if (index < 0 || index >= size || index != coordinate) {
                             return -1;
@@ -928,7 +934,7 @@
          * in the flattened array. In non-gridded case, this operation requires linear scan.
          */
         @Override int flatIndex(final DirectPosition source) {
-            return search(sources, source.getCoordinates());
+            return search(sources, source.getCoordinate());
         }
 
         /**
@@ -971,7 +977,7 @@
         verifySourceDimension(source.length);
         final int tgtDim = target.length;
         if (targets != null && tgtDim != targets.length) {
-            throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+            throw new MismatchedDimensionException(Errors.format(
                     Errors.Keys.MismatchedDimension_3, "target", targets.length, tgtDim));
         }
         int index;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
index fba1687..935ecfd 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
@@ -51,8 +51,8 @@
 import org.apache.sis.math.Vector;
 import static org.apache.sis.referencing.operation.builder.ResidualGrid.SOURCE_DIMENSION;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -383,9 +383,8 @@
                 return;
             }
         }
-        throw new org.opengis.geometry.MismatchedDimensionException(
-                Errors.format(Errors.Keys.MismatchedTransformDimension_4,
-                "sourceToGrid", isTarget, SOURCE_DIMENSION, dim));
+        throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedTransformDimension_4,
+                                               "sourceToGrid", isTarget, SOURCE_DIMENSION, dim));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ResidualGrid.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ResidualGrid.java
index ee77693..83d8695 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ResidualGrid.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ResidualGrid.java
@@ -309,11 +309,12 @@
         }
 
         @SuppressWarnings({"CloneInNonCloneableClass", "CloneDoesntCallSuperClone"})
-        @Override public Matrix  clone()        {return this;}
-        @Override public boolean isIdentity()   {return false;}
-        @Override public int     getNumCol()    {return getGridSize(0);}
-        @Override public int     getNumRow()    {return getGridSize(1);}
-        @Override public Number  apply(int[] p) {return getElement(p[1], p[0]);}
+        @Override public Matrix  clone()                            {return this;}
+        @Override public boolean isIdentity()                       {return false;}
+        @Override public int     getNumCol()                        {return getGridSize(0);}
+        @Override public int     getNumRow()                        {return getGridSize(1);}
+        @Override public Number  apply     (int[] p)                {return getElement(p[1], p[0]);}
+        @Override public void    setElement(int y, int x, double v) {throw new UnsupportedOperationException();}
 
         /** Computes the matrix element in the given row and column. */
         @Override public double  getElement(final int y, final int x) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
index f058efe..712fac3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
@@ -40,8 +40,8 @@
 import org.apache.sis.referencing.internal.Arithmetic;
 import org.apache.sis.referencing.operation.transform.MathTransforms;       // For javadoc
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -302,8 +302,8 @@
                         if (!same) {
                             scale = scale.negate();
                         }
-                        translate = scale.multiply((same ? srcCorner : srcOppositeCorner).getCoordinate(srcIndex), decimal);
-                        translate = DoubleDouble.of(dstCorner.getCoordinate(dstIndex), decimal).subtract(translate);
+                        translate = scale.multiply((same ? srcCorner : srcOppositeCorner).getOrdinate(srcIndex), decimal);
+                        translate = DoubleDouble.of(dstCorner.getOrdinate(dstIndex), decimal).subtract(translate);
 
                         matrix.setNumber(dstIndex, srcIndex,       scale);
                         matrix.setNumber(dstIndex, srcAxes.length, translate);
@@ -335,7 +335,7 @@
      * </ul>
      *
      * This method ignores the {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS}, which may be null.
-     * Actually this method is used more often for {@linkplain org.opengis.coverage.grid.GridEnvelope grid envelopes}
+     * Actually this method is used more often for grid envelopes
      * (which have no CRS) than geodetic envelopes.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
@@ -394,7 +394,7 @@
              * anti-meridian.
              */
             final double scale     = dstEnvelope.getSpan(i)   / srcEnvelope.getSpan(i);
-            final double translate = dstCorner.getCoordinate(i) - srcCorner.getCoordinate(i)*scale;
+            final double translate = dstCorner.getOrdinate(i) - srcCorner.getOrdinate(i)*scale;
             matrix.setElement(i, i,      scale);
             matrix.setElement(i, srcDim, translate);
         }
@@ -473,7 +473,7 @@
      * </ul>
      *
      * This method ignores the {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS}, which may be null.
-     * Actually this method is used more often for {@linkplain org.opengis.coverage.grid.GridEnvelope grid envelopes}
+     * Actually this method is used more often for grid envelopes
      * (which have no CRS) than geodetic envelopes.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
@@ -754,7 +754,7 @@
         }
         if (translation != null) {
             for (int j=0; j<numRow; j++) {
-                matrix.setElement(j, numCol, translation.getCoordinate(j));
+                matrix.setElement(j, numCol, translation.getOrdinate(j));
             }
         }
         return matrix;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
index 7c2775b..ae4b6a9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
@@ -16,8 +16,8 @@
  */
 package org.apache.sis.referencing.operation.matrix;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -31,7 +31,6 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.4
  *
- * @see org.opengis.coordinate.MismatchedCoordinateMetadataException
  * @see org.apache.sis.referencing.operation.MismatchedDatumException
  *
  * @since 0.4
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
index 1226a48..7e64327 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
@@ -52,8 +52,8 @@
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.Numerics;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -470,7 +470,7 @@
         for (final V variant : variants) {
             final String identifier = variant.getIdentifier();
             if (identifier != null) {
-                for (final Identifier id : method.getIdentifiers()) {
+                for (final ReferenceIdentifier id : method.getIdentifiers()) {
                     if (Constants.EPSG.equals(id.getCodeSpace()) && identifier.equals(id.getCode())) {
                         return variant;
                     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
index f755ea0..855e6ce 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
@@ -50,8 +50,8 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -191,7 +191,7 @@
      * @throws TransformException if the domain cannot be estimated.
      *
      * @see MathTransforms#getDomain(MathTransform)
-     * @see org.opengis.referencing.ObjectDomain#getDomainOfValidity()
+     * @see org.opengis.referencing.operation.CoordinateOperation#getDomainOfValidity()
      *
      * @since 1.3
      */
@@ -286,8 +286,7 @@
      * @param  dimension  the wrong dimension.
      */
     static MismatchedDimensionException mismatchedDimension(final String argument, final int expected, final int dimension) {
-        return new org.opengis.geometry.MismatchedDimensionException(
-                Errors.format(Errors.Keys.MismatchedDimension_3, argument, expected, dimension));
+        return new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, argument, expected, dimension));
     }
 
     /**
@@ -323,16 +322,16 @@
              */
             final double[] array;
             if (dimSource >= dimTarget) {
-                array = ptSrc.getCoordinates();
+                array = ptSrc.getCoordinate();
             } else {
                 array = new double[dimTarget];
                 for (int i=dimSource; --i>=0;) {
-                    array[i] = ptSrc.getCoordinate(i);
+                    array[i] = ptSrc.getOrdinate(i);
                 }
             }
             transform(array, 0, array, 0, false);
             for (int i=0; i<dimTarget; i++) {
-                ptDst.setCoordinate(i, array[i]);
+                ptDst.setOrdinate(i, array[i]);
             }
         } else {
             /*
@@ -344,10 +343,10 @@
             if (dimSource <= dimTarget) {
                 source = destination.coordinates;
                 for (int i=0; i<dimSource; i++) {
-                    source[i] = ptSrc.getCoordinate(i);
+                    source[i] = ptSrc.getOrdinate(i);
                 }
             } else {
-                source = ptSrc.getCoordinates();
+                source = ptSrc.getCoordinate();
             }
             transform(source, 0, destination.coordinates, 0, false);
             ptDst = destination;
@@ -814,7 +813,7 @@
     @Override
     public Matrix derivative(final DirectPosition point) throws TransformException {
         final int dimSource = getSourceDimensions();
-        final double[] coordinates = point.getCoordinates();
+        final double[] coordinates = point.getCoordinate();
         if (coordinates.length != dimSource) {
             throw mismatchedDimension("point", dimSource, coordinates.length);
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java
index 8b08301..4551bcd 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java
@@ -24,8 +24,8 @@
 import org.apache.sis.referencing.operation.matrix.Matrix1;
 import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -136,7 +136,7 @@
             coordinate = Double.NaN;
         } else {
             ensureDimensionMatches("point", 1, point);
-            coordinate = point.getCoordinate(0);
+            coordinate = point.getOrdinate(0);
         }
         return new Matrix1(derivative(coordinate));
     }
@@ -215,7 +215,7 @@
                 coordinate = Double.NaN;
             } else {
                 ensureDimensionMatches("point", 1, point);
-                coordinate = point.getCoordinate(0);
+                coordinate = point.getOrdinate(0);
             }
             return new Matrix1(derivative(coordinate));
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
index 8048ec3..3c0fcda 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
@@ -1604,22 +1604,6 @@
     }
 
     /**
-     * Creates a modifiable matrix of size {@code numRow}&nbsp;×&nbsp;{@code numCol}.
-     * Elements on the diagonal (<var>j</var> == <var>i</var>) are set to 1.
-     *
-     * @param  numRow  number of rows.
-     * @param  numCol  number of columns.
-     * @return a new matrix of the given size.
-     * @throws FactoryException if the matrix creation failed.
-     *
-     * @since 2.0 (temporary version number until this branch is released)
-     */
-    @Override
-    public Matrix createMatrix(int numRow, int numCol) throws FactoryException {
-        return Matrices.createDiagonal(numRow, numCol);
-    }
-
-    /**
      * Creates a transform by concatenating two existing transforms.
      * A concatenated transform acts in the same way as applying two transforms, one after the other.
      *
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java
index 244f76b..614fc12 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java
@@ -60,7 +60,7 @@
  *
  * @see MathTransforms#getDomain(MathTransform)
  * @see AbstractMathTransform#getDomain(DomainDefinition)
- * @see org.opengis.referencing.operation.CoordinateOperation#getDomains()
+ * @see org.opengis.referencing.operation.CoordinateOperation#getDomainOfValidity()
  *
  * @since 1.3
  */
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
index 2a7a028..9f119a2 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
@@ -500,10 +500,10 @@
         final double h;
         switch (dim) {
             default: throw mismatchedDimension("point", getSourceDimensions(), dim);
-            case 3:  wh = true;  h = point.getCoordinate(2); break;
+            case 3:  wh = true;  h = point.getOrdinate(2); break;
             case 2:  wh = false; h = 0; break;
         }
-        return transform(point.getCoordinate(0), point.getCoordinate(1), h, null, 0, true, wh);
+        return transform(point.getOrdinate(0), point.getOrdinate(1), h, null, 0, true, wh);
     }
 
     /**
@@ -850,7 +850,7 @@
          */
         @Override
         public Matrix derivative(final DirectPosition point) throws TransformException {
-            final double[] coordinate = point.getCoordinates();
+            final double[] coordinate = point.getCoordinate();
             ArgumentChecks.ensureDimensionMatches("point", 3, coordinate);
             return this.transform(coordinate, 0, coordinate, 0, true);
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/IdentityTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/IdentityTransform.java
index 1185d4f..a116c8e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/IdentityTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/IdentityTransform.java
@@ -161,7 +161,7 @@
         }
         ArgumentChecks.ensureDimensionMatches("ptDst", dimension, ptDst);
         for (int i=0; i<dimension; i++) {
-            ptDst.setCoordinate(i, ptSrc.getCoordinate(i));
+            ptDst.setOrdinate(i, ptSrc.getOrdinate(i));
         }
         return ptDst;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
index ec41fce..27d7a3d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
@@ -43,8 +43,8 @@
 import org.apache.sis.util.Static;
 import org.apache.sis.util.privy.DoubleDouble;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -296,8 +296,6 @@
      * @param  values    the output values (<var>y</var>) in the function range, or {@code null}.
      * @return the <i>y=f(x)</i> function.
      *
-     * @see org.opengis.coverage.InterpolationMethod
-     *
      * @since 0.7
      */
     public static MathTransform1D interpolate(final double[] preimage, final double[] values) {
@@ -706,7 +704,7 @@
         final int tgtDim = toApproximate.getTargetDimensions();
         double[] coordinates = new double[Math.max(tgtDim, srcDim + 1)];
         for (int i=0; i<srcDim; i++) {
-            coordinates[i] = tangentPoint.getCoordinate(i);
+            coordinates[i] = tangentPoint.getOrdinate(i);
         }
         final Matrix derivative = derivativeAndTransform(toApproximate, coordinates, 0, coordinates, 0);
         final MatrixSIS m = Matrices.createAffine(derivative, new DirectPositionView.Double(coordinates, 0, tgtDim));
@@ -717,7 +715,7 @@
          */
         coordinates = ArraysExt.resize(coordinates, srcDim + 1);
         for (int i=0; i<srcDim; i++) {
-            coordinates[i] = -tangentPoint.getCoordinate(i);
+            coordinates[i] = -tangentPoint.getOrdinate(i);
         }
         coordinates[srcDim] = 1;
         m.translate(coordinates);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
index 8fedcff..93c3736 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
@@ -40,8 +40,8 @@
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -258,9 +258,8 @@
         final int subDim = subTransform.getSourceDimensions();
         final int actual = modifiedCoordinates.cardinality();
         if (actual != subDim) {
-            throw new org.opengis.geometry.MismatchedDimensionException(
-                    Errors.format(Errors.Keys.MismatchedDimension_3,
-                    "modifiedCoordinates", subDim, actual));
+            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
+                                                   "modifiedCoordinates", subDim, actual));
         }
         final var sep = new TransformSeparator(subTransform, factory);
         MathTransform result = MathTransforms.identity(resultDim);
@@ -573,7 +572,7 @@
         ArgumentChecks.ensureDimensionMatches("point", transDim + nSkipped, point);
         final GeneralDirectPosition subPoint = new GeneralDirectPosition(transDim);
         for (int i=0; i<transDim; i++) {
-            subPoint.coordinates[i] = point.getCoordinate(i + firstAffectedCoordinate);
+            subPoint.coordinates[i] = point.getOrdinate(i + firstAffectedCoordinate);
         }
         return expand(MatrixSIS.castOrCopy(subTransform.derivative(subPoint)),
                 firstAffectedCoordinate, numTrailingCoordinates, 0);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java
index c46779b..601a1bb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ProjectiveTransform.java
@@ -615,7 +615,7 @@
         for (int i=0; i<srcDim; i++) {
             final double e = elt[mix++];
             if (e != 0) {                               // For avoiding NullPointerException if affine.
-                w += point.getCoordinate(i) * e;
+                w += point.getOrdinate(i) * e;
             }
         }
         /*
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SpecializableTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
index 1d533e2..a65cd90 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
@@ -561,7 +561,7 @@
          */
         @Override
         public final DirectPosition transform(final DirectPosition ptSrc, DirectPosition ptDst) throws TransformException {
-            final double[] source = ptSrc.getCoordinates();      // Needs to be first in case ptDst overwrites ptSrc.
+            final double[] source = ptSrc.getCoordinate();      // Needs to be first in case ptDst overwrites ptSrc.
             ptDst = global.transform(ptSrc, ptDst);
             final SubArea domain = forward.locate(ptDst);
             if (domain != null) {
@@ -576,7 +576,7 @@
          */
         @Override
         public final Matrix derivative(final DirectPosition point) throws TransformException {
-            return transform(point.getCoordinates(), 0, null, 0, true);
+            return transform(point.getCoordinate(), 0, null, 0, true);
         }
 
         /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AffineTransform2D.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AffineTransform2D.java
index f8bac53..c446aeb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AffineTransform2D.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AffineTransform2D.java
@@ -273,14 +273,14 @@
             }
         } else {
             if (ptDst == null) {
-                final DirectPosition2D point = new DirectPosition2D(ptSrc.getCoordinate(0), ptSrc.getCoordinate(1));
+                final DirectPosition2D point = new DirectPosition2D(ptSrc.getOrdinate(0), ptSrc.getOrdinate(1));
                 super.transform(point, point);
                 return point;
             }
             ArgumentChecks.ensureDimensionMatches("ptDst", DIMENSION, ptDst);
             if (ptDst instanceof Point2D) {
                 final Point2D point = (Point2D) ptDst;
-                point.setLocation(ptSrc.getCoordinate(0), ptSrc.getCoordinate(1));
+                point.setLocation(ptSrc.getOrdinate(0), ptSrc.getOrdinate(1));
                 super.transform(point, point);
                 return ptDst;
             }
@@ -288,10 +288,10 @@
         /*
          * At this point, we have no choice to create a temporary Point2D.
          */
-        final Point2D.Double point = new Point2D.Double(ptSrc.getCoordinate(0), ptSrc.getCoordinate(1));
+        final Point2D.Double point = new Point2D.Double(ptSrc.getOrdinate(0), ptSrc.getOrdinate(1));
         super.transform(point, point);
-        ptDst.setCoordinate(0, point.x);
-        ptDst.setCoordinate(1, point.y);
+        ptDst.setOrdinate(0, point.x);
+        ptDst.setOrdinate(1, point.y);
         return ptDst;
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java
index 4671df6..3826d39 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java
@@ -35,6 +35,11 @@
 import org.apache.sis.measure.Units;
 import static org.apache.sis.util.CharSequences.*;
 
+// Specific to the main branch:
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19111;
+
 
 /**
  * Utilities methods related to {@link AxisDirection}.
@@ -66,7 +71,58 @@
      *
      * @see #isUserDefined(AxisDirection)
      */
-    private static final int LAST_ORDINAL = UNSPECIFIED.ordinal();
+    private static final int LAST_ORDINAL = DISPLAY_DOWN.ordinal();
+
+    /**
+     * Forward direction.
+     * For an observer at the centre of the object this is will be towards its front, bow or nose.
+     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
+     */
+    @UML(identifier="forward", obligation=CONDITIONAL, specification=ISO_19111)
+    public static final AxisDirection FORWARD = AxisDirection.valueOf("FORWARD");
+
+    /**
+     * Starboard direction.
+     * For an observer at the centre of the object this will be towards its right.
+     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
+     */
+    @UML(identifier="starboard", obligation=CONDITIONAL, specification=ISO_19111)
+    public static final AxisDirection STARBOARD = AxisDirection.valueOf("STARBOARD");
+
+    /**
+     * Port direction.
+     * For an observer at the centre of the object this will be towards its left.
+     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
+     */
+    @UML(identifier="port", obligation=CONDITIONAL, specification=ISO_19111)
+    public static final AxisDirection PORT = AxisDirection.valueOf("PORT");
+
+    /**
+     * Direction of geographic angles (bearing).
+     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
+     */
+    @UML(identifier="clockwise", obligation=CONDITIONAL, specification=ISO_19111)
+    public static final AxisDirection CLOCKWISE = AxisDirection.valueOf("CLOCKWISE");
+
+    /**
+     * Direction of arithmetic angles. Used in polar coordinate systems.
+     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
+     */
+    @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=ISO_19111)
+    public static final AxisDirection COUNTER_CLOCKWISE = AxisDirection.valueOf("COUNTER_CLOCKWISE");
+
+    /**
+     * Distance from the origin in a polar coordinate system.
+     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
+     */
+    @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=ISO_19111)
+    public static final AxisDirection AWAY_FROM = AxisDirection.valueOf("AWAY_FROM");
+
+    /**
+     * Axis positive direction is unspecified.
+     */
+    @UML(identifier="unspecified", obligation=CONDITIONAL, specification=ISO_19111)
+    public static final AxisDirection UNSPECIFIED = AxisDirection.valueOf("UNSPECIFIED");
 
     /**
      * For each direction, the opposite direction.
@@ -103,7 +159,6 @@
     /**
      * Proposed abbreviations for some axis directions.
      */
-    @SuppressWarnings("deprecation")
     private static final Map<AxisDirection,String> ABBREVIATIONS = Map.of(
             FUTURE,            "t",
             COLUMN_POSITIVE,   "i",
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/CoordinateOperations.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/CoordinateOperations.java
index 7c90700..b7d2b1f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/CoordinateOperations.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/CoordinateOperations.java
@@ -254,7 +254,6 @@
      * @param  target  the target of the coordinate operation.
      * @return target dimensions where "wrap around" may happen, or an empty set if none.
      */
-    @SuppressWarnings("deprecation")
     public static Set<Integer> wrapAroundChanges(CoordinateReferenceSystem source, final CoordinateSystem target) {
         long changes = changes(source.getCoordinateSystem(), target);
         while (source instanceof GeneralDerivedCRS) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
index 626bfe9..5297466 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/DefinitionVerifier.java
@@ -309,7 +309,6 @@
      * The returned value is one of {@link #METHOD}, {@link #CONVERSION}, {@link #CS}, {@link #DATUM},
      * {@link #PRIME_MERIDIAN} or {@link #OTHER} constants.
      */
-    @SuppressWarnings("deprecation")
     private static int diffCode(final Iterator<SingleCRS> authoritative, final Iterator<SingleCRS> given) {
         while (authoritative.hasNext() && given.hasNext()) {
             final SingleCRS crsA = authoritative.next();
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
index 8dfe6c7..db88f21 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/EllipsoidalHeightCombiner.java
@@ -39,9 +39,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-
 
 /**
  * A class in charges of combining two-dimensional geographic or projected CRS with an ellipsoidal height into a
@@ -219,13 +216,11 @@
             ArgumentChecks.ensureNonNullElement("components", i, crs);
             if (i != 0) name.append(" + ");
             name.append(crs.getName().getCode());
-            for (ObjectDomain obj : crs.getDomains()) {
-                domain = Extents.intersection(domain, obj.getDomainOfValidity());
-            }
+            domain = Extents.intersection(domain, crs.getDomainOfValidity());
         }
         final var properties = new HashMap<String,Object>(4);
         properties.put(CoordinateReferenceSystem.NAME_KEY, name.toString());
-        properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, domain);
+        properties.put(CoordinateReferenceSystem.DOMAIN_OF_VALIDITY_KEY, domain);
         return properties;
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtendedPrecisionMatrix.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtendedPrecisionMatrix.java
index a353489..07f7c25 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtendedPrecisionMatrix.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtendedPrecisionMatrix.java
@@ -78,6 +78,7 @@
             @Override public int    getNumCol()              {return m.getNumCol();}
             @Override public String toString()               {return m.toString();}
             @Override public Matrix clone()                  {return m.clone();}
+            @Override public boolean isIdentity()            {return m.isIdentity();}
         };
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/Formulas.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/Formulas.java
index 53b5663..86350f4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/Formulas.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/Formulas.java
@@ -98,7 +98,7 @@
      * but come at a high cost on older machines without hardware support.
      */
     @Configuration
-    public static final boolean USE_FMA = true;
+    public static final boolean USE_FMA = false;
 
     /**
      * Do not allow instantiation of this class.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
index 778b401..42c5ae5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
@@ -61,8 +61,8 @@
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.parameter.Parameters;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.apache.sis.util.privy.TemporalDate;
 
 
 /**
@@ -186,7 +186,7 @@
         }
         if (description != null || bbox != null) {
             final DefaultExtent extent = new DefaultExtent(description, bbox, null, null);
-            properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, extent);
+            properties.put(CoordinateReferenceSystem.DOMAIN_OF_VALIDITY_KEY, extent);
         }
         return this;
     }
@@ -585,7 +585,7 @@
             if (datum == null) {
                 final Object remarks    = properties.remove(TemporalCRS.REMARKS_KEY);
                 final Object identifier = properties.remove(TemporalCRS.IDENTIFIERS_KEY);
-                datum = factories.getDatumFactory().createTemporalDatum(properties, origin);
+                datum = factories.getDatumFactory().createTemporalDatum(properties, TemporalDate.toDate(origin));
                 properties.put(TemporalCRS.IDENTIFIERS_KEY, identifier);
                 properties.put(TemporalCRS.REMARKS_KEY,     remarks);
                 properties.put(TemporalCRS.NAME_KEY, datum.getName());      // Share the Identifier instance.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/IntervalRectangle.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/IntervalRectangle.java
index 6145a60..f1693ab 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/IntervalRectangle.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/IntervalRectangle.java
@@ -117,10 +117,10 @@
      * @see Envelope#getUpperCorner()
      */
     public IntervalRectangle(final DirectPosition lower, final DirectPosition upper) {
-        xmin = lower.getCoordinate(0);
-        xmax = upper.getCoordinate(0);
-        ymin = lower.getCoordinate(1);
-        ymax = upper.getCoordinate(1);
+        xmin = lower.getOrdinate(0);
+        xmax = upper.getOrdinate(0);
+        ymin = lower.getOrdinate(1);
+        ymax = upper.getOrdinate(1);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/NilReferencingObject.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/NilReferencingObject.java
index b048979..0c3a931 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/NilReferencingObject.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/NilReferencingObject.java
@@ -26,6 +26,13 @@
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
 
+// Specific to the main branch:
+import java.util.Set;
+import java.util.Collection;
+import org.opengis.util.GenericName;
+import org.opengis.metadata.extent.Extent;
+import org.apache.sis.io.wkt.UnformattableObjectException;
+
 
 /**
  * A referencing object for which every methods return {@code null} or a neutral value.
@@ -83,4 +90,20 @@
     public InternationalString getScope() {
         return null;
     }
+
+    @Override public Collection<GenericName>  getAlias()       {return null;}
+    @Override public Set<ReferenceIdentifier> getIdentifiers() {return null;}
+    @Override public InternationalString      getRemarks()     {return null;}
+    @Override public Extent getDomainOfValidity()              {return null;}
+
+    /**
+     * Throws the exception in all cases.
+     *
+     * @return never return.
+     * @throws UnformattableObjectException always thrown.
+     */
+    @Override
+    public String toWKT() throws UnformattableObjectException {
+        throw new UnformattableObjectException();
+    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/PositionalAccuracyConstant.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/PositionalAccuracyConstant.java
index 7370c6c..81c6567 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/PositionalAccuracyConstant.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/PositionalAccuracyConstant.java
@@ -184,7 +184,7 @@
                         if (Units.isLinear(unit)) {
                             final Unit<Length> unitOfLength = unit.asType(Length.class);
                             for (final Record record : records) {
-                                for (final Object value : record.getFields().values()) {
+                                for (final Object value : record.getAttributes().values()) {
                                     if (value instanceof Number) {
                                         double v = ((Number) value).doubleValue();
                                         v = unitOfLength.getConverterTo(Units.METRE).convert(v);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingFactoryContainer.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingFactoryContainer.java
index aa9fbde..c0007a6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingFactoryContainer.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingFactoryContainer.java
@@ -37,8 +37,8 @@
 import org.apache.sis.util.iso.DefaultNameFactory;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.Factory;
+// Specific to the main branch:
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
 
 
 /**
@@ -121,7 +121,7 @@
      * Factory for fetching operation methods and creating defining conversions.
      * This is needed only for user-defined projected coordinate reference system.
      */
-    private CoordinateOperationFactory operationFactory;
+    private DefaultCoordinateOperationFactory operationFactory;
 
     /**
      * The {@linkplain org.opengis.referencing.operation.MathTransform math transform} factory.
@@ -154,7 +154,7 @@
         datumFactory     = (DatumFactory)               properties.get(DATUM_FACTORY);
         csFactory        = (CSFactory)                  properties.get(CS_FACTORY);
         crsFactory       = (CRSFactory)                 properties.get(CRS_FACTORY);
-        operationFactory = (CoordinateOperationFactory) properties.get(OPERATION_FACTORY);
+        operationFactory = (DefaultCoordinateOperationFactory) properties.get(OPERATION_FACTORY);
         mtFactory        = (MathTransformFactory)       properties.get(MT_FACTORY);
     }
 
@@ -180,7 +180,7 @@
         this.crsFactory        = crsFactory;
         this.csFactory         = csFactory;
         this.datumFactory      = datumFactory;
-        this.operationFactory  = operationFactory;
+        this.operationFactory  = (DefaultCoordinateOperationFactory) operationFactory;      // Because of GeoAPI 3.0 limitation.
         this.mtFactory         = mtFactory;
     }
 
@@ -206,12 +206,12 @@
      * @return {@code true} if the factory changed as a result of this method call.
      * @throws IllegalArgumentException if the {@code type} argument is not one of the valid values.
      */
-    public final <T extends Factory> boolean setFactory(final Class<T> type, final T factory) {
+    public final <T> boolean setFactory(final Class<T> type, final T factory) {
         if (type == NameFactory.class)                return nameFactory      != (nameFactory      = (NameFactory)                factory);
         if (type == DatumFactory.class)               return datumFactory     != (datumFactory     = (DatumFactory)               factory);
         if (type == CSFactory.class)                  return csFactory        != (csFactory        = (CSFactory)                  factory);
         if (type == CRSFactory.class)                 return crsFactory       != (crsFactory       = (CRSFactory)                 factory);
-        if (type == CoordinateOperationFactory.class) return operationFactory != (operationFactory = (CoordinateOperationFactory) factory);
+        if (type == CoordinateOperationFactory.class) return operationFactory != (operationFactory = (DefaultCoordinateOperationFactory) factory);
         if (type == MathTransformFactory.class)       return mtFactory        != (mtFactory        = (MathTransformFactory)       factory);
         throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "type", type));
     }
@@ -237,8 +237,8 @@
      * @return the factory for the given type.
      * @throws IllegalArgumentException if the {@code type} argument is not one of the valid values.
      */
-    public final <T extends Factory> T getFactory(final Class<T> type) {
-        final Factory f;
+    public final <T> T getFactory(final Class<T> type) {
+        final Object f;
              if (type == NameFactory.class)                f = getNameFactory();
         else if (type == DatumFactory.class)               f = getDatumFactory();
         else if (type == CSFactory.class)                  f = getCSFactory();
@@ -306,10 +306,15 @@
      *
      * @return the Coordinate Operation factory (never {@code null}).
      */
-    public final CoordinateOperationFactory getCoordinateOperationFactory() {
+    public final DefaultCoordinateOperationFactory getCoordinateOperationFactory() {
         if (operationFactory == null) {
-            operationFactory = CoordinateOperations.getCoordinateOperationFactory(defaultProperties, mtFactory, crsFactory, csFactory);
+            CoordinateOperationFactory op = CoordinateOperations.getCoordinateOperationFactory(defaultProperties, mtFactory, crsFactory, csFactory);
             defaultProperties = null;       // Not needed anymore.
+            if (op instanceof DefaultCoordinateOperationFactory) {
+                operationFactory = (DefaultCoordinateOperationFactory) op;
+            } else {
+                operationFactory = DefaultCoordinateOperationFactory.provider();
+            }
         }
         return operationFactory;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
index 5a4af59..bd55de5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
@@ -56,6 +56,13 @@
 import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory.Context;
 
+// Specific to the main branch:
+import java.util.Collection;
+import java.util.NoSuchElementException;
+import org.opengis.referencing.ReferenceIdentifier;
+import org.apache.sis.metadata.privy.Identifiers;
+import org.apache.sis.xml.NilObject;
+
 
 /**
  * A set of static methods working on GeoAPI referencing objects.
@@ -207,6 +214,51 @@
     }
 
     /**
+     * Copies all {@link SingleCRS} components from the given source to the given collection.
+     * For each {@link CompoundCRS} element found in the iteration, this method replaces the
+     * {@code CompoundCRS} by its {@linkplain CompoundCRS#getComponents() components}, which
+     * may themselves have other {@code CompoundCRS}. Those replacements are performed recursively
+     * until we obtain a flat view of CRS components.
+     *
+     * @param  source  the collection of single or compound CRS.
+     * @param  addTo   where to add the single CRS in order to obtain a flat view of {@code source}.
+     * @return {@code true} if this method found only single CRS in {@code source}, in which case {@code addTo}
+     *         got the same content (assuming that {@code addTo} was empty prior this method call).
+     * @throws NoSuchElementException if a CRS component is missing.
+     * @throws ClassCastException if a CRS is neither a {@link SingleCRS} or a {@link CompoundCRS}.
+     *
+     * @see org.apache.sis.referencing.CRS#getSingleComponents(CoordinateReferenceSystem)
+     */
+    public static boolean getSingleComponents(final Iterable<? extends CoordinateReferenceSystem> source,
+            final Collection<? super SingleCRS> addTo) throws ClassCastException
+    {
+        boolean sameContent = true;
+        for (final CoordinateReferenceSystem candidate : source) {
+            if (candidate instanceof CompoundCRS) {
+                getSingleComponents(((CompoundCRS) candidate).getComponents(), addTo);
+                sameContent = false;
+            } else if (candidate instanceof SingleCRS) {
+                addTo.add((SingleCRS) candidate);
+            } else {
+                /*
+                 * Illegal class. Try to provide a better error message, in particular when the CRS component
+                 * is nil because it is an unresolved xlink in a GML document. Nil objects are proxies, which
+                 * have hard to understand class names.
+                 */
+                final String message;
+                if (candidate instanceof NilObject) {
+                    message = Errors.format(Errors.Keys.NilObject_1, Identifiers.getNilReason((NilObject) candidate));
+                    throw new NoSuchElementException(message);
+                } else {
+                    message = Errors.format(Errors.Keys.NestedElementNotAllowed_1, getInterface(candidate));
+                    throw new ClassCastException(message);
+                }
+            }
+        }
+        return sameContent;
+    }
+
+    /**
      * Returns {@code true} if the type of the given datum is ellipsoidal. A vertical datum is not allowed
      * to be ellipsoidal according ISO 19111, but Apache SIS relaxes this restriction in some limited cases,
      * for example when parsing a string in the legacy WKT 1 format. Apache SIS should not expose those
@@ -220,7 +272,7 @@
      */
     public static boolean isEllipsoidalHeight(final VerticalDatum datum) {
         if (datum != null) {
-            return datum.getRealizationMethod().map(VerticalDatumTypes::ellipsoidal).orElse(false);
+            return VerticalDatumTypes.ellipsoidal(datum.getVerticalDatumType());
         }
         return false;
     }
@@ -306,7 +358,6 @@
      * @param  allow3D  whether this method is allowed to return three-dimensional CRS (with ellipsoidal height).
      * @return a two-dimensional geographic CRS with standard axes, or {@code null} if none.
      */
-    @SuppressWarnings("deprecation")
     public static GeographicCRS toNormalizedGeographicCRS(CoordinateReferenceSystem crs, final boolean latlon, final boolean allow3D) {
         /*
          * ProjectedCRS instances always have a GeographicCRS as their base.
@@ -389,7 +440,7 @@
      */
     public static Map<String,?> getPropertiesWithoutIdentifiers(final IdentifiedObject object, final Map<String,?> overwrite) {
         final Map<String,?> properties = IdentifiedObjects.getProperties(object, IdentifiedObject.IDENTIFIERS_KEY);
-        final Identifier name = object.getName();
+        final ReferenceIdentifier name = object.getName();
         final boolean keepName = name.getCodeSpace() == null && name.getAuthority() == null;
         if (keepName && overwrite == null) {
             return properties;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java
index b9ea8c0..3abcd68 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTKeywords.java
@@ -275,7 +275,7 @@
         addType(org.opengis.referencing.cs.CoordinateSystemAxis.class,      Axis);
         addType(org.apache.sis.referencing.datum.BursaWolfParameters.class, ToWGS84);
         addType(org.opengis.referencing.operation.MathTransform.class,      Param_MT, Concat_MT, Inverse_MT, PassThrough_MT);
-        addType(org.opengis.coordinate.CoordinateMetadata.class,            CoordinateMetadata);
+        addType(org.apache.sis.coordinate.DefaultCoordinateMetadata.class,  CoordinateMetadata);
         addType(org.opengis.geometry.DirectPosition.class,                  Point);
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
index ddb558e..5df26fb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java
@@ -62,8 +62,8 @@
 import org.apache.sis.math.Statistics;
 import org.apache.sis.math.Vector;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -342,7 +342,7 @@
      */
     public static boolean isEPSG(final GeneralParameterDescriptor descriptor, final boolean ifUndefined) {
         if (descriptor != null) {
-            final Identifier id = descriptor.getName();
+            final ReferenceIdentifier id = descriptor.getName();
             if (id != null) {
                 final String cs = id.getCodeSpace();
                 if (cs != null) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WraparoundApplicator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WraparoundApplicator.java
index c6d85d5..cdd40ea 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WraparoundApplicator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WraparoundApplicator.java
@@ -144,7 +144,7 @@
                 final CoordinateSystemAxis axis = targetCS.getAxis(wraparoundDimension);
                 m = (axis.getMinimumValue() + axis.getMaximumValue()) / 2;
             } else {
-                m = targetMedian.getCoordinate(wraparoundDimension);
+                m = targetMedian.getOrdinate(wraparoundDimension);
             }
             if (!Double.isFinite(m)) {
                 if (targetMedian != null) {
@@ -158,7 +158,7 @@
                  */
                 m = 0;
             }
-            sm = (sourceMedian != null) ? sourceMedian.getCoordinate(wraparoundDimension) : Double.NaN;
+            sm = (sourceMedian != null) ? sourceMedian.getOrdinate(wraparoundDimension) : Double.NaN;
         } catch (BackingStoreException e) {
             // Some `DirectPosition` implementations compute coordinates only when first needed.
             throw e.unwrapOrRethrow(TransformException.class);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameter.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameter.java
index 9a08fba..2f4ccb9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameter.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameter.java
@@ -479,8 +479,8 @@
     private static NamedIdentifier toNamedIdentifier(final Object name) {
         if (name == null || name.getClass() == NamedIdentifier.class) {
             return (NamedIdentifier) name;
-        } else if (name instanceof Identifier) {
-            return new NamedIdentifier((Identifier) name);
+        } else if (name instanceof ReferenceIdentifier) {
+            return new NamedIdentifier((ReferenceIdentifier) name);
         } else {
             return new NamedIdentifier((GenericName) name);
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ParametricDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ParametricDatum.java
index 036cdc1..74eb82f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ParametricDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ParametricDatum.java
@@ -20,9 +20,6 @@
 import org.apache.sis.xml.bind.gco.PropertyType;
 import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.ParametricDatum;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -31,7 +28,7 @@
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class CD_ParametricDatum extends PropertyType<CD_ParametricDatum, ParametricDatum> {
+public final class CD_ParametricDatum extends PropertyType<CD_ParametricDatum, DefaultParametricDatum> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,14 +43,14 @@
      * @return {@code ParametricDatum.class}
      */
     @Override
-    protected Class<ParametricDatum> getBoundType() {
-        return ParametricDatum.class;
+    protected Class<DefaultParametricDatum> getBoundType() {
+        return DefaultParametricDatum.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CD_ParametricDatum(final ParametricDatum datum) {
+    private CD_ParametricDatum(final DefaultParametricDatum datum) {
         super(datum);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CD_ParametricDatum wrap(final ParametricDatum datum) {
+    protected CD_ParametricDatum wrap(final DefaultParametricDatum datum) {
         return new CD_ParametricDatum(datum);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElement(name = "ParametricDatum")
     public DefaultParametricDatum getElement() {
-        return DefaultParametricDatum.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_VerticalDatumType.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_VerticalDatumType.java
index a6eeffe..619e4f5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_VerticalDatumType.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_VerticalDatumType.java
@@ -25,7 +25,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-@SuppressWarnings("deprecation")
 public final class CD_VerticalDatumType extends CodeListAdapter<VerticalDatumType> {
     /**
      * Empty constructor for JAXB only.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_ParametricCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_ParametricCS.java
index 201b786..3f43cfd 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_ParametricCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_ParametricCS.java
@@ -20,9 +20,6 @@
 import org.apache.sis.referencing.cs.DefaultParametricCS;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.cs.ParametricCS;
-
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -30,7 +27,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class CS_ParametricCS extends PropertyType<CS_ParametricCS, ParametricCS> {
+public final class CS_ParametricCS extends PropertyType<CS_ParametricCS, DefaultParametricCS> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -45,14 +42,14 @@
      * @return {@code ParametricCS.class}
      */
     @Override
-    protected Class<ParametricCS> getBoundType() {
-        return ParametricCS.class;
+    protected Class<DefaultParametricCS> getBoundType() {
+        return DefaultParametricCS.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CS_ParametricCS(final ParametricCS cs) {
+    private CS_ParametricCS(final DefaultParametricCS cs) {
         super(cs);
     }
 
@@ -64,7 +61,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CS_ParametricCS wrap(final ParametricCS cs) {
+    protected CS_ParametricCS wrap(final DefaultParametricCS cs) {
         return new CS_ParametricCS(cs);
     }
 
@@ -77,7 +74,7 @@
      */
     @XmlElement(name = "ParametricCS")
     public DefaultParametricCS getElement() {
-        return DefaultParametricCS.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_UserDefinedCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_UserDefinedCS.java
index c1022ef..9312025 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_UserDefinedCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CS_UserDefinedCS.java
@@ -30,7 +30,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-@SuppressWarnings("deprecation")
 public final class CS_UserDefinedCS extends PropertyType<CS_UserDefinedCS, UserDefinedCS> {
     /**
      * Empty constructor for JAXB only.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java
index f7cb318..eb9e21f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/Code.java
@@ -73,7 +73,7 @@
      *
      * @param identifier  the identifier from which to get the values.
      */
-    Code(final Identifier identifier) {
+    Code(final ReferenceIdentifier identifier) {
         code      = identifier.getCode();
         codeSpace = identifier.getCodeSpace();
         String version = identifier.getVersion();
@@ -162,12 +162,12 @@
      * @param  identifiers  the object identifiers, or {@code null} if none.
      * @return the {@code <gml:identifier>} as a {@code Code} instance, or {@code null} if none.
      */
-    public static Code forIdentifiedObject(final Class<?> type, final Iterable<? extends Identifier> identifiers) {
+    public static Code forIdentifiedObject(final Class<?> type, final Iterable<? extends ReferenceIdentifier> identifiers) {
         if (identifiers != null) {
             boolean isHTTP = false;
             boolean isEPSG = false;
-            Identifier fallback = null;
-            for (final Identifier identifier : identifiers) {
+            ReferenceIdentifier fallback = null;
+            for (final ReferenceIdentifier identifier : identifiers) {
                 final String code = identifier.getCode();
                 if (code == null) continue;                                                 // Paranoiac check.
                 if (code.regionMatches(true, 0, "urn:", 0, 4)) {
@@ -229,10 +229,12 @@
                             if (authority != null) {
                                 for (final Identifier id : authority.getIdentifiers()) {
                                     if (Constants.EPSG.equalsIgnoreCase(id.getCode())) {
-                                        final String cs = id.getCodeSpace();
-                                        if (cs != null) {
-                                            code.codeSpace = cs;
-                                            break;
+                                        if (id instanceof ReferenceIdentifier) {
+                                            final String cs = ((ReferenceIdentifier) id).getCodeSpace();
+                                            if (cs != null) {
+                                                code.codeSpace = cs;
+                                                break;
+                                            }
                                         }
                                     }
                                 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/RS_Identifier.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/RS_Identifier.java
index a9e8649..cd5e35c 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/RS_Identifier.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/RS_Identifier.java
@@ -19,6 +19,9 @@
 import jakarta.xml.bind.annotation.adapters.XmlAdapter;
 import org.opengis.metadata.Identifier;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * JAXB adapter mapping the GeoAPI {@link Identifier} to an implementation class that can be marshalled.
@@ -36,7 +39,7 @@
  *   <gml:identifier codeSpace="EPSG">4326</gml:identifier>
  * }
  *
- * If the {@code Identifier} to marshal contains a {@linkplain Identifier#getVersion() version},
+ * If the {@code Identifier} to marshal contains a {@linkplain ReferenceIdentifier#getVersion() version},
  * then this adapter concatenates the version to the codespace in a "URI-like" way like below:
  *
  * {@snippet lang="xml" :
@@ -58,7 +61,7 @@
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class RS_Identifier extends XmlAdapter<Code, Identifier> {
+public final class RS_Identifier extends XmlAdapter<Code, ReferenceIdentifier> {
     /**
      * Empty constructor for JAXB.
      */
@@ -73,7 +76,7 @@
      * @return an identifier which represents the value.
      */
     @Override
-    public Identifier unmarshal(final Code value) {
+    public ReferenceIdentifier unmarshal(final Code value) {
         return (value != null) ? value.getIdentifier() : null;
     }
 
@@ -85,7 +88,7 @@
      * @return the adapter for the given metadata.
      */
     @Override
-    public Code marshal(final Identifier value) {
+    public Code marshal(final ReferenceIdentifier value) {
         return (value != null) ? new Code(value) : null;
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/AbstractEnvelopeTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/AbstractEnvelopeTest.java
index cf7cd35..e46a616 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/AbstractEnvelopeTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/AbstractEnvelopeTest.java
@@ -30,6 +30,9 @@
 import static org.apache.sis.referencing.Assertions.assertDisjoint;
 import static org.apache.sis.referencing.crs.HardCodedCRS.WGS84;
 
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.PENDING_NEXT_GEOAPI_RELEASE;
+
 
 /**
  * Tests the methods defined in the {@link AbstractEnvelope} class.
@@ -90,7 +93,7 @@
             }
             default: throw new IllegalArgumentException(String.valueOf(type));
         }
-        if (type != RECTANGLE) {
+        if (PENDING_NEXT_GEOAPI_RELEASE) {
             validate(envelope);
         }
         return envelope;
@@ -195,9 +198,9 @@
             assertEquals(  50, envelope.getMaximum   (1), label);
             assertEquals(  40, envelope.getMedian    (1), label);
             assertEquals(  20, envelope.getSpan      (1), label);
-            assertEquals(  12, lower   .getCoordinate(0), label);
+            assertEquals(  12, lower   .getOrdinate  (0), label);
             assertEquals(-180, envelope.getMinimum   (0), label);
-            assertEquals(  -4, upper   .getCoordinate(0), label);
+            assertEquals(  -4, upper   .getOrdinate  (0), label);
             assertEquals(+180, envelope.getMaximum   (0), label);
             assertEquals(-176, envelope.getMedian    (0), label);
             assertEquals( 344, envelope.getSpan      (0), label);       // 360° - testSimpleEnvelope()
@@ -256,9 +259,9 @@
             assertEquals(  50, envelope.getMaximum   (1), label);
             assertEquals(  40, envelope.getMedian    (1), label);
             assertEquals(  20, envelope.getSpan      (1), label);
-            assertEquals(  12, lower   .getCoordinate(0), label);
+            assertEquals(  12, lower   .getOrdinate  (0), label);
             assertEquals(-180, envelope.getMinimum   (0), label);
-            assertEquals(-364, upper   .getCoordinate(0), label);
+            assertEquals(-364, upper   .getOrdinate  (0), label);
             assertEquals(+180, envelope.getMaximum   (0), label);
             assertEquals(   4, envelope.getMedian    (0), label);   // Note the alternance with the previous test methods.
             assertEquals( NaN, envelope.getSpan      (0), label);   // testCrossingAntiMeridian() + 360°.
@@ -309,9 +312,9 @@
             assertEquals(  50, envelope.getMaximum   (1), label);
             assertEquals(  40, envelope.getMedian    (1), label);
             assertEquals(  20, envelope.getSpan      (1), label);
-            assertEquals( 372, lower   .getCoordinate(0), label);
+            assertEquals( 372, lower   .getOrdinate  (0), label);
             assertEquals(-180, envelope.getMinimum   (0), label);
-            assertEquals(-364, upper   .getCoordinate(0), label);
+            assertEquals(-364, upper   .getOrdinate  (0), label);
             assertEquals(+180, envelope.getMaximum   (0), label);
             assertEquals(-176, envelope.getMedian    (0), label);   // Note the alternance with the previous test methods.
             assertEquals( NaN, envelope.getSpan      (0), label);   // testCrossingAntiMeridianTwice() + 360°.
@@ -413,9 +416,9 @@
             assertEquals(  50, envelope.getMaximum   (1), label);
             assertEquals(  40, envelope.getMedian    (1), label);
             assertEquals(  20, envelope.getSpan      (1), label);
-            assertEquals( 0.0, lower   .getCoordinate(0), label);
+            assertEquals( 0.0, lower   .getOrdinate  (0), label);
             assertEquals(-180, envelope.getMinimum   (0), label);
-            assertEquals(-0.0, upper   .getCoordinate(0), label);
+            assertEquals(-0.0, upper   .getOrdinate  (0), label);
             assertEquals(+180, envelope.getMaximum   (0), label);
             assertEquals( 180, envelope.getMedian    (0), label);
             assertEquals( 360, envelope.getSpan      (0), label);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/CoordinateFormatTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/CoordinateFormatTest.java
index 3fcb565..408693e 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/CoordinateFormatTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/CoordinateFormatTest.java
@@ -56,7 +56,7 @@
      */
     private static void assertPositionEquals(final DirectPosition expected, final DirectPosition actual) {
         assertNotSame(expected, actual);
-        assertArrayEquals(expected.getCoordinates(), actual.getCoordinates());
+        assertArrayEquals(expected.getCoordinate(), actual.getCoordinate());
     }
 
     /**
@@ -93,7 +93,7 @@
         final CoordinateFormat format = new CoordinateFormat(null, null);
         final ParsePosition charPos = new ParsePosition(0);
         DirectPosition position = format.parse("23.78 -12.74 127.9 3.25", charPos);
-        assertArrayEquals(new double[] {23.78, -12.74, 127.9, 3.25}, position.getCoordinates());
+        assertArrayEquals(new double[] {23.78, -12.74, 127.9, 3.25}, position.getCoordinate());
         assertEquals(-1, charPos.getErrorIndex());
         assertEquals(23, charPos.getIndex());
         /*
@@ -102,7 +102,7 @@
          */
         charPos.setIndex(0);
         position = format.parse("4.64 10.25 -3.12", charPos);
-        assertArrayEquals(new double[] {4.64, 10.25, -3.12}, position.getCoordinates());
+        assertArrayEquals(new double[] {4.64, 10.25, -3.12}, position.getCoordinate());
         assertEquals(-1, charPos.getErrorIndex());
         assertEquals(16, charPos.getIndex());
         /*
@@ -113,7 +113,7 @@
         assertEquals("; ", format.getSeparator());
         charPos.setIndex(0);
         position = format.parse("4.64;10.25 ;  -3.12", charPos);
-        assertArrayEquals(new double[] {4.64, 10.25, -3.12}, position.getCoordinates());
+        assertArrayEquals(new double[] {4.64, 10.25, -3.12}, position.getCoordinate());
         assertEquals(-1, charPos.getErrorIndex());
         assertEquals(19, charPos.getIndex());
     }
@@ -157,9 +157,9 @@
         final CoordinateFormat format = new CoordinateFormat(Locale.US, null);
         format.setDefaultCRS(HardCodedConversions.mercator());
         DirectPosition pos = format.parse("100 m W 300 m N", new ParsePosition(0));
-        assertArrayEquals(new double[] {-100, 300}, pos.getCoordinates());
+        assertArrayEquals(new double[] {-100, 300}, pos.getCoordinate());
         pos = format.parse("200 m E 100 m S", new ParsePosition(0));
-        assertArrayEquals(new double[] {200, -100}, pos.getCoordinates());
+        assertArrayEquals(new double[] {200, -100}, pos.getCoordinate());
     }
 
     /**
@@ -213,7 +213,7 @@
         format.setDefaultCRS(HardCodedCRS.GEOID_4D);
         final ParsePosition charPos = new ParsePosition(11);
         final DirectPosition pos = format.parse("(to skip); 23°46,8′E 12°44,4′S 127,9 m 22-09-2006 07:00 (ignore)", charPos);
-        assertArrayEquals(new double[] {23.78, -12.74, 127.90, 54000.25}, pos.getCoordinates());
+        assertArrayEquals(new double[] {23.78, -12.74, 127.90, 54000.25}, pos.getCoordinate());
         assertEquals(-1, charPos.getErrorIndex());
         assertEquals(55, charPos.getIndex());
         /*
@@ -248,7 +248,7 @@
         DirectPosition position = format.parse(buffer, charPos);
         assertEquals(buffer.length(), charPos.getIndex(), "Should have parsed the whole text.");
         assertEquals(2, position.getDimension(), "DirectPosition.getDimension()");
-        assertArrayEquals(new double[] {-3, 4}, position.getCoordinates());
+        assertArrayEquals(new double[] {-3, 4}, position.getCoordinate());
     }
 
     /**
@@ -264,7 +264,7 @@
         DirectPosition position = coordinateFormat.parse("[skip] 12", charPos);
         assertEquals(9, charPos.getIndex(), "Should have parsed the whole text.");
         assertEquals(1, position.getDimension(), "DirectPosition.getDimension()");
-        assertArrayEquals(new double[] {12}, position.getCoordinates());
+        assertArrayEquals(new double[] {12}, position.getCoordinate());
     }
 
     /**
@@ -337,7 +337,7 @@
         assertEquals("40°07′N 9°52′35,6″E ± 3 km", format.format(pos));
 
         final DirectPosition p = format.parseObject("40°07′N 9°52′35,6″E ± 3 km");
-        assertArrayEquals(new double[] {40.1166, 9.8765}, p.getCoordinates(), 0.0001);
+        assertArrayEquals(new double[] {40.1166, 9.8765}, p.getCoordinate(), 0.0001);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java
index 22b38cc..b1a34a1 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition1DTest.java
@@ -73,7 +73,7 @@
         assertTrue(p2.equals(p1));
         assertEquals(p2.hashCode(), p1.hashCode());
 
-        p1.setCoordinate(0, p1.getCoordinate(0) + 1);
+        p1.setOrdinate(0, p1.getOrdinate(0) + 1);
         assertFalse(p1.equals(p2));
         assertFalse(p2.equals(p1));
         assertNotEquals(p2.hashCode(), p1.hashCode());
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition2DTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition2DTest.java
index 6f83218..fd419a3 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition2DTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/DirectPosition2DTest.java
@@ -73,7 +73,7 @@
         assertTrue(p2.equals(p1));
         assertEquals(p2.hashCode(), p1.hashCode());
 
-        p1.setCoordinate(0, p1.getCoordinate(0) + 1);
+        p1.setOrdinate(0, p1.getOrdinate(0) + 1);
         assertFalse(p1.equals(p2));
         assertFalse(p2.equals(p1));
         assertNotEquals(p2.hashCode(), p1.hashCode());
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/GeneralEnvelopeTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/GeneralEnvelopeTest.java
index 5ecf0ba..3bf7fa7 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/GeneralEnvelopeTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/GeneralEnvelopeTest.java
@@ -36,6 +36,9 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.PENDING_NEXT_GEOAPI_RELEASE;
+
 
 /**
  * Tests the {@link GeneralEnvelope} class. The {@link Envelope2D} class will also be tested as a
@@ -57,7 +60,7 @@
      * This is set to {@code true} only when we intentionally want to create an invalid envelope,
      * for example in order to test normalization.
      */
-    boolean skipValidation;
+    boolean skipValidation = !PENDING_NEXT_GEOAPI_RELEASE;
 
     /**
      * Creates a new test case.
@@ -115,14 +118,14 @@
         }
         final DirectPosition lower = test.getLowerCorner();
         final DirectPosition upper = test.getUpperCorner();
-        assertEquals(xLower, lower.getCoordinate(0), "lower");
-        assertEquals(xUpper, upper.getCoordinate(0), "upper");
+        assertEquals(xLower, lower.getOrdinate  (0), "lower");
+        assertEquals(xUpper, upper.getOrdinate  (0), "upper");
         assertEquals(xmin,   test .getMinimum   (0), "xmin");
         assertEquals(xmax,   test .getMaximum   (0), "xmax");
         assertEquals(ymin,   test .getMinimum   (1), "ymin");
         assertEquals(ymax,   test .getMaximum   (1), "ymax");
-        assertEquals(ymin,   lower.getCoordinate(1), "ymin");
-        assertEquals(ymax,   upper.getCoordinate(1), "ymax");
+        assertEquals(ymin,   lower.getOrdinate  (1), "ymin");
+        assertEquals(ymax,   upper.getOrdinate  (1), "ymax");
         if (test instanceof Envelope2D ri) {
             assertEquals(xmin, ri.getMinX(), "xmin");
             assertEquals(xmax, ri.getMaxX(), "xmax");
@@ -615,8 +618,8 @@
     @Test
     public void testCornerModifications() {
         final GeneralEnvelope e = create(2, -4, 3, -3);
-        e.getLowerCorner().setCoordinate(0,  1);
-        e.getUpperCorner().setCoordinate(1, -1);
+        e.getLowerCorner().setOrdinate(0,  1);
+        e.getUpperCorner().setOrdinate(1, -1);
         assertEquals( 1, e.getLower(0));
         assertEquals(-4, e.getLower(1));
         assertEquals( 3, e.getUpper(0));
@@ -662,8 +665,8 @@
 
         envelope.setTimeRange(Instant.parse("2015-04-10T06:00:00Z"),
                               Instant.parse("2018-12-29T12:00:00Z"));
-        assertArrayEquals(new double[] {-20, -30, 57122.25}, envelope.getLowerCorner().getCoordinates());
-        assertArrayEquals(new double[] { 25,  12, 58481.50}, envelope.getUpperCorner().getCoordinates());
+        assertArrayEquals(new double[] {-20, -30, 57122.25}, envelope.getLowerCorner().getCoordinate());
+        assertArrayEquals(new double[] { 25,  12, 58481.50}, envelope.getUpperCorner().getCoordinate());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/WraparoundAdjustmentTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/WraparoundAdjustmentTest.java
index d044168..f4ccf20 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/WraparoundAdjustmentTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/geometry/WraparoundAdjustmentTest.java
@@ -230,7 +230,7 @@
 
         final WraparoundAdjustment wa = new WraparoundAdjustment(domainOfValidity, HardCodedConversions.mercator());
         DirectPosition actual = wa.shift(pointOfInterest);
-        assertEquals(-6679169, actual.getCoordinate(0), 1);
-        assertEquals(  221194, actual.getCoordinate(1), 1);
+        assertEquals(-6679169, actual.getOrdinate(0), 1);
+        assertEquals(  221194, actual.getOrdinate(1), 1);
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/CRSParserTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/CRSParserTest.java
deleted file mode 100644
index 70d4ed5..0000000
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/CRSParserTest.java
+++ /dev/null
@@ -1,733 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.io.wkt;
-
-import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.referencing.crs.VerticalCRS;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.datum.RealizationMethod;
-import org.apache.sis.metadata.privy.AxisNames;
-import org.apache.sis.referencing.factory.GeodeticObjectFactory;
-import org.apache.sis.system.Loggers;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Disabled;
-import static org.junit.jupiter.api.Assertions.*;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.api.extension.RegisterExtension;
-import org.opengis.test.referencing.WKTParserTest;
-import org.apache.sis.test.LoggingWatcher;
-import org.apache.sis.test.FailureDetailsReporter;
-
-
-/**
- * Tests Well-Known Text parser using the tests defined in GeoAPI. Those tests use the
- * {@link org.apache.sis.referencing.factory.GeodeticObjectFactory#createFromWKT(String)} method.
- *
- * @author  Martin Desruisseaux (IRD, Geomatys)
- */
-@ExtendWith(FailureDetailsReporter.class)
-public final class CRSParserTest extends WKTParserTest {
-    /**
-     * A JUnit extension for listening to log events.
-     */
-    @RegisterExtension
-    public final LoggingWatcher loggings;
-
-    /**
-     * Whether the test should replace the curly quotation marks “ and ” by the straight quotation mark ".
-     * The ISO 19162 specification uses only straight quotation marks, but SIS supports both.
-     * Curly quotation marks are convenient for identifying bugs, so we test them first.
-     */
-    private boolean useStraightQuotes;
-
-    /**
-     * Creates a new test case using the default {@code CRSFactory} implementation.
-     */
-    public CRSParserTest() {
-        super(GeodeticObjectFactory.provider());
-        loggings = new LoggingWatcher(Loggers.WKT);
-    }
-
-    /**
-     * Pre-process the WKT string before parsing. This method may replace curly quotation marks
-     * ({@code “} and {@code ”}) by straight quotation marks ({@code "}).
-     * The Apache SIS parser should understand both forms transparently.
-     *
-     * @param  wkt  the Well-Known Text to pre-process.
-     * @return the Well-Known Text to parse.
-     */
-    @Override
-    protected String preprocessWKT(String wkt) {
-        if (useStraightQuotes) {
-            wkt = super.preprocessWKT(wkt);
-        }
-        return wkt;
-    }
-
-    /**
-     * Verifies the axis names of a geographic CRS. This method is invoked when the parsed object is
-     * expected to have <q>Geodetic latitude</q> and <q>Geodetic longitude</q> names.
-     */
-    @SuppressWarnings("fallthrough")
-    private void verifyEllipsoidalCS() {
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        switch (cs.getDimension()) {
-            default: assertEquals(AxisNames.ELLIPSOIDAL_HEIGHT, cs.getAxis(2).getName().getCode(), "name");
-            case 2:  assertEquals(AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode(), "name");
-            case 1:  assertEquals(AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode(), "name");
-            case 0:  break;
-        }
-        switch (cs.getDimension()) {
-            default: assertEquals("h", cs.getAxis(2).getAbbreviation(), "abbreviation");
-            case 2:  assertEquals("λ", cs.getAxis(1).getAbbreviation(), "abbreviation");
-            case 1:  assertEquals("φ", cs.getAxis(0).getAbbreviation(), "abbreviation");
-            case 0:  break;
-        }
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  GEODCRS[“WGS 84”,
-     *    DATUM[“World Geodetic System 1984”,
-     *      ELLIPSOID[“WGS 84”, 6378137, 298.257223563,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,3],
-     *      AXIS[“(lat)”,north,ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      AXIS[“(lon)”,east,ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      AXIS[“ellipsoidal height (h)”,up,LENGTHUNIT[“metre”,1.0]]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographic3D() throws FactoryException {
-        super.testGeographic3D();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographic3D();                           // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  GEODCRS[“S-95”,
-     *    DATUM[“Pulkovo 1995”,
-     *      ELLIPSOID[“Krassowsky 1940”, 6378245, 298.3,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“degree”,0.0174532925199433],
-     *    REMARK[“Система Геодеэических Координвт года 1995(СК-95)”]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithUnicode() throws FactoryException {
-        super.testGeographicWithUnicode();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithUnicode();                  // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  GEODCRS[“NAD83”,
-     *    DATUM[“North American Datum 1983”,
-     *      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101, LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north],
-     *      AXIS[“longitude”,east],
-     *      ANGLEUNIT[“degree”,0.017453292519943],
-     *    ID[“EPSG”,4269],
-     *    REMARK[“1986 realisation”]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithIdentifier() throws FactoryException {
-        super.testGeographicWithIdentifier();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithIdentifier();               // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  GEODCRS[“NTF (Paris)”,
-     *    DATUM[“Nouvelle Triangulation Francaise”,
-     *      ELLIPSOID[“Clarke 1880 (IGN)”, 6378249.2, 293.4660213]],
-     *    PRIMEM[“Paris”,2.5969213],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“grad”,0.015707963267949],
-     *    REMARK[“Nouvelle Triangulation Française”]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithGradUnits() throws FactoryException {
-        super.testGeographicWithGradUnits();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithGradUnits();                // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  GEODETICCRS[“JGD2000”,
-     *    DATUM[“Japanese Geodetic Datum 2000”,
-     *      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]],
-     *    CS[Cartesian,3],
-     *      AXIS[“(X)”,geocentricX],
-     *      AXIS[“(Y)”,geocentricY],
-     *      AXIS[“(Z)”,geocentricZ],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    SCOPE[“Geodesy, topographic mapping and cadastre”],
-     *    AREA[“Japan”],
-     *    BBOX[17.09,122.38,46.05,157.64],
-     *    TIMEEXTENT[2002-04-01,2011-10-21],
-     *    ID[“EPSG”,4946,URI[“urn:ogc:def:crs:EPSG::4946”]],
-     *    REMARK[“注:JGD2000ジオセントリックは現在JGD2011に代わりました。”]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeocentric() throws FactoryException {
-        super.testGeocentric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.GEOCENTRIC_X, cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(AxisNames.GEOCENTRIC_Y, cs.getAxis(1).getName().getCode(), "name");
-        assertEquals(AxisNames.GEOCENTRIC_Z, cs.getAxis(2).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testGeocentric();                             // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Ignored for now, because the Lambert Azimuthal Equal Area projection method is not yet implemented.
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    @Disabled("Lambert Azimuthal Equal Area projection method not yet implemented.")
-    public void testProjectedYX() throws FactoryException {
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  PROJCRS[“NAD27 / Texas South Central”,
-     *    BASEGEODCRS[“NAD27”,
-     *      DATUM[“North American Datum 1927”,
-     *        ELLIPSOID[“Clarke 1866”, 20925832.164, 294.97869821,
-     *          LENGTHUNIT[“US survey foot”,0.304800609601219]]]],
-     *    CONVERSION[“Texas South Central SPCS27”,
-     *      METHOD[“Lambert Conic Conformal (2SP)”,ID[“EPSG”,9802]],
-     *      PARAMETER[“Latitude of false origin”,27.83333333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8821]],
-     *      PARAMETER[“Longitude of false origin”,-99.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8822]],
-     *      PARAMETER[“Latitude of 1st standard parallel”,28.383333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8823]],
-     *      PARAMETER[“Latitude of 2nd standard parallel”,30.283333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8824]],
-     *      PARAMETER[“Easting at false origin”,2000000.0,
-     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8826]],
-     *      PARAMETER[“Northing at false origin”,0.0,
-     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8827]]],
-     *    CS[Cartesian,2],
-     *      AXIS[“(x)”,east],
-     *      AXIS[“(y)”,north],
-     *      LENGTHUNIT[“US survey foot”,0.304800609601219],
-     *    REMARK[“Fundamental point: Meade’s Ranch KS, latitude 39°13'26.686"N, longitude 98°32'30.506"W.”]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testProjectedWithFootUnits() throws FactoryException {
-        super.testProjectedWithFootUnits();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.EASTING,  cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(AxisNames.NORTHING, cs.getAxis(1).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testProjectedWithFootUnits();                  // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters and the line feed in {@code REMARK}):
-     *
-     * <blockquote><pre>PROJCRS[“NAD83 UTM 10”,
-     *  BASEGEODCRS[“NAD83(86)”,
-     *    DATUM[“North American Datum 1983”,
-     *      ELLIPSOID[“GRS 1980”,6378137,298.257222101]],
-     *    ANGLEUNIT[“degree”,0.0174532925199433],
-     *    PRIMEM[“Greenwich”,0]],
-     *  CONVERSION[“UTM zone 10N”,ID[“EPSG”,16010],
-     *    METHOD[“Transverse Mercator”],
-     *    PARAMETER[“Latitude of natural origin”,0.0],
-     *    PARAMETER[“Longitude of natural origin”,-123.0],
-     *    PARAMETER[“Scale factor”,0.9996],
-     *    PARAMETER[“False easting”,500000.0],
-     *    PARAMETER[“False northing”,0.0]],
-     *  CS[Cartesian,2],
-     *    AXIS[“(E)”,east,ORDER[1]],
-     *    AXIS[“(N)”,north,ORDER[2]],
-     *    LENGTHUNIT[“metre”,1.0],
-     *  REMARK[“In this example units are implied. This is allowed for backward compatibility.
-     *          It is recommended that units are explicitly given in the string,
-     *          as in the previous two examples.”]]</pre></blockquote>
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testProjectedWithImplicitParameterUnits() throws FactoryException {
-        super.testProjectedWithImplicitParameterUnits();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.EASTING,  cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(AxisNames.NORTHING, cs.getAxis(1).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testProjectedWithImplicitParameterUnits();    // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name and vertical datum type.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  VERTCRS[“NAVD88”,
-     *    VDATUM[“North American Vertical Datum 1988”],
-     *    CS[vertical,1],
-     *      AXIS[“gravity-related height (H)”,up],LENGTHUNIT[“metre”,1.0]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testVertical() throws FactoryException {
-        super.testVertical();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.GRAVITY_RELATED_HEIGHT, cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(RealizationMethod.GEOID, ((VerticalCRS) object).getDatum().getRealizationMethod().orElse(null));
-
-        useStraightQuotes = true;
-        super.testVertical();                               // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  TIMECRS[“GPS Time”,
-     *    TDATUM[“Time origin”,TIMEORIGIN[1980-01-01T00:00:00.0Z]],
-     *    CS[temporal,1],AXIS[“time”,future],TIMEUNIT[“day”,86400.0]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testTemporal() throws FactoryException {
-        super.testTemporal();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.TIME, cs.getAxis(0).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testTemporal();                               // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  PARAMETRICCRS[“WMO standard atmosphere layer 0”,
-     *    PDATUM[“Mean Sea Level”,ANCHOR[“1013.25 hPa at 15°C”]],
-     *    CS[parametric,1],
-     *    AXIS[“pressure (hPa)”,up],
-     *    PARAMETRICUNIT[“hPa”,100.0]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testParametric() throws FactoryException {
-        super.testParametric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("pressure", cs.getAxis(0).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testParametric();                             // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  ENGINEERINGCRS[“Astra Minas Grid”,
-     *    ENGINEERINGDATUM[“Astra Minas”],
-     *    CS[Cartesian,2],
-     *      AXIS[“northing (X)”,north,ORDER[1]],
-     *      AXIS[“westing (Y)”,west,ORDER[2]],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    ID[“EPSG”,5800]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineering() throws FactoryException {
-        super.testEngineering();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.NORTHING, cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(AxisNames.WESTING,  cs.getAxis(1).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testEngineering();                            // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  ENGCRS[“A construction site CRS”,
-     *    EDATUM[“P1”,ANCHOR[“Peg in south corner”]],
-     *    CS[Cartesian,2],
-     *      AXIS[“site east”,southWest,ORDER[1]],
-     *      AXIS[“site north”,southEast,ORDER[2]],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    TIMEEXTENT[“date/time t1”,“date/time t2”]]
-     *  }
-     *
-     * In current Apache SIS version, this test produces a logs a warning saying
-     * that the {@code TimeExtent[…]} element is not yet supported.
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineeringRotated() throws FactoryException {
-        super.testEngineeringRotated();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("site east",  cs.getAxis(0).getName().getCode(), "name");
-        assertEquals("site north", cs.getAxis(1).getName().getCode(), "name");
-        loggings.assertNextLogContains("A construction site CRS", "TimeExtent[String,String]");
-
-        useStraightQuotes = true;
-        super.testEngineeringRotated();                     // Test again with “ and ” replaced by ".
-        loggings.assertNextLogContains("A construction site CRS", "TimeExtent[String,String]");
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  ENGCRS[“A ship-centred CRS”,
-     *    EDATUM[“Ship reference point”,ANCHOR[“Centre of buoyancy”]],
-     *    CS[Cartesian,3],
-     *      AXIS[“(x)”,forward],
-     *      AXIS[“(y)”,starboard],
-     *      AXIS[“(z)”,down],
-     *      LENGTHUNIT[“metre”,1.0]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineeringForShip() throws FactoryException {
-        super.testEngineeringForShip();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        /*
-         * In this case we had no axis names, so Apache SIS reused the abbreviations.
-         * This could change in any future SIS version if we update Transliterator.
-         */
-        assertEquals("x", cs.getAxis(0).getName().getCode(), "name");
-        assertEquals("y", cs.getAxis(1).getName().getCode(), "name");
-        assertEquals("z", cs.getAxis(2).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testEngineeringForShip();                     // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  GEODCRS[“ETRS89 Lambert Azimuthal Equal Area CRS”,
-     *    BASEGEODCRS[“WGS 84”,
-     *      DATUM[“WGS 84”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.2572236,LENGTHUNIT[“metre”,1.0]]]],
-     *    DERIVINGCONVERSION[“Atlantic pole”,
-     *      METHOD[“Pole rotation”,ID[“Authority”,1234]],
-     *      PARAMETER[“Latitude of rotated pole”,52.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Longitude of rotated pole”,-30.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Axis rotation”,-25.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“degree”,0.0174532925199433]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedGeodetic() throws FactoryException {
-        super.testDerivedGeodetic();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testDerivedGeodetic();                        // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  ENGCRS[“Topocentric example A”,
-     *    BASEGEODCRS[“WGS 84”,
-     *      DATUM[“WGS 84”,
-     *        ELLIPSOID[“WGS 84”, 6378137, 298.2572236, LENGTHUNIT[“metre”,1.0]]]],
-     *    DERIVINGCONVERSION[“Topocentric example A”,
-     *      METHOD[“Geographic/topocentric conversions”,ID[“EPSG”,9837]],
-     *      PARAMETER[“Latitude of topocentric origin”,55.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Longitude of topocentric origin”,5.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Ellipsoidal height of topocentric origin”,0.0,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[Cartesian,3],
-     *      AXIS[“Topocentric East (U)”,east,ORDER[1]],
-     *      AXIS[“Topocentric North (V)”,north,ORDER[2]],
-     *      AXIS[“Topocentric height (W)”,up,ORDER[3]],
-     *      LENGTHUNIT[“metre”,1.0]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedEngineeringFromGeodetic() throws FactoryException {
-        super.testDerivedEngineeringFromGeodetic();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("Topocentric East",   cs.getAxis(0).getName().getCode(), "name");
-        assertEquals("Topocentric North",  cs.getAxis(1).getName().getCode(), "name");
-        assertEquals("Topocentric height", cs.getAxis(2).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testDerivedEngineeringFromGeodetic();         // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     *
-     * @see org.apache.sis.referencing.operation.provider.SeismicBinGridMock
-     */
-    @Test
-    @Override
-    @Disabled("Pending implementation of EPSG:1049 — Seismic bin grid.")
-    public void testDerivedEngineeringFromProjected() throws FactoryException {
-        super.testDerivedEngineeringFromProjected();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        /*
-         * In this case we had no axis names, so Apache SIS reused the abbreviations.
-         * This could change in any future SIS version if we update Transliterator.
-         */
-        assertEquals("I", cs.getAxis(0).getName().getCode(), "name");
-        assertEquals("J", cs.getAxis(1).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testDerivedEngineeringFromProjected();        // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  COMPOUNDCRS[“NAD83 + NAVD88”,
-     *    GEODCRS[“NAD83”,
-     *      DATUM[“North American Datum 1983”,
-     *        ELLIPSOID[“GRS 1980”,6378137,298.257222101,
-     *          LENGTHUNIT[“metre”,1.0]]],
-     *        PRIMEMERIDIAN[“Greenwich”,0],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“latitude”,north,ORDER[1]],
-     *        AXIS[“longitude”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      VERTCRS[“NAVD88”,
-     *        VDATUM[“North American Vertical Datum 1988”],
-     *        CS[vertical,1],
-     *          AXIS[“gravity-related height (H)”,up],
-     *          LENGTHUNIT[“metre”,1]]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithVertical() throws FactoryException {
-        super.testCompoundWithVertical();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.GEODETIC_LATITUDE,      cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(AxisNames.GEODETIC_LONGITUDE,     cs.getAxis(1).getName().getCode(), "name");
-        assertEquals(AxisNames.GRAVITY_RELATED_HEIGHT, cs.getAxis(2).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testCompoundWithVertical();                   // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  COMPOUNDCRS[“GPS position and time”,
-     *    GEODCRS[“WGS 84”,
-     *      DATUM[“World Geodetic System 1984”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.257223563]],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“(lat)”,north,ORDER[1]],
-     *        AXIS[“(lon)”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *    TIMECRS[“GPS Time”,
-     *      TIMEDATUM[“Time origin”,TIMEORIGIN[1980-01-01]],
-     *      CS[temporal,1],
-     *        AXIS[“time (T)”,future],
-     *        TIMEUNIT[“day”,86400]]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithTime() throws FactoryException {
-        super.testCompoundWithTime();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode(), "name");
-        assertEquals(AxisNames.TIME,               cs.getAxis(2).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testCompoundWithTime();                       // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@snippet lang="wkt" :
-     *  COMPOUNDCRS[“ICAO layer 0”,
-     *    GEODETICCRS[“WGS 84”,
-     *      DATUM[“World Geodetic System 1984”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.257223563,
-     *          LENGTHUNIT[“metre”,1.0]]],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“latitude”,north,ORDER[1]],
-     *        AXIS[“longitude”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *    PARAMETRICCRS[“WMO standard atmosphere”,
-     *      PARAMETRICDATUM[“Mean Sea Level”,
-     *        ANCHOR[“Mean Sea Level = 1013.25 hPa”]],
-     *          CS[parametric,1],
-     *            AXIS[“pressure (P)”,unspecified],
-     *            PARAMETRICUNIT[“hPa”,100]]]
-     *  }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithParametric() throws FactoryException {
-        super.testCompoundWithParametric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals(AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode(), "name");
-        assertEquals(AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode(), "name");
-        assertEquals("pressure",                   cs.getAxis(2).getName().getCode(), "name");
-
-        useStraightQuotes = true;
-        super.testCompoundWithParametric();                 // Test again with “ and ” replaced by ".
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/FormatterTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/FormatterTest.java
index 913f4bb..6c4f5c0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/FormatterTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/FormatterTest.java
@@ -100,7 +100,7 @@
     }
 
     /**
-     * Tests (indirectly) {@link Formatter#append(ControlledVocabulary)}.
+     * Tests (indirectly) {@code Formatter.append(ControlledVocabulary)}.
      */
     @Test
     public void testAppendCodeList() {
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
index 6356d05..5d11c50 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
@@ -226,7 +226,7 @@
                 "  ANCHOR[“Tananarive observatory”]]");
 
         assertNameAndIdentifierEqual("Tananarive 1925", 0, datum);
-        assertEquals("Tananarive observatory", datum.getAnchorDefinition().get().toString());
+        assertEquals("Tananarive observatory", datum.getAnchorPoint().toString());
 
         final Ellipsoid ellipsoid = datum.getEllipsoid();
         assertNameAndIdentifierEqual("International 1924", 0, ellipsoid);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/MathTransformParserTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/MathTransformParserTest.java
index b2c6a84..9a8d8b0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/MathTransformParserTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/MathTransformParserTest.java
@@ -30,8 +30,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/TransliteratorTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/TransliteratorTest.java
index 3ba62d8..aaf43b9 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/TransliteratorTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/TransliteratorTest.java
@@ -28,6 +28,9 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.mock.CoordinateSystemAxisMock;
 
+// Specific to the main branch:
+import org.apache.sis.referencing.privy.AxisDirections;
+
 
 /**
  * Tests the {@link Transliterator} class.
@@ -101,7 +104,7 @@
         assertEquals("φ",  t.toUnicodeAbbreviation("ellipsoidal", AxisDirection.NORTH,     "P"), "P");
         assertEquals("φ",  t.toUnicodeAbbreviation("ellipsoidal", AxisDirection.NORTH,     "B"), "B");
         assertEquals("λ",  t.toUnicodeAbbreviation("ellipsoidal", AxisDirection.EAST,      "L"), "L");
-        assertEquals("θ",  t.toUnicodeAbbreviation("polar",       AxisDirection.CLOCKWISE, "U"), "U");
+        assertEquals("θ",  t.toUnicodeAbbreviation("polar",       AxisDirections.CLOCKWISE,"U"), "U");
         assertEquals("Ω",  t.toUnicodeAbbreviation("spherical",   AxisDirection.NORTH,     "U"), "U");
         assertEquals("θ",  t.toUnicodeAbbreviation("spherical",   AxisDirection.EAST,      "V"), "V");
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTDictionaryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTDictionaryTest.java
index d937e32..8934620 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTDictionaryTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTDictionaryTest.java
@@ -45,9 +45,9 @@
 import static org.apache.sis.test.Assertions.assertSetEquals;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -372,7 +372,7 @@
          * for checking precedence.
          */
         GeographicCRS crs = factory.createGeographicCRS("2C");
-        Identifier id = TestUtilities.getSingleton(crs.getIdentifiers());
+        ReferenceIdentifier id = TestUtilities.getSingleton(crs.getIdentifiers());
         assertEquals("TEST", id.getCodeSpace());
         assertEquals("21",   id.getCode());
         assertSame(crs, factory.createGeographicCRS("2C"));                         // Test caching.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
index c78574b..45a81ce 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
@@ -34,9 +34,6 @@
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.parameter.ParameterDirection;
-
 
 /**
  * Tests the {@link DefaultParameterDescriptorGroup} class.
@@ -105,7 +102,6 @@
      */
     @Test
     public void validateTestObjects() {
-        assertEquals(ParameterDirection.IN, M1_M1_O1_O2.getDirection());
         for (final GeneralParameterDescriptor descriptor : M1_M1_O1_O2.descriptors()) {
             AssertionError error = null;
             try {
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterValueTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterValueTest.java
index ae70183..315c247 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterValueTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/DefaultParameterValueTest.java
@@ -377,8 +377,8 @@
         final AxisDirection[] directions = {
             AxisDirection.NORTH,
             AxisDirection.SOUTH,
-            AxisDirection.DISPLAY_LEFT,
-            AxisDirection.PAST
+            AxisDirection.PAST,
+            AxisDirection.DISPLAY_LEFT
         };
         final ParameterDescriptor<AxisDirection> descriptor = DefaultParameterDescriptorTest.create(
                 "Direction", AxisDirection.class, directions, AxisDirection.NORTH);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParametersTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParametersTest.java
index 121ec08..afc3045 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParametersTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParametersTest.java
@@ -40,11 +40,6 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Optional;
-import org.opengis.parameter.ParameterDirection;
-import org.opengis.util.TypeName;
-
 
 /**
  * Tests the static methods in the {@link Parameters} class.
@@ -114,11 +109,8 @@
             @Override public Collection<GenericName>       getAlias()         {return descriptor.getAlias();}
             @Override public Set<ReferenceIdentifier>      getIdentifiers()   {return descriptor.getIdentifiers();}
             @Override public InternationalString           getRemarks()       {return descriptor.getRemarks();}
-            @Override public Optional<InternationalString> getDescription()   {return descriptor.getDescription();}
-            @Override public ParameterDirection            getDirection()     {return descriptor.getDirection();}
             @Override public int                           getMinimumOccurs() {return descriptor.getMinimumOccurs();}
             @Override public int                           getMaximumOccurs() {return descriptor.getMaximumOccurs();}
-            @Override public TypeName                      getValueType()     {return descriptor.getValueType();}
             @Override public Class<T>                      getValueClass()    {return descriptor.getValueClass();}
             @Override public Set<T>                        getValidValues()   {return descriptor.getValidValues();}
             @Override public Comparable<T>                 getMinimumValue()  {return descriptor.getMinimumValue();}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/TensorParametersTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/TensorParametersTest.java
index ad1d665..c91054f 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/TensorParametersTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/TensorParametersTest.java
@@ -34,9 +34,6 @@
 import static org.apache.sis.referencing.Assertions.assertEpsgIdentifierEquals;
 import static org.apache.sis.referencing.Assertions.assertAliasTipEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Validators.validate;
-
 
 /**
  * Tests the {@link TensorParameters} class.
@@ -281,7 +278,6 @@
                 }
                 final ParameterValueGroup group = param.createValueGroup(
                         Map.of(ParameterDescriptor.NAME_KEY, "Test"), matrix);
-                validate(group);
                 assertEquals(numRow, group.parameter(NUM_ROW).intValue());
                 assertEquals(numCol, group.parameter(NUM_COL).intValue());
                 assertEquals(matrix, param.toMatrix(group));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java
index c6eb2e5..9f2d2be 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java
@@ -39,9 +39,8 @@
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.referencing.IdentifiedObject.*;
-import static org.opengis.referencing.ObjectDomain.*;
+// Specific to the main branch:
+import static org.opengis.referencing.ReferenceSystem.*;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
index 360d535..d7a85f0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
@@ -54,6 +54,9 @@
 import org.apache.sis.test.TestUtilities;
 import static org.apache.sis.test.Assertions.assertMultilinesEquals;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Assertion methods used by the {@code org.apache.sis.referencing} module in addition of the ones inherited
@@ -88,7 +91,7 @@
      * @param expected  the expected identifier code.
      * @param actual    the identifier to verify.
      */
-    public static void assertOgcIdentifierEquals(final String expected, final Identifier actual) {
+    public static void assertOgcIdentifierEquals(final String expected, final ReferenceIdentifier actual) {
         assertNotNull(actual);
         assertEquals(expected,      actual.getCode(), "code");
         assertEquals(Constants.OGC, actual.getCodeSpace(), "codeSpace");
@@ -106,7 +109,7 @@
     public static void assertEpsgIdentifierEquals(final String expected, final Identifier actual) {
         assertNotNull(actual);
         assertEquals(expected,        actual.getCode(), "code");
-        assertEquals(Constants.EPSG,  actual.getCodeSpace(), "codeSpace");
+        assertEquals(Constants.EPSG,  (actual instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) actual).getCodeSpace() : null, "codeSpace");
         assertEquals(Constants.EPSG,  Citations.toCodeSpace(actual.getAuthority()), "authority");
         assertEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected, IdentifiedObjects.toString(actual), "identifier");
     }
@@ -308,8 +311,8 @@
             if (i < tolerances.length) {
                 tolerance = tolerances[i];
             }
-            if (abs(expectedLower.getCoordinate(i) - actualLower.getCoordinate(i)) > tolerance ||
-                abs(expectedUpper.getCoordinate(i) - actualUpper.getCoordinate(i)) > tolerance)
+            if (abs(expectedLower.getOrdinate(i) - actualLower.getOrdinate(i)) > tolerance ||
+                abs(expectedUpper.getOrdinate(i) - actualUpper.getOrdinate(i)) > tolerance)
             {
                 fail("Envelopes are not equal in dimension " + i + ":\n"
                         + "expected " + Envelopes.toString(expected) + "\n"
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/BuilderTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/BuilderTest.java
index e075067..2e19fc5 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/BuilderTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/BuilderTest.java
@@ -35,6 +35,9 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import org.apache.sis.test.TestCase;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Tests {@link Builder}.
@@ -79,17 +82,17 @@
         /*
          * The failed attempt to set a new codespace shall not have modified builder state.
          */
-        assertEquals("EPSG",         builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG",         builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  (Citations.EPSG, builder.properties.get(Identifier.AUTHORITY_KEY));
         /*
          * After a cleanup (normally after a createXXX(…) method call), user shall be allowed to
          * set a new codespace again. Note that the cleanup operation shall not clear the codespace.
          */
         builder.onCreate(true);
-        assertEquals("EPSG",         builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG",         builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  (Citations.EPSG, builder.properties.get(Identifier.AUTHORITY_KEY));
         builder.setCodeSpace(IOGP, "EPSG");
-        assertEquals("EPSG", builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG", builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  ( IOGP,  builder.properties.get(Identifier.AUTHORITY_KEY));
     }
 
@@ -212,7 +215,7 @@
                     assertSame(Citations.EPSG, value);      // Authority and codespace shall be unchanged.
                     break;
                 }
-                case Identifier.CODESPACE_KEY: {
+                case ReferenceIdentifier.CODESPACE_KEY: {
                     assertEquals("EPSG", value);            // Authority and codespace shall be unchanged.
                     break;
                 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
index 57725b1..a347e64 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
@@ -47,9 +47,6 @@
 import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-
 
 /**
  * Tests the {@link CRS} class.
@@ -246,7 +243,7 @@
                 default: throw new AssertionError(i);
             }
             properties.put(DefaultProjectedCRS.NAME_KEY, "CRS #" + i);
-            properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, new DefaultExtent(
+            properties.put(DefaultProjectedCRS.DOMAIN_OF_VALIDITY_KEY, new DefaultExtent(
                     null, new DefaultGeographicBoundingBox(-1, +1, ymin, ymax), null, null));
             crs[i] = new DefaultProjectedCRS(properties, baseCRS.geographic(), HardCodedConversions.MERCATOR, cs);
         }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java
index d2a66ce..a4d9837 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java
@@ -51,10 +51,10 @@
 import static org.apache.sis.test.TestUtilities.*;
 import static org.apache.sis.util.privy.Constants.UTC;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import org.opengis.referencing.datum.VerticalDatumType;
 import org.apache.sis.util.privy.TemporalDate;
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -214,24 +214,23 @@
      * Verifies the vertical datum enumeration.
      */
     @Test
-    @SuppressWarnings("deprecation")
     public void testVertical() {
         for (final CommonCRS.Vertical e : CommonCRS.Vertical.values()) {
-            final RealizationMethod method;
+            final VerticalDatumType method;
             final String axisName, datumName;
             switch (e) {
-                case NAVD88:         axisName = AxisNames.GRAVITY_RELATED_HEIGHT; datumName = "North American Vertical Datum 1988"; method = RealizationMethod. GEOID; break;
-                case BAROMETRIC:     axisName = "Barometric altitude";            datumName = "Constant pressure surface";          method = RealizationMethod.valueOf("BAROMETRIC"); break;
-                case MEAN_SEA_LEVEL: axisName = AxisNames.GRAVITY_RELATED_HEIGHT; datumName = "Mean Sea Level";                     method = RealizationMethod. TIDAL; break;
-                case DEPTH:          axisName = AxisNames.DEPTH;                  datumName = "Mean Sea Level";                     method = RealizationMethod. TIDAL; break;
+                case NAVD88:         axisName = AxisNames.GRAVITY_RELATED_HEIGHT; datumName = "North American Vertical Datum 1988"; method = VerticalDatumType. GEOIDAL;       break;
+                case BAROMETRIC:     axisName = "Barometric altitude";            datumName = "Constant pressure surface";          method = VerticalDatumType. BAROMETRIC;    break;
+                case MEAN_SEA_LEVEL: axisName = AxisNames.GRAVITY_RELATED_HEIGHT; datumName = "Mean Sea Level";                     method = VerticalDatumType. GEOIDAL;       break;
+                case DEPTH:          axisName = AxisNames.DEPTH;                  datumName = "Mean Sea Level";                     method = VerticalDatumType. GEOIDAL;       break;
                 case ELLIPSOIDAL:    axisName = AxisNames.ELLIPSOIDAL_HEIGHT;     datumName = "Ellipsoid";                          method = VerticalDatumTypes.ellipsoidal(); break;
-                case OTHER_SURFACE:  axisName = "Height";                         datumName = "Other surface";                      method = null; break;
+                case OTHER_SURFACE:  axisName = "Height";                         datumName = "Other surface";                      method = VerticalDatumType. OTHER_SURFACE; break;
                 default: throw new AssertionError(e);
             }
             final String        name  = e.name();
             final VerticalDatum datum = e.datum();
             final VerticalCRS   crs   = e.crs();
-            if (e.isEPSG) {
+            if (e.isEPSG && !name.startsWith("NAV")) {
                 /*
                  * BAROMETRIC and ELLIPSOIDAL uses an axis named "Height", which is not a valid
                  * axis name according ISO 19111. We skip the validation test for those enums.
@@ -241,10 +240,8 @@
             assertSame(datum, e.datum(), name);                         // Datum before CRS creation.
             assertSame(crs.getDatum(), e.datum(), name);                // Datum after CRS creation.
             assertEquals(datumName, datum.getName().getCode(), name);
+            assertEquals(method, datum.getVerticalDatumType(), name);
             assertEquals(axisName,  crs.getCoordinateSystem().getAxis(0).getName().getCode(), name);
-            if (!e.isEPSG) {  // Because the information is not in EPSG database 9.x.
-                assertEquals(method, datum.getRealizationMethod().orElse(null), name);
-            }
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
index f50e1b8..67ca991 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
@@ -44,6 +44,11 @@
  */
 public final class GeodesicsOnEllipsoidTest extends GeodeticCalculatorTest {
     /**
+     * Tolerance threshold for comparison of floating point numbers.
+     */
+    private static final double STRICT = 0;
+
+    /**
      * The {@link GeodesicsOnEllipsoid} instance to be tested.
      * A specialized type is used for tracking locale variables.
      */
@@ -315,8 +320,8 @@
          *   λ₂  —  end point longitude. Shifted by 10° compared to Karney because of λ₁ = 10°.
          *   α₂  —  azimuth at end point.
          */
-        assertEquals( 41.79331020506, endPoint.getCoordinate(1), 1E-11, "φ₂");
-        assertEquals(147.84490004377, endPoint.getCoordinate(0), 1E-11, "λ₂");
+        assertEquals( 41.79331020506, endPoint.getOrdinate(1), 1E-11, "φ₂");
+        assertEquals(147.84490004377, endPoint.getOrdinate(0), 1E-11, "λ₂");
         assertEquals(149.09016931807, testedEarth.getEndingAzimuth(), 1E-11, "α₂");
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticCalculatorTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticCalculatorTest.java
index 8b1c9bc..5d48afe 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticCalculatorTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticCalculatorTest.java
@@ -29,9 +29,9 @@
 import net.sf.geographiclib.Geodesic;
 import net.sf.geographiclib.GeodesicData;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.referencing.cs.AxisDirection;
 import org.apache.sis.referencing.privy.Formulas;
 import org.apache.sis.referencing.privy.ShapeUtilitiesExt;
 import org.apache.sis.geometry.DirectPosition2D;
@@ -50,9 +50,9 @@
 import org.apache.sis.test.widget.VisualCheck;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertBetween;
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertBetween;
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -83,8 +83,8 @@
      * @param ε  the tolerance threshold.
      */
     static void assertPositionEquals(final double φ, final double λ, final DirectPosition p, final double ε) {
-        assertEquals(φ, p.getCoordinate(0), ε, "φ");
-        assertEquals(λ, p.getCoordinate(1), ε, "λ");
+        assertEquals(φ, p.getOrdinate(0), ε, "φ");
+        assertEquals(λ, p.getOrdinate(1), ε, "λ");
     }
 
     /**
@@ -411,8 +411,8 @@
                     aErrors.accept(abs(c.getStartingAzimuth() - azimuth));
                     c.setStartingAzimuth(azimuth);
                     DirectPosition endPoint = c.getEndPoint();
-                    final double φ = endPoint.getCoordinate(0);
-                    final double λ = endPoint.getCoordinate(1);
+                    final double φ = endPoint.getOrdinate(0);
+                    final double λ = endPoint.getOrdinate(1);
                     double dy =              (buffer[0] - φ)      * toMetres;
                     double dx = IEEEremainder(buffer[1] - λ, 360) * toMetres * cos(toRadians(φ));
                     yError.accept(abs(dy) / resolution);
@@ -523,11 +523,11 @@
                 final DirectPosition start = c.getStartPoint();
                 final DirectPosition end   = c.getEndPoint();
                 try {
-                    assertEquals(expected[COLUMN_φ1], start.getCoordinate(0),  Formulas.ANGULAR_TOLERANCE, "φ₁");
-                    assertEquals(expected[COLUMN_λ1], start.getCoordinate(1),  Formulas.ANGULAR_TOLERANCE, "λ₁");
+                    assertEquals(expected[COLUMN_φ1], start.getOrdinate(0),    Formulas.ANGULAR_TOLERANCE, "φ₁");
+                    assertEquals(expected[COLUMN_λ1], start.getOrdinate(1),    Formulas.ANGULAR_TOLERANCE, "λ₁");
                     assertEquals(expected[COLUMN_α1], c.getStartingAzimuth(),  azimuthTolerance / cosφ1,   "α₁");
-                    assertEquals(expected[COLUMN_φ2], end.getCoordinate(0),    latitudeTolerance,          "φ₂");
-                    assertEquals(expected[COLUMN_λ2], end.getCoordinate(1),    longitudeTolerance,         "λ₂");
+                    assertEquals(expected[COLUMN_φ2], end.getOrdinate(0),      latitudeTolerance,          "φ₂");
+                    assertEquals(expected[COLUMN_λ2], end.getOrdinate(1),      longitudeTolerance,         "λ₂");
                     assertEquals(expected[COLUMN_α2], c.getEndingAzimuth(),    azimuthTolerance / cosφ2,   "α₂");
                     assertEquals(expected[COLUMN_Δs], c.getGeodesicDistance(), linearTolerance * relaxIfConfirmed(potentialProblem), "∆s");
                     clear();
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticObjectVerifier.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticObjectVerifier.java
index dec5d99..04536e6 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticObjectVerifier.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticObjectVerifier.java
@@ -38,8 +38,10 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceSystem;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.operation.CoordinateOperation;
 
 
 /**
@@ -64,11 +66,17 @@
      * @param  isMandatory  {@code true} if an absence of world extent is a failure.
      */
     private static void assertIsWorld(final IdentifiedObject object, boolean isMandatory) {
-        for (ObjectDomain domain : object.getDomains()) {
-            assertIsWorld(domain.getDomainOfValidity(), isMandatory);
-            isMandatory = false;
+        final Extent extent;
+        if (object instanceof ReferenceSystem) {
+            extent = ((ReferenceSystem) object).getDomainOfValidity();
+        } else if (object instanceof Datum) {
+            extent = ((Datum) object).getDomainOfValidity();
+        } else if (object instanceof CoordinateOperation) {
+            extent = ((CoordinateOperation) object).getDomainOfValidity();
+        } else {
+            extent = null;
         }
-        assertFalse(isMandatory, "Expected a world extent element.");
+        assertIsWorld(extent, isMandatory);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/ImmutableIdentifierTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/ImmutableIdentifierTest.java
index 0876f04..86e9a6d 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/ImmutableIdentifierTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/ImmutableIdentifierTest.java
@@ -39,8 +39,8 @@
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.metadata.Identifier.*;
+// Specific to the main branch:
+import static org.opengis.referencing.ReferenceIdentifier.*;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java
index 93959a3..0934b00 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java
@@ -32,8 +32,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -55,7 +55,7 @@
     @Test
     public void testCreateFromCode() {
         final NamedIdentifier identifier = new NamedIdentifier(Citations.EPSG, "EPSG", "4326", "8.3", null);
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
@@ -81,7 +81,7 @@
         final NameFactory factory = DefaultNameFactory.provider();
         final NameSpace scope = factory.createNameSpace(factory.createLocalName(null, "IOGP"), null);
         final NamedIdentifier identifier = new NamedIdentifier(factory.createGenericName(scope, "EPSG", "4326"));
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
@@ -118,7 +118,7 @@
     @Test
     public void testCreateFromInternationalString() {
         final NamedIdentifier identifier = createI18N();
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java
index c1cae87..755ac94 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java
@@ -41,8 +41,8 @@
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 import static org.apache.sis.referencing.Assertions.assertEpsgNameAndIdentifierEqual;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
index 84b99e3..10840b6 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
@@ -41,9 +41,9 @@
 import static org.apache.sis.referencing.Assertions.assertEpsgNameAndIdentifierEqual;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -133,7 +133,7 @@
     @Test
     public void testConstruction() {
         final DefaultDerivedCRS crs = createLongitudeRotation();
-        Validators.validate(crs);
+//      Validators.validate(crs);
 
         assertEquals("Back to Greenwich",                crs.getName().getCode());
         assertEquals("NTF (Paris)",                      crs.getBaseCRS().getName().getCode());
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultEngineeringCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultEngineeringCRSTest.java
index 58f3256..0f6551d 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultEngineeringCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultEngineeringCRSTest.java
@@ -34,8 +34,8 @@
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
index 7030e97..05bdb0c 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
@@ -32,8 +32,8 @@
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -94,7 +94,7 @@
     @Test
     public void testIdentifiers() {
         GeographicCRS crs = CommonCRS.WGS72.geographic();
-        Identifier identifier = getSingleton(crs.getIdentifiers());
+        ReferenceIdentifier identifier = getSingleton(crs.getIdentifiers());
         assertEquals("EPSG", identifier.getCodeSpace());
         assertEquals("4322", identifier.getCode());
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultImageCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultImageCRSTest.java
index a1cbedb..15dacd9 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultImageCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultImageCRSTest.java
@@ -37,8 +37,8 @@
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java
index 8376c09..0577630 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java
@@ -47,8 +47,8 @@
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 import static org.apache.sis.referencing.Assertions.assertEpsgNameAndIdentifierEqual;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRS.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRS.java
index 3a7a54c..19bee74 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRS.java
@@ -30,8 +30,8 @@
 import org.apache.sis.referencing.datum.HardCodedDatum;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.referencing.ObjectDomain.DOMAIN_OF_VALIDITY_KEY;
+// Specific to the main branch:
+import static org.opengis.referencing.ReferenceSystem.DOMAIN_OF_VALIDITY_KEY;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRSTest.java
index 3694edf..c3be229 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/HardCodedCRSTest.java
@@ -47,8 +47,8 @@
     public void validate() {
         final ValidatorContainer validators = new ValidatorContainer();
         validators.validate(WGS84);
-        validators.validate(WGS84_3D);           validators.crs.enforceStandardNames = false;
-        validators.validate(ELLIPSOIDAL_HEIGHT); validators.crs.enforceStandardNames = true;
+        validators.validate(WGS84_3D);
+        validators.validate(ELLIPSOIDAL_HEIGHT);
         validators.validate(GRAVITY_RELATED_HEIGHT);
         validators.validate(TIME);
         validators.validate(SPHERICAL);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/CoordinateSystemsTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/CoordinateSystemsTest.java
index 626e9a4..832f804 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/CoordinateSystemsTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/CoordinateSystemsTest.java
@@ -42,8 +42,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
index 0b44ba6..ec240fd 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
@@ -26,8 +26,9 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import org.apache.sis.referencing.privy.AxisDirections;
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -52,8 +53,8 @@
         final DefaultCylindricalCS normalized = cs.forConvention(AxesConvention.DISPLAY_ORIENTED);
         assertNotSame(cs, normalized);
         assertAxisDirectionsEqual(normalized,
-                AxisDirection.AWAY_FROM,
-                AxisDirection.COUNTER_CLOCKWISE,
+                AxisDirections.AWAY_FROM,
+                AxisDirections.COUNTER_CLOCKWISE,
                 AxisDirection.UP);
     }
 
@@ -75,7 +76,7 @@
         DefaultCylindricalCS normalized = cs.forConvention(AxesConvention.RIGHT_HANDED);
         assertNotSame(cs, normalized);
         assertAxisDirectionsEqual(normalized,
-                AxisDirection.CLOCKWISE,        // Interchanged (r,θ) order for making right handed.
+                AxisDirections.CLOCKWISE,                       // Interchanged (r,θ) order for making right handed.
                 AxisDirection.SOUTH,
                 AxisDirection.UP);
 
@@ -83,7 +84,7 @@
         assertNotSame(cs, normalized);
         assertAxisDirectionsEqual(normalized,
                 AxisDirection.SOUTH,                            // Not modified to North because radius cannot be negative.
-                AxisDirection.COUNTER_CLOCKWISE,
+                AxisDirections.COUNTER_CLOCKWISE,
                 AxisDirection.UP);
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
index bb21729..17c96d7 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
@@ -26,8 +26,9 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import org.apache.sis.referencing.privy.AxisDirections;
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -52,8 +53,8 @@
         final DefaultPolarCS normalized = cs.forConvention(AxesConvention.DISPLAY_ORIENTED);
         assertNotSame(cs, normalized);
         assertAxisDirectionsEqual(normalized,
-                AxisDirection.AWAY_FROM,
-                AxisDirection.COUNTER_CLOCKWISE);
+                AxisDirections.AWAY_FROM,
+                AxisDirections.COUNTER_CLOCKWISE);
     }
 
     /**
@@ -72,7 +73,7 @@
 
         DefaultPolarCS normalized = cs.forConvention(AxesConvention.RIGHT_HANDED);
         assertAxisDirectionsEqual(normalized,
-                AxisDirection.CLOCKWISE,
+                AxisDirections.CLOCKWISE,
                 AxisDirection.SOUTH);
         assertSame(cs, normalized);
 
@@ -80,6 +81,6 @@
         assertNotSame(cs, normalized);
         assertAxisDirectionsEqual(normalized,
                 AxisDirection.SOUTH,                            // Not modified to North because radius cannot be negative.
-                AxisDirection.COUNTER_CLOCKWISE);
+                AxisDirections.COUNTER_CLOCKWISE);
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
index 4aadf4b..0022abe 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
@@ -24,8 +24,9 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import org.apache.sis.referencing.privy.AxisDirections;
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -90,8 +91,8 @@
         final DefaultSphericalCS normalized = cs.forConvention(AxesConvention.NORMALIZED);
         assertNotSame(cs, normalized);          // Should create a new CoordinateSystem.
         assertAxisDirectionsEqual(normalized,
-                AxisDirection.COUNTER_CLOCKWISE,
+                AxisDirections.COUNTER_CLOCKWISE,
                 AxisDirection.UP,
-                AxisDirection.AWAY_FROM);
+                AxisDirections.AWAY_FROM);
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/HardCodedAxes.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/HardCodedAxes.java
index b7b43a9..bffb391 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/HardCodedAxes.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/HardCodedAxes.java
@@ -23,6 +23,9 @@
 import org.apache.sis.metadata.privy.AxisNames;
 import org.apache.sis.measure.Units;
 
+// Specific to the main branch:
+import org.apache.sis.referencing.privy.AxisDirections;
+
 
 /**
  * Collection of axes for testing purpose.
@@ -398,7 +401,7 @@
      * @see #GEOCENTRIC_RADIUS
      */
     public static final DefaultCoordinateSystemAxis DISTANCE = create("Distance", "r",
-            AxisDirection.AWAY_FROM, Units.METRE, 0, Double.POSITIVE_INFINITY, RangeMeaning.EXACT);
+            AxisDirections.AWAY_FROM, Units.METRE, 0, Double.POSITIVE_INFINITY, RangeMeaning.EXACT);
 
     /**
      * An axis with clockwise orientation.
@@ -406,7 +409,7 @@
      * (not to be confused with geodetic spherical coordinate system).
      */
     public static final DefaultCoordinateSystemAxis BEARING = create("Bearing", "θ",
-            AxisDirection.CLOCKWISE, Units.DEGREE, -180, +180, RangeMeaning.WRAPAROUND);
+            AxisDirections.CLOCKWISE, Units.DEGREE, -180, +180, RangeMeaning.WRAPAROUND);
 
     /**
      * An axis with for elevation angle.
@@ -469,7 +472,7 @@
      * for axes that were not properly defined.
      */
     public static final DefaultCoordinateSystemAxis UNDEFINED = create("Undefined", "m",
-            AxisDirection.UNSPECIFIED, Units.UNITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, null);
+            AxisDirections.UNSPECIFIED, Units.UNITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, null);
 
     /**
      * Creates a new axis of the given name, abbreviation, direction and unit.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/NormalizerTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/NormalizerTest.java
index 4b8a95d..3a6b058 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/NormalizerTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/NormalizerTest.java
@@ -113,7 +113,6 @@
      * with axes of legacy (WKT 1) axes.
      */
     @Test
-    @SuppressWarnings("deprecation")
     public void testSortWKT1() {
         assertOrdered(new AxisDirection[] {
             AxisDirection.OTHER,
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/BursaWolfParametersTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/BursaWolfParametersTest.java
index 7a1f3d3..a97e6cd 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/BursaWolfParametersTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/BursaWolfParametersTest.java
@@ -31,8 +31,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
index 15c196a..07fa963 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
@@ -48,8 +48,8 @@
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java
index 58b4981..0adab08 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java
@@ -34,14 +34,13 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.TestUtilities.getScope;
 
+// Specific to the main branch:
+import static org.opengis.referencing.ReferenceSystem.*;
+import static org.apache.sis.test.GeoapiAssert.assertIdentifierEquals;
+
 // Specific to the main and geoapi-3.1 branches:
 import org.apache.sis.util.privy.TemporalDate;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.referencing.ObjectDomain.*;
-import static org.opengis.referencing.IdentifiedObject.*;
-import static org.opengis.test.Assertions.assertIdentifierEquals;
-
 
 /**
  * Tests the {@link DefaultTemporalDatum} class.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
index 5a2241f..ae7c354 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
@@ -38,8 +38,9 @@
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import java.lang.reflect.Field;
+import org.opengis.referencing.datum.VerticalDatumType;
 
 
 /**
@@ -47,7 +48,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-@SuppressWarnings("deprecation")
 public final class DefaultVerticalDatumTest extends TestCase {
     /**
      * Creates a new test case.
@@ -69,12 +69,40 @@
     }
 
     /**
+     * Tests the {@link DefaultVerticalDatum#getVerticalDatumType()} method in a state
+     * simulating unmarshalling of GML 3.2 document.
+     *
+     * @throws NoSuchFieldException   Should never happen.
+     * @throws IllegalAccessException Should never happen.
+     */
+    @Test
+    public void testAfterUnmarshal() throws NoSuchFieldException, IllegalAccessException {
+        final Field typeField = DefaultVerticalDatum.class.getDeclaredField("type");
+        typeField.setAccessible(true);
+        assertEquals(VerticalDatumType .GEOIDAL,       typeForName(typeField, "Geoidal height"));
+        assertEquals(VerticalDatumType .DEPTH,         typeForName(typeField, "Some depth measurement"));
+        assertEquals(VerticalDatumTypes.ellipsoidal(), typeForName(typeField, "Ellipsoidal height"));
+        assertEquals(VerticalDatumType .OTHER_SURFACE, typeForName(typeField, "NotADepth"));
+    }
+
+    /**
+     * Returns the vertical datum type inferred by {@link DefaultVerticalDatum} for the given name.
+     */
+    private static VerticalDatumType typeForName(final Field typeField, final String name) throws IllegalAccessException {
+        final var datum = new DefaultVerticalDatum(
+                Map.of(DefaultVerticalDatum.NAME_KEY, name),
+                VerticalDatumType.OTHER_SURFACE);
+        typeField.set(datum, null);
+        return datum.getVerticalDatumType();
+    }
+
+    /**
      * Tests {@link DefaultVerticalDatum#toWKT()}.
      */
     @Test
     public void testToWKT() {
         DefaultVerticalDatum datum;
-        datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Geoidal"), RealizationMethod.GEOID);
+        datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Geoidal"), VerticalDatumType.GEOIDAL);
         assertWktEquals(Convention.WKT1, "VERT_DATUM[“Geoidal”, 2005]", datum);
         assertWktEquals(Convention.WKT2, "VDATUM[“Geoidal”]", datum);
         assertWktEquals(Convention.WKT2_SIMPLIFIED, "VerticalDatum[“Geoidal”]", datum);
@@ -94,7 +122,11 @@
     public void testXML() throws JAXBException {
         final DefaultVerticalDatum datum = unmarshalFile(DefaultVerticalDatum.class, openTestFile(false));
         assertIsMeanSeaLevel(datum, true);
-        assertTrue(datum.getRealizationMethod().isEmpty());
+        /*
+         * Following attribute does not exist in GML 3.2, so it has been inferred.
+         * Our datum name is "Mean Sea Level", which is mapped to the geoidal type.
+         */
+        assertEquals(VerticalDatumType.GEOIDAL, datum.getVerticalDatumType());
         /*
          * Values in the following tests are specific to our XML file.
          * The actual texts in the EPSG database are more descriptive.
@@ -126,7 +158,7 @@
         /*
          * Following attribute exists in GML 3.1 only.
          */
-        assertEquals(RealizationMethod.GEOID, datum.getRealizationMethod().orElse(null));
+        assertEquals(VerticalDatumType.GEOIDAL, datum.getVerticalDatumType());
         /*
          * The name, anchor definition and domain of validity are lost because
          * those property does not have the same XML element name (SIS-160).
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/GeodeticDatumMock.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/GeodeticDatumMock.java
index ba97fe6..b837b89 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/GeodeticDatumMock.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/GeodeticDatumMock.java
@@ -27,6 +27,11 @@
 // Test dependencies
 import org.apache.sis.test.mock.IdentifiedObjectMock;
 
+// Specific to the main branch:
+import java.util.Date;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.extent.Extent;
+
 
 /**
  * A dummy implementation of {@link GeodeticDatum}, which is also its own ellipsoid.
@@ -120,12 +125,16 @@
         return new Object[] {getCode(), alias, semiMajorAxis, semiMinorAxis, inverseFlattening, isIvfDefinitive};
     }
 
-    @Override public PrimeMeridian getPrimeMeridian()     {return PrimeMeridianMock.GREENWICH;}
-    @Override public Ellipsoid     getEllipsoid()         {return this;}
-    @Override public Unit<Length>  getAxisUnit()          {return Units.METRE;}
-    @Override public double        getSemiMajorAxis()     {return semiMajorAxis;}
-    @Override public double        getSemiMinorAxis()     {return semiMinorAxis;}
-    @Override public double        getInverseFlattening() {return inverseFlattening;}
-    @Override public boolean       isSphere()             {return semiMajorAxis == semiMinorAxis;}
-    @Override public boolean       isIvfDefinitive()      {return isIvfDefinitive;}
+    @Override public PrimeMeridian        getPrimeMeridian()      {return PrimeMeridianMock.GREENWICH;}
+    @Override public Ellipsoid            getEllipsoid()          {return this;}
+    @Override public Unit<Length>         getAxisUnit()           {return Units.METRE;}
+    @Override public double               getSemiMajorAxis()      {return semiMajorAxis;}
+    @Override public double               getSemiMinorAxis()      {return semiMinorAxis;}
+    @Override public double               getInverseFlattening()  {return inverseFlattening;}
+    @Override public boolean              isSphere()              {return semiMajorAxis == semiMinorAxis;}
+    @Override public boolean              isIvfDefinitive()       {return isIvfDefinitive;}
+    @Override public InternationalString  getAnchorPoint()        {return null;}
+    @Override public Date                 getRealizationEpoch()   {return null;}
+    @Override public Extent               getDomainOfValidity()   {return null;}
+    @Override public InternationalString  getScope()              {return null;}
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/HardCodedDatum.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/HardCodedDatum.java
index dedd076..e79d091 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/HardCodedDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/HardCodedDatum.java
@@ -28,10 +28,9 @@
 // Test dependencies
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
-import static org.opengis.referencing.IdentifiedObject.*;
-import static org.opengis.referencing.ObjectDomain.*;
+// Specific to the main branch:
+import org.opengis.referencing.datum.VerticalDatumType;
+import static org.opengis.referencing.datum.Datum.*;
 
 
 /**
@@ -126,10 +125,9 @@
     /**
      * Mean sea level, which can be used as an approximation of geoid.
      */
-    @SuppressWarnings("deprecation")
     public static final DefaultVerticalDatum MEAN_SEA_LEVEL = new DefaultVerticalDatum(
             properties("Mean Sea Level", "5100", "Hydrography."),
-            RealizationMethod.GEOID);
+            VerticalDatumType.GEOIDAL);
 
     /**
      * Default datum for time measured since January 1st, 1970 at 00:00 UTC.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/TimeDependentBWPTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/TimeDependentBWPTest.java
index 662d942..9aca865 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/TimeDependentBWPTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/TimeDependentBWPTest.java
@@ -30,8 +30,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/AuthorityFactoryMock.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/AuthorityFactoryMock.java
index daa8d5f..f176248 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/AuthorityFactoryMock.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/AuthorityFactoryMock.java
@@ -127,7 +127,6 @@
      * @throws NoSuchAuthorityCodeException if the given code is unknown.
      */
     @Override
-    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws NoSuchAuthorityCodeException {
         assertFalse(isClosed());
         final int n;
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
index 08182cb..6302cba 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
@@ -52,8 +52,8 @@
 import static org.apache.sis.referencing.Assertions.assertWktEqualsRegex;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -394,7 +394,7 @@
                 "\\E(?:  SCOPE\\[“.+”\\],\n)?\\Q" +                     // Ignore SCOPE[…] if present.
                 "  AREA[“World\\E.*\\Q”],\n" +
                 "  BBOX[-90.00, -180.00, 90.00, 180.00],\n" +
-                "  ID[“CRS”, 84, CITATION[“OGC:WMS”], URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]" +
+                "  ID[“CRS”, 84, CITATION[“WMS”], URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]" +
                 "\\E(?:,\n  REMARK\\[“.+”\\])?\\]",                     // Ignore trailing REMARK[…] if present.
                 crs);
         /*
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
deleted file mode 100644
index d96503c..0000000
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.factory;
-
-import java.util.Map;
-import javax.measure.Unit;
-import javax.measure.quantity.Angle;
-import javax.measure.quantity.Length;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.cs.CartesianCS;
-import org.opengis.referencing.cs.EllipsoidalCS;
-import org.opengis.referencing.cs.AxisDirection;
-import org.opengis.referencing.cs.CoordinateSystemAxis;
-import org.opengis.referencing.crs.GeodeticCRS;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.referencing.crs.ProjectedCRS;
-import org.opengis.referencing.datum.Ellipsoid;
-import org.opengis.referencing.datum.PrimeMeridian;
-import org.opengis.referencing.datum.GeodeticDatum;
-import org.opengis.referencing.operation.OperationMethod;
-import org.opengis.referencing.operation.Conversion;
-import org.opengis.parameter.ParameterValueGroup;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.referencing.operation.DefaultConversion;
-import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
-import org.apache.sis.io.wkt.Convention;
-import org.apache.sis.measure.Units;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.opengis.test.referencing.ObjectFactoryTest;
-import org.apache.sis.test.FailureDetailsReporter;
-import static org.apache.sis.referencing.Assertions.assertWktEquals;
-import static org.apache.sis.test.Assertions.assertMessageContains;
-
-
-/**
- * Tests {@link GeodeticObjectFactory} using the suite of tests provided in the GeoAPI project.
- * Note that this does not include authority factories tests or GIGS tests.
- *
- * @author  Cédric Briançon (Geomatys)
- */
-@ExtendWith(FailureDetailsReporter.class)
-public final class GeodeticObjectFactoryTest extends ObjectFactoryTest {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GeodeticObjectFactoryTest() {
-        super(GeodeticObjectFactory.provider(),
-              GeodeticObjectFactory.provider(),
-              GeodeticObjectFactory.provider(),
-              DefaultCoordinateOperationFactory.provider());
-    }
-
-    /**
-     * Tests {@link GeodeticObjectFactory#createFromWKT(String)}. We test only a very small WKT here because
-     * it is not the purpose of this class to test the parser. The main purpose of this test is to verify
-     * that {@link GeodeticObjectFactory} has been able to instantiate the parser.
-     *
-     * @throws FactoryException if the parsing failed.
-     */
-    @Test
-    public void testCreateFromWKT() throws FactoryException {
-        final GeodeticCRS crs = (GeodeticCRS) crsFactory.createFromWKT(
-                "GEOGCS[“WGS 84”,\n" +
-                "  DATUM[“World Geodetic System 1984”,\n" +
-                "    SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
-                "  PRIMEM[“Greenwich”, 0.0],\n" +
-                "  UNIT[“degree”, 0.017453292519943295]]");
-
-        assertEquals("WGS 84", crs.getName().getCode());
-        assertEquals("World Geodetic System 1984", crs.getDatum().getName().getCode());
-    }
-
-    /**
-     * Tests {@link GeodeticObjectFactory#createFromWKT(String)} with an erroneous projection parameter name.
-     * The intent is to verify that the expected exception is thrown.
-     *
-     * @throws FactoryException if the parsing failed for another reason than the expected one.
-     */
-    @Test
-    public void testInvalidParameterInWKT() throws FactoryException {
-        var e = assertThrows(InvalidGeodeticParameterException.class,
-                () -> crsFactory.createFromWKT(
-                "PROJCRS[“Custom”,\n" +
-                "  BASEGEODCRS[“North American 1983”,\n" +
-                "    DATUM[“North American 1983”,\n" +
-                "      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]]],\n" +
-                "  CONVERSION[“Custom”,\n" +
-                "    METHOD[“Lambert Conformal Conic”],\n" +
-                "    PARAMETER[“Standard parallel 1”, 43.0],\n" +
-                "    PARAMETER[“Standard parallel 2”, 45.5],\n" +
-                "    PARAMETER[“Central parallel”, 41.75]],\n" +       // Wrong parameter.
-                "  CS[Cartesian, 2],\n" +
-                "    AXIS[“(Y)”, north],\n" +
-                "    AXIS[“(X)”, east]]"),
-                "Should not have parsed a WKT with wrong projection parameter.");
-        assertMessageContains(e, "Central parallel");
-    }
-
-    /**
-     * Convenience method creating a map with only the "{@code name"} property.
-     * This is the only mandatory property for object creation.
-     */
-    private static Map<String,?> name(final String name) {
-        return Map.of(IdentifiedObject.NAME_KEY, name);
-    }
-
-    /**
-     * Tests step-by-step the creation of a new projected coordinate reference systems.
-     * This test creates every objects itself and compares with expected WKT 1 after each step.
-     *
-     * <p>Note that practical applications may use existing constants declared in the
-     * {@link CommonCRS} class instead of creating everything like this test does.</p>
-     *
-     * @throws FactoryException if the creation of a geodetic component failed.
-     */
-    @Test
-    public void testStepByStepCreation() throws FactoryException {
-        /*
-         * List of all objects to be created in this test.
-         */
-        final Unit<Length>         linearUnit;
-        final Unit<Angle>          angularUnit;
-        final Ellipsoid            ellipsoid;
-        final PrimeMeridian        meridian;
-        final GeodeticDatum        datum;
-        final CoordinateSystemAxis longitude, latitude, easting, northing;
-        final EllipsoidalCS        geographicCS;
-        final GeographicCRS        geographicCRS;
-        final OperationMethod      method;
-        final ParameterValueGroup  parameters;
-        final Conversion           projection;
-        final CartesianCS          projectedCS;
-        final ProjectedCRS         projectedCRS;
-        /*
-         * Prime meridian
-         */
-        angularUnit = Units.DEGREE;
-        meridian = datumFactory.createPrimeMeridian(name("Greenwich"), 0, angularUnit);
-        assertWktEquals(Convention.WKT1,
-                "PRIMEM[“Greenwich”, 0.0]", meridian);
-        /*
-         * Ellipsoid
-         */
-        linearUnit = Units.METRE;
-        ellipsoid = datumFactory.createEllipsoid(name("Airy1830"), 6377563.396, 6356256.910, linearUnit);
-        assertWktEquals(Convention.WKT1,
-                "SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]", ellipsoid);
-        /*
-         * Geodetic reference frame
-         */
-        datum = datumFactory.createGeodeticDatum(name("Airy1830"), ellipsoid, meridian);
-        assertWktEquals(Convention.WKT1,
-                "DATUM[“Airy1830”,\n" +
-                "  SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]]", datum);
-        /*
-         * Base coordinate reference system
-         */
-        longitude     =  csFactory.createCoordinateSystemAxis(name("Longitude"), "long", AxisDirection.EAST,  angularUnit);
-        latitude      =  csFactory.createCoordinateSystemAxis(name("Latitude"),  "lat",  AxisDirection.NORTH, angularUnit);
-        geographicCS  =  csFactory.createEllipsoidalCS(name("Ellipsoidal"), longitude, latitude);
-        geographicCRS = crsFactory.createGeographicCRS(name("Airy1830"), datum, geographicCS);
-        assertWktEquals(Convention.WKT1,
-                "GEOGCS[“Airy1830”,\n" +
-                "  DATUM[“Airy1830”,\n" +
-                "    SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]],\n" +
-                "    PRIMEM[“Greenwich”, 0.0],\n" +
-                "  UNIT[“degree”, 0.017453292519943295],\n" +
-                "  AXIS[“Longitude”, EAST],\n" +
-                "  AXIS[“Latitude”, NORTH]]", geographicCRS);
-        /*
-         * Defining conversion
-         */
-        method = copFactory.getOperationMethod("Transverse_Mercator");
-        parameters = method.getParameters().createValue();
-        parameters.parameter("semi_major")        .setValue(ellipsoid.getSemiMajorAxis());
-        parameters.parameter("semi_minor")        .setValue(ellipsoid.getSemiMinorAxis());
-        parameters.parameter("central_meridian")  .setValue(     49);
-        parameters.parameter("latitude_of_origin").setValue(     -2);
-        parameters.parameter("false_easting")     .setValue( 400000);
-        parameters.parameter("false_northing")    .setValue(-100000);
-        projection = new DefaultConversion(name("GBN grid"), method, null, parameters);
-        /*
-         * Projected coordinate reference system
-         */
-        easting      =  csFactory.createCoordinateSystemAxis(name("Easting"),  "x", AxisDirection.EAST,  linearUnit);
-        northing     =  csFactory.createCoordinateSystemAxis(name("Northing"), "y", AxisDirection.NORTH, linearUnit);
-        projectedCS  =  csFactory.createCartesianCS(name("Cartesian"), easting, northing);
-        projectedCRS = crsFactory.createProjectedCRS(name("Great_Britian_National_Grid"), geographicCRS, projection, projectedCS);
-        assertWktEquals(Convention.WKT1,
-                "PROJCS[“Great_Britian_National_Grid”,\n" +
-                "  GEOGCS[“Airy1830”,\n" +
-                "    DATUM[“Airy1830”,\n" +
-                "      SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]],\n" +
-                "      PRIMEM[“Greenwich”, 0.0],\n" +
-                "    UNIT[“degree”, 0.017453292519943295],\n" +
-                "    AXIS[“Longitude”, EAST],\n" +
-                "    AXIS[“Latitude”, NORTH]],\n" +
-                "  PROJECTION[“Transverse_Mercator”, AUTHORITY[“EPSG”, “9807”]],\n" +
-                "  PARAMETER[“latitude_of_origin”, -2.0],\n" +
-                "  PARAMETER[“central_meridian”, 49.0],\n" +
-                "  PARAMETER[“scale_factor”, 1.0],\n" +
-                "  PARAMETER[“false_easting”, 400000.0],\n" +
-                "  PARAMETER[“false_northing”, -100000.0],\n" +
-                "  UNIT[“metre”, 1],\n" +
-                "  AXIS[“Easting”, EAST],\n" +
-                "  AXIS[“Northing”, NORTH]]", projectedCRS);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/TestFactorySource.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/TestFactorySource.java
index a7a6104..6ee1a70 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/TestFactorySource.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/TestFactorySource.java
@@ -29,8 +29,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.junit.jupiter.api.Assumptions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertBetween;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertBetween;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
index ec03718..33b7f1f 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
@@ -69,8 +69,8 @@
 import static org.apache.sis.referencing.Assertions.assertAliasTipEquals;
 import static org.apache.sis.test.TestCase.TAG_SLOW;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
@@ -173,7 +173,7 @@
     @Test
     public void testGeocentric() throws FactoryException {
         final EPSGFactory factory = dataEPSG.factory();
-        final GeodeticCRS crs = factory.createGeodeticCRS("epsg:4915");
+        final GeodeticCRS crs = factory.createGeocentricCRS("epsg:4915");
         assertEpsgNameAndIdentifierEqual("ITRF93", 4915, crs);
         assertEpsgNameAndIdentifierEqual("International Terrestrial Reference Frame 1993", 6652, crs.getDatum());
         assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.GEOCENTRIC_X, AxisDirection.GEOCENTRIC_Y, AxisDirection.GEOCENTRIC_Z);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/epsg/README.md b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/epsg/README.md
index 6a90fc4..5238e57 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/epsg/README.md
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/epsg/README.md
@@ -98,7 +98,7 @@
 mvn clean install
 export CLASSPATH=~/.m2/repository/org/apache/derby/derby/10.14.2.0/derby-10.14.2.0.jar
 export CLASSPATH=$PWD/core/sis-metadata/target/test-classes:$CLASSPATH
-export CLASSPATH=$PWD/target/binaries/sis-referencing-1.x-SNAPSHOT.jar:$CLASSPATH
+export CLASSPATH=$PWD/target/binaries/sis-referencing-1.5-SNAPSHOT.jar:$CLASSPATH
 export CLASSPATH=$PWD/core/sis-metadata/target/test-classes:$CLASSPATH
 export CLASSPATH=$PWD/core/sis-referencing/target/test-classes:$CLASSPATH
 cd <path to local copy of http://svn.apache.org/repos/asf/sis/data/non-free/>
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
deleted file mode 100644
index d17bfae..0000000
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.geoapi;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.referencing.CRS;
-
-// Test dependencies
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.apache.sis.test.FailureDetailsReporter;
-
-
-/**
- * Runs the suite of transformation tests provided in the GeoAPI project.
- * The test suite uses the authority factory instance registered in {@link CRS}.
- * Some (not all) of those tests require the EPSG geodetic database to be installed.
- * If that database is not available, tests that cannot be executed will be automatically skipped.
- *
- * @author  Cédric Briançon (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
- * @since   1.1
- */
-@ExtendWith(FailureDetailsReporter.class)
-public final class AuthorityFactoryTest extends org.opengis.test.referencing.AuthorityFactoryTest {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     *
-     * @throws FactoryException if no factory can be returned for the given authority.
-     */
-    public AuthorityFactoryTest() throws FactoryException {
-        super(CRS.getAuthorityFactory(null), null, null);
-    }
-
-    /**
-     * Skips for now the <cite>Krovak</cite> projection.
-     */
-    @Override
-    @Disabled("Projection not yet implemented")
-    public void testEPSG_2065() {
-    }
-
-    /**
-     * Skips for now the <cite>Lambert Azimuthal Equal Area</cite> projection.
-     */
-    @Override
-    @Disabled("Projection not yet implemented")
-    public void testEPSG_3035() {
-    }
-
-    /**
-     * Skips for now the <cite>Hyperbolic Cassini-Soldner</cite> projection
-     * because projection derivative (Jacobian matrix) is not yet implemented.
-     */
-    @Override
-    @Disabled("Derivative (Jacobian) not yet implemented")
-    public void testEPSG_3139() {
-    }
-}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/ParameterizedTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/ParameterizedTransformTest.java
deleted file mode 100644
index a9c8a40..0000000
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/ParameterizedTransformTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.geoapi;
-
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.MathTransform2D;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.AfterEach;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.apache.sis.test.FailureDetailsReporter;
-
-
-/**
- * Runs a suite of tests provided in the GeoAPI project.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
- * @since   1.1
- */
-@ExtendWith(FailureDetailsReporter.class)
-public final class ParameterizedTransformTest extends org.opengis.test.referencing.ParameterizedTransformTest {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public ParameterizedTransformTest() {
-        super(DefaultMathTransformFactory.provider());
-    }
-
-    /**
-     * Every map projections shall be instances of {@link MathTransform2D}.
-     * Note that some tests inherited from the parent class are not about
-     * map projections.
-     */
-    @AfterEach
-    public void ensureMathTransform2D() {
-        final MathTransform tr = transform;
-        if (tr != null && tr.getSourceDimensions() == 2 && tr.getTargetDimensions() == 2) {
-            assertInstanceOf(MathTransform2D.class, tr);
-        }
-    }
-
-    /**
-     * Disables the derivative (Jacobian) tests because not yet implemented.
-     *
-     * @throws FactoryException if the math transform cannot be created.
-     * @throws TransformException if the example point cannot be transformed.
-     */
-    @Test
-    @Override
-    public void testModifiedAzimuthalEquidistant() throws FactoryException, TransformException {
-        isDerivativeSupported = false;
-        super.testModifiedAzimuthalEquidistant();
-    }
-
-    /**
-     * Disables the derivative (Jacobian) tests because not yet implemented.
-     *
-     * @throws FactoryException if the math transform cannot be created.
-     * @throws TransformException if the example point cannot be transformed.
-     */
-    @Test
-    @Override
-    public void testHyperbolicCassiniSoldner() throws FactoryException, TransformException {
-        isDerivativeSupported = false;
-        super.testHyperbolicCassiniSoldner();
-    }
-}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
index a9be269..00de10b 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
@@ -27,16 +27,12 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.datum.VerticalDatumType;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.datum.RealizationMethod;
-
 
 /**
  * Tests the {@link VerticalDatumTypes} class.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-@SuppressWarnings("deprecation")
 public final class VerticalDatumTypesTest extends TestCase {
     /**
      * Creates a new test case.
@@ -47,7 +43,7 @@
     /**
      * Verifies name constraint with values defined in {@link org.apache.sis.referencing.CommonCRS.Vertical}.
      * Some enumeration values must have the same names as the constants defined in {@link VerticalDatumTypes},
-     * because the realization method is obtained by a call to {@link RealizationMethod#valueOf(String)}.
+     * because the realization method is obtained by a call to {@link VerticalDatumType#valueOf(String)}.
      */
     @Test
     public void verifyNameConstraint() {
@@ -61,8 +57,8 @@
     @Test
     public void testFromLegacy() {
         assertEquals(VerticalDatumTypes.ellipsoidal(), VerticalDatumTypes.fromLegacy(2002));
-        assertEquals(RealizationMethod .GEOID,         VerticalDatumTypes.fromLegacy(2005));
-        assertEquals(RealizationMethod .TIDAL,         VerticalDatumTypes.fromLegacy(2006));
+        assertEquals(VerticalDatumType .GEOIDAL,       VerticalDatumTypes.fromLegacy(2005));
+        assertEquals(VerticalDatumType .DEPTH,         VerticalDatumTypes.fromLegacy(2006));
     }
 
     /**
@@ -80,9 +76,9 @@
      */
     @Test
     public void verifyCodeList() {
-        final RealizationMethod expected = VerticalDatumTypes.ellipsoidal();    // Must be first.
-        final RealizationMethod[] types = RealizationMethod.values();
-        assertEquals(RealizationMethod.LEVELLING, types[0]);
+        final VerticalDatumType expected = VerticalDatumTypes.ellipsoidal();    // Must be first.
+        final VerticalDatumType[] types = VerticalDatumType.values();
+        assertEquals(VerticalDatumType.OTHER_SURFACE, types[0]);
         assertTrue(ArraysExt.contains(types, expected));
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
index 6030884..d1c91e7 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
@@ -72,8 +72,8 @@
 import static org.apache.sis.test.Assertions.assertSetEquals;
 import static org.apache.sis.test.TestCase.STRICT;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.Assertions;
+// Specific to the main branch:
+import org.apache.sis.test.GeoapiAssert;
 
 
 /**
@@ -159,7 +159,7 @@
      * Verifies that the current transform is a linear transform with a matrix equals to the given one.
      */
     private void assertMatrixEquals(final Matrix expected) {
-        Assertions.assertMatrixEquals(expected,
+        GeoapiAssert.assertMatrixEquals(expected,
                 assertInstanceOf(LinearTransform.class, transform).getMatrix(),
                 STRICT, "transform.matrix");
     }
@@ -297,6 +297,7 @@
         if (targetCRS.getCoordinateSystem().getDimension() == 2) {
             target = TestUtilities.dropLastDimensions(target, 3, 2);
         }
+        tolerance = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(source, target);
         validate();
     }
@@ -947,7 +948,7 @@
         final var linear = assertInstanceOf(LinearTransform.class, transform);
         assertEquals(3, linear.getSourceDimensions());
         assertEquals(4, linear.getTargetDimensions());
-        Assertions.assertMatrixEquals(Matrices.create(5, 4, new double[] {
+        GeoapiAssert.assertMatrixEquals(Matrices.create(5, 4, new double[] {
                     1, 0, 0, 0,
                     0, 1, 0, 0,
                     0, 0, 0, 0,
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
index d6c33c5..46c5143 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
@@ -46,8 +46,8 @@
 import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
 import static org.apache.sis.referencing.Assertions.assertEpsgNameAndIdentifierEqual;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -266,6 +266,7 @@
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
         λDimension = new int[] {1};
+        tolerance  = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(new double[] {54.271680278,  0.098269657, 20.00},      // in grads east of Paris
                         new double[] {48.844443528,  2.424952028, 63.15});     // in degrees east of Greenwich
         validate();
@@ -300,6 +301,7 @@
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
         λDimension = new int[] {1};
+        tolerance  = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(new double[] {0.088442691, 48.844512250, 20.00},      // in degrees east of Paris
                         new double[] {2.424952028, 48.844443528, 63.15});     // in degrees east of Greenwich
         validate();
@@ -355,7 +357,7 @@
     private static void assertEpsgNameWithoutIdentifierEqual(final String name, final IdentifiedObject object) {
         assertNotNull(object, name);
         assertEquals(name, object.getName().getCode(), "name");
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             assertFalse("EPSG".equalsIgnoreCase(id.getCodeSpace()), "EPSG identifier not allowed for modified objects.");
         }
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java
index 440eb63..17f016e 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java
@@ -39,8 +39,8 @@
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertIdentifierEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertIdentifierEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java
index ba2fd69..5f53552 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java
@@ -50,8 +50,8 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
index ddf0677..6891d1a 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
@@ -37,6 +37,9 @@
 import static org.apache.sis.referencing.Assertions.assertEpsgNameAndIdentifierEqual;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Tests {@link DefaultOperationMethod}.
@@ -64,7 +67,7 @@
     {
         final Map<String,Object> properties = new HashMap<>(8);
         assertNull(properties.put(OperationMethod.NAME_KEY, method));
-        assertNull(properties.put(Identifier.CODESPACE_KEY, "EPSG"));
+        assertNull(properties.put(ReferenceIdentifier.CODESPACE_KEY, "EPSG"));
         assertNull(properties.put(Identifier.AUTHORITY_KEY, Citations.EPSG));
         /*
          * The parameter group for a Mercator projection is actually not empty, but it is not the purpose of
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
index 5f2dda1..c99c4aa 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
@@ -31,8 +31,8 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertIdentifierEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertIdentifierEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/SingleOperationMarshallingTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/SingleOperationMarshallingTest.java
index a7d05b2..1cc9107 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/SingleOperationMarshallingTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/SingleOperationMarshallingTest.java
@@ -51,9 +51,9 @@
 import static org.apache.sis.test.TestUtilities.getDomainOfValidity;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertIdentifierEquals;
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertIdentifierEquals;
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -156,7 +156,7 @@
         assertEquals("World Mercator", c.getName().getCode(), "name");
         assertEquals("3395", getSingleton(c.getIdentifiers()).getCode(), "identifier");
         assertEquals("Very small scale mapping.", getScope(c), "scope");
-        assertNull(c.getOperationVersion(), "operationVersion");
+        assertNull  (c.getOperationVersion(), "operationVersion");
 
         final GeographicBoundingBox e = getDomainOfValidity(c);
         assertEquals(+180, e.getEastBoundLongitude(), "eastBoundLongitude");
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
index 37be602..56fb1b3 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
@@ -37,8 +37,8 @@
 import org.apache.sis.referencing.operation.HardCodedConversions;
 import static org.apache.sis.test.Assertions.assertMapEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearizerTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearizerTest.java
index 71698d2..7df80a4 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearizerTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/LinearizerTest.java
@@ -29,8 +29,8 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.referencing.operation.HardCodedConversions;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.Assertions;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -71,7 +71,7 @@
         var expected = new Matrix3(111319, 0,   0,
                                    0, 110662, -62,
                                    0, 0, 1);
-        Assertions.assertMatrixEquals(expected, linear.getMatrix(), 0.5, "linear");
+        assertMatrixEquals(expected, linear.getMatrix(), 0.5, "linear");
         assertSame(points.create(null), linear, "Should have extracted the existing instance instead of computing a new one.");
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/ResidualGridTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
index 856ab06..3c1d996 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
@@ -26,8 +26,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java
index 29b28f5..35f4969 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java
@@ -35,8 +35,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertMultilinesEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/Matrix4Test.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
index e9a1bfe..9a4aa87 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
@@ -23,8 +23,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
index e94d7d1..504093a 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
@@ -31,8 +31,8 @@
 import org.apache.sis.test.TestUtilities;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
index bc37b79..5b280b7 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
@@ -25,8 +25,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
index 542d09c..a403a6a 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
@@ -27,9 +27,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.ToleranceModifier;
-
 
 /**
  * Tests the {@link AlbersEqualArea} class. We test using various values of standard parallels.
@@ -74,7 +71,6 @@
 
         final double delta = toRadians(100.0 / 60) / 1852;              // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
         final AlbersEqualArea kernel = (AlbersEqualArea) getKernel();
         assertTrue(isSpherical(kernel));
@@ -122,7 +118,6 @@
 
         final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
         final AlbersEqualArea kernel = (AlbersEqualArea) getKernel();
         assertFalse(isSpherical(kernel));
@@ -161,7 +156,6 @@
      */
     @Test
     public void compareWithPROJ() throws FactoryException, TransformException {
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
 
         // Spherical case
@@ -212,7 +206,6 @@
                 0);         // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         verifyTransform(new double[] {0,        0,
                                       0,      +90,
                                       0,      -90},
@@ -242,7 +235,6 @@
                 200);       // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
         verifyInDomain(new double[] {-40, 10},                  // Minimal input coordinate values
@@ -288,7 +280,6 @@
                 NaN);       // False northing (none)
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         /*
          * Skip inverse transform because the 176.003° become -183.997°. It is not the purpose
          * of this test to verify longitude wraparound in reverse projection (we do not expect
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java
index 9a89d60..289b922 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java
@@ -189,32 +189,4 @@
         verifyDerivative(toRadians(+3), toRadians(-10));
         verifyDerivative(toRadians(-4), toRadians(+10));
     }
-
-    /**
-     * Tests the <q>Cassini-Soldner</q> (EPSG:9806) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testCassiniSoldner()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(method(false)).testCassiniSoldner();
-    }
-
-    /**
-     * Tests the <q>Hyperbolic Cassini-Soldner</q> (EPSG:9833) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testHyperbolicCassiniSoldner()
-     */
-    @Test
-    public void runGeoapiHyperbolicTest() throws FactoryException, TransformException {
-        createGeoApiTestNoDerivatives(method(true)).testHyperbolicCassiniSoldner();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
index 04bca36..72a16dd 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
@@ -193,13 +193,6 @@
                 return projection.dy_dφ(sinφ, cos(φ)) * transform(φ);
             }
         };
-        isInverseTransformSupported = false;
-        derivativeDeltas = new double[] {2E-8};
-        tolerance = 1E-7;
-        verifyInDomain(new double[] {-89 * (PI/180)},                  // Minimal value to test.
-                       new double[] {+89 * (PI/180)},                  // Maximal value to test.
-                       new int[]    {100},                             // Number of points to test.
-                       TestUtilities.createRandomNumberGenerator());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
index 6c81e7a..93aca82 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
@@ -28,9 +28,6 @@
 // Test dependencies
 import org.junit.jupiter.api.Test;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.ToleranceModifier;
-
 
 /**
  * Tests the {@link CylindricalEqualArea} class.
@@ -77,7 +74,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222638.98;             // Test point from PROJ library.
@@ -106,7 +102,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222390.10;             // Anti-regression values (not from an external source).
@@ -138,7 +133,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222390.10;             // Anti-regression values (not from an external source).
@@ -169,7 +163,6 @@
                 200);       // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
         verifyInDomain(CoordinateDomain.GEOGRAPHIC_SAFE, 0);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/EquidistantCylindricalTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/EquidistantCylindricalTest.java
index cf14b0a..8e02203 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/EquidistantCylindricalTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/EquidistantCylindricalTest.java
@@ -49,7 +49,6 @@
 
     /**
      * Tests the point given in EPSG example.
-     * This is the same test as {@link #runGeoapiTest()} but is repeated here for easier debugging.
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
@@ -73,18 +72,4 @@
         assertEquals(λ, p.x, Formulas.ANGULAR_TOLERANCE);
         assertEquals(φ, p.y, Formulas.ANGULAR_TOLERANCE);
     }
-
-    /**
-     * Tests the <q>Equidistant Cylindrical</q> (EPSG:1028) projection.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testEquidistantCylindrical()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(provider()).testEquidistantCylindrical();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java
index d58e295..3e6f429 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java
@@ -345,17 +345,4 @@
         createProjection(false, 8, false);
         verifyDerivative(toRadians(-6), toRadians(2));
     }
-
-    /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertAzimuthalEqualArea()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(provider(true)).testLambertAzimuthalEqualArea();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
index fae92fe..0efe429 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
@@ -37,8 +37,9 @@
 import org.apache.sis.test.TestUtilities;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.apache.sis.referencing.operation.provider.LambertConformalMichigan;
+// Specific to the main branch:
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.apache.sis.test.GeoapiAssert.PENDING_NEXT_GEOAPI_RELEASE;
 
 
 /**
@@ -184,8 +185,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     public void testLambertConicConformal1SP() throws FactoryException, TransformException {
@@ -198,8 +197,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     public void testLambertConicConformal2SP() throws FactoryException, TransformException {
@@ -212,8 +209,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     public void testLambertConicConformalBelgium() throws FactoryException, TransformException {
@@ -226,12 +221,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformalMichigan()
      */
     @Test
     public void testLambertConicConformalMichigan() throws FactoryException, TransformException {
-        createGeoApiTest(new LambertConformalMichigan()).testLambertConicConformalMichigan();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
index 83aebfe..94e1457 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
@@ -40,9 +40,6 @@
 import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.referencing.ParameterizedTransformTest;
-
 
 /**
  * Base class of map projection tests.
@@ -84,24 +81,8 @@
      * @param  provider  the provider of the projection to test.
      * @return the GeoAPI test class using the given provider.
      */
-    static ParameterizedTransformTest createGeoApiTest(final MapProjection provider) {
-        return new ParameterizedTransformTest(new MathTransformFactoryMock(provider));
-    }
-
-    /**
-     * Instantiates the object to use for running GeoAPI test without derivative tests.
-     *
-     * @param  provider  the provider of the projection to test.
-     * @return the GeoAPI test class using the given provider.
-     */
-    static ParameterizedTransformTest createGeoApiTestNoDerivatives(final MapProjection provider) {
-        final class Tester extends ParameterizedTransformTest {
-            Tester(final MapProjection provider) {
-                super(new MathTransformFactoryMock(provider));
-                isDerivativeSupported = false;
-            }
-        }
-        return new Tester(provider);
+    static ParameterizedTransformTestMock createGeoApiTest(final MapProjection provider) {
+        return new ParameterizedTransformTestMock(new MathTransformFactoryMock(provider));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MercatorTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MercatorTest.java
index aa87936..c6ffd51 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MercatorTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/MercatorTest.java
@@ -37,10 +37,10 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.apache.sis.referencing.operation.provider.MercatorSpherical;
-import org.apache.sis.referencing.operation.provider.RegionalMercator;
-import static org.opengis.test.Assertions.assertBetween;
+// Specific to the main branch:
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.apache.sis.test.GeoapiAssert.assertBetween;
+import static org.apache.sis.test.GeoapiAssert.PENDING_NEXT_GEOAPI_RELEASE;
 
 
 /**
@@ -185,8 +185,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercator1SP()
      */
     @Test
     public void testMercator1SP() throws FactoryException, TransformException {
@@ -199,8 +197,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercator2SP()
      */
     @Test
     public void testMercator2SP() throws FactoryException, TransformException {
@@ -213,12 +209,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercatorVariantC()
      */
     @Test
     public void testRegionalMercator() throws FactoryException, TransformException {
-        createGeoApiTest(new RegionalMercator()).testMercatorVariantC();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
@@ -227,12 +221,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercatorSpherical()
      */
     @Test
     public void testMercatorSpherical() throws FactoryException, TransformException {
-        createGeoApiTest(new MercatorSpherical()).testMercatorSpherical();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
@@ -241,8 +233,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPseudoMercator()
      */
     @Test
     public void testPseudoMercator() throws FactoryException, TransformException {
@@ -255,8 +245,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMiller()
      */
     @Test
     public void testMiller() throws FactoryException, TransformException {
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
index 5f27f61..c90b9cd 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
@@ -74,20 +74,6 @@
     }
 
     /**
-     * Tests the <q>Modified Azimuthal Equidistant</q> (EPSG:9832) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testModifiedAzimuthalEquidistant()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTestNoDerivatives(method()).testModifiedAzimuthalEquidistant();
-    }
-
-    /**
      * Not yet supported.
      */
     @Test
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
index af16b32..323e947 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
@@ -25,8 +25,8 @@
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.apache.sis.test.FailureDetailsReporter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.referencing.TransformTestCase;
+// Specific to the main branch:
+import org.apache.sis.referencing.operation.transform.TransformTestCase;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
index 8438d9a..517c8fa 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
@@ -27,17 +27,12 @@
 // Test dependencies
 import org.junit.jupiter.api.Test;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.FactoryException;
-import org.opengis.test.ToleranceModifier;
-
 
 /**
  * Tests the {@link ObliqueMercator} class.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @author  Emmanuel Giasson (Thales)
  */
 public final class ObliqueMercatorTest extends MapProjectionTestCase {
     /**
@@ -84,60 +79,4 @@
         verifyDerivative(toRadians(15), toRadians(40));
         verifyDerivative(toRadians(10), toRadians(60));
     }
-
-    /**
-     * Tests with an azimuth of 90°.
-     *
-     * @throws TransformException if an error occurred while converting a point.
-     */
-    @Test
-    public void testAzimuth90() throws TransformException {
-        tolerance = 1E-9;
-        transform = create(10, 20, 90);
-        validate();
-
-        final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
-        derivativeDeltas = new double[] {delta, delta};
-        verifyInverse(toRadians(15), toRadians(25));
-    }
-
-    /**
-     * Tests with a latitude close to 90°.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while converting a point.
-     *
-     * <a href="https://issues.apache.org/jira/browse/SIS-532">SIS-532</a>
-     */
-    @Test
-    public void testPole() throws TransformException, FactoryException {
-        tolerance = 0.01;
-        transform = create(179.8, 89.8, -174).createMapProjection(context(null, null));
-        transform = transform.inverse();
-        validate();
-        /*
-         * The projection of (180, 90) with SIS 1.1 is (+0.004715980030596256, 22338.795490272343).
-         * Empirical cordinates shifted by 0.01 meter: (-0.005463426921067797, 22338.792057282844).
-         * With those shifted coordinated, Apache SIS 1.1 was used to compute φ = NaN because the
-         * U′ value in `ObliqueMercator.inverseTransform(…)` was slightly greater than 1.
-         */
-        isInverseTransformSupported = false;
-        toleranceModifier = ToleranceModifier.GEOGRAPHIC;
-        verifyTransform(new double[] {-0.005464, 22338.792057},
-                        new double[] {300, 90});
-    }
-
-    /**
-     * Tests the <q>Hotine Oblique Mercator (variant B)</q> (EPSG:9815) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testHotineObliqueMercator()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new ObliqueMercatorCenter()).testHotineObliqueMercator();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
index fb792d0..21ce377 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
@@ -31,8 +31,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.apache.sis.referencing.operation.matrix.Matrix2;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -220,20 +220,6 @@
     }
 
     /**
-     * Tests the <cite>Oblique Stereographic</cite> case (EPSG:9809).
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testObliqueStereographic()
-     */
-    @Test
-    public void testObliqueStereographic() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.referencing.operation.provider.ObliqueStereographic()).testObliqueStereographic();
-    }
-
-    /**
      * Tests consistency between forward and reverse projection using a point that was known to fail.
      *
      * @throws FactoryException if an error occurred while creating the map projection.
@@ -251,7 +237,7 @@
         p.parameter("Longitude of natural origin").setValue(-70, Units.DEGREE);
         createCompleteTransform(op, p);
         tolerance = Formulas.ANGULAR_TOLERANCE;
-        verifyInverse(30, 45);
+        verifyInverse(new double[] {30, 45});
     }
 
     /**
@@ -363,8 +349,7 @@
         final Matrix derivative = spherical.transform(srcPts, 0, null, 0, true);
 
         tolerance = 1E-12;
-        assertMatrixEquals(reference, derivative, new Matrix2(tolerance, tolerance, tolerance, tolerance),
-                           "Spherical derivative");
+        assertMatrixEquals(reference, derivative, tolerance, "Spherical derivative");
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/OrthographicTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/OrthographicTest.java
index 3cbe5e5..2a658fd 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/OrthographicTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/OrthographicTest.java
@@ -25,9 +25,6 @@
 // Test dependencies
 import org.junit.jupiter.api.Test;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.FactoryException;
-
 
 /**
  * Tests the {@link Orthographic} class.
@@ -149,18 +146,4 @@
         verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_SOUTH, 753524735);
         verifyDerivative(toRadians(5), toRadians(-85));
     }
-
-    /**
-     * Tests the <q>Orthographic</q> (EPSG:9840) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testOrthographic()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.referencing.operation.provider.Orthographic()).testOrthographic();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java
new file mode 100644
index 0000000..ca6cdec
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.projection;
+
+import org.opengis.referencing.operation.MathTransformFactory;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.apache.sis.test.GeoapiAssert.PENDING_NEXT_GEOAPI_RELEASE;
+
+
+/**
+ * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+ * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class ParameterizedTransformTestMock {
+    ParameterizedTransformTestMock(MathTransformFactory factory) {
+        // See GeoAPI 3.1 for real construction.
+    }
+
+    public void testMercator1SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testMercator2SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testPseudoMercator() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testMiller() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformal1SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformal2SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformalBelgium() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
index a788098..b32cba9 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
@@ -120,12 +120,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicA()
      */
     @Test
     public void testPolarStereographicA() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicA()).testPolarStereographicA();
+        new PolarStereographicA();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -134,12 +132,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicB()
      */
     @Test
     public void testPolarStereographicB() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicB()).testPolarStereographicB();
+        new PolarStereographicB();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -148,12 +144,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicC()
      */
     @Test
     public void testPolarStereographicC() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicC()).testPolarStereographicC();
+        new PolarStereographicC();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolyconicTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolyconicTest.java
index fbcba8d..c108ed5 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolyconicTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/PolyconicTest.java
@@ -140,15 +140,4 @@
         verifyDerivative( -56, 50);
         verifyDerivative( -20, 47);
     }
-
-    /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException   if the transform cannot be created.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.referencing.operation.provider.Polyconic()).testPolyconic();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
index d1b6d18..254b440 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
@@ -91,16 +91,7 @@
          * coordinate values. The transform results are between 0 and 1, while the inverse transform results
          * are between -90° and 90°, which is an increase in magnitude close to ×100.
          */
-        toleranceModifier = (tolerance, coordinate, mode) -> {
-            switch (mode) {
-                case INVERSE_TRANSFORM: {
-                    for (int i=0; i<tolerance.length; i++) {
-                        tolerance[i] *= 50;
-                    }
-                    break;
-                }
-            }
-        };
+        tolerance *= 50;    // Finer tolerance setting require GeoAPI 3.1.
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
index d79ee1f..254cae0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
@@ -36,9 +36,6 @@
 import org.apache.sis.test.OptionalTestData;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.CalculationType;
-
 
 /**
  * Tests the {@link TransverseMercator} class.
@@ -112,12 +109,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercator()
      */
     @Test
     public void testTransverseMercator() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.referencing.operation.provider.TransverseMercator()).testTransverseMercator();
+        // Test creation only, as GeoAPI 3.0 did not yet had the test method.
+        new org.apache.sis.referencing.operation.provider.TransverseMercator();
     }
 
     /**
@@ -126,12 +122,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercatorSouthOrientated()
      */
     @Test
     public void testTransverseMercatorSouthOrientated() throws FactoryException, TransformException {
-        createGeoApiTest(new TransverseMercatorSouth()).testTransverseMercatorSouthOrientated();
+        new TransverseMercatorSouth();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -269,7 +263,7 @@
                     else if (longitude <= 66) tolerance = 0.1;
                     else                      tolerance = 0.7;
                     transform.transform(source, 0, source, 0, 1);
-                    assertCoordinateEquals(target, source, reader.getLineNumber(), CalculationType.DIRECT_TRANSFORM, line);
+                    assertCoordinateEquals(line, target, source, reader.getLineNumber(), false);
                 }
             }
         }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/CoordinateFrameRotationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/CoordinateFrameRotationTest.java
index f1bf857..3de1417 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/CoordinateFrameRotationTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/CoordinateFrameRotationTest.java
@@ -80,6 +80,7 @@
         tolerance  = Formulas.ANGULAR_TOLERANCE;
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         createTransform(new CoordinateFrameRotation3D());
         assertFalse(transform instanceof LinearTransform);
         verifyTransform(PositionVector7ParamTest.samplePoint(1),
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/FranceGeocentricInterpolationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/FranceGeocentricInterpolationTest.java
index 70c569b..bef2372 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/FranceGeocentricInterpolationTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/FranceGeocentricInterpolationTest.java
@@ -40,7 +40,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
  * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
  */
 public final class FranceGeocentricInterpolationTest extends DatumShiftTestCase {
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java
index 7be31fd..afcafcb 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeocentricTranslationTest.java
@@ -38,11 +38,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Arrays;
-import org.opengis.test.ToleranceModifier;
-import org.apache.sis.referencing.datum.HardCodedDatum;
-
 
 /**
  * Tests {@link GeocentricTranslation} and {@link GeocentricTranslation3D}.
@@ -227,6 +222,7 @@
      */
     private void datumShift(final int sourceStep, final int targetStep) throws TransformException {
         tolerance = precision(targetStep);
+        tolerance = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         verifyTransform(samplePoint(sourceStep), samplePoint(targetStep));
         validate();
     }
@@ -259,43 +255,11 @@
         derivativeDeltas = new double[] {delta, delta, 100};    // (Δλ, Δφ, Δh)
         zTolerance = Formulas.LINEAR_TOLERANCE / 2;             // Half the precision of h value given by EPSG
         zDimension = new int[] {2};                             // Dimension of h where to apply zTolerance
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         datumShift(1, 4);
     }
 
     /**
-     * Tests the point used in {@link FranceGeocentricInterpolationTest}. We use this test for making sure
-     * that we get the expected value when using the real geocentric translation method. This test will be
-     * completed by an equivalent test in {@code MolodenskyTransformTest} for verifying the quality of our
-     * approximation.
-     *
-     * @throws FactoryException if an error occurred while creating the transform.
-     * @throws TransformException if transformation of a point failed.
-     *
-     * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
-     */
-    @Test
-    public void testFranceGeocentricInterpolationPoint() throws FactoryException, TransformException {
-        transform = createDatumShiftForGeographic3D(DefaultMathTransformFactory.provider(),
-                 HardCodedDatum.NTF.getEllipsoid(),
-                 CommonCRS.ETRS89.ellipsoid(),
-                -FranceGeocentricInterpolation.TX,
-                -FranceGeocentricInterpolation.TY,
-                -FranceGeocentricInterpolation.TZ);
-
-        final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres
-        derivativeDeltas = new double[] {delta, delta, 100};                // (Δλ, Δφ, Δh)
-        tolerance  = FranceGeocentricInterpolationTest.ANGULAR_TOLERANCE;
-        zTolerance = 0.005;                       // Half the precision of the target[2] value set below.
-        zDimension = new int[] {2};
-
-        final double[] source   = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(1), 3);
-        final double[] expected = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(2), 3);
-        expected[2] = 43.15;  // Anti-regression (this value is not provided in NTG_88 guidance note).
-        verifyTransform(source, expected);
-        validate();
-    }
-
-    /**
      * Tests conversion of random points.
      *
      * @throws FactoryException if an error occurred while creating the transform.
@@ -305,7 +269,7 @@
     public void testRandomPoints() throws FactoryException, TransformException {
         testGeographicDomain();                     // For creating the transform.
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.GEOGRAPHIC;
+//      toleranceModifier = ToleranceModifier.GEOGRAPHIC;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 831342815);
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/Geographic3Dto2DTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/Geographic3Dto2DTest.java
index 350e3bc..b919a88 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/Geographic3Dto2DTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/Geographic3Dto2DTest.java
@@ -30,8 +30,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/LongitudeRotationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/LongitudeRotationTest.java
index 9500908..4077e35 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/LongitudeRotationTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/LongitudeRotationTest.java
@@ -29,8 +29,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/MapProjectionTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/MapProjectionTest.java
index ce32a37..5bcc419 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/MapProjectionTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/MapProjectionTest.java
@@ -30,8 +30,8 @@
 import static org.apache.sis.referencing.Assertions.assertOgcIdentifierEquals;
 import static org.apache.sis.referencing.Assertions.assertEpsgIdentifierEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -131,7 +131,7 @@
         assertEquals(isMandatory ? 1 : 0, actual.getMinimumOccurs());
         if (epsgName != null) {
             for (final GenericName alias : actual.getAlias()) {
-                if (alias instanceof Identifier id && id.getAuthority() != Citations.EPSG) {
+                if (alias instanceof ReferenceIdentifier id && id.getAuthority() != Citations.EPSG) {
                     assertOgcIdentifierEquals(ogcName, id);
                     return;
                 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NADCONTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NADCONTest.java
index 3db9ad2..65b4e67 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NADCONTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NADCONTest.java
@@ -37,8 +37,8 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NTv2Test.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NTv2Test.java
index fd50842..31ded14 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NTv2Test.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/NTv2Test.java
@@ -47,8 +47,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.test.TestCase.TAG_SLOW;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -57,7 +57,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
  * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
  */
 public final class NTv2Test extends DatumShiftTestCase {
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ParameterNameTableGenerator.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ParameterNameTableGenerator.java
deleted file mode 100644
index 26a2875..0000000
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ParameterNameTableGenerator.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.operation.provider;
-
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-import java.lang.reflect.Field;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.FileVisitResult;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import javax.measure.Unit;
-import org.opengis.util.GenericName;
-import org.opengis.metadata.Identifier;
-import org.apache.sis.referencing.NamedIdentifier;
-import org.apache.sis.parameter.DefaultParameterDescriptor;
-import org.apache.sis.measure.Angle;
-import org.apache.sis.measure.Latitude;
-import org.apache.sis.measure.Longitude;
-import org.apache.sis.measure.Range;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.StringBuilders;
-
-// Test dependencies
-import static org.junit.jupiter.api.Assertions.*;
-import org.apache.sis.test.ProjectDirectories;
-
-
-/**
- * Inserts comments with parameter names in the javadoc of parameters.
- * This class needs to be run explicitly; it is not part of JUnit tests.
- * After execution, files in the provider packages may be overwritten.
- * Developer should execute {@code "git diff"} and inspect the changes.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class ParameterNameTableGenerator extends SimpleFileVisitor<Path> {
-    /**
-     * Value as the kind of object expected in {@link DefaultParameterDescriptor}.
-     */
-    private static final Double ONE           =  1d,
-                                POSITIVE_ZERO = +0d,
-                                NEGATIVE_ZERO = -0d,
-                                MIN_LONGITUDE = Longitude.MIN_VALUE,
-                                MAX_LONGITUDE = Longitude.MAX_VALUE,
-                                MIN_LATITUDE  =  Latitude.MIN_VALUE,
-                                MAX_LATITUDE  =  Latitude.MAX_VALUE;
-
-    /**
-     * The directory of Java source code to scan.
-     */
-    private final Path directory;
-
-    /**
-     * Pattern of the lines to search.
-     */
-    private final Pattern toSearch;
-
-    /**
-     * A temporary buffer for creating lines of comment.
-     */
-    private final StringBuilder buffer;
-
-    /**
-     * All lines in the file being processed.
-     */
-    private List<String> lines;
-
-    /**
-     * For {@link #main(String[])} only.
-     */
-    private ParameterNameTableGenerator() {
-        directory = new ProjectDirectories(getClass()).getSourcesPackageDirectory("core/sis-referencing");
-        toSearch  = Pattern.compile(".*\\s+static\\s+.*ParameterDescriptor<\\w+>\\s*(\\w+)\\s*[=;].*");
-        buffer    = new StringBuilder();
-    }
-
-    /**
-     * Launches the insertion of comment lines.
-     *
-     * @param  args  ignored.
-     * @throws IOException if an error occurred while reading or writing a file.
-     */
-    public static void main(final String[] args) throws IOException {
-        final ParameterNameTableGenerator cg = new ParameterNameTableGenerator();
-        Files.walkFileTree(cg.directory, cg);
-    }
-
-    /**
-     * Invoked before to enter in a sub-directory. This implementation skips all sub-directories.
-     *
-     * @param  dir    the directory in which to enter.
-     * @param  attrs  ignored.
-     * @return flag instructing whether to scan that directory or not.
-     */
-    @Override
-    public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) {
-        return dir.equals(directory) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
-    }
-
-    /**
-     * Invoked for each file in the scanned directory. If the file is a Java file,
-     * searches for {@code ParameterDescriptor} declarations. Otherwise ignore.
-     *
-     * @param  file   the file.
-     * @param  attrs  ignored.
-     * @return {@link FileVisitResult#CONTINUE}.
-     * @throws IOException if an error occurred while reading or writing the given file.
-     */
-    @Override
-    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
-        final String name = file.getFileName().toString();
-        if (name.endsWith(".java")) {
-            addCommentsToJavaFile(file);
-        }
-        return super.visitFile(file, attrs);
-    }
-
-    /**
-     * Returns the class for the given Java source file.
-     */
-    private Class<?> getClass(final Path file) throws ClassNotFoundException {
-        String name = file.getFileName().toString();
-        name = name.substring(0, name.lastIndexOf('.'));    // Remove the ".java" suffix.
-        name = getClass().getPackageName() + '.' + name;
-        return Class.forName(name);
-    }
-
-    /**
-     * Finds the parameter descriptors in the given file, and add parameter names in comments.
-     */
-    private void addCommentsToJavaFile(final Path file) throws IOException {
-        Class<?> classe = null;
-        final Matcher matcher = toSearch.matcher("");
-        lines = Files.readAllLines(file);
-        for (int i=lines.size(); --i >= 0;) {
-            final String line = lines.get(i);
-            if (matcher.reset(line).matches()) {
-                final String fieldName = matcher.group(1);
-                final DefaultParameterDescriptor<?> descriptor;
-                try {
-                    if (classe == null) {
-                        classe = getClass(file);
-                    }
-                    final Field field = classe.getDeclaredField(fieldName);
-                    field.setAccessible(true);
-                    descriptor = (DefaultParameterDescriptor<?>) field.get(null);
-                } catch (ReflectiveOperationException e) {
-                    throw new AssertionError(e);
-                }
-                /*
-                 * Find the line where to insert comments. We detect if the comment to insert already exists
-                 * by looking for this class name (expected in a HTML comment) before the start of comments.
-                 * If we find that line, then we delete everything between it and the end of comments before
-                 * to regenerate them. Developer can execute "git diff" on the command line for checking if
-                 * any lines changed as a result.
-                 */
-                int insertAt = i;
-                String previous;
-                do {
-                    previous = lines.get(--insertAt).trim();
-                    if (!previous.startsWith("*") && !previous.startsWith("@")) {
-                        fail("Unexpected content in " + file.getFileName() + " at line " + insertAt);
-                    }
-                } while (!previous.equals("*/"));
-                previous = lines.get(insertAt);
-                buffer.setLength(0);
-                buffer.append(previous, 0, previous.indexOf('*') + 1);
-                for (int check = insertAt;;) {
-                    previous = lines.get(--check).trim();
-                    if (previous.equals("/**")) break;
-                    if (previous.contains("ParameterNameTableGenerator")) {
-                        lines.subList(check, insertAt).clear();
-                        insertAt = check;
-                        break;
-                    }
-                }
-                /*
-                 * Format a HTML table in the comment with the name and aliases of each parameter.
-                 */
-                write(insertAt++, "<!-- Generated by ParameterNameTableGenerator -->");
-                write(insertAt++, "<table class=\"sis\">");
-                write(insertAt++, "  <caption>Parameter names</caption>");
-                write(insertAt++, descriptor.getName());
-                for (final GenericName alias : descriptor.getAlias()) {
-                    write(insertAt++, (alias instanceof Identifier) ? (Identifier) alias : new NamedIdentifier(alias));
-                }
-                write(insertAt++, "</table>");
-                /*
-                 * Format other information: default value, value domain, whether the value is mandatory, etc.
-                 * Default value of zero are omitted (i.e. unless otherwise specified, default values are zero
-                 * in the tables that we format). Range of values of [-90 … 90]° for latitude or [-180 … 180]°
-                 * for longitude are also omitted. In other words, we report only "unusual" things in the notes.
-                 */
-                Object  defaultValue = descriptor.getDefaultValue();
-                Range<?> valueDomain = descriptor.getValueDomain();
-                boolean  isOptional  = descriptor.getMinimumOccurs() == 0;
-                boolean  noDefault   = (defaultValue == null);
-                Object   minValue    = null;
-                Object   maxValue    = null;
-                if (valueDomain != null) {
-                    minValue = valueDomain.getMinValue();
-                    maxValue = valueDomain.getMaxValue();
-                    final boolean inclusive = valueDomain.isMinIncluded() && valueDomain.isMaxIncluded();
-                    if (fieldName.contains("LATITUDE") || fieldName.contains("PARALLEL")) {
-                        if (inclusive && MIN_LATITUDE.equals(minValue) && MAX_LATITUDE.equals(maxValue)) {
-                            valueDomain = null;
-                        }
-                    } else if (fieldName.contains("LONGITUDE") || fieldName.contains("MERIDIAN")) {
-                        if (inclusive && MIN_LONGITUDE.equals(minValue) && MAX_LONGITUDE.equals(maxValue)) {
-                            valueDomain = null;
-                        }
-                    } else if (fieldName.contains("SCALE")) {
-                        if (!inclusive && POSITIVE_ZERO.equals(minValue) && maxValue == null) {
-                            valueDomain = null;
-                        }
-                    } else if (minValue == null && maxValue == null) {
-                        valueDomain = null;
-                    }
-                }
-                if ((fieldName.contains("SCALE") ? ONE : POSITIVE_ZERO).equals(defaultValue)) {
-                    defaultValue = null;
-                }
-                if (defaultValue != null || valueDomain != null || isOptional || noDefault) {
-                    write(insertAt++, "<b>Notes:</b>");
-                    write(insertAt++, "<ul>");
-                    if (valueDomain != null) {
-                        final int p = buffer.length();
-                        buffer.append("   <li>");
-                        if ((minValue != null && minValue.equals(maxValue)) ||
-                            (NEGATIVE_ZERO.equals(minValue) && POSITIVE_ZERO.equals(maxValue)))
-                        {
-                            buffer.append("Value restricted to ").append(maxValue);
-                            StringBuilders.trimFractionalPart(buffer);
-                        } else {
-                            buffer.append("Value domain: ").append(valueDomain);
-                        }
-                        lines.add(insertAt++, buffer.append("</li>").toString());
-                        buffer.setLength(p);
-                    }
-                    if (defaultValue != null) {
-                        final int p = buffer.length();
-                        final boolean isText = !(defaultValue instanceof Number || defaultValue instanceof Angle);
-                        buffer.append("   <li>").append("Default value: ");
-                        if (isText) buffer.append("{@code ");
-                        buffer.append(defaultValue);
-                        if (isText) buffer.append('}');
-                        StringBuilders.trimFractionalPart(buffer);
-                        final Unit<?> unit = descriptor.getUnit();
-                        if (unit != null) {
-                            final String symbol = unit.getSymbol();
-                            if (!symbol.isEmpty()) {
-                                if (Character.isLetterOrDigit(symbol.charAt(0))) {
-                                    buffer.append(' ');
-                                }
-                                buffer.append(symbol);
-                            }
-                        }
-                        lines.add(insertAt++, buffer.append("</li>").toString());
-                        buffer.setLength(p);
-                    } else if (noDefault) {
-                        write(insertAt++, "  <li>No default value</li>");
-                    }
-                    if (isOptional) {
-                        write(insertAt++, "  <li>Optional</li>");
-                    }
-                    write(insertAt++, "</ul>");
-                }
-            }
-        }
-        /*
-         * If at least one table has been formatted, rewrite the file.
-         */
-        if (classe != null) {
-            Files.write(file, lines);
-        }
-        lines = null;
-    }
-
-    /**
-     * Appends the given line at the given position. This method writes the margin
-     * (typically spaces followed by {@code '*'} and a single space) before the line.
-     */
-    private void write(final int insertAt, final String line) {
-        final int p = buffer.length();
-        if (!line.isEmpty()) {
-            buffer.append(' ').append(line);
-        }
-        lines.add(insertAt, buffer.toString());
-        buffer.setLength(p);
-    }
-
-    /**
-     * Appends the given authority and name at the given position. This method writes the margin
-     * (typically spaces followed by {@code '*'} and a single space) before the line.
-     */
-    private void write(final int insertAt, final Identifier id) {
-        final int p = buffer.length();
-        final String authority = id.getCodeSpace();
-        buffer.append("   <tr><td> ").append(authority).append(':')
-              .append(CharSequences.spaces(8 - authority.length()))
-              .append("</td><td> ").append(id.getCode()).append(" </td></tr>");
-        lines.add(insertAt, buffer.toString());
-        buffer.setLength(p);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/PositionVector7ParamTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/PositionVector7ParamTest.java
index 7ffcdbc..f7ac5b1 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/PositionVector7ParamTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/PositionVector7ParamTest.java
@@ -135,6 +135,7 @@
         tolerance  = Formulas.ANGULAR_TOLERANCE;
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         createTransform(new PositionVector7Param3D());
         assertFalse(transform instanceof LinearTransform);
         verifyTransform(samplePoint(1), samplePoint(4));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProviderMock.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProviderMock.java
index 7ff33eb..f2904eb 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProviderMock.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProviderMock.java
@@ -29,7 +29,7 @@
 /**
  * Base class of mock provider for coordinate operations not yet implemented in Apache SIS.
  * This is used for operations needed for executing some Well Known Text (WKT) parsing tests
- * in the {@link org.apache.sis.io.wkt.CRSParserTest} class, without doing any real coordinate
+ * in the {@code org.apache.sis.io.wkt.CRSParserTest} class, without doing any real coordinate
  * operations with the parsed objects.
  *
  * <p>Subclasses may be promoted to a real operation if we implement their formulas in a future Apache SIS version.</p>
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
index b4a1c3e..e1b9691 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
@@ -30,8 +30,9 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertBetween;
+// Specific to the main branch:
+import org.apache.sis.parameter.DefaultParameterDescriptor;
+import static org.apache.sis.test.GeoapiAssert.assertBetween;
 
 
 /**
@@ -231,8 +232,8 @@
      */
     @Test
     public void testDescription() {
-        assertNotEquals(0, SatelliteTracking.SATELLITE_ORBIT_INCLINATION.getDescription().orElseThrow().length());
-        assertNotEquals(0, SatelliteTracking.SATELLITE_ORBITAL_PERIOD   .getDescription().orElseThrow().length());
-        assertNotEquals(0, SatelliteTracking.ASCENDING_NODE_PERIOD      .getDescription().orElseThrow().length());
+        assertNotEquals(0, ((DefaultParameterDescriptor<Double>) SatelliteTracking.SATELLITE_ORBIT_INCLINATION).getDescription().orElseThrow().length());
+        assertNotEquals(0, ((DefaultParameterDescriptor<Double>) SatelliteTracking.SATELLITE_ORBITAL_PERIOD   ).getDescription().orElseThrow().length());
+        assertNotEquals(0, ((DefaultParameterDescriptor<Double>) SatelliteTracking.ASCENDING_NODE_PERIOD      ).getDescription().orElseThrow().length());
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/SeismicBinGridMock.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/SeismicBinGridMock.java
index 1ce7cc9..b025664 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/SeismicBinGridMock.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/SeismicBinGridMock.java
@@ -26,7 +26,7 @@
  * The provider for <q>P6 (I = J-90°) seismic bin grid transformation</q> transformation (EPSG:1049).
  *
  * This conversion is not yet implemented in Apache SIS, but we need to at least accept the parameters
- * for a Well Known Text (WKT) parsing test in the {@link org.apache.sis.io.wkt.CRSParserTest} class.
+ * for a Well Known Text (WKT) parsing test in the {@code org.apache.sis.io.wkt.CRSParserTest} class.
  *
  * <p>This class may be promoted to a real operation if we implement the formulas in a future Apache SIS version.</p>
  *
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
index 31916c6..7c3e87b 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
@@ -28,9 +28,6 @@
 import org.apache.sis.referencing.datum.HardCodedDatum;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.CalculationType;
-
 
 /**
  * Tests {@link AbridgedMolodenskyTransform2D}. This class takes {@link MolodenskyTransform}
@@ -57,8 +54,6 @@
      *
      * @throws FactoryException if an error occurred while creating a transform step.
      * @throws TransformException if a transformation failed.
-     *
-     * @see MolodenskyTransformTest#compareWithGeocentricTranslation()
      */
     @Test
     public void compareWithReferenceImplementation() throws FactoryException, TransformException {
@@ -74,7 +69,7 @@
         for (int i=0; i<sources.length; i+=2) {
             transform.transform(sources, i, targets,  0, 1);        // Use the overridden method.
             transform.transform(sources, i, expected, 0, false);    // Use the MolodenskyTransform method.
-            assertCoordinateEquals(expected, targets, 0, CalculationType.DIRECT_TRANSFORM, "transform");
+            assertCoordinateEquals("transform", expected, targets, 0, false);
         }
     }
 
@@ -109,7 +104,6 @@
     public void testConsistency() throws FactoryException, TransformException {
         transform = create();
         validate();
-        isDerivativeSupported = false;
         isInverseTransformSupported = false;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, -1941624852762631518L);
         /*
@@ -118,7 +112,6 @@
          * we are not completely sure that there is no bug in derivative formula).
          */
         tolerance *= 10;
-        isDerivativeSupported = true;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 4350796528249596132L);
     }
 
@@ -133,7 +126,6 @@
     public void testSerialization() throws FactoryException, TransformException {
         transform = assertSerializedEquals(create());
         validate();
-        isDerivativeSupported = false;
         isInverseTransformSupported = false;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 3534897149662911157L);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
index f44d234..9c448e1 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
@@ -25,9 +25,6 @@
 import org.apache.sis.test.FailureDetailsReporter;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.referencing.TransformTestCase;
-
 
 /**
  * Tests {@link CartesianToPolar}.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
index 2688ab2..9cdc5eb 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
@@ -29,9 +29,6 @@
 import org.apache.sis.test.FailureDetailsReporter;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.referencing.TransformTestCase;
-
 
 /**
  * Tests {@link CartesianToSpherical}.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
index aa40be2..9b15e1d 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
@@ -31,8 +31,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.test.TestCase.STRICT;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.Assertions;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -179,7 +179,7 @@
          * Dropping a dimension is not a problem.
          */
         final MathTransform c = MathTransforms.concatenate(tr1, tr2.inverse());
-        Assertions.assertMatrixEquals(Matrices.create(3, 4, new double[] {
+        assertMatrixEquals(Matrices.create(3, 4, new double[] {
                     4, 0, 0, 0,     // scale = 8/2
                     0, 2, 0, 0,     // scale = 6/3
                     0, 0, 0, 1
@@ -191,7 +191,7 @@
          * this concatenation was built from a transform which was putting value 5 in third dimension,
          * we can complete the matrix as below with value 10 in third dimension.
          */
-        Assertions.assertMatrixEquals(Matrices.create(4, 3, new double[] {
+        assertMatrixEquals(Matrices.create(4, 3, new double[] {
                     0.25, 0,    0,
                     0,    0.5,  0,
                     0,    0,   10,   // Having value 10 instead of NaN is the main purpose of this test.
@@ -214,6 +214,6 @@
         final MathTransform tr2 = MathTransforms.linear(new Matrix2(-1, 0, 0, 1));
         final MathTransform c   = MathTransforms.concatenate(tr1, tr2);
         final Matrix m          = ((LinearTransform) c).getMatrix();
-        Assertions.assertMatrixEquals(new Matrix2(-4.9E-324, 5387, 0, 1), m, STRICT, "Concatenate");
+        assertMatrixEquals(new Matrix2(-4.9E-324, 5387, 0, 1), m, STRICT, "Concatenate");
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
index 6e9a8ab..bd3f7b3 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
@@ -33,8 +33,8 @@
 import org.apache.sis.parameter.DefaultParameterDescriptorGroupTest;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
index 5a29446..ce076f6 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
@@ -50,8 +50,8 @@
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
index 04596f44..15f34db 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
@@ -39,9 +39,6 @@
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import org.apache.sis.referencing.operation.provider.GeocentricTranslationTest;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.ToleranceModifier;
-
 
 /**
  * Tests {@link EllipsoidToCentricTransform}.
@@ -124,6 +121,7 @@
         tolerance  = GeocentricTranslationTest.precision(1);        // Required precision for (λ,φ)
         zTolerance = Formulas.LINEAR_TOLERANCE / 2;                 // Required precision for h
         zDimension = new int[] {2};                                 // Dimension of h where to apply zTolerance
+        tolerance  = 1E-4;                                          // Other SIS branches use a stricter threshold.
         verifyTransform(GeocentricTranslationTest.samplePoint(2),   // X = 3771793.968,  Y = 140253.342,  Z = 5124304.349 metres
                         GeocentricTranslationTest.samplePoint(1));  // 53°48'33.820"N, 02°07'46.380"E, 73.00 metres
         loggings.assertNoUnexpectedLog();
@@ -140,7 +138,7 @@
         final double delta = toRadians(100.0 / 60) / 1852;          // Approximately 100 metres
         derivativeDeltas  = new double[] {delta, delta, 100};       // (Δλ, Δφ, Δh)
         tolerance         = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
+//      toleranceModifier = ToleranceModifier.PROJECTION;
         createGeodeticConversion(CommonCRS.WGS84.ellipsoid(), true);
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 306954540);
         loggings.assertNoUnexpectedLog();
@@ -162,8 +160,8 @@
         final double delta = toRadians(100.0 / 60) / 1852;
         derivativeDeltas  = new double[] {delta, delta, 100};
         tolerance         = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
-        verifyInverse(40, 30, 10000);
+//      toleranceModifier = ToleranceModifier.PROJECTION;
+        verifyInverse(new double[] {40, 30, 10000});
         loggings.assertNoUnexpectedLog();
     }
 
@@ -184,7 +182,7 @@
          */
         tolerance = 1E-2;
         derivativeDeltas = new double[] {toRadians(1.0 / 60) / 1852};           // Approximately one metre.
-        verifyDerivative(point.getCoordinates());
+        verifyDerivative(point.getCoordinate());
         /*
          * Derivative of the inverse transform.
          */
@@ -192,7 +190,7 @@
         transform = transform.inverse();
         tolerance = 1E-8;
         derivativeDeltas = new double[] {1};                                    // Approximately one metre.
-        verifyDerivative(point.getCoordinates());
+        verifyDerivative(point.getCoordinate());
         loggings.assertNoUnexpectedLog();
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
index 688a13c..9957d3e 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
@@ -25,12 +25,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.referencing.Assertions.assertIsNotIdentity;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.EnumSet;
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
-import org.opengis.test.ToleranceModifiers;
-
 
 /**
  * Tests the {@link ExponentialTransform1D} class. Note that this is closely related to
@@ -58,9 +52,8 @@
      * Creates a new test case.
      */
     public ExponentialTransform1DTest() {
-        tolerance         = 1E-14;
-        toleranceModifier = ToleranceModifier.RELATIVE;
-        derivativeDeltas  = new double[] {0.001};
+        tolerance         = 1E-5; // Tolerance is much smaller on other branches.
+//      toleranceModifier = ToleranceModifier.RELATIVE; // Not available on GeoAPI 3.0.
     }
 
     /**
@@ -96,7 +89,6 @@
             expected[i] = value;
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                      // Test at a hard-coded point.
     }
 
     /**
@@ -130,13 +122,6 @@
     private void testAffinePostConcatenation(final double base) throws TransformException {
         transform = MathTransforms.concatenate(ExponentialTransform1D.create(base, SCALE),
                 LinearTransform1D.create(C1, C0));
-        /*
-         * The inverse transforms in this test case have high rounding errors.
-         * Those errors are low for values close to zero, and increase fast for higher values.
-         * We scale the default tolerance (1E-14) by 1E+8, which give us a tolerance of 1E-6.
-         */
-        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
-                ToleranceModifiers.scale(EnumSet.of(CalculationType.INVERSE_TRANSFORM), 1E+8));
         run(ConcatenatedTransformDirect1D.class, base, SCALE, false, true);
     }
 
@@ -146,10 +131,6 @@
     private void testAffineConcatenations(final double base) throws TransformException {
         final LinearTransform1D linear = LinearTransform1D.create(C1, C0);
         transform = MathTransforms.concatenate(linear, ExponentialTransform1D.create(base, SCALE), linear);
-
-        // See testAffinePostConcatenation for an explanation about why we relax tolerance.
-        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
-                ToleranceModifiers.scale(EnumSet.of(CalculationType.INVERSE_TRANSFORM), 1E+8));
         run(ConcatenatedTransformDirect1D.class, base, SCALE, true, true);
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
index 764a565..e2257e0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
@@ -28,9 +28,6 @@
 import org.apache.sis.test.FailureDetailsReporter;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.referencing.TransformTestCase;
-
 
 /**
  * Test {@link LinearInterpolator1D} class.
@@ -39,7 +36,7 @@
  * @author  Martin Desruisseaux (Geomatys).
  */
 @ExtendWith(FailureDetailsReporter.class)
-public final class LinearInterpolator1DTest extends TransformTestCase {
+public final class LinearInterpolator1DTest extends MathTransformTestCase {
     /**
      * The values of the <i>y=f(x)</i> function to test.
      */
@@ -248,7 +245,6 @@
         verifyTransform(new double[] {0,  1, 0.5, -0.5, -1, -2,   3, 3.5,   4,   5},        // Values to transform.
                         new double[] {5, 10, 7.5,  2.5,  0, -5, 250, 325, 400, 550});       // Expected results.
 
-        verifyConsistency(0f, 1f, 0.5f, -0.5f, -1f, -2f, 3f, 3.5f, 4f, 5f);
         verifyDerivative(0.25);     // Interpolation (verified by safety)
         verifyDerivative(-8);       // Extrapolation
         verifyDerivative( 8);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
index e0a9b70..f1da0a8 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
@@ -26,6 +26,9 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
+// Specific to the main branch:
+import org.junit.jupiter.api.Disabled;
+
 
 /**
  * Tests various implementation of the {@link LinearTransform} interface by inheriting the tests defined
@@ -64,6 +67,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testIdentity1D() throws FactoryException, TransformException {
         super.testIdentity1D();
         assertInstanceOf(IdentityTransform1D.class, transform, "Unexpected implementation.");
@@ -77,6 +81,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testIdentity2D() throws FactoryException, TransformException {
         super.testIdentity2D();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -90,6 +95,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testIdentity3D() throws FactoryException, TransformException {
         super.testIdentity3D();
         assertInstanceOf(IdentityTransform.class, transform, "Unexpected implementation.");
@@ -103,6 +109,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testAxisSwapping2D() throws FactoryException, TransformException {
         super.testAxisSwapping2D();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -116,6 +123,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testSouthOrientated2D() throws FactoryException, TransformException {
         super.testSouthOrientated2D();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -129,6 +137,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testTranslatation2D() throws FactoryException, TransformException {
         super.testTranslatation2D();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -142,6 +151,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testUniformScale2D() throws FactoryException, TransformException {
         super.testUniformScale2D();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -155,6 +165,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testGenericScale2D() throws FactoryException, TransformException {
         super.testGenericScale2D();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -168,6 +179,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testRotation2D() throws FactoryException, TransformException {
         super.testRotation2D();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -181,6 +193,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testGeneral() throws FactoryException, TransformException {
         super.testGeneral();
         assertInstanceOf(AffineTransform2D.class, transform, "Unexpected implementation.");
@@ -194,6 +207,7 @@
      */
     @Test
     @Override
+    @Disabled(MESSAGE)
     public void testDimensionReduction() throws FactoryException, TransformException {
         super.testDimensionReduction();
         assertInstanceOf(ProjectiveTransform.class, transform, "Unexpected implementation.");
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
index 8e9731c..ffae3b7 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
@@ -25,9 +25,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.referencing.Assertions.assertIsNotIdentity;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.ToleranceModifier;
-
 
 /**
  * Tests the {@link LogarithmicTransform1D} class. Note that this is closely related to
@@ -50,9 +47,8 @@
      * Creates a new test case.
      */
     public LogarithmicTransform1DTest() {
-        tolerance         = 2E-14;
-        toleranceModifier = ToleranceModifier.RELATIVE;
-        derivativeDeltas  = new double[] {0.001};
+        tolerance         = 1E-5; // Tolerance is much smaller on other branches.
+//      toleranceModifier = ToleranceModifier.RELATIVE; // Not available on GeoAPI 3.0.
     }
 
     /**
@@ -90,7 +86,6 @@
             expected[i] = value;
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                          // Test at a hard-coded point.
     }
 
     /**
@@ -211,6 +206,5 @@
             expected[i] = ExponentialTransform1DTest.SCALE * pow(10, log(value) / lnBase + C0);
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                  // Test at a hard-coded point.
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
index b8c252b..070b9c4 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
@@ -95,12 +95,6 @@
 
     /** Default implementation throws an exception. */
     @Override
-    public Matrix createMatrix(int numRow, int numCol) throws FactoryException {
-        throw new FactoryException(MESSAGE);
-    }
-
-    /** Default implementation throws an exception. */
-    @Override
     public MathTransform createConcatenatedTransform(MathTransform transform1, MathTransform transform2) throws FactoryException {
         throw new FactoryException(MESSAGE);
     }
@@ -113,6 +107,13 @@
 
     /** Default implementation throws an exception. */
     @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) throws FactoryException {
+        throw new FactoryException(MESSAGE);
+    }
+
+    /** Default implementation throws an exception. */
+    @Override
     public MathTransform createFromWKT(String wkt) throws FactoryException {
         throw new FactoryException(MESSAGE);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
index 762fce4..188bd52 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
@@ -34,9 +34,6 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.apache.sis.referencing.operation.matrix.Matrices;
-
 
 /**
  * A dummy implementation of {@link MathTransformFactory}, which contains exactly one operation method.
@@ -147,18 +144,6 @@
     }
 
     /**
-     * Delegates to {@link Matrices}.
-     *
-     * @param  numRow  number of rows.
-     * @param  numCol  number of columns.
-     * @return a new matrix of the given size.
-     */
-    @Override
-    public Matrix createMatrix(int numRow, int numCol) {
-        return Matrices.createDiagonal(numRow, numCol);
-    }
-
-    /**
      * Delegates to {@link MathTransforms}.
      *
      * @param  transform1  first transform to concatenate.
@@ -201,6 +186,18 @@
     /**
      * Unimplemented method.
      *
+     * @param  xml  ignored.
+     * @return never returned.
+     */
+    @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unimplemented method.
+     *
      * @param  wkt  ignored.
      * @return never returned.
      */
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
index 3b2398d..58c17b4 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
@@ -44,12 +44,6 @@
 import org.apache.sis.test.FailureDetailsReporter;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.geometry.DirectPosition;
-import org.apache.sis.measure.Longitude;
-import org.opengis.test.CalculationType;
-import org.opengis.test.referencing.TransformTestCase;
-
 
 /**
  * Base class for tests of {@link AbstractMathTransform} implementations.
@@ -58,7 +52,6 @@
  * <p>Various assertion methods:</p>
  * <ul>
  *   <li>{@link #assertCoordinateEquals assertCoordinateEquals(…)}  — from GeoAPI</li>
- *   <li>{@link #assertMatrixEquals     assertMatrixEquals(…)}      — from GeoAPI</li>
  *   <li>{@link #assertParameterEquals  assertParameterEquals(…)}   — from Apache SIS</li>
  *   <li>{@link #assertWktEquals        assertWktEquals(…)}         — from Apache SIS</li>
  * </ul>
@@ -120,17 +113,6 @@
      * Creates a new test case.
      */
     protected MathTransformTestCase() {
-        /*
-         * Use `zTolerance` threshold instead of `tolerance` when comparing vertical coordinate values.
-         */
-        toleranceModifier = (final double[] tolerances, final DirectPosition coordinates, final CalculationType mode) -> {
-            if (mode != CalculationType.IDENTITY) {
-                final int i = forComparison(zDimension, mode);
-                if (i >= 0 && i < tolerances.length) {
-                    tolerances[i] = zTolerance;
-                }
-            }
-        };
     }
 
     /**
@@ -148,41 +130,6 @@
         zTolerance = 0;
         tolerance  = 0;
         derivativeDeltas = null;
-        configurationTip = null;
-    }
-
-    /**
-     * Returns the value to use from the {@link #λDimension} or {@link zDimension} for the
-     * given comparison mode, or -1 if none.
-     */
-    @SuppressWarnings("fallthrough")
-    private static int forComparison(final int[] config, final CalculationType mode) {
-        if (config != null) {
-            switch (mode) {
-                case INVERSE_TRANSFORM: if (config.length >= 2) return config[1];       // Intentional fallthrough.
-                case DIRECT_TRANSFORM:  if (config.length >= 1) return config[0];
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Invoked by all {@code assertCoordinateEquals(…)} methods before two positions are compared.
-     * The SIS implementation ensures that longitude values are contained in the ±180° range,
-     * applying 360° shifts if needed.
-     *
-     * @param  expected  the expected coordinate values provided by the test case.
-     * @param  actual    the coordinate values computed by the {@linkplain #transform transform} being tested.
-     * @param  mode      indicates if the coordinates being compared are the result of a direct
-     *                   or inverse transform, or if strict equality is requested.
-     */
-    @Override
-    protected final void normalize(final DirectPosition expected, final DirectPosition actual, final CalculationType mode) {
-        final int i = forComparison(λDimension, mode);
-        if (i >= 0) {
-            expected.setCoordinate(i, Longitude.normalize(expected.getCoordinate(i)));
-            actual  .setCoordinate(i, Longitude.normalize(actual  .getCoordinate(i)));
-        }
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
index 6866fb0..dc8e1d0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
@@ -28,8 +28,8 @@
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.io.wkt.UnformattableObjectException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformsTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
index 01af411..c4644fb 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
@@ -35,8 +35,8 @@
 import static org.apache.sis.test.TestCase.STRICT;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
index a1152f3..f4f27e4 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
@@ -36,22 +36,9 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.referencing.datum.HardCodedDatum;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.io.IOException;
-import org.opengis.referencing.operation.MathTransform;
-import org.apache.sis.referencing.operation.provider.AbridgedMolodensky;
-import org.apache.sis.math.StatisticsFormat;
-import org.apache.sis.math.Statistics;
-import static org.apache.sis.metadata.privy.ReferencingServices.NAUTICAL_MILE;
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
-import org.opengis.test.ToleranceModifiers;
-import org.opengis.test.referencing.ParameterizedTransformTest;
-import org.apache.sis.test.TestCase;
-
 
 /**
- * Tests {@link MolodenskyTransform}. The {@link #compareWithGeocentricTranslation()}
+ * Tests {@link MolodenskyTransform}. The {@code compareWithGeocentricTranslation()}
  * method uses {@link EllipsoidToCentricTransform} as a reference implementation.
  * The errors compared to geocentric translations should not be greater than
  * approximately 1 centimetre.
@@ -74,67 +61,6 @@
     }
 
     /**
-     * Compares the Molodensky (non-abridged) transform with a geocentric translation.
-     * Molodensky is an approximation of geocentric translation, so we test here how good this approximation is.
-     * If {@link TestCase#VERBOSE} is {@code true}, then this method will print error statistics.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     * @throws IOException should never happen.
-     *
-     * @see #compareWithGeocentricTranslation()
-     */
-    @SuppressWarnings("fallthrough")
-    private void compareWithGeocentricTranslation(
-            final Ellipsoid source, final Ellipsoid target,
-            final double tX,   final double tY,   final double tZ,
-            final double xmin, final double ymin, final double zmin,
-            final double xmax, final double ymax, final double zmax)
-            throws FactoryException, TransformException, IOException
-    {
-        final MathTransform reference;
-        final MathTransformFactory factory = DefaultMathTransformFactory.provider();
-        transform = MolodenskyTransform.createGeodeticTransformation(factory, source, true, target, true, tX, tY, tZ, false);
-        reference = GeocentricTranslationTest.createDatumShiftForGeographic3D(factory, source, target, tX, tY, tZ);
-        final float[] srcPts = verifyInDomain(
-                new double[] {xmin, ymin, zmin},
-                new double[] {xmax, ymax, zmax},
-                new int[]    {  10,   10,   10},
-                TestUtilities.createRandomNumberGenerator(103627524044558476L));
-        /*
-         * Transform the same input coordinates using Molodensky transform (actual) and using the reference
-         * implementation (expected). If we were asked to print statistics, compute them before to test the
-         * values since the statistics may be a useful information in case of problem.
-         */
-        final double[] actual   = new double[srcPts.length];
-        final double[] expected = new double[srcPts.length];
-        transform.transform(srcPts, 0, actual,   0, srcPts.length / 3);
-        reference.transform(srcPts, 0, expected, 0, srcPts.length / 3);
-        if (TestCase.VERBOSE) {
-            final Statistics[] stats = {
-                new Statistics("|Δλ| (~cm)"),
-                new Statistics("|Δφ| (~cm)"),
-                new Statistics("|Δh| (cm)")
-            };
-            for (int i=0; i<srcPts.length; i++) {
-                double Δ = actual[i] - expected[i];
-                final int j = i % stats.length;
-                switch (j) {
-                    case 0: Δ *= cos(toRadians(expected[i+1]));     // Fall through
-                    case 1: Δ *= 60 * NAUTICAL_MILE; break;         // Approximate conversion to metres
-                }
-                Δ *= 100;   // Conversion to centimetres.
-                stats[j].accept(abs(Δ));
-            }
-            StatisticsFormat.getInstance().format(stats, TestCase.out);
-        }
-        assertCoordinatesEqual(3, expected, 0,
-                actual, 0, expected.length / 3,
-                CalculationType.DIRECT_TRANSFORM,
-                "Comparison of Molodensky and geocentric translation");
-    }
-
-    /**
      * Creates a Molodensky transform for a datum shift from WGS84 to ED50.
      * Tolerance thresholds are also initialized.
      *
@@ -203,6 +129,7 @@
         final double[] sample   = GeocentricTranslationTest.samplePoint(1);
         final double[] expected = GeocentricTranslationTest.samplePoint(5);
         isInverseTransformSupported = false;
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         verifyTransform(sample, expected);
         /*
          * When testing the inverse transformation, we need to relax slightly
@@ -231,6 +158,7 @@
         final double[] sample   = GeocentricTranslationTest.samplePoint(1);
         final double[] expected = GeocentricTranslationTest.samplePoint(4);
         isInverseTransformSupported = false;
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         verifyTransform(sample, expected);
         /*
          * When testing the inverse transformation, we need to relax slightly
@@ -249,8 +177,6 @@
      *
      * @throws FactoryException if an error occurred while creating the transform.
      * @throws TransformException if transformation of a point failed.
-     *
-     * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
      */
     @Test
     public void testFranceGeocentricInterpolationPoint() throws FactoryException, TransformException {
@@ -266,7 +192,7 @@
          * Code below is a copy-and-paste of GeocentricTranslationTest.testFranceGeocentricInterpolationPoint(),
          * but with the tolerance threshold increased. We do not let the error goes beyond 1 cm however.
          */
-        tolerance = min(Formulas.ANGULAR_TOLERANCE, FranceGeocentricInterpolationTest.ANGULAR_TOLERANCE * 6);
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         final double[] source   = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(1), 3);
         final double[] expected = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(2), 3);
         expected[2] = 43.15;  // Anti-regression (this value is not provided in NTG_88 guidance note).
@@ -275,46 +201,6 @@
     }
 
     /**
-     * Compares the Molodensky (non-abridged) transforms with geocentric translations.
-     * Molodensky is an approximation of geocentric translation, so we test here how good this
-     * approximation is. This test performs the comparison for the following transformations:
-     *
-     * <ul>
-     *   <li>Transformation from NTF to RGF93. Those CRS are the source and target of <q>France geocentric
-     *       interpolation</q> (ESPG:9655). This test allows us to verify the accuracy documented in
-     *       {@link InterpolatedGeocentricTransform}.</li>
-     *   <li>(More areas may be added later).</li>
-     * </ul>
-     *
-     * If {@link TestCase#VERBOSE} is {@code true}, then this method will print error statistics.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     * @throws IOException should never happen.
-     *
-     * @see #testFranceGeocentricInterpolationPoint()
-     */
-    @Test
-    public void compareWithGeocentricTranslation() throws FactoryException, TransformException, IOException {
-        /*
-         * Disable the test for inverse transformations because they are not the purpose of this test.
-         * Errors of inverse transformations are added to the error of forward transformations, which
-         * would force us to double the tolerance threshold.
-         */
-        isInverseTransformSupported = false;
-        tolerance         = 3*Formulas.LINEAR_TOLERANCE; // To be converted in degrees by ToleranceModifier.GEOGRAPHIC
-        zTolerance        = 4*Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
-        compareWithGeocentricTranslation(HardCodedDatum.NTF.getEllipsoid(),   // Clarke 1880 (IGN)
-                                         CommonCRS.ETRS89.ellipsoid(),        // GRS 1980 ellipsoid
-                                         FranceGeocentricInterpolation.TX,
-                                         FranceGeocentricInterpolation.TY,
-                                         FranceGeocentricInterpolation.TZ,
-                                         -5.5, 41.0, -200,   // Geographic area of GR2DF97A datum shift grid.
-                                         10.0, 52.0, +200);
-    }
-
-    /**
      * Tests conversion of random points. The test is performed with the Molodensky transform,
      * not the abridged one, because the errors caused by the abridged Molodensky method are
      * too high for this test.
@@ -327,7 +213,7 @@
         create(false);
         tolerance  = Formulas.LINEAR_TOLERANCE * 3;     // To be converted in degrees by ToleranceModifier.GEOGRAPHIC
         zTolerance = Formulas.LINEAR_TOLERANCE * 2;
-        toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
+//      toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
         verifyInDomain(new double[] {-179, -85, -500},
                        new double[] {+179, +85, +500},
                        new int[]    {   8,   8,    8},
@@ -361,17 +247,6 @@
     }
 
     /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        new ParameterizedTransformTest(new MathTransformFactoryMock(new AbridgedMolodensky())).testAbridgedMolodensky();
-    }
-
-    /**
      * Verifies that creating a Molodensky operation with same source and target ellipsoid and zero translation
      * results in an identity affine transform.
      *
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
index 3764122..a5823eb 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
@@ -34,8 +34,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.operation.PointMotionOperation;
+// Specific to the main branch:
+import org.opengis.referencing.operation.PassThroughOperation;
 
 
 /**
@@ -152,9 +152,9 @@
         assertEquals(Set.of(nad), shifts);
         assertEquals(Set.of(nad), shifts);
         /*
-         * Test filtering: the test should not contain any point motion operation.
+         * Test filtering: the test should not contain any pass-through operation.
          */
-        assertEmpty(create(PointMotionOperation.class, methods));
+        assertEmpty(create(PassThroughOperation.class, methods));
         /*
          * Opportunist tests.
          */
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
index d6d365e..6af766c 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
@@ -35,9 +35,8 @@
 import org.apache.sis.test.TestUtilities;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -215,21 +214,19 @@
          * Now process to the transform and compares the results with the expected ones.
          */
         tolerance         = 0;          // Results should be strictly identical because we used the same inputs.
-        toleranceModifier = null;
         final double[] transformedData = new double[StrictMath.max(sourceDim, targetDim) * numPts];
         transform.transform(passthroughData, 0, transformedData, 0, numPts);
-        assertCoordinatesEqual(targetDim, expectedData, 0, transformedData, 0, numPts, CalculationType.DIRECT_TRANSFORM,
-                               "PassThroughTransform results do not match the results computed by this test.");
+        assertCoordinatesEqual("PassThroughTransform results do not match the results computed by this test.",
+                targetDim, expectedData, 0, transformedData, 0, numPts, false);
         /*
          * Test inverse transform.
          */
         if (isInverseTransformSupported) {
             tolerance         = 1E-8;
-            toleranceModifier = ToleranceModifier.RELATIVE;
             Arrays.fill(transformedData, Double.NaN);
             transform.inverse().transform(expectedData, 0, transformedData, 0, numPts);
-            assertCoordinatesEqual(sourceDim, passthroughData, 0, transformedData, 0, numPts, CalculationType.INVERSE_TRANSFORM,
-                                   "Inverse of PassThroughTransform do not give back the original data.");
+            assertCoordinatesEqual("Inverse of PassThroughTransform do not give back the original data.",
+                    sourceDim, passthroughData, 0, transformedData, 0, numPts, false);
         }
         /*
          * Verify the consistency between different 'transform(…)' methods.
@@ -237,16 +234,6 @@
         final float[] sourceAsFloat = ArraysExt.copyAsFloats(passthroughData);
         final float[] targetAsFloat = verifyConsistency(sourceAsFloat);
         assertEquals(expectedData.length, targetAsFloat.length, "Unexpected length of transformed array.");
-        /*
-         * We use a relatively high tolerance threshold because result are
-         * computed using inputs stored as float values.
-         */
-        if (transform instanceof LinearTransform) {
-            tolerance         = 1E-4;
-            toleranceModifier = ToleranceModifier.RELATIVE;
-            assertCoordinatesEqual(sourceDim, expectedData, 0, targetAsFloat, 0, numPts, CalculationType.DIRECT_TRANSFORM,
-                                   "PassThroughTransform.transform(…) variants produce inconsistent results.");
-        }
     }
 
     /**
@@ -283,8 +270,8 @@
                 0, 0, 0, 1, 0, 0, 0,
                 0, 0, 0, 0, 0, 1, 0,
                 0, 0, 0, 0, 0, 0, 1});
-        assertMatrixEquals(m, MathTransforms.getMatrix(steps.get(0)), null,
-                           "Expected removal of dimensions 0 and 4 before pass-through");
+        assertMatrixEquals(m, MathTransforms.getMatrix(steps.get(0)), 0,
+                "Expected removal of dimensions 0 and 4 before pass-through");
         /*
          * The number of pass-through dimensions have decreased from 2 to 1 on both sides of the sub-transform.
          */
@@ -301,8 +288,8 @@
                 0, 0, 0, 1, 0, 0,
                 0, 0, 0, 0, 1, 0,
                 0, 0, 0, 0, 0, 1});
-        assertMatrixEquals(m, MathTransforms.getMatrix(steps.get(2)), null,
-                           "Expected removal of dimensions 1 and 2 after pass-through");
+        assertMatrixEquals(m, MathTransforms.getMatrix(steps.get(2)), 0,
+                "Expected removal of dimensions 1 and 2 after pass-through");
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
index f858d2b..d6b642b 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
@@ -26,9 +26,6 @@
 import org.apache.sis.test.FailureDetailsReporter;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.referencing.TransformTestCase;
-
 
 /**
  * Tests {@link PolarToCartesian}.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
index 8f737b5..e315d47 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
@@ -41,9 +41,11 @@
 import org.apache.sis.test.FailureDetailsReporter;
 import static org.apache.sis.test.TestCase.STRICT;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.Assertions;
-import org.opengis.test.referencing.AffineTransformTest;
+// Specific to the main branch:
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.junit.jupiter.api.Disabled;
+import org.opengis.test.referencing.TransformTestCase;
+import org.apache.sis.test.GeoapiAssert;
 
 
 /**
@@ -54,8 +56,19 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("doclint:missing")
 @ExtendWith(FailureDetailsReporter.class)
-public class ProjectiveTransformTest extends AffineTransformTest {
+public class ProjectiveTransformTest extends TransformTestCase {
+    /**
+     * The factory to use for creating linear transforms.
+     */
+    private final MathTransformFactory mtFactory;
+
+    /**
+     * The matrix for the tested transform.
+     */
+    private Matrix matrix;
+
     /**
      * A math transform factory which delegates instantiations to the enclosing test class.
      * This is a workaround while waiting for JEP 447: Statements before super(…).
@@ -74,7 +87,7 @@
      * Creates a new test suite.
      */
     public ProjectiveTransformTest() {
-        super(new Proxy());
+        mtFactory = new Proxy();
         ((Proxy) mtFactory).test = this;
     }
 
@@ -116,7 +129,10 @@
     }
 
     /*
-     * Inherit all the tests from GeoAPI:
+     * GeoAPI 3.1 defines the following tests. However since those tests are not available
+     * in GeoAPI 3.0, we put empty placeholder. For running the real test, see for example
+     * the JDK6 branch of Apache SIS.
+     *
      *    - testIdentity1D()
      *    - testIdentity2D()
      *    - testIdentity3D()
@@ -130,6 +146,64 @@
      *    - testDimensionReduction()
      */
 
+    static final String MESSAGE = "This test is not available in GeoAPI 3.0. "
+            + "See Apache SIS geoapi-4.0 branch for the actual tests.";
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testIdentity1D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testIdentity2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testIdentity3D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testAxisSwapping2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testSouthOrientated2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testTranslatation2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testUniformScale2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testGenericScale2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testRotation2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testGeneral() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Disabled(MESSAGE)
+    public void testDimensionReduction() throws FactoryException, TransformException {
+    }
+
     /**
      * Tests {@link ProjectiveTransform#optimize()}. In particular this method verifies that a non-square matrix
      * that looks like diagonal is not confused with a real diagonal matrix.
@@ -148,14 +222,13 @@
         });
         transform = mtFactory.createAffineTransform(matrix);
         assertInstanceOf(ProjectiveTransform.class, transform, "Non-diagonal matrix shall not be handled by ScaleTransform.");
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
+        verifyConsistency(new float[] {1, 2, 3,   -3, -2, -1});
         /*
          * Remove the "problematic" row. The new transform should now be optimizable.
          */
         matrix = ((MatrixSIS) matrix).removeRows(3, 4);
         transform = mtFactory.createAffineTransform(matrix);
         assertInstanceOf(ScaleTransform.class, getOptimizedTransform(), "Diagonal matrix should be handled by a specialized class.");
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
     }
 
     /**
@@ -169,9 +242,8 @@
     public void testOptimizeConstant() throws FactoryException, TransformException {
         matrix = new Matrix2(0, 10, 0, 1);
         transform = mtFactory.createAffineTransform(matrix);
-        Assertions.assertMatrixEquals(matrix, assertInstanceOf(LinearTransform.class, transform).getMatrix(), STRICT,
+        GeoapiAssert.assertMatrixEquals(matrix, assertInstanceOf(LinearTransform.class, transform).getMatrix(), STRICT,
                                       "Transform shall use the given matrix unmodified.");
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
index b3c21ad..a9f99aa 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
@@ -27,8 +27,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.test.TestCase.STRICT;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.Assertions;
+// Specific to the main branch:
+import org.apache.sis.test.GeoapiAssert;
 
 
 /**
@@ -55,7 +55,7 @@
         final ScaleTransform tr = new ScaleTransform(matrix.getNumRow(), matrix.getNumCol(), elements);
         assertEquals(sourceDimensions, tr.getSourceDimensions());
         assertEquals(targetDimensions, tr.getTargetDimensions());
-        Assertions.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
+        GeoapiAssert.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
         assertArrayEquals(elements, TranslationTransformTest.getElementAsNumbers(tr));
         transform = tr;
         validate();
@@ -135,7 +135,7 @@
         final ScaleTransform tr = new ScaleTransform(4, 4, elements);
         assertEquals(3, tr.getSourceDimensions());
         assertEquals(3, tr.getTargetDimensions());
-        Assertions.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
+        GeoapiAssert.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
 
         TranslationTransformTest.replaceZeroByNull(elements, O);
         assertArrayEquals(elements, tr.getElementAsNumbers(false));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
index 7ae496b..c972493 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
@@ -135,7 +135,6 @@
     public void testForwardConsistency() throws TransformException {
         transform = create(false);
         tolerance = 1E-14;
-        isDerivativeSupported = false;          // Actually supported, but our test transform has discontinuities.
         verifyInDomain(CoordinateDomain.RANGE_10, -672445632505596619L);
     }
 
@@ -152,7 +151,6 @@
     public void testInverseConsistency() throws TransformException {
         transform = create(false).inverse();
         tolerance = 1E-12;
-        isDerivativeSupported = false;          // Actually supported, but our test transform has discontinuities.
         verifyInDomain(CoordinateDomain.RANGE_100, 4308397764777385180L);
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
index 3aef178..9b91460 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
@@ -30,9 +30,6 @@
 import org.apache.sis.test.FailureDetailsReporter;
 import org.apache.sis.test.TestUtilities;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.referencing.TransformTestCase;
-
 
 /**
  * Tests {@link SphericalToCartesian}.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
index d99bb74..bfd3256 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
@@ -28,8 +28,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformResultComparator.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
index c7e5654..845f210 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
@@ -28,9 +28,9 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -113,9 +113,9 @@
      */
     @Override
     public DirectPosition transform(DirectPosition ptSrc, DirectPosition ptDst) throws TransformException {
-        final double[] expected = reference.transform(ptSrc, ptDst).getCoordinates();
+        final double[] expected = reference.transform(ptSrc, ptDst).getCoordinate();
         final DirectPosition value = tested.transform(ptSrc, ptDst);
-        assertArrayEquals(expected, value.getCoordinates(), tolerance);
+        assertArrayEquals(expected, value.getCoordinate(), tolerance);
         return value;
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
index 0064454..ecbf385 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
@@ -39,8 +39,8 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import org.apache.sis.referencing.datum.HardCodedDatum;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
@@ -395,7 +395,7 @@
         DirectPosition actual   = null;
         for (int t=0; t<50; t++) {
             for (int i=source.getDimension(); --i>=0;) {
-                source.setCoordinate(i, random.nextDouble());
+                source.setOrdinate(i, random.nextDouble());
             }
             step     = tr1 .transform(source,   step);
             expected = tr2 .transform(step, expected);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformTestCase.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformTestCase.java
new file mode 100644
index 0000000..b78d573
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TransformTestCase.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.transform;
+
+import java.util.Random;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.apache.sis.test.GeoapiAssert.PENDING_NEXT_GEOAPI_RELEASE;
+
+
+/**
+ * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+ * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public class TransformTestCase extends org.opengis.test.referencing.TransformTestCase {
+    /**
+     * The deltas to use for approximating math transform derivatives by the finite differences method.
+     */
+    protected double[] derivativeDeltas;
+
+    /**
+     * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+     * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+     *
+     * @param coordinate Ignored.
+     */
+    protected final void verifyDerivative(final double... coordinate) {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    /**
+     * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+     * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+     *
+     * @param minOrdinates    Ignored.
+     * @param maxOrdinates    Ignored.
+     * @param numOrdinates    Ignored.
+     * @param randomGenerator Ignored.
+     */
+    protected final void verifyInDomain(final double[] minOrdinates, final double[] maxOrdinates,
+            final int[] numOrdinates, final Random randomGenerator)
+    {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
index 7dc07bb..176916a 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
@@ -28,8 +28,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.test.TestCase.STRICT;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.Assertions;
+// Specific to the main branch:
+import org.apache.sis.test.GeoapiAssert;
 
 
 /**
@@ -55,7 +55,7 @@
         final TranslationTransform tr = new TranslationTransform(matrix.getNumRow(), elements);
         assertEquals(dimensions, tr.getSourceDimensions());
         assertEquals(dimensions, tr.getTargetDimensions());
-        Assertions.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
+        GeoapiAssert.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
         assertArrayEquals(elements, getElementAsNumbers(tr));
         transform = tr;
         validate();
@@ -96,7 +96,7 @@
         final TranslationTransform tr = new TranslationTransform(4, elements);
         assertEquals(3, tr.getSourceDimensions());
         assertEquals(3, tr.getTargetDimensions());
-        Assertions.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
+        GeoapiAssert.assertMatrixEquals(matrix, tr.getMatrix(), STRICT, "matrix");
 
         replaceZeroByNull(elements, O);
         assertArrayEquals(elements, tr.getElementAsNumbers(false));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
index c34e5f3..976103b 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
@@ -32,8 +32,8 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertMatrixEquals;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java
index b926d08..f50eb7f 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java
@@ -32,6 +32,12 @@
 import org.apache.sis.referencing.cs.HardCodedCS;
 import org.apache.sis.test.TestCase;
 
+// Specific to the main branch:
+import static org.apache.sis.referencing.privy.AxisDirections.AWAY_FROM;
+import static org.apache.sis.referencing.privy.AxisDirections.CLOCKWISE;
+import static org.apache.sis.referencing.privy.AxisDirections.COUNTER_CLOCKWISE;
+import static org.apache.sis.referencing.privy.AxisDirections.UNSPECIFIED;
+
 
 /**
  * Tests the {@link AxisDirections} class.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/EllipsoidalHeightCombinerTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/EllipsoidalHeightCombinerTest.java
index 376493f..104eed6 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/EllipsoidalHeightCombinerTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/EllipsoidalHeightCombinerTest.java
@@ -40,8 +40,8 @@
 import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata;
 import static org.apache.sis.test.Assertions.assertArrayEqualsIgnoreMetadata;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/GeodeticObjectBuilderTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/GeodeticObjectBuilderTest.java
index 66fde6a..7c675be 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/GeodeticObjectBuilderTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/GeodeticObjectBuilderTest.java
@@ -28,8 +28,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/ReferencingUtilitiesTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/ReferencingUtilitiesTest.java
index c6d794d..9fe54f8 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/ReferencingUtilitiesTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/ReferencingUtilitiesTest.java
@@ -119,10 +119,10 @@
         assertEquals("cylindricalCS",    toPropertyName(CoordinateSystem.class, CylindricalCS   .class).toString());
         assertEquals("ellipsoidalCS",    toPropertyName(CoordinateSystem.class, EllipsoidalCS   .class).toString());
         assertEquals("linearCS",         toPropertyName(CoordinateSystem.class, LinearCS        .class).toString());
-        assertEquals("parametricCS",     toPropertyName(CoordinateSystem.class, ParametricCS    .class).toString());
+//      assertEquals("parametricCS",     toPropertyName(CoordinateSystem.class, ParametricCS    .class).toString());
         assertEquals("polarCS",          toPropertyName(CoordinateSystem.class, PolarCS         .class).toString());
         assertEquals("sphericalCS",      toPropertyName(CoordinateSystem.class, SphericalCS     .class).toString());
-        assertEquals("temporalCS",       toPropertyName(CoordinateSystem.class, TimeCS          .class).toString());
+        assertEquals("timeCS",           toPropertyName(CoordinateSystem.class, TimeCS          .class).toString());
         assertEquals("verticalCS",       toPropertyName(CoordinateSystem.class, VerticalCS      .class).toString());
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/WKTUtilitiesTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/WKTUtilitiesTest.java
index 7779637..961b004 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/WKTUtilitiesTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/WKTUtilitiesTest.java
@@ -53,7 +53,7 @@
         assertEquals(WKTKeywords.cylindrical, toType(CoordinateSystem.class, CylindricalCS   .class));
         assertEquals(WKTKeywords.ellipsoidal, toType(CoordinateSystem.class, EllipsoidalCS   .class));
         assertEquals(WKTKeywords.linear,      toType(CoordinateSystem.class, LinearCS        .class));
-        assertEquals(WKTKeywords.parametric,  toType(CoordinateSystem.class, ParametricCS    .class));
+//      assertEquals(WKTKeywords.parametric,  toType(CoordinateSystem.class, ParametricCS    .class));
         assertEquals(WKTKeywords.polar,       toType(CoordinateSystem.class, PolarCS         .class));
         assertEquals(WKTKeywords.spherical,   toType(CoordinateSystem.class, SphericalCS     .class));
         assertEquals(WKTKeywords.temporal,    toType(CoordinateSystem.class, TimeCS          .class));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/report/CoordinateOperationMethods.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/report/CoordinateOperationMethods.java
index 1327cf3..fbb5b1d 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/report/CoordinateOperationMethods.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/report/CoordinateOperationMethods.java
@@ -51,9 +51,9 @@
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 
 
 /**
@@ -238,7 +238,7 @@
          * ────────────────    EPSG IDENTIFIERS    ────────────────────────────────────
          */
         final StringBuilder buffer = new StringBuilder();
-        for (final Identifier id : method.getIdentifiers()) {
+        for (final ReferenceIdentifier id : method.getIdentifiers()) {
             if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 if (buffer.length() != 0) {
                     buffer.append(", ");
@@ -380,7 +380,7 @@
     private void writeName(final ParameterDescriptor<?> param) throws IOException {
         final int td = openTag("td class=\"sep\"");
         openTag("details");
-        final Identifier name = param.getName();
+        final ReferenceIdentifier name = param.getName();
         final String codeSpace = name.getCodeSpace();
         if (Constants.EPSG.equalsIgnoreCase(codeSpace)) {
             println("summary", escape(name.getCode()));
@@ -414,14 +414,14 @@
             final CRSAuthorityFactory factory) throws FactoryException
     {
         final Map<String, DefaultGeographicBoundingBox> domainOfValidity = new HashMap<>();
-        for (final String code : factory.getAuthorityCodes(DerivedCRS.class)) {
+        for (final String code : factory.getAuthorityCodes(GeneralDerivedCRS.class)) {
             final CoordinateReferenceSystem crs;
             try {
                 crs = factory.createCoordinateReferenceSystem(code);
             } catch (FactoryException e) {
                 continue;                                                   // Ignore and inspect the next element.
             }
-            if (crs instanceof DerivedCRS derived) {
+            if (crs instanceof GeneralDerivedCRS derived) {
                 final GeographicBoundingBox candidate = CRS.getGeographicBoundingBox(derived);
                 if (candidate != null) {
                     final String name = derived.getConversionFromBase().getMethod().getName().getCode();
@@ -529,8 +529,8 @@
     /**
      * Returns the first EPSG code found in the given collection, or {@code null} if none.
      */
-    private static String getFirstEpsgCode(final Iterable<? extends Identifier> identifiers) {
-        for (final Identifier id : identifiers) {
+    private static String getFirstEpsgCode(final Iterable<? extends ReferenceIdentifier> identifiers) {
+        for (final ReferenceIdentifier id : identifiers) {
             if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 return id.getCode();
             }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/report/CoordinateReferenceSystems.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/report/CoordinateReferenceSystems.java
deleted file mode 100644
index feab090..0000000
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/report/CoordinateReferenceSystems.java
+++ /dev/null
@@ -1,823 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.referencing.report;
-
-import java.util.Locale;
-import java.util.Set;
-import java.util.Map;
-import java.util.HashSet;
-import java.util.TreeMap;
-import java.util.NavigableMap;
-import java.util.Optional;
-import java.io.File;
-import java.io.IOException;
-import org.opengis.metadata.Identifier;
-import org.opengis.util.FactoryException;
-import org.opengis.util.InternationalString;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.cs.CartesianCS;
-import org.opengis.referencing.cs.SphericalCS;
-import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.referencing.crs.CompoundCRS;
-import org.opengis.referencing.crs.VerticalCRS;
-import org.opengis.referencing.crs.GeodeticCRS;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.referencing.crs.EngineeringCRS;
-import org.opengis.referencing.crs.DerivedCRS;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.RealizationMethod;
-import org.opengis.referencing.operation.OperationMethod;
-import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.internal.DeprecatedCode;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.Deprecable;
-import org.apache.sis.util.Utilities;
-import org.apache.sis.util.Version;
-import org.apache.sis.util.privy.Constants;
-import org.apache.sis.referencing.crs.AbstractCRS;
-import org.apache.sis.referencing.cs.AxesConvention;
-import org.apache.sis.util.iso.DefaultNameSpace;
-import org.apache.sis.util.logging.Logging;
-
-// Test dependencies
-import static org.junit.jupiter.api.Assertions.*;
-import org.opengis.test.report.AuthorityCodesReport;
-
-
-/**
- * Generates a list of supported Coordinate Reference Systems in the current directory.
- * This class is for manual execution after the EPSG database has been updated,
- * or the projection implementations changed.
- *
- * <p><b>WARNING:</b>
- * this class implements heuristic rules for nicer sorting (e.g. of CRS having numbers as Roman letters).
- * Those heuristic rules were determined specifically for the EPSG dataset expanded with WMS codes.
- * This class is not likely to produce good results for any other authorities, and many need to be updated
- * after any upgrade of the EPSG dataset.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class CoordinateReferenceSystems extends AuthorityCodesReport {
-    /**
-     * The titles of some sections where to group CRS. By default CRS are grouped by datum names.
-     * But if a name is listed in this map, then that alternative name will be used for grouping purpose.
-     * Sometimes the change is only cosmetic (e.g. "Reseau Geodesique Francais" → "Réseau Géodésique Français").
-     * But sometimes the changes have the effect of merging different datum under the same section.
-     * For example, we merge the "Arc 1950" and "Arc 1960" sections into a single "Arc" section,
-     * since those sections were small and we do not want to scatter the HTML page with too many sections.
-     * However, we do not merge "NAD83" and "NAD83(HARN)" because those sections are already quite large,
-     * and merging them will result in a too large section.
-     *
-     * <p>The decision to merge or not is arbitrary. Generally, we try to avoid small sections (less that 5 CRS)
-     * but without merging together unrelated datum. Every CRS having a datum whose name <em>starts</em> with a
-     * value in the left column will be reported in the section given in the right column.</p>
-     */
-    private static final NavigableMap<String,String> SECTION_TITLES = new TreeMap<>();
-    static {
-        rd("American Samoa",                                          "American Samoa");
-        rd("Arc",                                                     "Arc");
-        rd("Ancienne Triangulation Francaise",                        "Ancienne Triangulation Française");
-        rd("Australian Geodetic Datum",                               "Australian Geodetic Datum");
-        rd("Australian Height Datum",                                 "Australian Height Datum");
-        rd("Azores Central Islands",                                  "Azores Islands");
-        rd("Azores Occidental Islands",                               "Azores Islands");
-        rd("Azores Oriental Islands",                                 "Azores Islands");
-        rd("Baltic",                                                  "Baltic");
-        rd("Batavia",                                                 "Batavia");
-        rd("Bermuda",                                                 "Bermuda");
-        rd("Bogota 1975",                                             "Bogota 1975");
-        rd("Carthage",                                                "Carthage");
-        rd("Bern 1938",                                               "Bern / CH1903");
-        rd("Cais",                                                    "Cais");
-        rd("Cayman Brac",                                             "Cayman Islands");
-        rd("Cayman Islands",                                          "Cayman Islands");
-        rd("CH1903",                                                  "Bern / CH1903");
-        rd("CH1903+",                                                 "Bern / CH1903");
-        rd("Canadian Geodetic Vertical Datum",                        "Canadian Geodetic Vertical Datum");
-        rd("Chatham Islands Datum",                                   "Chatham Islands Datum");
-        rd("Corrego Alegre",                                          "Corrego Alegre");
-        rd("Croatian Terrestrial Reference System",                   "Croatian Reference System");
-        rd("Croatian Vertical Reference Datum",                       "Croatian Reference System");
-        rd("Danger 1950",                                             "Saint Pierre et Miquelon 1950");
-        rd("Dansk",                                                   "Dansk");
-        rd("Dealul Piscului",                                         "Dealul Piscului");
-        rd("Deutsches Haupthoehennetz",                               "Deutsches Haupthoehennetz");
-        rd("Douala",                                                  "Douala");
-        rd("Dunedin",                                                 "Dunedin");
-        rd("Dunedin-Bluff",                                           "Dunedin");
-        rd("EGM2008 geoid",                                           "EGM geoid");
-        rd("EGM84 geoid",                                             "EGM geoid");
-        rd("EGM96 geoid",                                             "EGM geoid");
-        rd("Egypt",                                                   "Egypt");
-        rd("EPSG example",                                            "EPSG example");
-        rd("Estonia",                                                 "Estonia");
-        rd("European Datum",                                          "European Datum");
-        rd("European Terrestrial Reference Frame",                    "European Terrestrial Reference Frame");
-        rd("European Vertical Reference Frame",                       "European Vertical Reference Frame");
-        rd("Fahud",                                                   "Fahud");
-        rd("Fao",                                                     "Fao");
-        rd("Fehmarnbelt",                                             "Fehmarnbelt");
-        rd("Faroe Datum",                                             "Faroe Islands");
-        rd("Faroe Islands",                                           "Faroe Islands");
-        rd("fk89",                                                    "Faroe Islands");
-        rd("Fiji",                                                    "Fiji");
-        rd("Gan 1970",                                                "Gandajika");
-        rd("Grand Cayman",                                            "Grand Cayman");
-        rd("Greek",                                                   "Greek");
-        rd("Greenland",                                               "Greenland");
-        rd("Guadeloupe",                                              "Guadeloupe");
-        rd("Guam",                                                    "Guam");
-        rd("Gunung Segara",                                           "Gunung Segara");
-        rd("Helsinki",                                                "Helsinki");
-        rd("High Water",                                              "High Water");
-        rd("Higher High Water",                                       "High Water");
-        rd("Highest Astronomical Tide",                               "High Water");
-        rd("Hong Kong",                                               "Hong Kong");
-        rd("Hungarian",                                               "Hungarian Datum");
-        rd("IG05",                                                    "Israeli Grid");
-        rd("IGb",                                                     "IGb");
-        rd("IGN",                                                     "IGN");
-        rd("IGS",                                                     "IGS");
-        rd("Indian",                                                  "Indian");
-        rd("International Great Lakes Datum",                         "International Great Lakes Datum");
-        rd("International Terrestrial Reference Frame",               "International Terrestrial Reference Frame");
-        rd("Islands Net",                                             "Islands Net");
-        rd("Israeli Geodetic Datum",                                  "Israeli Geodetic Datum");
-        rd("Jamaica",                                                 "Jamaica");
-        rd("Japanese Geodetic Datum 2000",                            "Japanese Geodetic Datum 2000");
-        rd("Japanese Geodetic Datum 2011",                            "Japanese Geodetic Datum 2011");
-        rd("Japanese Standard Levelling Datum",                       "Japanese Standard Levelling Datum");
-        rd("Kalianpur",                                               "Kalianpur");
-        rd("Kertau",                                                  "Kertau");
-        rd("KOC Construction Datum",                                  "KOC Construction Datum / Well Datum");
-        rd("KOC Well Datum",                                          "KOC Construction Datum / Well Datum");
-        rd("Korean Datum",                                            "Korean Datum");
-        rd("Kuwait Oil Company",                                      "Kuwait Oil Company / Kuwait Utility");
-        rd("Kuwait PWD",                                              "Kuwait Oil Company / Kuwait Utility");
-        rd("Kuwait Utility",                                          "Kuwait Oil Company / Kuwait Utility");
-        rd("Lao",                                                     "Lao");
-        rd("Latvia",                                                  "Latvia");
-        rd("Lisbon",                                                  "Lisbon");
-        rd("Lower Low Water Large Tide",                              "Low Water");
-        rd("Lowest Astronomical Tide",                                "Low Water");
-        rd("Macao",                                                   "Macao");
-        rd("Makassar",                                                "Makassar");
-        rd("Manoca",                                                  "Manoca");
-        rd("Martinique",                                              "Martinique");
-        rd("Maupiti",                                                 "Maupiti");
-        rd("Mean High Water",                                         "Mean Sea Level");
-        rd("Mean Higher High Water",                                  "Mean Sea Level");
-        rd("Mean Low Water",                                          "Mean Sea Level");
-        rd("Mean Lower Low Water",                                    "Mean Sea Level");
-        rd("Missao Hidrografico Angola y Sao Tome 1951",              "Missao Hidrografico Angola y Sao Tome");
-        rd("Mhast (offshore)",                                        "Missao Hidrografico Angola y Sao Tome");
-        rd("Mhast (onshore)",                                         "Missao Hidrografico Angola y Sao Tome");
-        rd("Militar-Geographische Institut (Ferro)",                  "Militar-Geographische Institut");
-        rd("MOMRA",                                                   "MOMRA");
-        rd("Monte Mario (Rome)",                                      "Monte Mario");
-        rd("Moorea",                                                  "Moorea");
-        rd("Nahrwan",                                                 "Nahrwan");
-        rd("Naparima",                                                "Naparima");
-        rd("Nivellement General de la Corse",                         "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de la France",                        "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de Nouvelle Caledonie",               "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de Polynesie Francaise",              "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General du Luxembourg",                       "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General Guyanais",                            "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("NGO 1948",                                                "NGO 1948");
-        rd("Nouvelle Triangulation Francaise",                        "Nouvelle Triangulation Française");
-        rd("NAD83 Canadian Spatial Reference System",                 "North American Datum 1983 — Canadian Spatial Reference System");
-        rd("NAD83 (Continuously Operating Reference Station 1996)",   "North American Datum 1983 — Continuously Operating Reference Station 1996");       // For better sort order.
-        rd("NAD83 (Federal Base Network)",                            "North American Datum 1983 — Federal Base Network");
-        rd("NAD83 (High Accuracy Reference Network)",                 "North American Datum 1983 — High Accuracy Reference Network");
-        rd("NAD83 (High Accuracy Reference Network - Corrected)",     "North American Datum 1983 — High Accuracy Reference Network");
-        rd("NAD83 (National Spatial Reference System 2007)",          "North American Datum 1983 — National Spatial Reference System 2007");
-        rd("NAD83 (National Spatial Reference System 2011)",          "North American Datum 1983 — National Spatial Reference System 2011");
-        rd("NAD83 (National Spatial Reference System MA11)",          "North American Datum 1983 — National Spatial Reference System MA11 / PA11");
-        rd("NAD83 (National Spatial Reference System PA11)",          "North American Datum 1983 — National Spatial Reference System MA11 / PA11");
-        rd("North American Datum of 1983 (CSRS)",                     "North American Datum 1983 — CSRS");
-        rd("North American Datum of 1983 (CSRS96)",                   "North American Datum 1983 — CSRS");
-        rd("New Zealand Vertical Datum",                              "New Zealand Vertical Datum");
-        rd("Norway Normal Null",                                      "Norway Normal Null");
-        rd("Ordnance Datum Newlyn",                                   "Ordnance Datum Newlyn");
-        rd("OSGB",                                                    "OSGB");
-        rd("Parametry Zemli 1990",                                    "Parametry Zemli 1990");
-        rd("PDO Height Datum 1993",                                   "PDO Survey / Height Datum 1993");
-        rd("PDO Survey Datum 1993",                                   "PDO Survey / Height Datum 1993");
-        rd("Pitcairn",                                                "Pitcairn");
-        rd("Port Moresby",                                            "Port Moresby");
-        rd("Porto Santo",                                             "Porto Santo");
-        rd("Posiciones Geodésicas Argentinas",                        "Posiciones Geodésicas Argentinas");
-        rd("Puerto Rico",                                             "Puerto Rico");
-        rd("Qatar",                                                   "Qatar");
-        rd("Qornoq",                                                  "Qornoq");
-        rd("Reseau Geodesique de Nouvelle Caledonie",                 "Réseau Géodésique de Nouvelle-Calédonie");
-        rd("Reseau National Belge",                                   "Réseau National Belge");
-        rd("Reunion",                                                 "Réunion");
-        rd("Rikets hojdsystem",                                       "Rikets hojdsystem");
-        rd("Santa Cruz",                                              "Santa Cruz");
-        rd("Serbian",                                                 "Serbian Reference System / Network");
-        rd("Sierra Leone",                                            "Sierra Leone");
-        rd("SIRGAS",                                                  "SIRGAS");
-        rd("Slovenia",                                                "Slovenia");
-        rd("Slovenian",                                               "Slovenia");
-        rd("South American Datum",                                    "South American Datum");
-        rd("Sri Lanka",                                               "Sri Lanka");
-        rd("Stockholm 1938",                                          "Stockholm 1938");
-        rd("St. Helena",                                              "St. Helena");
-        rd("System of the Unified Trigonometrical Cadastral Network", "System of the Unified Trigonometrical Cadastral Network");
-        rd("Tahaa",                                                   "Tahaa");
-        rd("Tahiti",                                                  "Tahiti");
-        rd("Taiwan",                                                  "Taiwan");
-        rd("Tananarive 1925",                                         "Tananarive 1925");
-        rd("Tokyo",                                                   "Tokyo");
-        rd("Viti Levu",                                               "Viti Levu");
-        rd("Voirol",                                                  "Voirol");
-        rd("WGS 72 Transit Broadcast Ephemeris",                      "World Geodetic System 1972 — Transit Broadcast Ephemeris");
-        rd("World Geodetic System 1984",                              "World Geodetic System 1984");
-        rd("Yellow Sea",                                              "Yellow Sea");
-    }
-
-    /**
-     * The datums from the above list which are deprecated, but that we do not want to replace by the non-deprecated
-     * datum. We disable some replacements when they allow better sorting of deprecated CRS.
-     */
-    private static final Set<String> KEEP_DEPRECATED_DATUM = Set.of(
-        "Dealul Piscului 1970");            // Datum does not exist but is an alias for S-42 in Romania.
-
-    /**
-     * Shortcut for {@link #SECTION_TITLES} initialization.
-     * {@code "rd"} stands for "rename datum".
-     */
-    private static void rd(final String datum, final String display) {
-        assertNull(datum, SECTION_TITLES.put(datum, display));
-    }
-
-    /**
-     * Words to ignore in a datum name in order to detect if a CRS name is the acronym of the datum name.
-     */
-    private static final Set<String> DATUM_WORDS_TO_IGNORE = Set.of(
-            "of",           // VIVD:   Virgin Islands Vertical Datum of 2009
-            "de",           // RRAF:   Reseau de Reference des Antilles Francaises
-            "des",          // RGAF:   Reseau Geodesique des Antilles Francaises
-            "la",           // RGR:    Reseau Geodesique de la Reunion
-            "et",           // RGSPM:  Reseau Geodesique de Saint Pierre et Miquelon
-            "para",         // SIRGAS: Sistema de Referencia Geocentrico para America del Sur 1995
-            "del",          // SIRGAS: Sistema de Referencia Geocentrico para America del Sur 1995
-            "las",          // SIRGAS: Sistema de Referencia Geocentrico para las AmericaS 2000
-            "Tides");       // MLWS:   Mean Low Water Spring Tides
-
-    /**
-     * The keywords before which to cut the CRS names when sorting by alphabetical order.
-     * The main intent here is to preserve the "far west", "west", "central west", "central",
-     * "central east", "east", "far east" order.
-     */
-    private static final String[] CUT_BEFORE = {
-        " far west",        // "MAGNA-SIRGAS / Colombia Far West zone"
-        " far east",
-        " west",            // "Bogota 1975 / Colombia West zone"
-        " east",            // "Bogota 1975 / Colombia East Central zone"
-        " central",         // "Korean 1985 / Central Belt" (between "East Belt" and "West Belt")
-        " old central",     // "NAD Michigan / Michigan Old Central"
-        " bogota zone",     // "Bogota 1975 / Colombia Bogota zone"
-        // Do not declare "North" and "South" as it causes confusion with "WGS 84 / North Pole" and other cases.
-    };
-
-    /**
-     * The keywords after which to cut the CRS names when sorting by alphabetical order.
-     *
-     * Note: alphabetical sorting of Roman numbers work for zones from I to VIII inclusive.
-     * If there is more zones (for example with "JGD2000 / Japan Plane Rectangular"), then
-     * we need to cut before those numbers in order to use sorting by EPSG codes instead.
-     *
-     * Note 2: if alphabetical sorting is okay for Roman numbers, it is actually preferable
-     * because it give better position of names with height like "zone II + NGF IGN69 height".
-     */
-    private static final String[] CUT_AFTER = {
-        " cs ",                     // "JGD2000 / Japan Plane Rectangular CS IX"
-        " tm",                      // "ETRS89 / TM35FIN(E,N)" — we want to not interleave them between "TM35" and "TM36".
-        " dktm",                    // "ETRS89 / DKTM1 + DVR90 height"
-        "-gk",                      // "ETRS89 / ETRS-GK19FIN"
-        " philippines zone ",       // "Luzon 1911 / Philippines zone IV"
-        " california zone ",        // "NAD27 / California zone V"
-        " ngo zone ",               // "NGO 1948 (Oslo) / NGO zone I"
-        " lambert zone ",           // "NTF (Paris) / Lambert zone II + NGF IGN69 height"
-        "fiji 1956 / utm zone "     // Two zones: 60S and 1S with 60 before 1.
-    };
-
-    /**
-     * The symbol to write in from of EPSG code of CRS having an axis order different
-     * then the (longitude, latitude) one.
-     */
-    private static final char YX_ORDER = '\u21B7';
-
-    /**
-     * The factory which create CRS instances.
-     */
-    private final CRSAuthorityFactory factory;
-
-    /**
-     * The datum from the {@link #SECTION_TITLES} that we didn't found after we processed all codes.
-     * Used for verification purpose only.
-     */
-    private final Set<String> unusedDatumMapping;
-
-    /**
-     * Creates a new instance.
-     */
-    private CoordinateReferenceSystems() throws FactoryException {
-        super(null);
-        unusedDatumMapping = new HashSet<>(SECTION_TITLES.keySet());
-        properties.setProperty("TITLE",           "Apache SIS™ Coordinate Reference System (CRS) codes");
-        properties.setProperty("PRODUCT.NAME",    "Apache SIS™");
-        properties.setProperty("PRODUCT.VERSION", getVersion());
-        properties.setProperty("PRODUCT.URL",     "https://sis.apache.org");
-        properties.setProperty("JAVADOC.GEOAPI",  "https://www.geoapi.org/snapshot/javadoc");
-        properties.setProperty("FACTORY.NAME",    "EPSG");
-        properties.setProperty("FACTORY.VERSION", "9.9.1");
-        properties.setProperty("FACTORY.VERSION.SUFFIX", ", together with other sources");
-        properties.setProperty("PRODUCT.VERSION.SUFFIX", " (provided that <a href=\"https://sis.apache.org/epsg.html\">a connection to an EPSG database exists</a>)");
-        properties.setProperty("DESCRIPTION", "<p><b>Notation:</b></p>\n" +
-                "<ul>\n" +
-                "  <li>The " + YX_ORDER + " symbol in front of authority codes (${PERCENT.ANNOTATED} of them) identifies" +
-                " left-handed coordinate systems (for example with <var>latitude</var> axis before <var>longitude</var>).</li>\n" +
-                "  <li>The <del>codes with a strike</del> (${PERCENT.DEPRECATED} of them) identify deprecated CRS." +
-                " In some cases, the remarks column indicates the replacement.</li>\n" +
-                "</ul>");
-        factory = CRS.getAuthorityFactory(null);
-        add(factory);
-    }
-
-    /**
-     * Generates the HTML report.
-     *
-     * @param  args  ignored.
-     * @throws FactoryException if an error occurred while fetching the CRS.
-     * @throws IOException if an error occurred while writing the HTML file.
-     */
-    @SuppressWarnings("UseOfSystemOutOrSystemErr")
-    public static void main(final String[] args) throws FactoryException, IOException {
-        Locale.setDefault(Locale.US);   // We have to use this hack for now because exceptions are formatted in the current locale.
-        final CoordinateReferenceSystems writer = new CoordinateReferenceSystems();
-        final File file = writer.write(new File("CoordinateReferenceSystems.html"));
-        System.out.println("Created " + file.getAbsolutePath());
-        if (!writer.unusedDatumMapping.isEmpty()) {
-            System.out.println();
-            System.out.println("WARNING: the following datums were expected but not found. Maybe their spelling changed?");
-            for (final String name : writer.unusedDatumMapping) {
-                System.out.print("  - ");
-                System.out.println(name);
-            }
-        }
-    }
-
-    /**
-     * Returns the current Apache SIS version, with the {@code -SNAPSHOT} trailing part omitted.
-     *
-     * @return the current Apache SIS version.
-     */
-    private static String getVersion() {
-        String version = Version.SIS.toString();
-        final int snapshot = version.lastIndexOf('-');
-        if (snapshot >= 2) {
-            version = version.substring(0, snapshot);
-        }
-        return version;
-    }
-
-    /**
-     * Creates the text to show in the "Remarks" column for the given CRS.
-     */
-    private String getRemark(final CoordinateReferenceSystem crs) {
-        if (crs instanceof GeographicCRS) {
-            return (crs.getCoordinateSystem().getDimension() == 3) ? "Geographic 3D" : "Geographic";
-        }
-        if (crs instanceof DerivedCRS derived) {
-            final OperationMethod method = derived.getConversionFromBase().getMethod();
-            final Identifier identifier = IdentifiedObjects.getIdentifier(method, Citations.EPSG);
-            if (identifier != null) {
-                return "<a href=\"CoordinateOperationMethods.html#" + identifier.getCode()
-                       + "\">" + method.getName().getCode().replace('_', ' ') + "</a>";
-            }
-        }
-        if (crs instanceof GeodeticCRS) {
-            final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs instanceof CartesianCS) {
-                return "Geocentric (Cartesian coordinate system)";
-            } else if (cs instanceof SphericalCS) {
-                return "Geocentric (spherical coordinate system)";
-            }
-            return "Geodetic";
-        }
-        if (crs instanceof VerticalCRS vertical) {
-            final Optional<RealizationMethod> method = vertical.getDatum().getRealizationMethod();
-            if (method.isPresent()) {
-                return CharSequences.camelCaseToSentence(method.get().name().toLowerCase(getLocale())) + " realization method";
-            }
-        }
-        if (crs instanceof CompoundCRS compound) {
-            final StringBuilder buffer = new StringBuilder();
-            for (final CoordinateReferenceSystem component : compound.getComponents()) {
-                if (buffer.length() != 0) {
-                    buffer.append(" + ");
-                }
-                buffer.append(getRemark(component));
-            }
-            return buffer.toString();
-        }
-        if (crs instanceof EngineeringCRS) {
-            return "Engineering (" + crs.getCoordinateSystem().getName().getCode() + ')';
-        }
-        return "";
-    }
-
-    /**
-     * Omits the trailing number, if any.
-     * For example if the given name is "Abidjan 1987", then this method returns "Abidjan".
-     */
-    private static String omitTrailingNumber(String name) {
-        int i = CharSequences.skipTrailingWhitespaces(name, 0, name.length());
-        while (i != 0) {
-            final char c = name.charAt(--i);
-            if (c < '0' || c > '9') {
-                name = name.substring(0, CharSequences.skipTrailingWhitespaces(name, 0, i+1));
-                break;
-            }
-        }
-        return name;
-    }
-
-    /**
-     * If the first word of the CRS name seems to be an acronym of the datum name,
-     * puts that acronym in a {@code <abbr title="datum name">...</abbr>} element.
-     */
-    static String insertAbbreviationTitle(final String crsName, final String datumName) {
-        int s = crsName.indexOf(' ');
-        if (s < 0) s = crsName.length();
-        int p = crsName.indexOf('(');
-        if (p >= 0 && p < s) s = p;
-        p = datumName.indexOf('(');
-        if (p < 0) p = datumName.length();
-        final String acronym = crsName.substring(0, s);
-        final String ar = omitTrailingNumber(acronym);
-        final String dr = omitTrailingNumber(datumName.substring(0, p));
-        if (dr.startsWith(ar)) {
-            return crsName;                                 // Avoid redudancy between CRS name and datum name.
-        }
-        /*
-         * If the first CRS word does not seem to be an acronym of the datum name, verify
-         * if there is some words that we should ignore in the datum name and try again.
-         */
-        if (!CharSequences.isAcronymForWords(ar, dr)) {
-            final String[] words = (String[]) CharSequences.split(dr, ' ');
-            int n = 0;
-            for (final String word : words) {
-                if (!DATUM_WORDS_TO_IGNORE.contains(word)) {
-                    words[n++] = word;
-                }
-            }
-            if (n == words.length || n < 2) {
-                return crsName;
-            }
-            final StringBuilder b = new StringBuilder();
-            for (int i=0; i<n; i++) {
-                if (i != 0) b.append(' ');
-                b.append(words[i]);
-            }
-            if (!CharSequences.isAcronymForWords(ar, b)) {
-                return crsName;
-            }
-        }
-        return "<abbr title=\"" + datumName + "\">" + acronym + "</abbr>" + crsName.substring(s);
-    }
-
-    /**
-     * Invoked when a CRS has been successfully created. This method modifies the default
-     * {@link org.opengis.test.report.AuthorityCodesReport.Row} attribute values created
-     * by GeoAPI.
-     *
-     * @param  code    the authority code of the created object.
-     * @param  object  the object created from the given authority code.
-     * @return the created row, or {@code null} if the row should be ignored.
-     */
-    @Override
-    protected Row createRow(final String code, final IdentifiedObject object) {
-        final Row row = super.createRow(code, object);
-        final CoordinateReferenceSystem crs = (CoordinateReferenceSystem) object;
-        final CoordinateReferenceSystem crsXY = AbstractCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED);
-        if (!Utilities.deepEquals(crs.getCoordinateSystem(), crsXY.getCoordinateSystem(), ComparisonMode.IGNORE_METADATA)) {
-            row.annotation = YX_ORDER;
-        }
-        CoordinateReferenceSystem replacement = crs;
-        row.remark = getRemark(crs);
-        /*
-         * If the object is deprecated, find the replacement.
-         * We do not take the whole comment because it may be pretty long.
-         */
-        if (object instanceof Deprecable dep) {
-            row.isDeprecated = dep.isDeprecated();
-            if (row.isDeprecated) {
-                String replacedBy = null;
-                InternationalString i18n = object.getRemarks();
-                for (final Identifier id : object.getIdentifiers()) {
-                    if (id instanceof Deprecable did && did.isDeprecated()) {
-                        i18n = did.getRemarks();
-                        if (id instanceof DeprecatedCode dc) {
-                            replacedBy = dc.replacedBy;
-                        }
-                        break;
-                    }
-                }
-                if (i18n != null) {
-                    row.remark = i18n.toString(getLocale());
-                }
-                /*
-                 * If a replacement exists for a deprecated CRS, use the datum of the replacement instead of
-                 * the datum of the deprecated CRS for determining in which section to put the CRS. The reason
-                 * is that some CRS are deprecated because they were associated to the wrong datum, in which
-                 * case the deprecated CRS would appear in the wrong section if we do not apply this correction.
-                 */
-                if (!KEEP_DEPRECATED_DATUM.contains(CRS.getSingleComponents(crs).get(0).getDatum().getName().getCode())) {
-                    if (replacedBy != null) try {
-                        replacement = factory.createCoordinateReferenceSystem("EPSG:" + replacedBy);
-                    } catch (FactoryException e) {
-                        // Ignore - keep the datum of the deprecated object.
-                    }
-                }
-            }
-        }
-        ((ByName) row).setup(CRS.getSingleComponents(replacement).get(0).getDatum(), unusedDatumMapping);
-        return row;
-    }
-
-    /**
-     * Invoked when a CRS creation failed. This method modifies the default
-     * {@link org.opengis.test.report.AuthorityCodesReport.Row} attribute values
-     * created by GeoAPI.
-     *
-     * @param  code       the authority code of the object to create.
-     * @param  exception  the exception that occurred while creating the identified object.
-     * @return the created row, or {@code null} if the row should be ignored.
-     */
-    @Override
-    protected Row createRow(final String code, final FactoryException exception) {
-        if (code.startsWith(Constants.PROJ4 + DefaultNameSpace.DEFAULT_SEPARATOR)) {
-            return null;
-        }
-        final Row row = super.createRow(code, exception);
-        try {
-            row.name = factory.getDescriptionText(CoordinateReferenceSystem.class, code).get().toString(getLocale());
-        } catch (FactoryException e) {
-            Logging.unexpectedException(null, CoordinateReferenceSystems.class, "createRow", e);
-        }
-        if (code.startsWith("AUTO2:")) {
-            // It is normal to be unable to instantiate an "AUTO" CRS,
-            // because those authority codes need parameters.
-            row.hasError = false;
-            row.remark = "Projected";
-            ((ByName) row).setup(CommonCRS.WGS84.datum(), unusedDatumMapping);
-        } else {
-            row.remark = exception.getLocalizedMessage();
-            ((ByName) row).setup(null, unusedDatumMapping);
-        }
-        return row;
-    }
-
-    /**
-     * Invoked by {@link AuthorityCodesReport} for creating a new row instance.
-     *
-     * @return the new row instance.
-     */
-    @Override
-    protected Row newRow() {
-        return new ByName();
-    }
-
-
-
-
-    /**
-     * A row with a natural ordering that use the first part of the name before to use the authority code.
-     * We use only the part of the name prior some keywords (e.g. {@code "zone"}).
-     * For example if the following codes:
-     *
-     * <pre class="text">
-     *    EPSG:32609    WGS 84 / UTM zone 9N
-     *    EPSG:32610    WGS 84 / UTM zone 10N</pre>
-     *
-     * We compare only the "WGS 84 / UTM" string, then the code. This is a reasonably easy way to keep a more
-     * natural ordering ("9" sorted before "10", "UTM North" projections kept together and same for South).
-     */
-    private static final class ByName extends Row {
-        /**
-         * A string derived from the {@link #name} to use for sorting.
-         */
-        private String reducedName;
-
-        /**
-         * The datum name, or {@code null} if unknown.
-         * If non-null, this is used for grouping CRS names by sections.
-         */
-        String section;
-
-        /**
-         * Creates a new row.
-         */
-        ByName() {
-        }
-
-        /**
-         * Computes the {@link #reducedName} field value.
-         */
-        final void setup(final Datum datum, final Set<String> unusedDatumMapping) {
-            final String datumName;
-            if (datum != null) {
-                datumName = datum.getName().getCode();
-            } else {
-                // Temporary patch (TODO: remove after we implemented the missing methods in SIS)
-                if (name.startsWith("NSIDC EASE-Grid")) {
-                    datumName = "Unspecified datum";
-                } else if (code.equals("EPSG:2163")) {
-                    datumName = "Unspecified datum";
-                } else if (code.equals("EPSG:5818")) {
-                    datumName = "Seismic bin grid datum";
-                } else {
-                    datumName = null;       // Keep ordering based on the name.
-                }
-            }
-            if (datumName != null) {
-                final String prefix;
-                final Map.Entry<String,String> group = SECTION_TITLES.floorEntry(datumName);
-                if (group != null && datumName.startsWith(prefix = group.getKey())) {
-                    unusedDatumMapping.remove(prefix);
-                    section = group.getValue();
-                } else {
-                    section = datumName;
-                }
-            }
-            /*
-             * Get a copy of the name in all lower case.
-             */
-            final StringBuilder b = new StringBuilder(name);
-            for (int i=0; i<b.length(); i++) {
-                b.setCharAt(i, Character.toLowerCase(b.charAt(i)));
-            }
-            /*
-             * Cut the string to a shorter length if we find a keyword.
-             * This will result in many string equals, which will then be sorted by EPSG codes.
-             * This is useful when the EPSG codes give a better ordering than the alphabetic one
-             * (for example with Roman numbers).
-             */
-            int s = 0;
-            for (final String keyword : CUT_BEFORE) {
-                int i = b.lastIndexOf(keyword);
-                if (i > 0 && (s == 0 || i < s)) s = i;
-            }
-            for (final String keyword : CUT_AFTER) {
-                int i = b.lastIndexOf(keyword);
-                if (i >= 0) {
-                    i += keyword.length();
-                    if (i > s) s = i;
-                }
-            }
-            if (s != 0) b.setLength(s);
-            uniformizeZoneNumber(b);
-            reducedName = b.toString();
-            if (datumName != null) {
-                name = insertAbbreviationTitle(name, datumName);
-            }
-        }
-
-        /**
-         * If the string ends with a number optionally followed by "N" or "S", replaces the hemisphere
-         * symbol by a sign and makes sure that the number uses at least 3 digits (e.g. "2N" → "+002").
-         * This string will be used for better sorting order.
-         */
-        private static void uniformizeZoneNumber(final StringBuilder b) {
-            if (b.indexOf("/") < 0) {
-                /*
-                 * Do not process names like "WGS 84". We want to process only names like "WGS 84 / UTM zone 2N",
-                 * otherwise the replacement of "WGS 84" by "WGS 084" causes unexpected sorting.
-                 */
-                return;
-            }
-            int  i = b.length();
-            char c = b.charAt(i - 1);
-            if (c == ')') {
-                // Ignore suffix like " (ftUS)".
-                i = b.lastIndexOf(" (");
-                if (i < 0) return;
-                c = b.charAt(i - 1);
-            }
-            char sign;
-            switch (c) {
-                default:            sign =  0;       break;
-                case 'e': case 'n': sign = '+'; i--; break;
-                case 'w': case 's': sign = '-'; i--; break;
-            }
-            int upper = i;
-            do {
-                if (i == 0) return;
-                c = b.charAt(--i);
-            } while (c >= '0' && c <= '9');
-            switch (upper - ++i) {
-                case 2: b.insert(i,  '0'); upper++;  break;     // Found 2 digits.
-                case 1: b.insert(i, "00"); upper+=2; break;     // Only one digit found.
-                case 0: return;                                 // No digit.
-            }
-            if (sign != 0) {
-                b.insert(i, sign);
-                upper++;
-            }
-            b.setLength(upper);
-        }
-
-        /**
-         * Compares this row with the given row for ordering by name.
-         */
-        @Override
-        public int compareTo(final Row o) {
-            int n = reducedName.compareTo(((ByName) o).reducedName);
-            if (n == 0) {
-                n = super.compareTo(o);
-            }
-            return n;
-        }
-    }
-
-    /**
-     * Sorts the rows, then inserts sections between CRS instances that use different datums.
-     */
-    @Override
-    protected void sortRows() {
-        super.sortRows();
-        @SuppressWarnings("SuspiciousToArrayCall")
-        final ByName[] data = rows.toArray(ByName[]::new);
-        final Map<String,String> sections = new TreeMap<>();
-        for (final ByName row : data) {
-            final String section = row.section;
-            if (section != null) {
-                sections.put(CharSequences.toASCII(section).toString().toLowerCase(), section);
-            }
-        }
-        rows.clear();
-        /*
-         * Recopy the rows, but section-by-section. We do this sorting here instead of in the Row.compareTo(Row)
-         * method in order to preserve the alphabetical order of rows with unknown datum.
-         * Algorithm below is inefficient, but this class should be rarely used anyway and only by site maintainer.
-         */
-        for (final String section : sections.values()) {
-            final Row separator = new Row();
-            separator.isSectionHeader = true;
-            separator.name = section;
-            rows.add(separator);
-            boolean found = false;
-            for (int i=0; i<data.length; i++) {
-                final ByName row = data[i];
-                if (row != null) {
-                    if (row.section != null) {
-                        found = section.equals(row.section);
-                    }
-                    if (found) {
-                        rows.add(row);
-                        data[i] = null;
-                        found = true;
-                    }
-                }
-            }
-        }
-        boolean found = false;
-        for (final ByName row : data) {
-            if (row != null) {
-                if (!found) {
-                    final Row separator = new Row();
-                    separator.isSectionHeader = true;
-                    separator.name = "Unknown";
-                    rows.add(separator);
-                }
-                rows.add(row);
-                found = true;
-            }
-        }
-    }
-}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateOperationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateOperationTest.java
index 5170f4c..3a6dce0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateOperationTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateOperationTest.java
@@ -215,7 +215,7 @@
          */
         DirectPosition source = new DirectPosition2D(latitude, longitude);
         DirectPosition target = completeTransform.transform(source, null);
-        final double[] coordinate = target.getCoordinates();
+        final double[] coordinate = target.getCoordinate();
         assertEquals(expectedX, coordinate[0], 0.01);
         assertEquals(expectedY, coordinate[1], 0.01);
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateReferenceSystemTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateReferenceSystemTest.java
index 2d467cb..bd92e66 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateReferenceSystemTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/CoordinateReferenceSystemTest.java
@@ -32,6 +32,9 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.crs.GeodeticCRS;
 
+// Specific to the main branch:
+import org.opengis.referencing.crs.GeneralDerivedCRS;
+
 
 /**
  * Advanced CRS constructions requiring the EPSG geodetic dataset.
@@ -70,7 +73,7 @@
         assertInstanceOf(DerivedCRS .class, crs);
         assertInstanceOf(GeodeticCRS.class, crs);
         assertInstanceOf(CartesianCS.class, crs.getCoordinateSystem());
-        assertInstanceOf(CartesianCS.class, ((DerivedCRS) crs).getBaseCRS().getCoordinateSystem());
+        assertInstanceOf(CartesianCS.class, ((GeneralDerivedCRS) crs).getBaseCRS().getCoordinateSystem());
         /*
          * Some tests are disabled because `EPSGDataAccess` confuses CRS type.
          * We are waiting for upgrade to EPSG database 10+ before to re-evaluate
@@ -82,6 +85,6 @@
 //      assertInstanceOf(DerivedCRS .class, crs);
 //      assertInstanceOf(GeodeticCRS.class, crs);
         assertInstanceOf(CartesianCS.class, crs.getCoordinateSystem());
-        assertInstanceOf(EllipsoidalCS.class, ((DerivedCRS) crs).getBaseCRS().getCoordinateSystem());
+        assertInstanceOf(EllipsoidalCS.class, ((GeneralDerivedCRS) crs).getBaseCRS().getCoordinateSystem());
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataTest.java
index a3a48f2..7505b27 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataTest.java
@@ -70,9 +70,9 @@
 import org.apache.sis.xml.test.DocumentComparator;
 import org.apache.sis.xml.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-import org.opengis.referencing.datum.RealizationMethod;
+// Specific to the main branch:
+import org.opengis.referencing.datum.VerticalDatumType;
+import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList;
 
 
 /**
@@ -98,6 +98,18 @@
     }
 
     /**
+     * Creates a telephone number of the given type.
+     *
+     * @param type Either {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     */
+    private static DefaultTelephone telephone(final String number, final String type) {
+        final DefaultTelephone tel = new DefaultTelephone();
+        tel.setNumber(number);
+        tel.setNumberType(UnsupportedCodeList.valueOf(type));
+        return tel;
+    }
+
+    /**
      * Programmatically creates the metadata to marshal, or to compare against the unmarshalled metadata.
      *
      * @return the hard-coded representation of {@value #XML_FILE} content.
@@ -113,7 +125,6 @@
          * with only the role changed. Note that we need to create an instance of the deprecated class,
          * because this is what will be unmarshalled from the XML document.
          */
-        @SuppressWarnings("deprecation")
         final var author  = new DefaultResponsibleParty(Role.AUTHOR);
         final var country = new Anchor(URI.create("SDN:C320:2:FR"), "France"); // Non-public SIS class.
         {
@@ -122,8 +133,8 @@
             final var contact = new DefaultContact(online);
             contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "IFREMER");
             contact.setPhones(List.of(
-                    new DefaultTelephone("+33 (0)2 xx.xx.xx.x6", TelephoneType.VOICE),
-                    new DefaultTelephone("+33 (0)2 xx.xx.xx.x4", TelephoneType.FACSIMILE)
+                    telephone("+33 (0)2 xx.xx.xx.x6", "VOICE"),
+                    telephone("+33 (0)2 xx.xx.xx.x4", "FACSIMILE")
             ));
             final var address = new DefaultAddress();
             address.setDeliveryPoints(Set.of("Brest institute"));
@@ -145,14 +156,13 @@
                     new DefaultCitationDate(LocalDate.of(1990, 6, 5), DateType.REVISION),
                     new DefaultCitationDate(LocalDate.of(1979, 8, 3), DateType.CREATION)));
             {
-                @SuppressWarnings("deprecation")
                 final var originator = new DefaultResponsibleParty(Role.ORIGINATOR);
                 final var online = new DefaultOnlineResource(URI.create("http://www.com.univ-mrs.fr/LOB/"));
                 online.setProtocol(Constants.HTTP);
                 final var contact = new DefaultContact(online);
                 contact.setPhones(List.of(
-                        new DefaultTelephone("+33 (0)4 xx.xx.xx.x5", TelephoneType.VOICE),
-                        new DefaultTelephone("+33 (0)4 xx.xx.xx.x8", TelephoneType.FACSIMILE)
+                        telephone("+33 (0)4 xx.xx.xx.x5", "VOICE"),
+                        telephone("+33 (0)4 xx.xx.xx.x8", "FACSIMILE")
                 ));
                 final var address = new DefaultAddress();
                 address.setDeliveryPoints(Set.of("Oceanology institute"));
@@ -169,7 +179,6 @@
                     Locale.ENGLISH,                                             // Language,
                     TopicCategory.OCEANS);                                      // Topic category
             {
-                @SuppressWarnings("deprecation")
                 final var custodian = new DefaultResponsibleParty((DefaultResponsibility) author);
                 custodian.setRole(Role.CUSTODIAN);
                 identification.setPointOfContacts(Set.of(custodian));
@@ -202,21 +211,20 @@
              */
             {
                 final var constraint = new DefaultLegalConstraints();
-                constraint.setAccessConstraints(Set.of(Restriction.LICENCE));
+                constraint.setAccessConstraints(Set.of(Restriction.LICENSE));
                 identification.setResourceConstraints(Set.of(constraint));
             }
             /*
              * Data indentification / Aggregate information.
              */
             {
-                @SuppressWarnings("deprecation")
                 final var aggregateInfo = new DefaultAggregateInformation();
                 final var name = new DefaultCitation("Some oceanographic campaign");
                 name.setAlternateTitles(Set.of(new SimpleInternationalString("Pseudo group of data")));
                 name.setDates(Set.of(new DefaultCitationDate(LocalDate.of(1990, 6, 5), DateType.REVISION)));
                 aggregateInfo.setName(name);
                 aggregateInfo.setInitiativeType(InitiativeType.CAMPAIGN);
-                aggregateInfo.setAssociationType(AssociationType.LARGER_WORK_CITATION);
+                aggregateInfo.setAssociationType(AssociationType.LARGER_WORD_CITATION); // There is a typo ("WORD" → "WORK"), but we have to use the wrong spelling for this branch.
                 identification.setAssociatedResources(Set.of(aggregateInfo));
             }
             /*
@@ -230,7 +238,7 @@
                         nameAndIdentifier("depth", "Depth", null), axis);
 
                 final var datum = new DefaultVerticalDatum(
-                        nameAndIdentifier("D28", "Depth below D28", "For testing purpose"), (RealizationMethod) null);
+                        nameAndIdentifier("D28", "Depth below D28", "For testing purpose"), VerticalDatumType.OTHER_SURFACE);
 
                 final var vcrs = new DefaultVerticalCRS(
                         nameAndIdentifier("D28", "Depth below D28", "CRS for testing purpose"), datum, cs);
@@ -301,7 +309,6 @@
          * Distribution information.
          */
         {
-            @SuppressWarnings("deprecation")
             final var distributor = new DefaultResponsibleParty((DefaultResponsibility) author);
             final var distributionInfo = new DefaultDistribution();
             distributor.setRole(Role.DISTRIBUTOR);
@@ -335,7 +342,7 @@
         properties.put(DefaultVerticalDatum.NAME_KEY, new NamedIdentifier(null, name));
         properties.put(DefaultVerticalDatum.IDENTIFIERS_KEY, new NamedIdentifier(null, "test", identifier, null, null));
         if (scope != null) {
-            properties.put(ObjectDomain.SCOPE_KEY, scope);
+            properties.put(DefaultVerticalDatum.SCOPE_KEY, scope);
         }
         return properties;
     }
@@ -373,6 +380,7 @@
                      "<gmx:Anchor xlink:href=\"SDN:L231:3:CDI\">Pseudo Common Data Index record</gmx:Anchor>");
         replace(xml, "<gcol:CharacterString>4326</gcol:CharacterString>",
                      "<gmx:Anchor xlink:href=\"SDN:L101:2:4326\">4326</gmx:Anchor>");
+        replace(xml, "License", "Licence");
         /*
          * Ignore the "gml:id" attribute because SIS generates different values than the ones in our test XML file,
          * and those values may change in future SIS version.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java
index 949b1c0..56863a6 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java
@@ -48,9 +48,10 @@
 import static org.apache.sis.test.TestUtilities.getScope;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.nio.charset.StandardCharsets;
-import static org.opengis.test.Assertions.assertIdentifierEquals;
+// Specific to the main branch:
+import org.opengis.metadata.identification.CharacterSet;
+import org.opengis.referencing.datum.VerticalDatumType;
+import static org.apache.sis.test.GeoapiAssert.assertIdentifierEquals;
 
 
 /**
@@ -88,10 +89,10 @@
     @Test
     public void testMetadataWithVerticalCRS() throws JAXBException {
         final Metadata metadata = unmarshalFile(Metadata.class, openTestFile());
-        assertEquals("20090901",               metadata.getMetadataIdentifier().getCode());
-        assertEquals(Locale.ENGLISH,           getSingleton(metadata.getLocalesAndCharsets().keySet()));
-        assertEquals(StandardCharsets.UTF_8,   getSingleton(metadata.getLocalesAndCharsets().values()));
-        assertEquals(LocalDate.of(2014, 1, 4), TemporalDate.toTemporal(getSingleton(metadata.getDateInfo()).getDate()));
+        assertEquals("20090901",               metadata.getFileIdentifier());
+        assertEquals(Locale.ENGLISH,           metadata.getLanguage());
+        assertEquals(CharacterSet.UTF_8,       metadata.getCharacterSet());
+        assertEquals(LocalDate.of(2014, 1, 4), TemporalDate.toTemporal(metadata.getDateStamp()));
         /*
          * <gmd:contact>
          *   <gmd:CI_ResponsibleParty>
@@ -99,13 +100,10 @@
          *   </gmd:CI_ResponsibleParty>
          * </gmd:contact>
          */
-        final Responsibility contact        = getSingleton(metadata   .getContacts());
-        final Party          party          = getSingleton(contact    .getParties());
-        final Contact        contactInfo    = getSingleton(party      .getContactInfo());
-        final OnlineResource onlineResource = getSingleton(contactInfo.getOnlineResources());
-        assertInstanceOf(Organisation.class, party);
+        final ResponsibleParty contact = getSingleton(metadata.getContacts());
+        final OnlineResource onlineResource = contact.getContactInfo().getOnlineResource();
         assertNotNull(onlineResource);
-        assertEquals("Apache SIS", party.getName().toString());
+        assertEquals("Apache SIS", contact.getOrganisationName().toString());
         assertEquals(URI.create("http://sis.apache.org"), onlineResource.getLinkage());
         assertEquals(OnLineFunction.INFORMATION, onlineResource.getFunction());
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, contact.getRole());
@@ -139,7 +137,7 @@
         assertInstanceOf(NilObject.class, citation);
         assertEquals(NilReason.MISSING, ((NilObject) citation).getNilReason());
         assertEquals("SIS test", identification.getAbstract().toString());
-        assertEquals(Locale.ENGLISH, getSingleton(identification.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.ENGLISH, getSingleton(identification.getLanguages()));
         /*
          * <gmd:geographicElement>
          *   <gmd:EX_GeographicBoundingBox>
@@ -170,6 +168,7 @@
         final VerticalDatum datum = crs.getDatum();
         verifyIdentifiers("test2", datum);
         assertEquals("World", getScope(datum));
+        assertEquals(VerticalDatumType.DEPTH, datum.getVerticalDatumType());    // Inferred from the name.
         final VerticalCS cs = crs.getCoordinateSystem();
         verifyIdentifiers("test3", cs);
         final CoordinateSystemAxis axis = cs.getAxis(0);
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_OperationParameterGroupTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_OperationParameterGroupTest.java
index 669a1cc..170b303 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_OperationParameterGroupTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_OperationParameterGroupTest.java
@@ -195,7 +195,7 @@
                                              final ParameterDescriptor<?> actual)
     {
         assertEquals(expected.getName(),         actual.getName());
-        assertEquals(expected.getDescription(),  actual.getDescription());
+//      assertEquals(expected.getDescription(),  actual.getDescription());
         assertEquals(expected.getValueClass(),   actual.getValueClass());
         assertEquals(expected.getValidValues(),  actual.getValidValues());
         assertEquals(expected.getUnit(),         actual.getUnit());
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CodeTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CodeTest.java
index 4013f29..21b7eab 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CodeTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CodeTest.java
@@ -29,8 +29,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -46,10 +46,10 @@
     }
 
     /**
-     * Tests the {@link Code#Code(Identifier)} constructor with {@code "EPSG:4326"} identifier.
+     * Tests the {@link Code#Code(ReferenceIdentifier)} constructor with {@code "EPSG:4326"} identifier.
      * This test intentionally uses an identifier with the {@code IOGP} authority instead of
      * EPSG in order to make sure that the {@code codeSpace} attribute is set from
-     * {@link Identifier#getCodeSpace()}, not from {@link Identifier#getAuthority()}.
+     * {@code Identifier.getCodeSpace()}, not from {@code Identifier.getAuthority()}.
      */
     @Test
     public void testSimple() {
@@ -70,7 +70,7 @@
     }
 
     /**
-     * Tests the {@link Code#Code(Identifier)} constructor with {@code "EPSG:8.3:4326"} identifier.
+     * Tests the {@link Code#Code(ReferenceIdentifier)} constructor with {@code "EPSG:8.3:4326"} identifier.
      * This test intentionally uses an identifier with the {@code IOGP} authority instead of EPSG
      * for the same reason as {@link #testSimple()}.
      */
diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
index c358371..656745a 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
+++ b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
@@ -94,11 +94,7 @@
             sampleDimension = new DefaultSampleDimension();
         }
         sampleDimension.setDescription(band.title);
-        if (band.group.reflectance) {
-            sampleDimension.setUnits(Units.UNITY);
-        } else {
-            // W/(m² sr um)/DN
-        }
+        // Can not set units in GeoAPI 3.0 because the API is restricted to units of length.
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
index 2abfe48..81d0d7f 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
+++ b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
@@ -849,7 +849,7 @@
      */
     final Metadata getMetadata() throws FactoryException {
         addLanguage(Locale.ENGLISH, StandardCharsets.US_ASCII, MetadataBuilder.Scope.METADATA);
-        addResourceScope(ScopeCode.COVERAGE, null);
+        addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
         addTopicCategory(TopicCategory.GEOSCIENTIFIC_INFORMATION);
         try {
             flushSceneTime();
diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
index ee9a6ad..d51b8ad 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
+++ b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
@@ -23,29 +23,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import org.opengis.metadata.Metadata;
-import org.opengis.metadata.acquisition.Context;
-import org.opengis.metadata.acquisition.OperationType;
-import org.opengis.metadata.citation.Role;
-import org.opengis.metadata.citation.DateType;
-import org.opengis.metadata.content.CoverageContentType;
-import org.opengis.metadata.content.TransferFunctionType;
-import org.opengis.metadata.identification.Progress;
-import org.opengis.metadata.identification.TopicCategory;
-import org.opengis.metadata.extent.TemporalExtent;
-import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.metadata.spatial.DimensionNameType;
-import org.opengis.util.FactoryException;
-import org.apache.sis.storage.AbstractResource;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.event.StoreListeners;
-import org.opengis.test.dataset.ContentVerifier;
-import static org.apache.sis.test.TestUtilities.date;
-
 
 /**
  * Tests {@link MetadataReader}.
@@ -70,219 +47,4 @@
         assertTrue(m.find());
         assertEquals(22, m.end());
     }
-
-    /**
-     * Tests {@link MetadataReader#read(BufferedReader)}.
-     *
-     * <p><b>Note for maintainer:</b> if the result of this test changes, consider updating
-     * <a href="./doc-files/MetadataMapping.html">./doc-files/MetadataMapping.html</a> accordingly.</p>
-     *
-     * @throws IOException if an error occurred while reading the test file.
-     * @throws DataStoreException if a property value cannot be parsed as a number or a date.
-     * @throws FactoryException if an error occurred while creating the Coordinate Reference System.
-     */
-    @Test
-    public void testRead() throws IOException, DataStoreException, FactoryException {
-        final Metadata actual;
-        try (BufferedReader in = new BufferedReader(new InputStreamReader(
-                MetadataReaderTest.class.getResourceAsStream("LandsatTest.txt"), "UTF-8")))
-        {
-            final MetadataReader reader = new MetadataReader(null, "LandsatTest.txt", createListeners());
-            reader.read(in);
-            actual = reader.getMetadata();
-        }
-        final ContentVerifier verifier = new ContentVerifier();
-        verifier.addPropertyToIgnore(Metadata.class, "metadataStandard");           // Because hard-coded in SIS.
-        verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo");        // Very verbose and depends on EPSG connection.
-        verifier.addPropertyToIgnore(TemporalExtent.class, "extent");               // Because currently time-zone sensitive.
-        verifier.addMetadataToVerify(actual);
-        verifier.addExpectedValues(
-            "defaultLocale+otherLocale[0]",                                                          "en",
-            "metadataIdentifier.code",                                                               "LandsatTest",
-            "metadataScope[0].resourceScope",                                                        ScopeCode.COVERAGE,
-            "dateInfo[0].date",                                                                      date("2016-06-27 16:48:12"),
-            "dateInfo[0].dateType",                                                                  DateType.CREATION,
-            "identificationInfo[0].topicCategory[0]",                                                TopicCategory.GEOSCIENTIFIC_INFORMATION,
-            "identificationInfo[0].citation.date[0].date",                                           date("2016-06-27 16:48:12"),
-            "identificationInfo[0].citation.date[0].dateType",                                       DateType.CREATION,
-            "identificationInfo[0].citation.title",                                                  "LandsatTest",
-            "identificationInfo[0].credit[0]",                                                       "Derived from U.S. Geological Survey data",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title",             "GeoTIFF Coverage Encoding Profile",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "GeoTIFF",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name", "Open Geospatial Consortium",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role", Role.PRINCIPAL_INVESTIGATOR,
-            "identificationInfo[0].extent[0].geographicElement[0].extentTypeCode",                   true,
-            "identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude",               108.34,
-            "identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude",               110.44,
-            "identificationInfo[0].extent[0].geographicElement[0].southBoundLatitude",                10.50,
-            "identificationInfo[0].extent[0].geographicElement[0].northBoundLatitude",                12.62,
-            "identificationInfo[0].spatialResolution[0].distance",                                    15.0,
-            "identificationInfo[0].spatialResolution[1].distance",                                    30.0,
-
-            "acquisitionInformation[0].platform[0].identifier.code",               "Pseudo LANDSAT",
-            "acquisitionInformation[0].platform[0].instrument[0].identifier.code", "Pseudo TIRS",
-            "acquisitionInformation[0].acquisitionRequirement[0].identifier.code", "Software unit tests",
-            "acquisitionInformation[0].operation[0].significantEvent[0].context",  Context.ACQUISITION,
-            "acquisitionInformation[0].operation[0].significantEvent[0].time",     date("2016-06-26 03:02:01.090"),
-            "acquisitionInformation[0].operation[0].status",                       Progress.COMPLETED,
-            "acquisitionInformation[0].operation[0].type",                         OperationType.REAL,
-
-            "contentInfo[0].processingLevelCode.authority.title",          "Landsat",
-            "contentInfo[0].processingLevelCode.codeSpace",                "Landsat",
-            "contentInfo[0].processingLevelCode.code",                     "Pseudo LT1",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].description",   "Coastal Aerosol",
-            "contentInfo[0].attributeGroup[0].attribute[1].description",   "Blue",
-            "contentInfo[0].attributeGroup[0].attribute[2].description",   "Green",
-            "contentInfo[0].attributeGroup[0].attribute[3].description",   "Red",
-            "contentInfo[0].attributeGroup[0].attribute[4].description",   "Near-Infrared",
-            "contentInfo[0].attributeGroup[0].attribute[5].description",   "Short Wavelength Infrared (SWIR) 1",
-            "contentInfo[0].attributeGroup[0].attribute[6].description",   "Short Wavelength Infrared (SWIR) 2",
-            "contentInfo[0].attributeGroup[0].attribute[7].description",   "Cirrus",
-            "contentInfo[0].attributeGroup[1].attribute[0].description",   "Panchromatic",
-            "contentInfo[0].attributeGroup[2].attribute[0].description",   "Thermal Infrared Sensor (TIRS) 1",
-            "contentInfo[0].attributeGroup[2].attribute[1].description",   "Thermal Infrared Sensor (TIRS) 2",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].minValue",      1.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].minValue",      1.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].maxValue",      65535.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].peakResponse",    433.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].peakResponse",    482.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].peakResponse",    562.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].peakResponse",    655.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].peakResponse",    865.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].peakResponse",   1610.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].peakResponse",   2200.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].peakResponse",   1375.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].peakResponse",    590.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].peakResponse",  10800.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].peakResponse",  12000.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[1].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[2].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[3].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[4].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[5].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[6].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[7].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[1].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[2].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[2].attribute[1].transferFunctionType",  TransferFunctionType.LINEAR,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[1].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[2].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[3].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[4].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[5].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[6].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[7].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[1].attribute[0].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[2].attribute[0].scaleFactor",  0.000334,
-            "contentInfo[0].attributeGroup[2].attribute[1].scaleFactor",  0.000334,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[1].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[2].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[3].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[4].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[5].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[6].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[7].offset",      -0.1,
-            "contentInfo[0].attributeGroup[1].attribute[0].offset",      -0.1,
-            "contentInfo[0].attributeGroup[2].attribute[0].offset",       0.1,
-            "contentInfo[0].attributeGroup[2].attribute[1].offset",       0.1,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[1].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[2].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[3].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[4].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[5].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[6].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[7].units", "",
-            "contentInfo[0].attributeGroup[1].attribute[0].units", "",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[1].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[2].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[3].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[4].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[5].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[6].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[7].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[1].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[2].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[2].attribute[1].boundUnits",   "nm",
-
-            "contentInfo[0].attributeGroup[0].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-            "contentInfo[0].attributeGroup[1].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-            "contentInfo[0].attributeGroup[2].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-
-            "contentInfo[0].cloudCoverPercentage",         8.3,
-            "contentInfo[0].illuminationAzimuthAngle",   116.9,
-            "contentInfo[0].illuminationElevationAngle",  58.8,
-
-            "spatialRepresentationInfo[0].numberOfDimensions",                       2,
-            "spatialRepresentationInfo[1].numberOfDimensions",                       2,
-            "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE,
-            "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE,
-            "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE,
-            "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE,
-            "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionSize", 7600,
-            "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionSize", 7800,
-            "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionSize", 15000,
-            "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionSize", 15500,
-            "spatialRepresentationInfo[0].transformationParameterAvailability",      false,
-            "spatialRepresentationInfo[1].transformationParameterAvailability",      false,
-            "spatialRepresentationInfo[0].checkPointAvailability",                   false,
-            "spatialRepresentationInfo[1].checkPointAvailability",                   false,
-
-            "resourceLineage[0].source[0].description", "Pseudo GLS");
-
-        verifier.assertMetadataEquals();
-    }
-
-    /**
-     * Creates a dummy set of store listeners.
-     * Used only for constructors that require a non-null {@link StoreListeners} instance.
-     *
-     * @return a dummy set of listeners.
-     */
-    private static StoreListeners createListeners() {
-        final class DummyResource extends AbstractResource {
-            /** Creates a dummy resource without parent. */
-            DummyResource() {
-                super(null, false);
-            }
-
-            /** Makes listeners accessible to this package. */
-            StoreListeners listeners() {
-                return listeners;
-            }
-        }
-        return new DummyResource().listeners();
-    }
 }
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 87b7972..204f76c 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -385,7 +385,7 @@
             listeners.warning(e);
         }
         builder.addLanguage(Locale.ENGLISH, encoding, MetadataBuilder.Scope.METADATA);
-        builder.addResourceScope(ScopeCode.COVERAGE, null);
+        builder.addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
index c508fd5..2f2a65f 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
@@ -1142,7 +1142,7 @@
                     cs = replaceLinearUnit(cs, linearUnit);
                 }
                 // TODO: datum should be DatumEnsemble for some case such as EPSG:4326.
-                final GeodeticCRS crs = getCRSFactory().createGeodeticCRS(properties(getOrDefault(names, GCRS)), datum, null, cs);
+                final GeodeticCRS crs = getCRSFactory().createGeocentricCRS(properties(getOrDefault(names, GCRS)), datum, cs);
                 lastName = crs.getName();
                 return crs;
             }
@@ -1152,7 +1152,7 @@
                  * But if the file also defines the components, verify that those components are consistent
                  * with what we would expect for a CRS of the given EPSG code.
                  */
-                final GeodeticCRS crs = getCRSAuthorityFactory().createGeodeticCRS(String.valueOf(epsg));
+                final GeodeticCRS crs = getCRSAuthorityFactory().createGeocentricCRS(String.valueOf(epsg));
                 verify(crs);
                 return crs;
             }
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/AttributeNames.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/AttributeNames.java
index 31d6985..79f82b6 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/AttributeNames.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/AttributeNames.java
@@ -350,7 +350,7 @@
      * This is a character array with a line for each invocation of a program that has modified the dataset.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getResourceLineages() resourceLineage} /
+     * {@link org.apache.sis.metadata.iso.DefaultMetadata#getResourceLineages() resourceLineage} /
      * {@link Lineage#getStatement() statement}</li></ul>
      *
      * <h4>Departure from convention</h4>
@@ -367,7 +367,7 @@
      * The {@value} attribute name for the method of production of the original data (<em>Recommended</em>).
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getResourceLineages() resourceLineage} /
+     * {@link org.apache.sis.metadata.iso.DefaultMetadata#getResourceLineages() resourceLineage} /
      * {@link Lineage#getSources() source} /
      * {@link Source#getDescription() description}</li></ul>
      *
@@ -401,7 +401,7 @@
      * subgroup.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getDateInfo() dateInfo}
+     * {@code dateInfo}
      * {@link CitationDate#getDate() date} with {@link DateType#CREATION}</li></ul>
      */
     public static final String METADATA_CREATION = "metadata_creation";
@@ -411,7 +411,7 @@
      * (<em>Suggested</em>).
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getDateInfo() dateInfo}
+     * {@code dateInfo}
      * {@link CitationDate#getDate() date} with {@link DateType#REVISION}</li></ul>
      *
      * @since 0.8
@@ -1158,7 +1158,7 @@
      * For example, it may be the URL to an ISO 19115 metadata in XML format.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getMetadataLinkages() metadataLinkage} /
+     * {@code metadataLinkage} /
      * {@link OnlineResource#getLinkage() linkage}</li></ul>
      *
      * @see <a href="http://wiki.esipfed.org/index.php/Attribute_Convention_for_Data_Discovery#metadata_link">ESIP reference</a>
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java
index 977a5fb..868fff0 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -76,8 +76,8 @@
 import org.apache.sis.math.Vector;
 import static org.apache.sis.storage.netcdf.AttributeNames.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.NameFactory;
+// Specific to the main branch:
+import org.apache.sis.util.iso.DefaultNameFactory;
 
 
 /**
@@ -464,16 +464,15 @@
          * If we cannot share the whole existing instance, we usually can share parts of it like the address.
          */
         ResponsibleParty responsibility = pointOfContact;
-        Contact        contact        = null;
-        Address        address        = null;
-        OnlineResource resource       = null;
+        Contact          contact        = null;
+        Address          address        = null;
+        OnlineResource   resource       = null;
         if (responsibility != null) {
-            final Party party = CollectionsExt.first(responsibility.getParties());
-            if (party != null) {
-                contact = CollectionsExt.first(party.getContactInfo());
+            { // Additional indentation for having the same level than SIS branches for GeoAPI snapshots (makes merges easier).
+                contact = responsibility.getContactInfo();
                 if (contact != null) {
-                    address  = CollectionsExt.first(contact.getAddresses());
-                    resource = CollectionsExt.first(contact.getOnlineResources());
+                    address  = contact.getAddress();
+                    resource = contact.getOnlineResource();
                 }
                 if (!canShare(resource, url)) {
                     resource       = null;
@@ -486,14 +485,9 @@
                     responsibility = null;
                 }
                 if (responsibility != null) {
-                    if (party instanceof Organisation) {
-                        // Individual (if any) is considered an organisation member. See comment in next block.
-                        if (!canShare(party.getName(), organisationName) ||
-                            !canShare(CollectionsExt.first(((Organisation) party).getIndividual()).getName(), individualName))
-                        {
-                            responsibility = null;
-                        }
-                    } else if (!canShare(party.getName(), individualName)) {
+                    if (!canShare(responsibility.getOrganisationName(), organisationName) ||
+                        !canShare(responsibility.getIndividualName(),   individualName))
+                    {
                         responsibility = null;
                     }
                 }
@@ -513,7 +507,7 @@
             if (individualName != null || organisationName != null || contact != null) {        // Do not test role.
                 AbstractParty party = null;
                 if (individualName   != null) party = new DefaultIndividual(individualName, null, null);
-                if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (Individual) party, null);
+                if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (DefaultIndividual) party, null);
                 if (party            == null) party = isOrganisation(keys) ? new DefaultOrganisation() : new DefaultIndividual();
                 if (contact          != null) party.setContactInfo(Set.of(contact));
                 responsibility = new DefaultResponsibleParty(role);
@@ -596,9 +590,9 @@
                 addCitedResponsibleParty(contributor, null);
             }
             final ResponsibleParty r = createResponsibleParty(PUBLISHER, false);
-            if (r != null) {
+            if (r instanceof DefaultResponsibility) {
                 addDistributor(r);
-                for (final Party party : r.getParties()) {
+                for (final AbstractParty party : ((DefaultResponsibility) r).getParties()) {
                     publisher = addIfNonNull(publisher, party.getName());
                 }
             }
@@ -648,8 +642,8 @@
         addUseLimitation          (stringValue(LICENSE));
         addKeywords(standard,  KeywordType.THEME,       stringValue(STANDARD_NAME.VOCABULARY));
         addKeywords(keywords,  KeywordType.THEME,       stringValue(KEYWORDS.VOCABULARY));
-        addKeywords(project,   KeywordType.PROJECT,     null);
-        addKeywords(publisher, KeywordType.DATA_CENTRE, null);
+        addKeywords(project,   KeywordType.valueOf("PROJECT"), null);
+        addKeywords(publisher, KeywordType.valueOf("DATA_CENTRE"), null);
         /*
          * Add geospatial bounds as a geometric object. This optional operation requires
          * an external library (ESRI or JTS) to be present on the module path.
@@ -945,7 +939,7 @@
         newSampleDimension();
         final String name = Strings.trimOrNull(variable.getName());
         if (name != null) {
-            final NameFactory f = decoder.nameFactory;
+            final DefaultNameFactory f = decoder.nameFactory;
             final StringBuilder buffer = new StringBuilder(20);
             variable.writeDataTypeName(buffer);
             setBandIdentifier(f.createMemberName(null, name, f.createTypeName(null, buffer.toString())));
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java
index 8bff296..9ca1fbd 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java
@@ -580,7 +580,7 @@
          */
         final String alt = coordinates.getAttributeAsString(CDM.LONG_NAME);
         if (alt != null && !similar(alt, name)) {
-            properties.put(org.opengis.metadata.Identifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
+            properties.put(org.apache.sis.referencing.ImmutableIdentifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
             if (!similar(alt, standardName)) {
                 aliases.add(new NamedIdentifier(null, alt));                        // Additional alias.
             }
@@ -630,7 +630,7 @@
             } else switch (order) {
                 case 0:  dir = AxisDirection.COLUMN_POSITIVE; break;
                 case 1:  dir = AxisDirection.ROW_POSITIVE;    break;
-                default: dir = AxisDirection.UNSPECIFIED;     break;
+                default: dir = AxisDirections.UNSPECIFIED;    break;
             }
         }
         final String abbr;
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
index e8cdfc6..1e280ae 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
@@ -59,8 +59,9 @@
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.operation.CoordinateOperationFactory;
+// Specific to the main branch:
+import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 
 
 /**
@@ -140,12 +141,6 @@
     protected D datum;
 
     /**
-     * The datum ensemble created by {@link #createDatum(DatumFactory, Map)}.
-     * At least one of {@link #datum} and {@code datumEnsemble} shall be initialized.
-     */
-    protected DatumEnsemble<D> datumEnsemble;
-
-    /**
      * The coordinate system created by {@link #createCS(CSFactory, Map, CoordinateSystemAxis[])}.
      */
     protected CS coordinateSystem;
@@ -691,8 +686,10 @@
         @Override void createCS(CSFactory factory, Map<String,?> properties, CoordinateSystemAxis[] axes) throws FactoryException {
             if (axes.length > 2) {
                 coordinateSystem = factory.createSphericalCS(properties, axes[0], axes[1], axes[2]);
+            } else if (factory instanceof GeodeticObjectFactory) {
+                coordinateSystem = ((GeodeticObjectFactory) factory).createSphericalCS(properties, axes[0], axes[1]);
             } else {
-                coordinateSystem = factory.createSphericalCS(properties, axes[0], axes[1]);
+                throw new FactoryException("Unsupported factory implementation.");
             }
         }
 
@@ -701,7 +698,7 @@
          * This method is invoked under conditions similar to the ones of above {@code createCS(…)} method.
          */
         @Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException {
-            referenceSystem = factory.createGeodeticCRS(properties, datum, datumEnsemble, coordinateSystem);
+            referenceSystem = factory.createGeocentricCRS(properties, datum, coordinateSystem);
         }
     }
 
@@ -795,7 +792,7 @@
          */
         private static final Conversion UNKNOWN_PROJECTION;
         static {
-            final CoordinateOperationFactory factory = DefaultCoordinateOperationFactory.provider();
+            final DefaultCoordinateOperationFactory factory = DefaultCoordinateOperationFactory.provider();
             try {
                 final OperationMethod method = factory.getOperationMethod(Equirectangular.NAME);
                 UNKNOWN_PROJECTION = factory.createDefiningConversion(
@@ -897,9 +894,8 @@
         /**
          * Creates a {@link VerticalDatum} for <q>Unknown datum based on Mean Sea Level</q>.
          */
-        @SuppressWarnings("deprecation")
         @Override void createDatum(DatumFactory factory, Map<String,?> properties) throws FactoryException {
-            datum = factory.createVerticalDatum(properties, RealizationMethod.GEOID);
+            datum = factory.createVerticalDatum(properties, VerticalDatumType.GEOIDAL);
         }
 
         /**
@@ -973,7 +969,7 @@
                     datum = c.datum();
                 } else {
                     properties = properties("Time since " + epoch);
-                    datum = factory.createTemporalDatum(properties, epoch);
+                    datum = factory.createTemporalDatum(properties, TemporalDate.toDate(epoch));
                 }
             }
         }
@@ -1042,7 +1038,7 @@
             try {
                 switch (axes.length) {
                     case 0:  break;     // Should never happen but we are paranoiac.
-                    case 1:  coordinateSystem = factory.createParametricCS(properties, axes[0]); return;
+                    case 1:  coordinateSystem = new org.apache.sis.referencing.cs.DefaultParametricCS(properties, axes[0]); return;
                     case 2:  coordinateSystem = factory.createAffineCS(properties, axes[0], axes[1]); return;
                     default: coordinateSystem = factory.createAffineCS(properties, axes[0], axes[1], axes[2]); return;
                 }
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java
index e264766..a5cfe8b 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java
@@ -51,9 +51,6 @@
 import org.apache.sis.util.iso.DefaultNameFactory;
 import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.NameFactory;
-
 
 /**
  * The API used internally by Apache SIS for fetching variables and attribute values from a netCDF file.
@@ -110,7 +107,7 @@
     /**
      * The factory to use for creating variable identifiers.
      */
-    public final NameFactory nameFactory;
+    public final DefaultNameFactory nameFactory;
 
     /**
      * The library for geometric objects, or {@code null} for the default.
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/FeatureSet.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/FeatureSet.java
index 66fe896..12ddf77 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/FeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/FeatureSet.java
@@ -51,10 +51,10 @@
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.math.Vector;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Attribute;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractAttribute;
 
 
 /**
@@ -87,7 +87,7 @@
 
     /**
      * The number of instances for each feature, or {@code null} if none. If non-null, then the number of features
-     * is the length of this vector and each {@link Feature} instance has multi-valued properties with a number of
+     * is the length of this vector and each {@code Feature} instance has multi-valued properties with a number of
      * elements given by this count. If null, the number of features is determined by the length of other variables.
      *
      * @see #getFeatureCount()
@@ -168,7 +168,7 @@
     /**
      * The type of all features to be read by this {@code FeatureSet}.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * Creates a new discrete sampling parser for features identified by the given variable.
@@ -578,7 +578,7 @@
      * Returns the type of all features to be read by this {@code FeatureSet}.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -637,7 +637,7 @@
      * @param  parallel  ignored, since current version does not support parallelism.
      */
     @Override
-    public Stream<Feature> features(boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(boolean parallel) throws DataStoreException {
         try {
             return StreamSupport.stream(new Iter(), false);
         } catch (IOException e) {
@@ -648,7 +648,7 @@
     /**
      * Implementation of the iterator returned by {@link #features(boolean)}.
      */
-    private final class Iter implements Spliterator<Feature> {
+    private final class Iter implements Spliterator<AbstractFeature> {
         /**
          * Expected number of feature instances.
          */
@@ -741,8 +741,8 @@
          * @throws BackingStoreException if an {@link IOException} or {@link DataStoreException} occurred.
          */
         @Override
-        public boolean tryAdvance(final Consumer<? super Feature> action) {
-            final Feature feature = type.newInstance();
+        public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
+            final AbstractFeature feature = type.newInstance();
             final Vector[] coordinateValues;
             int offset, length;
             try {
@@ -866,7 +866,7 @@
              * The time vector is the first vector after the geometry dimensions.
              */
             if (hasTime) {
-                MovingFeatures.setTimes((Attribute<?>) feature.getProperty(TRAJECTORY),
+                MovingFeatures.setTimes((AbstractAttribute<?>) feature.getProperty(TRAJECTORY),
                                         coordinateValues[geometryDimension], timeCRS);
             }
             action.accept(feature);
@@ -925,7 +925,7 @@
          * Current implementation cannot split this iterator.
          */
         @Override
-        public Spliterator<Feature> trySplit() {
+        public Spliterator<AbstractFeature> trySplit() {
             return null;
         }
 
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
index 1a9d5ca..6e99cec 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
@@ -77,8 +77,8 @@
 import org.apache.sis.io.wkt.Warnings;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.operation.CoordinateOperationFactory;
+// Specific to the main branch:
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
 
 
 /**
@@ -217,7 +217,7 @@
              * than OGC names, but Apache SIS implementations of map projections know how to handle them, including
              * the redundant parameters like "inverse_flattening" and "earth_radius".
              */
-            final CoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
+            final DefaultCoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
             final OperationMethod method = opFactory.getOperationMethod((String) definition.remove(CF.GRID_MAPPING_NAME));
             final ParameterValueGroup parameters = method.getParameters().createValue();
             for (final Iterator<Map.Entry<String,Object>> it = definition.entrySet().iterator(); it.hasNext();) {
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/FeaturesWrapper.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/FeaturesWrapper.java
index dddf78a..091843b 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/FeaturesWrapper.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/FeaturesWrapper.java
@@ -23,9 +23,9 @@
 import org.apache.sis.storage.netcdf.base.DiscreteSampling;
 import org.apache.sis.storage.event.StoreListeners;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -59,7 +59,7 @@
     }
 
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         throw new UnsupportedOperationException();      // TODO
     }
 
@@ -67,7 +67,7 @@
      * Returns the stream of features.
      */
     @Override
-    public Stream<Feature> features(boolean parallel) {
+    public Stream<AbstractFeature> features(boolean parallel) {
         throw new UnsupportedOperationException();      // TODO
     }
 }
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java
index 001d70f..e1e6b7d 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java
@@ -38,9 +38,9 @@
 import org.apache.sis.storage.netcdf.classic.ChannelDecoderTest;
 import static org.apache.sis.test.TestUtilities.date;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.ContentVerifier;
-import org.opengis.test.dataset.TestData;
+// Specific to the main branch:
+import org.apache.sis.storage.netcdf.base.TestData;
+import org.apache.sis.test.ContentVerifier;
 
 
 /**
@@ -77,7 +77,7 @@
         final Decoder input = ChannelDecoderTest.createChannelDecoder(TestData.NETCDF_2D_GEOGRAPHIC);
         final Metadata metadata = new MetadataReader(input).read();
         input.close(new DataStoreMock("lock"));
-        compareToExpected(metadata).assertMetadataEquals();
+        compareToExpected(metadata);
     }
 
     /**
@@ -92,22 +92,21 @@
         final Decoder input = createDecoder(TestData.NETCDF_2D_GEOGRAPHIC);
         final Metadata metadata = new MetadataReader(input).read();
         input.close(new DataStoreMock("lock"));
-        final ContentVerifier verifier = compareToExpected(metadata);
-        verifier.addExpectedValue("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[1]", "NetCDF-3/CDM");
-        verifier.assertMetadataEquals();
+        compareToExpected(metadata);
     }
 
     /**
-     * Creates comparator for the string representation of the given metadata object with the expected one.
+     * Compares the string representation of the given metadata object with the expected one.
      * The given metadata shall have been created from the {@link TestData#NETCDF_2D_GEOGRAPHIC} dataset.
      */
-    static ContentVerifier compareToExpected(final Metadata actual) {
+    static void compareToExpected(final Metadata actual) {
         final ContentVerifier verifier = new ContentVerifier();
         verifier.addPropertyToIgnore(Metadata.class, "metadataStandard");
         verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo");
+        verifier.addPropertyToIgnore(Metadata.class, "alternateTitle");
         verifier.addPropertyToIgnore(TemporalExtent.class, "extent");
         verifier.addMetadataToVerify(actual);
-        verifier.addExpectedValues(
+        verifier.assertMetadataEquals(
             // Hard-coded
             "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "NetCDF",
             "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title", "NetCDF Classic and 64-bit Offset Format",
@@ -162,7 +161,5 @@
             "contentInfo[0].attributeGroup[0].attribute[0].units",                     "°C",
 
             "resourceLineage[0].statement", "Decimated and modified by GeoAPI for inclusion in conformance test suite.");
-
-        return verifier;
     }
 }
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
index 5bf3b2f..8152b8e 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
@@ -32,8 +32,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.storage.netcdf.base.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
+// Specific to the main branch:
+import org.apache.sis.storage.netcdf.base.TestData;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
index 540ec9a..3aa4af4 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
@@ -27,8 +27,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCaseWithLogs;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
+// Specific to the main branch:
+import org.apache.sis.storage.netcdf.base.TestData;
 
 
 /**
@@ -66,7 +66,7 @@
             metadata = store.getMetadata();
             assertSame(metadata, store.getMetadata(), "Should be cached.");
         }
-        MetadataReaderTest.compareToExpected(metadata).assertMetadataEquals();
+        MetadataReaderTest.compareToExpected(metadata);
         loggings.skipNextLogIfContains("EPSG:4019");        // Deprecated EPSG code.
         loggings.assertNoUnexpectedLog();
     }
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/DecoderTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/DecoderTest.java
index 607ef17..cc73129 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/DecoderTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/DecoderTest.java
@@ -25,9 +25,6 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
-
 
 /**
  * Tests the {@link Decoder} implementation. The default implementation tests
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/FeatureSetTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/FeatureSetTest.java
deleted file mode 100644
index f76d3ba..0000000
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/FeatureSetTest.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.storage.netcdf.base;
-
-import java.awt.Shape;
-import java.awt.geom.PathIterator;
-import java.util.Iterator;
-import java.util.Collection;
-import java.util.Optional;
-import java.io.IOException;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.metadata.Metadata;
-import org.opengis.parameter.ParameterValueGroup;
-import org.apache.sis.feature.privy.AttributeConvention;
-import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.DataStoreException;
-
-// Test dependencies
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import org.opengis.test.dataset.TestData;
-
-
-/**
- * Tests the {@link FeatureSet} implementation. The default implementation uses the UCAR library,
- * which is is our reference implementation. Subclass overrides {@link #createDecoder(TestData)}
- * method in order to test a different implementation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public class FeatureSetTest extends TestCase {
-    /**
-     * Type of the features read from the netCDF file.
-     */
-    private FeatureType type;
-
-    /**
-     * Index of the feature to verify.
-     */
-    private int featureIndex;
-
-    /**
-     * Instant from which time are measured.
-     */
-    private final Instant timeOrigin;
-
-    /**
-     * Creates a new test case.
-     */
-    public FeatureSetTest() {
-        timeOrigin = Instant.parse("2014-11-29T00:00:00Z");
-    }
-
-    /**
-     * Forgets the data used by the current test. This method makes {@code this} instance
-     * ready for another test method reusing the decoders that are already opened.
-     */
-    @Override
-    @AfterEach
-    public void reset() {
-        super.reset();
-        type = null;
-        featureIndex = 0;
-    }
-
-    /**
-     * Returns a dummy data store implementation for the sole purpose of providing a non-null lock.
-     */
-    private static DataStore lock() {
-        return new DataStore() {
-            @Override public Optional<ParameterValueGroup> getOpenParameters() {return Optional.empty();}
-            @Override public Metadata getMetadata() {return null;}
-            @Override public void close() {}
-        };
-    }
-
-    /**
-     * Tests {@link FeatureSet} with a moving features file.
-     *
-     * @throws IOException if an I/O error occurred while opening the file.
-     * @throws DataStoreException if a logical error occurred.
-     */
-    @Test
-    public void testMovingFeatures() throws IOException, DataStoreException {
-        final DataStore lock = lock();
-        final FeatureSet[] features;
-        synchronized (lock) {
-            features = FeatureSet.create(selectDataset(TestData.MOVING_FEATURES), lock);
-        }
-        assertEquals(1, features.length);
-        type = features[0].getType();
-        verifyType(type.getProperties(false).iterator());
-        features[0].features(false).forEach(this::verifyInstance);
-    }
-
-    /**
-     * Verifies that given properties are the expected ones
-     * for {@link TestData#MOVING_FEATURES} feature type.
-     */
-    private static void verifyType(final Iterator<? extends PropertyType> it) {
-        assertEquals("sis:identifier", it.next().getName().toString());
-        assertEquals("sis:envelope",   it.next().getName().toString());
-        assertEquals("sis:geometry",   it.next().getName().toString());
-
-        AttributeType<?> at = (AttributeType<?>) it.next();
-        assertEquals("features", at.getName().toString());
-
-        at = (AttributeType<?>) it.next();
-        assertEquals("trajectory", at.getName().toString());
-        assertEquals(Shape.class, at.getValueClass());
-
-        at = (AttributeType<?>) it.next();
-        assertEquals("stations", at.getName().toString());
-        assertEquals(String.class, at.getValueClass());
-
-        assertFalse(it.hasNext());
-    }
-
-    /**
-     * Verifies the given feature instance.
-     */
-    private void verifyInstance(final Feature instance) {
-        assertSame(type, instance.getType());
-        final float[] longitudes, latitudes;
-        final short[] times;                    // In minutes since 2014-11-29 00:00:00.
-        final String[] stations;
-        final String identifier;
-        switch (featureIndex++) {
-            case 0: {
-                identifier = "a4078a16";
-                longitudes = new float[] {139.622715f, 139.696899f, 139.740440f, 139.759640f, 139.763328f, 139.766084f};
-                latitudes  = new float[] { 35.466188f,  35.531328f,  35.630152f,  35.665498f,  35.675069f,  35.681382f};
-                times      = new short[] {       1068,        1077,        1087,        1094,        1096,        1098};
-                stations   = new String[] {
-                    "Yokohama", "Kawasaki", "Shinagawa", "Shinbashi", "Yurakucho", "Tokyo"
-                };
-                break;
-            }
-            case 1: {
-                identifier = "1e146c16";
-                longitudes = new float[] {139.700258f, 139.730667f, 139.763786f, 139.774219f};
-                latitudes  = new float[] { 35.690921f,  35.686014f,  35.699855f,  35.698683f};
-                times      = new short[] {       1075,        1079,        1087,        1090};
-                stations   = new String[] {
-                    "Shinjuku", "Yotsuya", "Ochanomizu", "Akihabara"
-                };
-                break;
-            }
-            case 2: {
-                identifier = "f50ff004";
-                longitudes = new float[] {139.649867f, 139.665652f, 139.700258f};
-                latitudes  = new float[] { 35.705385f,  35.706032f,  35.690921f};
-                times      = new short[] {       3480,        3482,        3486};
-                stations   = new String[] {
-                    "Koenji", "Nakano", "Shinjuku"
-                };
-                break;
-            }
-            default: {
-                fail("Unexpected feature instance.");
-                return;
-            }
-        }
-        // Convert the time vector to an array of instants.
-        final Instant[] instants = new Instant[times.length];
-        for (int i=0; i<times.length; i++) {
-            instants[i] = timeOrigin.plus(times[i], ChronoUnit.MINUTES);
-        }
-        /*
-         * Verify property values and characteristics.
-         */
-        assertEquals(identifier, instance.getPropertyValue("features"));
-        final Attribute<?> trajectory = (Attribute<?>) instance.getProperty("trajectory");
-        asserLineStringEquals((Shape) trajectory.getValue(), longitudes, latitudes);
-        assertArrayEquals(stations, ((Collection<?>) instance.getPropertyValue("stations")).toArray());
-        assertArrayEquals(instants, trajectory.characteristics().get("datetimes").getValues().toArray());
-        assertInstanceOf(GeographicCRS.class, AttributeConvention.getCRSCharacteristic(trajectory));
-    }
-
-    /**
-     * Asserts the given shape is a line string with the following coordinates.
-     *
-     * @param  trajectory  the shape to verify.
-     * @param  x           expected X coordinates.
-     * @param  y           expected Y coordinates.
-     */
-    private static void asserLineStringEquals(final Shape trajectory, final float[] x, final float[] y) {
-        assertEquals(x.length, y.length);
-        final PathIterator it = trajectory.getPathIterator(null);
-        final float[] point = new float[2];
-        for (int i=0; i < x.length; i++) {
-            assertFalse(it.isDone());
-            assertEquals(i == 0 ? PathIterator.SEG_MOVETO : PathIterator.SEG_LINETO, it.currentSegment(point));
-            assertEquals(x[i], point[0], "x");
-            assertEquals(y[i], point[1], "y");
-            it.next();
-        }
-        assertTrue(it.isDone());
-    }
-}
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/GridTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/GridTest.java
index 6d7a28d..a0d1db5 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/GridTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/GridTest.java
@@ -26,9 +26,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
-
 
 /**
  * Tests the {@link Grid} implementation. The default implementation tests
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
index 8a25100..d5ae778 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
@@ -42,9 +42,6 @@
 import org.junit.jupiter.api.parallel.ExecutionMode;
 import org.apache.sis.storage.DataStoreMock;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
-
 
 /**
  * Base class of netCDF tests. The base class uses the UCAR decoder, which is taken as a reference implementation.
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestData.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestData.java
new file mode 100644
index 0000000..f2cabd7
--- /dev/null
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestData.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.netcdf.base;
+
+import java.net.URL;
+import java.io.InputStream;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assumptions.abort;
+
+
+/**
+ * Place-holder for a GeoAPI 3.1 class. Used only for allowing the code to compile.
+ * For real test execution, see the development branches on GeoAPI 4.0-SNAPSHOT.
+ */
+@SuppressWarnings("doclint:missing")
+public enum TestData {
+    NETCDF_2D_GEOGRAPHIC,
+
+    NETCDF_4D_PROJECTED;
+
+    public URL location() {
+        abort("This test requires GeoAPI 3.1.");
+        return null;
+    }
+
+    public InputStream open() {
+        abort("This test requires GeoAPI 3.1.");
+        return null;
+    }
+
+    public byte[] content() {
+        abort("This test requires GeoAPI 3.1.");
+        return null;
+    }
+}
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/VariableTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/VariableTest.java
index f7250df..aedc9a8 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/VariableTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/VariableTest.java
@@ -30,9 +30,6 @@
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
-
 
 /**
  * Tests the {@link Variable} implementation. The default implementation tests
@@ -296,18 +293,4 @@
             assertEquals(-180 + 5*i, data.floatValue(i), "Longitude value");
         }
     }
-
-    /**
-     * Tests {@link Variable#readAnyType()} on strings.
-     *
-     * @throws IOException if an error occurred while reading the netCDF file.
-     * @throws DataStoreException if a logical error occurred.
-     */
-    @Test
-    public void testReadStrings() throws IOException, DataStoreException {
-        final Variable variable = selectDataset(TestData.MOVING_FEATURES).findVariable("features");
-        assertEquals("features", variable.getName());
-        final List<?> identifiers = variable.readAnyType();
-        assertArrayEquals(new String[] {"a4078a16", "1e146c16", "f50ff004", "", ""}, identifiers.toArray());
-    }
 }
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/ChannelDecoderTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/ChannelDecoderTest.java
index 0dca724..779cd13 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/ChannelDecoderTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/ChannelDecoderTest.java
@@ -28,8 +28,8 @@
 // Test dependencies
 import org.apache.sis.storage.netcdf.base.DecoderTest;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
+// Specific to the main branch:
+import org.apache.sis.storage.netcdf.base.TestData;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/FeatureSetTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/FeatureSetTest.java
deleted file mode 100644
index 19e5e16..0000000
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/FeatureSetTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.storage.netcdf.classic;
-
-import java.io.IOException;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.netcdf.base.Decoder;
-
-// Test dependencies
-import org.opengis.test.dataset.TestData;
-
-
-/**
- * Tests the {@link org.apache.sis.storage.netcdf.base.FeatureSet} implementation
- * using the Apache SIS implementation of netCDF reader.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class FeatureSetTest extends org.apache.sis.storage.netcdf.base.FeatureSetTest {
-    /**
-     * Creates a new test case.
-     */
-    public FeatureSetTest() {
-    }
-
-    /**
-     * Creates a new decoder for the specified dataset.
-     *
-     * @param  file  the dataset as one of the above-cited constants.
-     * @return the decoder for the specified dataset.
-     * @throws IOException if an I/O error occurred while opening the file.
-     * @throws DataStoreException if a logical error occurred.
-     */
-    @Override
-    protected Decoder createDecoder(final TestData file) throws IOException, DataStoreException {
-        return ChannelDecoderTest.createChannelDecoder(file);
-    }
-}
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/GridInfoTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/GridInfoTest.java
index 2878392..2ca041d 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/GridInfoTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/GridInfoTest.java
@@ -25,8 +25,8 @@
 // Test dependencies
 import org.apache.sis.storage.netcdf.base.GridTest;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
+// Specific to the main branch:
+import org.apache.sis.storage.netcdf.base.TestData;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/VariableInfoTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/VariableInfoTest.java
index bed3b34..7f11bc0 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/VariableInfoTest.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/classic/VariableInfoTest.java
@@ -23,8 +23,8 @@
 // Test dependencies
 import org.apache.sis.storage.netcdf.base.VariableTest;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.test.dataset.TestData;
+// Specific to the main branch:
+import org.apache.sis.storage.netcdf.base.TestData;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
index f96ad09..fd0419b 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
@@ -82,7 +82,7 @@
     private final GeometryLibrary geomLibrary;
 
     /**
-     * The result of inspecting database schema for deriving {@link org.opengis.feature.FeatureType}s.
+     * The result of inspecting database schema for deriving {@code FeatureType}s.
      * Created when first needed. May be discarded and recreated if the store needs a refresh.
      */
     private Database<?> model;
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAdapter.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAdapter.java
index e077654..ccfedb9 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAdapter.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAdapter.java
@@ -31,13 +31,13 @@
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.WeakValueHashMap;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
- * Converter of {@link ResultSet} rows to {@link Feature} instances.
+ * Converter of {@link ResultSet} rows to {@code Feature} instances.
  * Each {@code FeatureAdapter} instance is specific to the set of rows given by a SQL query,
  * ignoring {@code DISTINCT}, {@code ORDER BY} and filter conditions in the {@code WHERE} clause.
  * This class does not hold JDBC resources; {@link ResultSet} must be provided by the caller.
@@ -61,7 +61,7 @@
      *
      * @see Table#featureType
      */
-    private final FeatureType featureType;
+    private final DefaultFeatureType featureType;
 
     /**
      * Attributes in feature instances, excluding operations and associations to other tables.
@@ -320,8 +320,8 @@
      * @return the feature with attribute values initialized.
      * @throws Exception if an error occurred while reading the database or converting values.
      */
-    final Feature createFeature(final InfoStatements stmts, final ResultSet result) throws Exception {
-        final Feature feature = featureType.newInstance();
+    final AbstractFeature createFeature(final InfoStatements stmts, final ResultSet result) throws Exception {
+        final AbstractFeature feature = featureType.newInstance();
         for (int i=0; i<attributes.length; i++) {
             final Column column = attributes[i];
             final Object value = column.valueGetter.getValue(stmts, result, i+1);
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAnalyzer.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAnalyzer.java
index 189e137..7f53a4d 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAnalyzer.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAnalyzer.java
@@ -36,8 +36,8 @@
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.privy.CollectionsExt;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -400,7 +400,7 @@
      *
      * @return the feature type.
      */
-    final FeatureType buildFeatureType() throws DataStoreException, SQLException {
+    final DefaultFeatureType buildFeatureType() throws DataStoreException, SQLException {
         String remarks = id.freeText;
         if (remarks != null) {
             feature.setDefinition(remarks);
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
index eb60040..d8d2e69 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
@@ -30,16 +30,16 @@
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.collection.WeakValueHashMap;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.SortBy;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.SortOrder;
+import org.apache.sis.pending.geoapi.filter.SortProperty;
+import org.apache.sis.pending.geoapi.filter.SortBy;
 
 
 /**
  * Iterator over feature instances.
- * This iterator converters {@link ResultSet} rows to {@link Feature} instances.
+ * This iterator converters {@link ResultSet} rows to {@code Feature} instances.
  * Each {@code FeatureIterator} iterator is created for one specific SQL query
  * and can be used for only one iteration.
  *
@@ -50,7 +50,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
  */
-final class FeatureIterator implements Spliterator<Feature>, AutoCloseable {
+final class FeatureIterator implements Spliterator<AbstractFeature>, AutoCloseable {
     /**
      * Characteristics of the iterator. The value returned by {@link #characteristics()}
      * must be consistent with the value given to {@code DeferredStream} constructor.
@@ -60,7 +60,7 @@
     static final int CHARACTERISTICS = NONNULL;
 
     /**
-     * The converter from a {@link ResultSet} row to a {@link Feature} instance.
+     * The converter from a {@link ResultSet} row to a {@code Feature} instance.
      */
     private final FeatureAdapter adapter;
 
@@ -113,7 +113,7 @@
      * @param count       maximum number of rows to return, or ≤ 0 for no limit.
      */
     FeatureIterator(final Table table, final Connection connection,
-             final boolean distinct, final String filter, final SortBy<? super Feature> sort,
+             final boolean distinct, final String filter, final SortBy<? super AbstractFeature> sort,
              final long offset, final long count)
             throws SQLException, InternalDataStoreException
     {
@@ -130,7 +130,7 @@
             }
             if (sort != null) {
                 String separator = " ORDER BY ";
-                for (final SortProperty<? super Feature> s : sort.getSortProperties()) {
+                for (final SortProperty<? super AbstractFeature> s : sort.getSortProperties()) {
                     builder.append(separator).appendIdentifier(s.getValueReference().getXPath());
                     final SortOrder order = s.getSortOrder();
                     if (order != null) {
@@ -155,7 +155,7 @@
      * Creates a new iterator over the dependencies of a feature.
      *
      * @param table       the source table, or {@code null} if we are creating an iterator for a dependency.
-     * @param adapter     converter from a {@link ResultSet} row to a {@link Feature} instance.
+     * @param adapter     converter from a {@link ResultSet} row to a {@code Feature} instance.
      * @param connection  connection to the database, used for creating statement.
      * @param filter      condition to append, not including the {@code WHERE} keyword.
      * @param distinct    whether the set should contain distinct feature instances.
@@ -206,7 +206,7 @@
      * @return always {@code null}.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         return null;
     }
 
@@ -214,7 +214,7 @@
      * Gives the next feature to the given consumer.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
         try {
             return fetch(action, false);
         } catch (RuntimeException e) {
@@ -228,7 +228,7 @@
      * Gives all remaining features to the given consumer.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
         try {
             fetch(action, true);
         } catch (RuntimeException e) {
@@ -242,13 +242,13 @@
      * Gives at least the next feature to the given consumer.
      * Gives all remaining features if {@code all} is {@code true}.
      *
-     * @param  action  the action to execute for each {@link Feature} instances fetched by this method.
+     * @param  action  the action to execute for each {@code Feature} instances fetched by this method.
      * @param  all     {@code true} for reading all remaining feature instances, or {@code false} for only the next one.
      * @return {@code true} if we have read an instance and {@code all} is {@code false} (so there is maybe other instances).
      */
-    private boolean fetch(final Consumer<? super Feature> action, final boolean all) throws Exception {
+    private boolean fetch(final Consumer<? super AbstractFeature> action, final boolean all) throws Exception {
         while (result.next()) {
-            final Feature feature = adapter.createFeature(spatialInformation, result);
+            final AbstractFeature feature = adapter.createFeature(spatialInformation, result);
             for (int i=0; i < dependencies.length; i++) {
                 WeakValueHashMap<?,Object> instances = null;
                 Object key = null, value = null;
@@ -296,8 +296,8 @@
      * @param  owner  if the features to fetch are components of another feature, that container feature instance.
      * @return the feature as a singleton {@code Feature} or as a {@code Collection<Feature>}.
      */
-    private Object fetchReferenced(final Feature owner) throws Exception {
-        final List<Feature> features = new ArrayList<>();
+    private Object fetchReferenced(final AbstractFeature owner) throws Exception {
+        final List<AbstractFeature> features = new ArrayList<>();
         try (ResultSet r = statement.executeQuery()) {
             result = r;
             fetch(features::add, true);
@@ -305,7 +305,7 @@
             result = null;
         }
         if (owner != null && adapter.deferredAssociation != null) {
-            for (final Feature feature : features) {
+            for (final AbstractFeature feature : features) {
                 feature.setPropertyValue(adapter.deferredAssociation, owner);
             }
         }
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
index 4f9417a..268a3ab 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
@@ -38,10 +38,10 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.collection.BackingStoreException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.SortBy;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.SortBy;
 
 
 /**
@@ -57,7 +57,7 @@
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class FeatureStream extends DeferredStream<Feature> {
+final class FeatureStream extends DeferredStream<AbstractFeature> {
     /**
      * The table which is the source of features.
      */
@@ -98,7 +98,7 @@
     /**
      * The {@code ORDER BY} clauses, or {@code null} if none.
      */
-    private SortBy<? super Feature> sort;
+    private SortBy<? super AbstractFeature> sort;
 
     /**
      * Number of rows to skip in underlying SQL query, or 0 for none.
@@ -130,7 +130,7 @@
      * Marks this stream as inactive and returns an empty stream.
      * This method is invoked when an operation resulted in an empty stream.
      */
-    private Stream<Feature> empty() {
+    private Stream<AbstractFeature> empty() {
         count = 0;
         delegate();                 // Mark this stream as inactive.
         return Stream.empty();
@@ -153,7 +153,7 @@
      * to express the filter using SQL statements.
      */
     @Override
-    public Stream<Feature> filter(final Predicate<? super Feature> predicate) {
+    public Stream<AbstractFeature> filter(final Predicate<? super AbstractFeature> predicate) {
         Objects.requireNonNull(predicate);
         if (predicate == Filter.include()) return this;
         if (predicate == Filter.exclude()) return empty();
@@ -180,8 +180,8 @@
          */
         final Optimization optimization = new Optimization();
         optimization.setFeatureType(table.featureType);
-        Stream<Feature> stream = this;
-        for (final Filter<? super Feature> filter : optimization.applyAndDecompose((Filter<? super Feature>) predicate)) {
+        Stream<AbstractFeature> stream = this;
+        for (final Filter<? super AbstractFeature> filter : optimization.applyAndDecompose((Filter<? super AbstractFeature>) predicate)) {
             if (filter == Filter.include()) continue;
             if (filter == Filter.exclude()) return empty();
             if (!selection.tryAppend(filterToSQL, filter)) {
@@ -198,7 +198,7 @@
      * This operation will be done with a SQL {@code DISTINCT} clause if possible.
      */
     @Override
-    public Stream<Feature> distinct() {
+    public Stream<AbstractFeature> distinct() {
         if (isPagined()) {
             return delegate().distinct();
         } else {
@@ -211,7 +211,7 @@
      * Returns an equivalent stream that is unordered.
      */
     @Override
-    public Stream<Feature> unordered() {
+    public Stream<AbstractFeature> unordered() {
         if (isPagined()) {
             return delegate().unordered();
         } else {
@@ -223,10 +223,10 @@
     /**
      * Returns an equivalent stream that is sorted by feature natural order.
      * This is defined as a matter of principle, but will cause a {@link ClassCastException} to be thrown
-     * when a terminal operation will be executed because {@link Feature} instances are not comparable.
+     * when a terminal operation will be executed because {@code Feature} instances are not comparable.
      */
     @Override
-    public Stream<Feature> sorted() {
+    public Stream<AbstractFeature> sorted() {
         if (isPagined()) {
             return delegate().sorted();
         } else {
@@ -238,11 +238,11 @@
      * Returns a stream with features of this stream sorted using the given comparator.
      */
     @Override
-    public Stream<Feature> sorted(final Comparator<? super Feature> comparator) {
+    public Stream<AbstractFeature> sorted(final Comparator<? super AbstractFeature> comparator) {
         if (isPagined() || hasComparator) {
             return delegate().sorted(comparator);
         }
-        final SortBy<? super Feature> c = SortByComparator.concatenate(sort, comparator);
+        final SortBy<? super AbstractFeature> c = SortByComparator.concatenate(sort, comparator);
         if (c != null) {
             sort = c;
             return this;
@@ -256,7 +256,7 @@
      * This operation will be done with a SQL {@code OFFSET} clause.
      */
     @Override
-    public Stream<Feature> skip(final long n) {
+    public Stream<AbstractFeature> skip(final long n) {
         // Do not require this stream to be active because this method may be invoked by `PaginedStream`.
         ArgumentChecks.ensurePositive("n", n);
         offset = Math.addExact(offset, n);
@@ -274,7 +274,7 @@
      * This operation will be done with a SQL {@code FETCH NEXT} clause.
      */
     @Override
-    public Stream<Feature> limit(final long maxSize) {
+    public Stream<AbstractFeature> limit(final long maxSize) {
         // Do not require this stream to be active because this method may be invoked by `PaginedStream`.
         ArgumentChecks.ensurePositive("maxSize", maxSize);
         if (maxSize == 0) {
@@ -290,7 +290,7 @@
      * be optimized.
      */
     @Override
-    public <R> Stream<R> map(final Function<? super Feature, ? extends R> mapper) {
+    public <R> Stream<R> map(final Function<? super AbstractFeature, ? extends R> mapper) {
         return new PaginedStream<>(super.map(mapper), this);
     }
 
@@ -389,7 +389,7 @@
      * @throws SQLException if an error occurs while executing the SQL statement.
      */
     @Override
-    protected Spliterator<Feature> createSourceIterator() throws Exception {
+    protected Spliterator<AbstractFeature> createSourceIterator() throws Exception {
         final String filter = (selection != null && !selection.isEmpty()) ? selection.toString() : null;
         selection = null;             // Let the garbage collector do its work.
 
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index 0010191..50d68de 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@ -51,8 +51,8 @@
 import org.apache.sis.io.wkt.Warnings;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -477,7 +477,7 @@
              * If we cannot find an identifier that we can map to a SRID, then this loop may be
              * executed more times with CRS from EPSG database that are equal, ignore axis order.
              */
-            for (final Identifier id : candidate.getIdentifiers()) {
+            for (final ReferenceIdentifier id : candidate.getIdentifiers()) {
                 final String authority = id.getCodeSpace();
                 if (authority == null) continue;
                 final String code = id.getCode();
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SchemaModifier.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SchemaModifier.java
index 46a1b40..1e15adc 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SchemaModifier.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SchemaModifier.java
@@ -21,8 +21,8 @@
 import org.apache.sis.io.stream.InternalOptionKey;
 import org.apache.sis.setup.OptionKey;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -49,7 +49,7 @@
      * @throws DataStoreException if an error occurred while modifying the feature type.
      * @return the feature type to use for the specified table.
      */
-    default FeatureType editFeatureType(TableReference table, FeatureTypeBuilder feature) throws DataStoreException {
+    default DefaultFeatureType editFeatureType(TableReference table, FeatureTypeBuilder feature) throws DataStoreException {
         return feature.build();
     }
 
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
index 819d204..9fc13b9 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
@@ -25,10 +25,10 @@
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 import org.apache.sis.metadata.sql.privy.SQLBuilder;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -67,7 +67,7 @@
      *
      * @param  ref  reference to a property to insert in SQL statement.
      */
-    final void appendColumnName(final ValueReference<Feature,?> ref) {
+    final void appendColumnName(final ValueReference<AbstractFeature,?> ref) {
         final Column c = table.getColumn(ref.getXPath());
         if (c != null) {
             appendIdentifier(c.name);
@@ -158,7 +158,7 @@
      * @param  filter  the filter to try to convert to SQL statements.
      * @return {@code true} on success, or {@code false} in this {@code SelectionClause} is unchanged.
      */
-    final boolean tryAppend(final SelectionClauseWriter writer, final Filter<? super Feature> filter) {
+    final boolean tryAppend(final SelectionClauseWriter writer, final Filter<? super AbstractFeature> filter) {
         final int pos = buffer.length();
         if (pos != 0) {
             buffer.append(" AND ");
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
index d575e0a..c7d5e98 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
@@ -28,19 +28,18 @@
 import org.apache.sis.filter.privy.FunctionNames;
 import org.apache.sis.filter.privy.Visitor;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.CodeList;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.BetweenComparisonOperator;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
+import org.apache.sis.pending.geoapi.filter.LogicalOperator;
+import org.apache.sis.pending.geoapi.filter.LogicalOperatorName;
+import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
+import org.apache.sis.pending.geoapi.filter.BinaryComparisonOperator;
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator;
 
 
 /**
@@ -61,7 +60,7 @@
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> {
+public class SelectionClauseWriter extends Visitor<AbstractFeature, SelectionClause> {
     /**
      * The default instance.
      */
@@ -80,14 +79,14 @@
         setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, new Comparison(" >= "));
         setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,                new Comparison(" < "));
         setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    new Comparison(" <= "));
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN), (f,sql) -> {
-            final BetweenComparisonOperator<Feature>  filter = (BetweenComparisonOperator<Feature>) f;
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_BETWEEN, (f,sql) -> {
+            final BetweenComparisonOperator<AbstractFeature>  filter = (BetweenComparisonOperator<AbstractFeature>) f;
             /* Nothing to append */  if (write(sql, filter.getExpression()))    return;
             sql.append(" BETWEEN "); if (write(sql, filter.getLowerBoundary())) return;
             sql.append(" AND ");         write(sql, filter.getUpperBoundary());
         });
         setNullAndNilHandlers((filter, sql) -> {
-            final List<Expression<Feature, ?>> expressions = filter.getExpressions();
+            final List<Expression<AbstractFeature, ?>> expressions = filter.getExpressions();
             if (expressions.size() == 1) {
                 write(sql, expressions.get(0));
                 sql.append(" IS NULL");
@@ -113,8 +112,8 @@
         setExpressionHandler(FunctionNames.Subtract, new Arithmetic(" - "));
         setExpressionHandler(FunctionNames.Divide,   new Arithmetic(" / "));
         setExpressionHandler(FunctionNames.Multiply, new Arithmetic(" * "));
-        setExpressionHandler(FunctionNames.Literal, (e,sql) -> sql.appendLiteral(((Literal<Feature,?>) e).getValue()));
-        setExpressionHandler(FunctionNames.ValueReference, (e,sql) -> sql.appendColumnName((ValueReference<Feature,?>) e));
+        setExpressionHandler(FunctionNames.Literal, (e,sql) -> sql.appendLiteral(((Literal<AbstractFeature,?>) e).getValue()));
+        setExpressionHandler(FunctionNames.ValueReference, (e,sql) -> sql.appendColumnName((ValueReference<AbstractFeature,?>) e));
         // Filters created from Filter Encoding XML can specify "PropertyName" instead of "Value reference".
         setExpressionHandler("PropertyName", getExpressionHandler(FunctionNames.ValueReference));
     }
@@ -161,7 +160,7 @@
             final boolean lowerCase = metadata.storesLowerCaseIdentifiers();
             final boolean upperCase = metadata.storesUpperCaseIdentifiers();
             for (final SpatialOperatorName type : SpatialOperatorName.values()) {
-                final BiConsumer<Filter<Feature>, SelectionClause> function = getFilterHandler(type);
+                final BiConsumer<Filter<AbstractFeature>, SelectionClause> function = getFilterHandler(type);
                 if (function instanceof Function) {
                     String name = ((Function) function).name;
                     if (lowerCase) name = name.toLowerCase(Locale.US);
@@ -204,7 +203,7 @@
      * may be truncated (later) to the length that it has the last time that it was valid.
      */
     @Override
-    protected final void typeNotFound(CodeList<?> type, Filter<Feature> filter, SelectionClause sql) {
+    protected final void typeNotFound(Enum<?> type, Filter<AbstractFeature> filter, SelectionClause sql) {
         sql.invalidate();
     }
 
@@ -213,7 +212,7 @@
      * and may be truncated (later) to the length that it has the last time that it was valid.
      */
     @Override
-    protected final void typeNotFound(String type, Expression<Feature,?> expression, SelectionClause sql) {
+    protected final void typeNotFound(String type, Expression<AbstractFeature,?> expression, SelectionClause sql) {
         sql.invalidate();
     }
 
@@ -229,8 +228,8 @@
      * @return value of {@link SelectionClause#isInvalid} flag, for allowing caller to short-circuit.
      */
     @SuppressWarnings("unchecked")
-    final boolean write(final SelectionClause sql, final Filter<? super Feature> filter) {
-        visit((Filter<Feature>) filter, sql);
+    final boolean write(final SelectionClause sql, final Filter<? super AbstractFeature> filter) {
+        visit((Filter<AbstractFeature>) filter, sql);
         return sql.isInvalid;
     }
 
@@ -241,7 +240,7 @@
      * @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.
      */
-    private boolean write(final SelectionClause sql, final Expression<Feature, ?> expression) {
+    private boolean write(final SelectionClause sql, final Expression<AbstractFeature, ?> expression) {
         visit(expression, sql);
         return sql.isInvalid;
     }
@@ -254,7 +253,7 @@
      * @param filter    the filter for which to append the expressions.
      * @param operator  the operator to write between the expressions.
      */
-    protected final void writeBinaryOperator(final SelectionClause sql, final Filter<Feature> filter, final String operator) {
+    protected final void writeBinaryOperator(final SelectionClause sql, final Filter<AbstractFeature> filter, final String operator) {
         writeParameters(sql, filter.getExpressions(), operator, true);
     }
 
@@ -266,7 +265,7 @@
      * @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<Feature,?>> expressions,
+    private void writeParameters(final SelectionClause sql, final List<Expression<AbstractFeature,?>> expressions,
                                  final String separator, final boolean binary)
     {
         final int n = expressions.size();
@@ -291,7 +290,7 @@
      * The filter can contain an arbitrary number of operands, all separated by the same keyword.
      * All operands are grouped between parenthesis.
      */
-    private final class Logic implements BiConsumer<Filter<Feature>, SelectionClause> {
+    private final class Logic implements BiConsumer<Filter<AbstractFeature>, SelectionClause> {
         /**
          * The {@code AND}, {@code OR} or {@code NOT} keyword.
          * Shall contain a trailing space and eventually a leading space.
@@ -311,9 +310,9 @@
         }
 
         /** 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<Feature>> operands = filter.getOperands();
+        @Override public void accept(final Filter<AbstractFeature> f, final SelectionClause sql) {
+            final var filter = (LogicalOperator<AbstractFeature>) f;
+            final List<Filter<AbstractFeature>> operands = filter.getOperands();
             final int n = operands.size();
             if (unary ? (n != 1) : (n == 0)) {
                 sql.invalidate();
@@ -339,7 +338,7 @@
      * into SQL clauses. The filter is expected to contain exactly two operands, otherwise the
      * SQL is declared invalid.
      */
-    private final class Comparison implements BiConsumer<Filter<Feature>, SelectionClause> {
+    private final class Comparison implements BiConsumer<Filter<AbstractFeature>, SelectionClause> {
         /** The comparison operator symbol. */
         private final String operator;
 
@@ -349,8 +348,8 @@
         }
 
         /** Invoked when a comparison needs to be converted to SQL clause. */
-        @Override public void accept(final Filter<Feature> f, final SelectionClause sql) {
-            final BinaryComparisonOperator<Feature> filter = (BinaryComparisonOperator<Feature>) f;
+        @Override public void accept(final Filter<AbstractFeature> f, final SelectionClause sql) {
+            final BinaryComparisonOperator<AbstractFeature> filter = (BinaryComparisonOperator<AbstractFeature>) f;
             if (filter.isMatchingCase()) {
                 writeBinaryOperator(sql, filter, operator);
             } else {
@@ -366,7 +365,7 @@
      * Handler for converting {@code +}, {@code -}, {@code *} or {@code /} filter into SQL clauses.
      * The filter is expected to contain exactly two operands, otherwise the SQL is declared invalid.
      */
-    private final class Arithmetic implements BiConsumer<Expression<Feature,?>, SelectionClause> {
+    private final class Arithmetic implements BiConsumer<Expression<AbstractFeature,?>, SelectionClause> {
         /** The arithmetic operator symbol. */
         private final String operator;
 
@@ -376,7 +375,7 @@
         }
 
         /** Invoked when an arithmetic expression needs to be converted to SQL clause. */
-        @Override public void accept(final Expression<Feature,?> expression, final SelectionClause sql) {
+        @Override public void accept(final Expression<AbstractFeature,?> expression, final SelectionClause sql) {
             writeParameters(sql, expression.getParameters(), operator, true);
         }
     }
@@ -389,7 +388,7 @@
      * This method stops immediately if a parameter cannot be expressed in SQL, leaving
      * the trailing part of the SQL in an invalid state.
      */
-    private final class Function implements BiConsumer<Filter<Feature>, SelectionClause> {
+    private final class Function implements BiConsumer<Filter<AbstractFeature>, SelectionClause> {
         /** Name the function. */
         final String name;
 
@@ -399,7 +398,7 @@
         }
 
         /** Writes the function as an SQL statement. */
-        @Override public void accept(final Filter<Feature> filter, final SelectionClause sql) {
+        @Override public void accept(final Filter<AbstractFeature> filter, final SelectionClause sql) {
             sql.append(name);
             writeParameters(sql, filter.getExpressions(), ", ", false);
         }
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
index 322600b..9744bf0 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
@@ -36,18 +36,17 @@
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.iso.DefaultNameSpace;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
  * Description of a table in the database, including columns, primary keys and foreigner keys.
- * This class contains a {@link FeatureType} inferred from the table structure.
- * The {@link FeatureType} contains an {@link AttributeType} for each table column,
- * except foreigner keys which are represented by {@link FeatureAssociationRole}s.
+ * This class contains a {@code FeatureType} inferred from the table structure.
+ * The {@code FeatureType} contains an {@code AttributeType} for each table column,
+ * except foreigner keys which are represented by {@code FeatureAssociationRole}s.
  *
  * <h2>Multi-threading</h2>
  * This class is immutable (except for the cache) and safe for concurrent use by many threads.
@@ -71,7 +70,7 @@
      *
      * @see #getType()
      */
-    final FeatureType featureType;
+    final DefaultFeatureType featureType;
 
     /**
      * The SQL query to execute for fetching data, or {@code null} for querying the table identified by {@link #name}.
@@ -95,7 +94,7 @@
      * This array shall not be modified after construction.
      *
      * <p>Columns may have alias if it was necessary to avoid name collisions.
-     * The alias is given by {@link Column#propertyName} and will be the name used in {@link FeatureType}.</p>
+     * The alias is given by {@link Column#propertyName} and will be the name used in {@code FeatureType}.</p>
      */
     final Column[] attributes;
 
@@ -134,7 +133,7 @@
     private Map<String,Column> attributeToColumns;
 
     /**
-     * The converter of {@link ResultSet} rows to {@link Feature} instances.
+     * The converter of {@link ResultSet} rows to {@code Feature} instances.
      * Created when first needed.
      *
      * @see #adapter(Connection)
@@ -187,7 +186,7 @@
      *
      * @todo This constructor is not yet used because it is an unfinished work.
      *       We need to invent some mechanism for using a subset of the columns.
-     *       A starting point is {@link org.apache.sis.storage.FeatureQuery#expectedType(FeatureType)}.
+     *       A starting point is {@link org.apache.sis.storage.FeatureQuery#expectedType(DefaultFeatureType)}.
      */
     Table(final Table parent) {
         super(parent.listeners, false);
@@ -227,8 +226,8 @@
                      * have been set to association names. If `ClassCastException` occurs here, it is a bug
                      * in our object constructions.
                      */
-                    final FeatureAssociationRole association =
-                            (FeatureAssociationRole) featureType.getProperty(relation.propertyName);
+                    final DefaultAssociationRole association =
+                            (DefaultAssociationRole) featureType.getProperty(relation.propertyName);
 
                     final Table table = tables.get(association.getValueType().getName());
                     if (table == null) {
@@ -312,7 +311,7 @@
      * Returns the feature type inferred from the database structure analysis.
      */
     @Override
-    public final FeatureType getType() {
+    public final DefaultFeatureType getType() {
         return featureType;
     }
 
@@ -455,7 +454,7 @@
     }
 
     /**
-     * Returns the converter of {@link ResultSet} rows to {@link Feature} instances.
+     * Returns the converter of {@link ResultSet} rows to {@code Feature} instances.
      * The converter is created the first time that this method is invoked, then cached.
      *
      * @param  connection  source of database metadata to use if the adapter needs to be created.
@@ -475,7 +474,7 @@
      * @throws DataStoreException if an error occurred while creating the stream.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         return new FeatureStream(this, parallel);
     }
 }
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/package-info.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/package-info.java
index d4b582c..edfce4d 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/package-info.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/package-info.java
@@ -17,7 +17,7 @@
 
 
 /**
- * Build {@link org.opengis.feature.FeatureType}s by inspection of database schemas.
+ * Build {@link org.apache.sis.feature.DefaultFeatureType}s by inspection of database schemas.
  * The work done here is similar to reverse engineering.
  *
  * <STRONG>Do not use!</STRONG>
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
index d075f6c..bf8c64d 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
@@ -19,11 +19,11 @@
 /**
  * Data store capable to read and create features from a JDBC connection to a database.
  * {@link org.apache.sis.storage.sql.SQLStore} takes a one or more tables at construction time.
- * Each enumerated table is represented by a {@link org.opengis.feature.FeatureType}.
- * Each row in those table represents a {@link org.opengis.feature.Feature} instance.
- * Each relation defined by a foreigner key is represented by an {@link org.opengis.feature.FeatureAssociationRole}
+ * Each enumerated table is represented by a {@code FeatureType}.
+ * Each row in those table represents a {@code Feature} instance.
+ * Each relation defined by a foreigner key is represented by an {@code FeatureAssociationRole}
  * to another feature (with transitive dependencies automatically resolved), and the other columns are represented
- * by {@link org.opengis.feature.AttributeType}.
+ * by {@code AttributeType}.
  *
  * <p>The storage of spatial features in SQL databases is described by the
  * <a href="https://www.ogc.org/standards/sfs">OGC Simple feature access - Part 2: SQL option</a>
@@ -35,7 +35,7 @@
  * <p>A subset of features can be obtained by applying filters on the stream returned by
  * {@link org.apache.sis.storage.FeatureSet#features(boolean)}.
  * While the filter can be any {@link java.util.function.Predicate},
- * performances will be much better if they are instances of {@link org.opengis.filter.Filter}
+ * performances will be much better if they are instances of {@link org.apache.sis.filter.Filter}
  * because Apache SIS will know how to translate some of them to SQL statements.</p>
  *
  * <p>In filter expressions like {@code ST_Intersects(A,B)} where the <var>A</var> and <var>B</var> parameters are
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java
index f2e40cd..1401921 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedClauseWriter.java
@@ -18,8 +18,8 @@
 
 import org.apache.sis.storage.sql.feature.SelectionClauseWriter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.SpatialOperatorName;
+// Specific to the main branch:
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
index a797cfe..ed3f348 100644
--- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
+++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -41,14 +41,12 @@
 import org.apache.sis.metadata.sql.TestDatabase;
 import static org.apache.sis.test.Assertions.assertSetEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.SortOrder;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
@@ -95,12 +93,12 @@
      * This feature should appear twice, and all those occurrences should use the exact same instance.
      * We use that for verifying the {@code Table.instanceForPrimaryKeys} caching.
      */
-    private Feature canada;
+    private AbstractFeature canada;
 
     /**
      * Factory to use for creating filter objects.
      */
-    private final FilterFactory<Feature,Object,Object> FF;
+    private final DefaultFilterFactory<AbstractFeature,Object,Object> FF;
 
     /**
      * Creates a new test.
@@ -168,7 +166,7 @@
         try (SQLStore store = new SQLStore(new SQLStoreProvider(), connector, table)) {
             verifyFeatureTypes(store);
             final Map<String,Integer> countryCount = new HashMap<>();
-            try (Stream<Feature> features = store.findResource("Cities").features(false)) {
+            try (Stream<AbstractFeature> features = store.findResource("Cities").features(false)) {
                 features.forEach((f) -> verifyContent(f, countryCount));
             }
             assertEquals(Integer.valueOf(2), countryCount.remove("CAN"));
@@ -222,9 +220,9 @@
     /**
      * Verifies the result of analyzing the structure of the {@code "Cities"} table.
      */
-    private static void verifyFeatureType(final FeatureType type, final String[] expectedNames, final Object[] expectedTypes) {
+    private static void verifyFeatureType(final DefaultFeatureType type, final String[] expectedNames, final Object[] expectedTypes) {
         int i = 0;
-        for (PropertyType pt : type.getProperties(false)) {
+        for (AbstractIdentifiedType pt : type.getProperties(false)) {
             if (i >= expectedNames.length) {
                 fail("Returned feature-type contains more properties than expected. Example: " + pt.getName());
             }
@@ -235,10 +233,10 @@
                 final Object value;
                 if (expectedType instanceof Class<?>) {
                     label = "attribute type";
-                    value = ((AttributeType<?>) pt).getValueClass();
+                    value = ((DefaultAttributeType<?>) pt).getValueClass();
                 } else {
                     label = "association type";
-                    value = ((FeatureAssociationRole) pt).getValueType().getName().toString();
+                    value = ((DefaultAssociationRole) pt).getValueType().getName().toString();
                 }
                 assertEquals(expectedType, value, label);
             }
@@ -254,7 +252,7 @@
      * @param  feature       a feature returned by the stream.
      * @param  countryCount  number of time that the each country has been seen while iterating over the cities.
      */
-    private void verifyContent(final Feature feature, final Map<String,Integer> countryCount) {
+    private void verifyContent(final AbstractFeature feature, final Map<String,Integer> countryCount) {
         final String city = feature.getPropertyValue("native_name").toString();
         final City c;
         boolean isCanada = false;
@@ -278,7 +276,7 @@
          */
         assertEquals(c.countryName, getIndirectPropertyValue(feature, "country", "native_name"));
         if (isCanada) {
-            final Feature f = (Feature) feature.getPropertyValue("country");
+            final AbstractFeature f = (AbstractFeature) feature.getPropertyValue("country");
             if (canada == null) {
                 canada = f;
             } else {
@@ -295,7 +293,7 @@
         assertEquals(c.parks.length, actualParks.size());
         final Collection<String> expectedParks = new HashSet<>(Arrays.asList(c.parks));
         for (final Object park : actualParks) {
-            final Feature pf = (Feature) park;
+            final AbstractFeature pf = (AbstractFeature) park;
             final String npn = (String) pf.getPropertyValue("native_name");
             final String epn = (String) pf.getPropertyValue("english_name");
             assertNotNull(npn, "park.native_name");
@@ -315,10 +313,10 @@
     /**
      * Follows an association in the given feature.
      */
-    private static Object getIndirectPropertyValue(final Feature feature, final String p1, final String p2) {
+    private static Object getIndirectPropertyValue(final AbstractFeature feature, final String p1, final String p2) {
         final Object dependency = feature.getPropertyValue(p1);
         assertNotNull(dependency, p1);
-        return assertInstanceOf(Feature.class, dependency, p1).getPropertyValue(p2);
+        return assertInstanceOf(AbstractFeature.class, dependency, p1).getPropertyValue(p2);
     }
 
     /**
@@ -344,21 +342,19 @@
         final FeatureSet   parks = dataset.findResource("Parks");
         final FeatureQuery query = new FeatureQuery();
         query.setProjection(FF.property(desiredProperty));
-        query.setSortBy(FF.sort(FF.property("country"),       SortOrder.DESCENDING),
-                        FF.sort(FF.property(desiredProperty), SortOrder.ASCENDING));
         final FeatureSet subset = parks.subset(query);
         /*
-         * Verify that all features have the expected property, then verify the sorted values.
+         * Verify that all features have the expected property.
          */
         final Object[] values;
-        try (Stream<Feature> features = subset.features(false)) {
+        try (Stream<AbstractFeature> features = subset.features(false)) {
             values = features.map(f -> {
-                final PropertyType p = TestUtilities.getSingleton(f.getType().getProperties(true));
+                final AbstractIdentifiedType p = TestUtilities.getSingleton(f.getType().getProperties(true));
                 assertEquals(desiredProperty, p.getName().toString(), "Feature has wrong property.");
                 return f.getPropertyValue(desiredProperty);
             }).toArray();
         }
-        assertArrayEquals(expectedValues, values, "Values are not sorted as expected.");
+        assertEquals(new HashSet<>(Arrays.asList(expectedValues)), new HashSet<>(Arrays.asList(values)));
     }
 
     /**
@@ -399,7 +395,7 @@
         final FeatureQuery query  = new FeatureQuery();
         query.setSelection(FF.equal(FF.property("country"), FF.literal("CAN")));
         final Object[] names;
-        try (Stream<Feature> features = cities.subset(query).features(false)) {
+        try (Stream<AbstractFeature> features = cities.subset(query).features(false)) {
             names = features.map(f -> f.getPropertyValue(desiredProperty)).toArray();
         }
         assertSetEquals(Arrays.asList(expectedValues), Arrays.asList(names));
@@ -420,7 +416,7 @@
         query.setSelection(FF.equal(FF.property("sis:identifier"), FF.literal("CAN")));
         final String executionMode;
         final Object[] names;
-        try (Stream<Feature> features = countries.subset(query).features(false)) {
+        try (Stream<AbstractFeature> features = countries.subset(query).features(false)) {
             executionMode = features.toString();
             names = features.map(f -> f.getPropertyValue(desiredProperty)).toArray();
         }
@@ -442,7 +438,7 @@
      * @param  cities  a feature set containing all cities defined for the test class.
      */
     private void verifyStreamOperations(final FeatureSet cities) throws DataStoreException {
-        try (Stream<Feature> features = cities.features(false)) {
+        try (Stream<AbstractFeature> features = cities.features(false)) {
             final AtomicInteger peekCount = new AtomicInteger();
             final AtomicInteger mapCount  = new AtomicInteger();
             final long actualPopulations = features.peek(f -> peekCount.incrementAndGet())
@@ -479,7 +475,7 @@
         {
             final FeatureSet cities = store.findResource("LargeCities");
             final Map<String,Integer> countryCount = new HashMap<>();
-            try (Stream<Feature> features = cities.features(false)) {
+            try (Stream<AbstractFeature> features = cities.features(false)) {
                 features.forEach((f) -> verifyContent(f, countryCount));
             }
             assertEquals(Integer.valueOf(1), countryCount.remove("CAN"));
@@ -495,28 +491,27 @@
      */
     private void verifyNestedSQLQuery(final StorageConnector connector) throws Exception {
         try (SQLStore store = new SQLStore(null, connector, ResourceDefinition.query("MyParks",
-                "SELECT * FROM " + SCHEMA + ".\"Parks\"")))
+                "SELECT * FROM " + SCHEMA + ".\"Parks\" ORDER BY \"native_name\" DESC")))
         {
             final FeatureSet parks = store.findResource("MyParks");
             /*
              * Add a filter for parks in France.
              */
             final FeatureQuery query = new FeatureQuery();
-            query.setSortBy(FF.sort(FF.property("native_name"), SortOrder.DESCENDING));
             query.setSelection(FF.equal(FF.property("country"), FF.literal("FRA")));
             query.setProjection(FF.property("native_name"));
             final FeatureSet frenchParks = parks.subset(query);
             /*
              * Verify the feature type.
              */
-            final PropertyType property = TestUtilities.getSingleton(frenchParks.getType().getProperties(true));
+            final AbstractIdentifiedType property = TestUtilities.getSingleton(frenchParks.getType().getProperties(true));
             assertEquals("native_name", property.getName().toString());
-            assertEquals(String.class, ((AttributeType<?>) property).getValueClass());
+            assertEquals(String.class, ((DefaultAttributeType<?>) property).getValueClass());
             /*
              * Verify the values.
              */
             final Object[] result;
-            try (Stream<Feature> fs = frenchParks.features(false)) {
+            try (Stream<AbstractFeature> fs = frenchParks.features(false)) {
                 result = fs.map(f -> f.getPropertyValue("native_name")).toArray();
             }
             assertArrayEquals(new String[] {"Jardin du Luxembourg", "Jardin des Tuileries"}, result);
@@ -536,8 +531,8 @@
                 "OFFSET 2 ROWS FETCH NEXT 3 ROWS ONLY")))
         {
             final FeatureSet parks = store.findResource("MyQuery");
-            final FeatureType type = parks.getType();
-            final AttributeType<?> property = (AttributeType<?>) TestUtilities.getSingleton(type.getProperties(true));
+            final DefaultFeatureType type = parks.getType();
+            final DefaultAttributeType<?> property = (DefaultAttributeType<?>) TestUtilities.getSingleton(type.getProperties(true));
             assertEquals("title", property.getName().toString(), "Property name should be label defined in query");
             assertEquals(String.class, property.getValueClass(), "Attribute should be a string");
             assertEquals(0, property.getMinimumOccurs(), "Column should be nullable.");
@@ -562,7 +557,7 @@
      * then returns the values of the "title" property of all features.
      */
     private static String[] getTitles(final FeatureSet parks, final long skip, final long limit) throws DataStoreException {
-        try (Stream<Feature> in = parks.features(false).skip(skip).limit(limit)) {
+        try (Stream<AbstractFeature> in = parks.features(false).skip(skip).limit(limit)) {
             return in.map(f -> f.getPropertyValue("title").toString()).toArray(String[]::new);
         }
     }
@@ -576,7 +571,7 @@
                 "SELECT \"country\" FROM " + SCHEMA + ".\"Parks\" ORDER BY \"country\"")))
         {
             final FeatureSet countries = store.findResource("Countries");
-            try (Stream<Feature> features = countries.features(false).distinct()) {
+            try (Stream<AbstractFeature> features = countries.features(false).distinct()) {
                 expected = features.map(f -> f.getPropertyValue("country")).toArray();
             }
         }
diff --git a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java
index 457f7d0..4254022 100644
--- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java
+++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java
@@ -33,12 +33,10 @@
 import org.apache.sis.metadata.sql.TestDatabase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.SpatialOperator;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Filter;
 
 
 /**
@@ -51,7 +49,7 @@
     /**
      * The factory to use for creating the filter objects.
      */
-    private final FilterFactory<Feature,Object,Object> FF;
+    private final DefaultFilterFactory<AbstractFeature,Object,Object> FF;
 
     /**
      * A dummy table for testing purpose.
@@ -89,7 +87,7 @@
      * Creates a filter without geometry and verifies that it is translated to the expected SQL fragment.
      */
     private void testSimpleFilter() {
-        final Filter<Feature> filter = FF.and(
+        final Filter<AbstractFeature> filter = FF.and(
                 FF.greater(FF.property("ALPHA"), FF.property("BETA")),
                 FF.or(FF.isNull(FF.property("GAMMA")),
                       FF.equal(FF.literal(3.14), FF.property("PI"))));
@@ -101,7 +99,7 @@
      * Creates a filter with a geometric objects and verifies that it is translated to the expected SQL fragment.
      */
     private void testGeometricFilter() {
-        final SpatialOperator<Feature> filter = FF.intersects(
+        final Filter<AbstractFeature> filter = FF.intersects(
                 FF.property("ALPHA"),
                 FF.literal(new GeneralEnvelope(new double[] {-12.3, 43.3}, new double[] {2.1, 51.7})));
 
@@ -114,7 +112,7 @@
      * This method add a CRS on a property for testing purpose.
      */
     @Override
-    public FeatureType editFeatureType(final TableReference table, final FeatureTypeBuilder feature) {
+    public DefaultFeatureType editFeatureType(final TableReference table, final FeatureTypeBuilder feature) {
         assertEquals("",     table.catalog);
         assertEquals("APP",  table.schema);
         assertEquals("TEST", table.table);
@@ -129,7 +127,7 @@
         final GeneralEnvelope bbox = new GeneralEnvelope(HardCodedCRS.WGS84_LATITUDE_FIRST);
         bbox.setEnvelope(-10, 20, -5, 25);
 
-        Filter<Feature> filter = FF.intersects(FF.property("BETA"), FF.literal(bbox));
+        Filter<AbstractFeature> filter = FF.intersects(FF.property("BETA"), FF.literal(bbox));
         final Optimization optimization = new Optimization();
         optimization.setFeatureType(table.featureType);
         verifySQL(optimization.apply(filter), "ST_Intersects(\"BETA\", " +
@@ -140,7 +138,7 @@
      * 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<Feature> filter, final String expected) {
+    private void verifySQL(final Filter<AbstractFeature> filter, final String expected) {
         final SelectionClause sql = new SelectionClause(table);
         assertTrue(sql.tryAppend(SelectionClauseWriter.DEFAULT, filter));
         assertEquals(expected, sql.toString());
diff --git a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
index 75437a4..0a82acf 100644
--- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
+++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
@@ -57,8 +57,8 @@
 import org.apache.sis.metadata.sql.TestDatabase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -130,7 +130,7 @@
                  * Tests through public API.
                  */
                 final FeatureSet resource = store.findResource("SpatialData");
-                try (Stream<Feature> features = resource.features(false)) {
+                try (Stream<AbstractFeature> features = resource.features(false)) {
                     features.forEach(PostgresTest::validate);
                 }
                 final Envelope envelope = resource.getEnvelope().get();
@@ -189,7 +189,7 @@
      * Invoked for each feature instances for performing some checks on the feature.
      * This method performs only a superficial verification of geometries.
      */
-    private static void validate(final Feature feature) {
+    private static void validate(final AbstractFeature feature) {
         final String       filename = feature.getPropertyValue("filename").toString();
         final Geometry     geometry = (Geometry) feature.getPropertyValue("geometry");
         final GridCoverage raster   = (GridCoverage) feature.getPropertyValue("image");
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java
index 6a9ba51..ddc9e23 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java
@@ -37,11 +37,14 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
-import org.opengis.metadata.identification.BrowseGraphic;
-import org.apache.sis.util.SimpleInternationalString;
+// Specific to the main branch:
+import org.opengis.metadata.citation.Contact;
+import org.opengis.metadata.citation.Series;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+import org.apache.sis.metadata.iso.constraint.DefaultConstraints;
 
 
 /**
@@ -61,15 +64,11 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class Copyright implements LegalConstraints, Responsibility, Party, Citation, CitationDate {
+public final class Copyright implements LegalConstraints, ResponsibleParty, Citation, CitationDate {
     /**
      * The copyright holder.
      * This field is mandatory in principle, but {@code Copyright} implementation is robust to null value.
-     * This field is mapped to the {@linkplain #getName() responsible party name} in ISO 19115 metadata.
-     *
-     * @see #getResponsibleParties()
-     * @see #getParties()
-     * @see #getName()
+     * This field is mapped to the {@code getName() responsible party name} in ISO 19115 metadata.
      */
     @XmlAttribute(name = Attributes.AUTHOR, required = true)
     public String author;
@@ -85,9 +84,7 @@
 
     /**
      * Link to an external file containing the license text, or {@code null} if none.
-     * This field is mapped to the {@linkplain #getOnlineResources() online resources} in ISO 19115 metadata.
-     *
-     * @see #getOnlineResources()
+     * This field is mapped to the online resources in ISO 19115 metadata.
      */
     @XmlElement(name = Tags.LICENSE)
     public URI license;
@@ -103,13 +100,16 @@
      * Copies properties from the given ISO 19115 metadata.
      */
     private Copyright(final LegalConstraints c, final Locale locale) {
-resp:   for (final Responsibility r : c.getResponsibleParties()) {
-            for (final Party p : r.getParties()) {
+        if (!(c instanceof DefaultConstraints)) {
+            return;
+        }
+resp:   for (final DefaultResponsibility r : ((DefaultConstraints) c).getResponsibleParties()) {
+            for (final AbstractParty p : r.getParties()) {
                 author = Types.toString(p.getName(), locale);
                 if (author != null) break resp;
             }
         }
-        for (final Citation ci : c.getReferences()) {
+        for (final Citation ci : ((DefaultConstraints) c).getReferences()) {
             for (final CitationDate d : ci.getDates()) {
                 final Date date = d.getDate();
                 if (date != null) {
@@ -117,7 +117,8 @@
                     break;
                 }
             }
-            for (final OnlineResource r : ci.getOnlineResources()) {
+            if (!(ci instanceof DefaultCitation)) continue;
+            for (final OnlineResource r : ((DefaultCitation) ci).getOnlineResources()) {
                 license = r.getLinkage();
                 if (license != null) break;
             }
@@ -150,13 +151,11 @@
      * ISO 19115 metadata property determined by the {@link #license} field.
      *
      * @return restrictions or limitations or warnings on using the data.
-     *
-     * @see #getReferences()
      */
     @Override
     public Collection<Restriction> getUseConstraints() {
         if (license != null) {
-            return List.of(Restriction.COPYRIGHT, Restriction.LICENCE);
+            return List.of(Restriction.COPYRIGHT, Restriction.valueOf("LICENCE"));
         } else {
             return Collections.singleton(Restriction.COPYRIGHT);
         }
@@ -165,44 +164,21 @@
     /**
      * ISO 19115 metadata property not specified by GPX.
      *
-     * @return graphics or symbols indicating the constraint.
+     * @return other restrictions and legal prerequisites for accessing and using the resource.
      */
     @Override
-    public Collection<BrowseGraphic> getGraphics() {
+    public Collection<InternationalString> getOtherConstraints() {
         return Collections.emptySet();
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #year} and {@link #license} fields.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getDate()} and {@link #getOnlineResources()} methods.
+     * ISO 19115 metadata property not specified by GPX.
      *
-     * @return citations for the limitation of constraint.
-     *
-     * @see #getTitle()
-     * @see #getDates()
-     * @see #getPresentationForms()
-     * @see #getOnlineResources()
+     * @return limitation affecting the fitness for use of the resource.
      */
     @Override
-    public Collection<? extends Citation> getReferences() {
-        return thisOrEmpty(year != null || license != null);
-    }
-
-    /**
-     * ISO 19115 metadata property determined by the {@link #author} field.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getName()} method.
-     *
-     * @return parties responsible for the resource constraints.
-     *
-     * @see #getRole()
-     * @see #getParties()
-     * @see #getCitedResponsibleParties()
-     */
-    @Override
-    public Collection<? extends Responsibility> getResponsibleParties() {
-        return thisOrEmpty(author != null);
+    public Collection<InternationalString> getUseLimitations() {
+        return Collections.emptySet();
     }
 
 
@@ -213,7 +189,7 @@
 
     /**
      * ISO 19115 metadata property fixed to {@link Role#OWNER}.
-     * This is part of the properties returned by {@link #getResponsibleParties()}.
+     * This is part of the properties returned by {@code getResponsibleParties()}.
      *
      * @return function performed by the responsible party.
      */
@@ -223,29 +199,46 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #author} field.
-     * This is part of the properties returned by {@link #getResponsibleParties()}.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getName()} method.
+     * ISO 19115 metadata property not specified by GPX. Actually could be the {@link #author},
+     * but we have no way to know if the author is an individual or an organization.
      *
-     * @return information about the parties.
-     *
-     * @see #getName()
+     * @return name of the organization, or {@code null} if none.
      */
     @Override
-    public Collection<? extends Party> getParties() {
-        return thisOrEmpty(author != null);
+    public InternationalString getOrganisationName() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return position of the individual in the organization, or {@code null} if none.
+     */
+    @Override
+    public InternationalString getPositionName() {
+        return null;
     }
 
     /**
      * ISO 19115 metadata property determined by the {@link #author} field.
-     * This is part of the properties returned by {@link #getParties()}.
+     * This is part of the properties returned by {@code getParties()}.
      *
      * @return name of the party, or {@code null} if none.
      */
     @Override
-    public InternationalString getName() {
-        return (author != null) ? new SimpleInternationalString(author) : null;
+    public String getIndividualName() {
+        return author;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getParties()}.
+     *
+     * @return contact information for the party.
+     */
+    @Override
+    public Contact getContactInfo() {
+        return null;
     }
 
     /**
@@ -266,7 +259,7 @@
 
     /**
      * ISO 19115 metadata property not specified by GPX.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      * It would be the license title if that information was provided.
      *
      * @return the license name.
@@ -277,8 +270,19 @@
     }
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return other names for the resource.
+     */
+    @Override
+    public Collection<InternationalString> getAlternateTitles() {
+        return Collections.emptySet();
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #year} field.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
      * to the {@link #getDate()} method.
      *
@@ -307,19 +311,53 @@
     }
 
     /**
-     * ISO 19115 metadata property fixed to {@link DateType#IN_FORCE}.
+     * ISO 19115 metadata property fixed to {@code DateType.IN_FORCE}.
      * This is part of the properties returned by {@link #getDates()}.
      *
      * @return event used for reference date.
      */
     @Override
     public DateType getDateType() {
-        return DateType.IN_FORCE;
+        return DateType.valueOf("IN_FORCE");
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the license version, or {@code null} if none.
+     */
+    @Override
+    public InternationalString getEdition() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the license edition date, or {@code null} if none.
+     */
+    @Override
+    public Date getEditionDate() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     * It would be the license author if that information was provided.
+     *
+     * @return the information for individuals or organisations that are responsible for the license.
+     */
+    @Override
+    public Collection<ResponsibleParty> getCitedResponsibleParties() {
+        return Collections.emptySet();
     }
 
     /**
      * ISO 19115 metadata property fixed to {@link PresentationForm#DOCUMENT_DIGITAL}.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      *
      * @return the presentation mode of the license.
      */
@@ -329,14 +367,59 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #license} field.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
      *
-     * @return online references to the cited resource.
+     * @return the series or aggregate dataset of which the dataset is a part.
      */
     @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (license != null) ? Collections.singleton(new Link(license)) : Collections.emptySet();
+    public Series getSeries() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return other details.
+     */
+    @Override
+    public InternationalString getOtherCitationDetails() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the common title.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getCollectiveTitle() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the International Standard Book Number.
+     */
+    @Override
+    public String getISBN() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the International Standard Serial Number.
+     */
+    @Override
+    public String getISSN() {
+        return null;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Link.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Link.java
index 0593a47..575b32b 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Link.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Link.java
@@ -155,6 +155,26 @@
     }
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return connection protocol to be used.
+     */
+    @Override
+    public String getProtocol() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return application profile that can be used with the online resource.
+     */
+    @Override
+    public String getApplicationProfile() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #text} field.
      *
      * @return name of the online resource.
@@ -185,16 +205,6 @@
     }
 
     /**
-     * ISO 19115 metadata property not specified by GPX.
-     *
-     * @return request used to access the resource, or {@code null}.
-     */
-    @Override
-    public String getProtocolRequest() {
-        return null;
-    }
-
-    /**
      * Compares this {@code Link} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Link}.
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
index 439c0c7..c7f1458 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
@@ -53,9 +53,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Responsibility;
-import org.apache.sis.metadata.iso.citation.Citations;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
 
 
 /**
@@ -134,8 +133,6 @@
 
     /**
      * URLs associated with the location described in the file, or {@code null} if none.
-     *
-     * @see #getOnlineResources()
      */
     @XmlElement(name = Tags.LINK)
     public List<Link> links;
@@ -208,8 +205,10 @@
                         if (time != null) break;
                     }
                 }
-                for (final OnlineResource r : ci.getOnlineResources()) {
-                    links = addIfNonNull(links, Link.castOrCopy(r, locale));
+                if (ci instanceof DefaultCitation) {
+                    for (final OnlineResource r : ((DefaultCitation) ci).getOnlineResources()) {
+                        links = addIfNonNull(links, Link.castOrCopy(r, locale));
+                    }
                 }
             }
             if (description == null) {
@@ -219,7 +218,7 @@
              * identificationInfo.pointOfContact.party.name   →   creator        if role is ORIGINATOR
              * identificationInfo.pointOfContact.party.name   →   author.name    if role is AUTHOR
              */
-            for (final Responsibility r : id.getPointOfContacts()) {
+            for (final ResponsibleParty r : id.getPointOfContacts()) {
                 final Person p = Person.castOrCopy(r, locale);
                 if (p != null) {
                     if (p.isCreator) {
@@ -373,17 +372,6 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #links} field.
-     * This is part of the information returned by {@link #getCitation()}.
-     *
-     * @return online references to the cited resource.
-     */
-    @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (links != null) ? Collections.unmodifiableList(links) : Collections.emptyList();
-    }
-
-    /**
      * Names of features types available for the GPX format, or an empty list if none.
      * This property is not part of metadata described in GPX file; it is rather a hard-coded value shared by all
      * GPX files. Users could however filter the list of features, for example with only routes and no tracks.
@@ -418,16 +406,6 @@
     }
 
     /**
-     * Citation(s) for the standard(s) to which the metadata conform.
-     *
-     * @return ISO 19115-1 citation.
-     */
-    @Override
-    public Collection<Citation> getMetadataStandards() {
-        return Collections.singleton(Citations.ISO_19115.get(0));
-    }
-
-    /**
      * Compares this {@code Metadata} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Metadata}.
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Person.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Person.java
index e194656..24272c7 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Person.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Person.java
@@ -32,16 +32,6 @@
 import org.opengis.metadata.citation.Telephone;
 import org.opengis.metadata.citation.ResponsibleParty;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
-import org.apache.sis.util.SimpleInternationalString;
-import org.apache.sis.util.iso.Types;
-
-// Specific to the geoapi-3.1 branch:
-import java.util.AbstractSet;
-import java.util.Iterator;
-
 
 /**
  * Information about a person or organization.
@@ -56,19 +46,17 @@
  * Those properties can be read or modified directly. All methods defined in this class are bridges to
  * the ISO 19115 metadata model and can be ignored if the user only wants to manipulate the GPX model.
  *
- * <p>Note that {@link Party} is an abstract type in ISO 19115 model. We are supposed to implement a subtype
- * ({@link org.opengis.metadata.citation.Individual} or {@link org.opengis.metadata.citation.Organisation}).
+ * <p>Note that {@code Party} is an abstract type in ISO 19115 model. We are supposed to implement a subtype
+ * ({@code Individual} or {@code Organisation}).
  * However, the GPX metadata does not specifies whether the "person" is actually an individual or an organization.
  * In this situation of doubt, we do not select a subtype for avoiding to provide a wrong information.</p>
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class Person implements ResponsibleParty, Party, Contact, Address {
+public final class Person implements ResponsibleParty, Contact, Address {
     /**
      * Name of person or organization.
-     *
-     * @see #getName()
      */
     @XmlElement(name = Tags.NAME)
     public String name;
@@ -89,8 +77,6 @@
 
     /**
      * Link to Web site or other external information about person.
-     *
-     * @see #getOnlineResources()
      */
     @XmlElement(name = Tags.LINK)
     public Link link;
@@ -117,21 +103,19 @@
      * @param  locale  the locale to use for localized strings.
      * @return the GPX metadata, or {@code null}.
      */
-    public static Person castOrCopy(final Responsibility r, final Locale locale) {
+    public static Person castOrCopy(final ResponsibleParty r, final Locale locale) {
         if (r == null || r instanceof Person) {
             return (Person) r;
         }
         final Role role = r.getRole();
         final boolean isCreator = (role == Role.ORIGINATOR);
-        if (isCreator || role == Role.AUTHOR) {
-            for (final Party p : r.getParties()) {
-                final String name = Types.toString(p.getName(), locale);
-                if (name != null) {
-                    final Person pr = new Person();
-                    pr.name = name;
-                    pr.isCreator = isCreator;
-                    return pr;
-                }
+        if (isCreator || Role.AUTHOR.equals(role)) {
+            final String name = r.getIndividualName();
+            if (name != null) {
+                final Person pr = new Person();
+                pr.name = name;
+                pr.isCreator = isCreator;
+                return pr;
             }
         }
         return null;
@@ -147,21 +131,6 @@
         return isCreator ? Role.ORIGINATOR : Role.AUTHOR;
     }
 
-    /**
-     * ISO 19115 metadata property determined by the {@link #name}, {@link #email} and {@link #link} fields.
-     * Invoking this method is one of the steps in the path from the {@code Responsibility} root to the
-     * {@link #getName()}, {@link #getElectronicMailAddresses()} and {@link #getOnlineResources()} methods.
-     *
-     * @return information about the parties.
-     *
-     * @see #getName()
-     * @see #getContactInfo()
-     */
-    @Override
-    public Collection<? extends Party> getParties() {
-        return thisOrEmpty(name != null || email != null || link != null);
-    }
-
 
     /* ---------------------------------------------------------------------------------
      * Implementation of the Party object returned by Responsibility.getParties().
@@ -169,16 +138,6 @@
      * --------------------------------------------------------------------------------- */
 
     /**
-     * ISO 19115 metadata property determined by the {@link #name} field.
-     *
-     * @return name of the party, or {@code null} if none.
-     */
-    @Override
-    public InternationalString getName() {
-        return (name != null) ? new SimpleInternationalString(name) : null;
-    }
-
-    /**
      * ISO 19115 metadata property not specified by GPX. Actually could be the {@link #name},
      * but we have no way to know if the author is an individual or an organization.
      *
@@ -212,36 +171,13 @@
     /**
      * ISO 19115 metadata property determined by the {@link #email} and {@link #link} fields.
      * Invoking this method is one of the steps in the path from the {@code Responsibility} root
-     * to the {@link #getElectronicMailAddresses()} and {@link #getOnlineResources()} methods.
+     * to the {@link #getElectronicMailAddresses()} and {@link #getOnlineResource()} methods.
      *
      * @return contact information for the party.
-     *
-     * @see #getAddresses()
-     * @see #getOnlineResources()
      */
     @Override
-    public Proxy getContactInfo() {        // Both Contact singleton and Collection<Contact>.
-        return new Proxy();
-    }
-
-    private final class Proxy extends AbstractSet<Contact> implements Contact {
-        @Override public int size() {
-            return (email != null || link != null) ? 1 : 0;
-        }
-
-        @Override public Iterator<Contact> iterator() {
-            return Collections.<Contact>singleton(Person.this).iterator();
-        }
-
-        @Override public Collection<? extends Telephone> getPhones()            {return Person.this.getPhones();}
-        @Override public Telephone                     getPhone()               {return Person.this.getPhone();}
-        @Override public Collection<? extends Address> getAddresses()           {return Person.this.getAddresses();}
-        @Override public Address                       getAddress()             {return Person.this.getAddress();}
-        @Override public Collection<OnlineResource>    getOnlineResources()     {return Person.this.getOnlineResources();}
-        @Override public OnlineResource                getOnlineResource()      {return Person.this.getOnlineResource();}
-        @Override public InternationalString           getHoursOfService()      {return Person.this.getHoursOfService();}
-        @Override public InternationalString           getContactInstructions() {return Person.this.getContactInstructions();}
-        @Override public InternationalString           getContactType()         {return Person.this.getContactType();}
+    public Contact getContactInfo() {
+        return (email != null || link != null) ? this : null;
     }
 
 
@@ -251,6 +187,16 @@
      * --------------------------------------------------------------------------------- */
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return telephone numbers at which the organization or individual may be contacted.
+     */
+    @Override
+    public Telephone getPhone() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #email} field.
      * Invoking this method is one of the steps in the path from the {@code Responsibility} root
      * to the {@link #getElectronicMailAddresses()} method.
@@ -260,8 +206,8 @@
      * @see #getElectronicMailAddresses()
      */
     @Override
-    public Collection<? extends Address> getAddresses() {
-        return thisOrEmpty(email != null);
+    public Address getAddress() {
+        return (email != null) ? this : null;
     }
 
     /**
@@ -270,8 +216,28 @@
      * @return on-line information that can be used to contact the individual or organization.
      */
     @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (link != null) ? Collections.singleton(link) : Collections.emptySet();
+    public OnlineResource getOnlineResource() {
+        return link;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return time period when individuals can contact the organization or individual.
+     */
+    @Override
+    public InternationalString getHoursOfService() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return supplemental instructions on how or when to contact the individual or organization.
+     */
+    @Override
+    public InternationalString getContactInstructions() {
+        return null;
     }
 
 
@@ -281,6 +247,56 @@
      * --------------------------------------------------------------------------------- */
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return address line for the location.
+     */
+    @Override
+    public Collection<String> getDeliveryPoints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return the city of the location.
+     */
+    @Override
+    public InternationalString getCity() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return state, province of the location.
+     */
+    @Override
+    public InternationalString getAdministrativeArea() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return ZIP or other postal code, or {@code null}.
+     */
+    @Override
+    public String getPostalCode() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return country of the physical address, or {@code null}.
+     */
+    @Override
+    public InternationalString getCountry() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #email} field.
      *
      * @return address of the electronic mailbox of the responsible organization or individual.
@@ -291,14 +307,6 @@
     }
 
     /**
-     * Returns this object as a singleton if the given condition is {@code true},
-     * or an empty set if the given condition is {@code false}.
-     */
-    private Collection<Person> thisOrEmpty(final boolean condition) {
-        return condition ? Collections.singleton(this) : Collections.emptySet();
-    }
-
-    /**
      * Compares this {@code Person} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Person}.
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Reader.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Reader.java
index 1fba069..0b9125d 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Reader.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Reader.java
@@ -36,8 +36,8 @@
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -316,7 +316,7 @@
      *         or various {@link RuntimeException}.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) throws BackingStoreException {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) throws BackingStoreException {
         try {
             return parse(action, false);
         } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
@@ -333,7 +333,7 @@
      *         or various {@link RuntimeException}.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) throws BackingStoreException {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) throws BackingStoreException {
         try {
             parse(action, true);
         } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
@@ -357,7 +357,7 @@
      * @throws EOFException if the file seems to be truncated.
      */
     @SuppressWarnings("fallthrough")
-    private boolean parse(final Consumer<? super Feature> action, final boolean all) throws Exception {
+    private boolean parse(final Consumer<? super AbstractFeature> action, final boolean all) throws Exception {
         for (int type = reader.getEventType(); ; type = reader.next()) {
             /*
              * We do not need to check 'reader.hasNext()' in above loop
@@ -365,7 +365,7 @@
              */
             switch (type) {
                 case START_ELEMENT: {
-                    final Feature f;
+                    final AbstractFeature f;
                     switch (isGPX() ? reader.getLocalName() : "") {
                         case Tags.WAY_POINT: f = parseWayPoint(++wayPointId); break;
                         case Tags.ROUTES:    f = parseRoute   (++routeId);    break;
@@ -391,7 +391,7 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseWayPoint(final int index) throws Exception {
+    private AbstractFeature parseWayPoint(final int index) throws Exception {
         assert reader.isStartElement();
         /*
          * Way points might be located in different elements: <wpt>, <rtept> and <trkpt>.
@@ -406,7 +406,7 @@
                     (lat == null) ? Attributes.LATITUDE : Attributes.LONGITUDE, tagName));
         }
         final Types types = ((Store) owner).types;
-        final Feature feature = types.wayPoint.newInstance();
+        final AbstractFeature feature = types.wayPoint.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
         feature.setPropertyValue(AttributeConvention.GEOMETRY, types.geometries.createPoint(parseDouble(lon), parseDouble(lat)));
         List<Link> links = null;
@@ -469,11 +469,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseRoute(final int index) throws Exception {
+    private AbstractFeature parseRoute(final int index) throws Exception {
         assert reader.isStartElement() && Tags.ROUTES.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.route.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.route.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> wayPoints = null;
+        List<AbstractFeature> wayPoints = null;
         List<Link> links = null;
         while (true) {
             /*
@@ -525,11 +525,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseTrackSegment(final int index) throws Exception {
+    private AbstractFeature parseTrackSegment(final int index) throws Exception {
         assert reader.isStartElement() && Tags.TRACK_SEGMENTS.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.trackSegment.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.trackSegment.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> wayPoints = null;
+        List<AbstractFeature> wayPoints = null;
         while (true) {
             /*
              * We do not need to check 'reader.hasNext()' in above loop
@@ -568,11 +568,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseTrack(final int index) throws Exception {
+    private AbstractFeature parseTrack(final int index) throws Exception {
         assert reader.isStartElement() && Tags.TRACKS.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.track.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.track.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> segments = null;
+        List<AbstractFeature> segments = null;
         List<Link> links = null;
         while (true) {
             /*
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java
index 9ca7831..0defffe 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java
@@ -40,9 +40,9 @@
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.distribution.DefaultFormat;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -72,7 +72,7 @@
     private Reader reader;
 
     /**
-     * The {@link org.opengis.feature.FeatureType} for routes, tracks, way points, <i>etc</i>.
+     * The {@code FeatureType} for routes, tracks, way points, <i>etc</i>.
      * Currently always {@link Types#DEFAULT}, but we use a field for keeping {@code Reader}
      * and {@code Writer} ready to handle profiles or extensions.
      */
@@ -182,7 +182,7 @@
      * @return base type of all GPX types.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return types.parent;
     }
 
@@ -198,7 +198,7 @@
      *             more experience with the API proposed by {@link org.apache.sis.storage.FeatureSet}.
      */
     @Deprecated(since="0.8")
-    public FeatureType getFeatureType(final String name) throws IllegalNameException {
+    public DefaultFeatureType getFeatureType(final String name) throws IllegalNameException {
         return types.names.get(this, name);
     }
 
@@ -213,7 +213,7 @@
      * @throws DataStoreException if an error occurred while creating the feature stream.
      */
     @Override
-    public final synchronized Stream<Feature> features(boolean parallel) throws DataStoreException {
+    public final synchronized Stream<AbstractFeature> features(boolean parallel) throws DataStoreException {
         Reader r = reader;
         reader = null;
         if (r == null) try {
@@ -226,7 +226,7 @@
         } catch (Exception e) {
             throw new DataStoreException(e);
         }
-        final Stream<Feature> features = StreamSupport.stream(r, false);
+        final Stream<AbstractFeature> features = StreamSupport.stream(r, false);
         return features.onClose(r);
     }
 
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Types.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Types.java
index 352a74d..99f3280 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Types.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Types.java
@@ -44,9 +44,9 @@
 import org.apache.sis.storage.base.FeatureCatalogBuilder;
 import org.apache.sis.util.iso.DefaultNameFactory;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Operation;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -61,27 +61,27 @@
     /**
      * The parent of all other feature types.
      */
-    final FeatureType parent;
+    final DefaultFeatureType parent;
 
     /**
      * Way point GPX feature type.
      */
-    final FeatureType wayPoint;
+    final DefaultFeatureType wayPoint;
 
     /**
      * Route GPX feature type.
      */
-    final FeatureType route;
+    final DefaultFeatureType route;
 
     /**
      * Track GPX feature type.
      */
-    final FeatureType track;
+    final DefaultFeatureType track;
 
     /**
      * Track segment GPX feature type.
      */
-    final FeatureType trackSegment;
+    final DefaultFeatureType trackSegment;
 
     /**
      * The list of feature types to be given to GPC metadata objects.
@@ -96,7 +96,7 @@
      *             more experience with the API proposed by {@link org.apache.sis.storage.FeatureSet}.
      */
     @Deprecated(since="0.8")
-    final FeatureNaming<FeatureType> names;
+    final FeatureNaming<DefaultFeatureType> names;
 
     /**
      * Accessor to the geometry implementation in use (Java2D, ESRI or JTS).
@@ -217,7 +217,7 @@
          * │ rtept          │ WayPoint       │ gpx:wptType           │   [0 … ∞]    │
          * └────────────────┴────────────────┴───────────────────────┴──────────────┘
          */
-        Operation groupOp = groupAsPolyline(geomInfo, Tags.ROUTE_POINTS, wayPoint);
+        AbstractOperation groupOp = groupAsPolyline(geomInfo, Tags.ROUTE_POINTS, wayPoint);
         builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("Route");
         builder.addProperty(groupOp);
         builder.addProperty(FeatureOperations.envelope(envpInfo, null, groupOp));
@@ -298,7 +298,7 @@
      * @param  previous  previously created international strings as array of length 2.
      *                   The first element is the designation and the second element is the definition.
      */
-    private static FeatureType create(final FeatureTypeBuilder builder, final Map<String,InternationalString[]> previous) {
+    private static DefaultFeatureType create(final FeatureTypeBuilder builder, final Map<String,InternationalString[]> previous) {
         for (final PropertyTypeBuilder p : builder.properties()) {
             final GenericName name = p.getName();
             if (!AttributeConvention.contains(name)) {
@@ -320,7 +320,7 @@
      * @param components  name of the property providing the geometries to group as a polyline.
      * @param type        type of the property identified by {@code components}.
      */
-    private Operation groupAsPolyline(final Map<String,?> geomInfo, final String components, final FeatureType type) {
+    private AbstractOperation groupAsPolyline(final Map<String,?> geomInfo, final String components, final DefaultFeatureType type) {
         var c = new DefaultAssociationRole(Map.of(DefaultAssociationRole.NAME_KEY, components), type, 1, 1);
         return FeatureOperations.groupAsPolyline(geomInfo, geometries.library, c);
     }
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Updater.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Updater.java
index e96c028..2815fc8 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Updater.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Updater.java
@@ -25,8 +25,8 @@
 import org.apache.sis.storage.xml.stream.RewriteOnUpdate;
 import org.apache.sis.storage.xml.stream.StaxStreamWriter;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -58,7 +58,7 @@
      * @throws DataStoreException if an error occurred while fetching the features.
      */
     @Override
-    protected Stream<? extends Feature> features() throws DataStoreException {
+    protected Stream<? extends AbstractFeature> features() throws DataStoreException {
         metadata = Metadata.castOrCopy(source.getMetadata(), getLocale());
         return super.features();
     }
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/WritableStore.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/WritableStore.java
index 71a4317..22a96c1 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/WritableStore.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/WritableStore.java
@@ -30,9 +30,9 @@
 import org.apache.sis.storage.IllegalFeatureTypeException;
 import org.apache.sis.util.collection.BackingStoreException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -64,7 +64,7 @@
      * @throws DataStoreException if the given type is not compatible with the types supported by the store.
      */
     @Override
-    public void updateType(final FeatureType newType) throws DataStoreException {
+    public void updateType(final DefaultFeatureType newType) throws DataStoreException {
         if (!newType.equals(getType())) {
             throw new IllegalFeatureTypeException(getLocale(), StoreProvider.NAME, newType.getName());
         }
@@ -78,7 +78,7 @@
      * @throws DataStoreException if the feature stream cannot be obtained or updated.
      */
     @Override
-    public synchronized void add(final Iterator<? extends Feature> features) throws DataStoreException {
+    public synchronized void add(final Iterator<? extends AbstractFeature> features) throws DataStoreException {
         try (Updater updater = updater()) {
             updater.add(features);
             updater.flush();
@@ -92,7 +92,7 @@
      * @throws DataStoreException if the feature stream cannot be obtained or updated.
      */
     @Override
-    public synchronized void removeIf(final Predicate<? super Feature> filter) throws DataStoreException {
+    public synchronized void removeIf(final Predicate<? super AbstractFeature> filter) throws DataStoreException {
         try (Updater updater = updater()) {
             updater.removeIf(filter);
             updater.flush();
@@ -104,11 +104,11 @@
      * If the given operator returns {@code null}, then the filtered feature is removed.
      *
      * @param  filter       a predicate which returns {@code true} for feature instances to be updated.
-     * @param  replacement  operation called for each matching {@link Feature} instance. May return {@code null}.
+     * @param  replacement  operation called for each matching {@code Feature} instance. May return {@code null}.
      * @throws DataStoreException if the feature stream cannot be obtained or updated.
      */
     @Override
-    public synchronized void replaceIf(final Predicate<? super Feature> filter, final UnaryOperator<Feature> replacement)
+    public synchronized void replaceIf(final Predicate<? super AbstractFeature> filter, final UnaryOperator<AbstractFeature> replacement)
             throws DataStoreException
     {
         try (Updater updater = updater()) {
@@ -145,7 +145,7 @@
      * @see <a href="https://issues.apache.org/jira/browse/SIS-411">SIS-411</a>
      */
     @Deprecated(since="1.3")
-    public synchronized void write(final Metadata metadata, final Stream<? extends Feature> features) throws DataStoreException {
+    public synchronized void write(final Metadata metadata, final Stream<? extends AbstractFeature> features) throws DataStoreException {
         try {
             /*
              * If we created a reader for reading metadata, we need to close that reader now otherwise the call
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Writer.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Writer.java
index 5eed73d..e8b5770 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Writer.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Writer.java
@@ -30,9 +30,9 @@
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.util.Version;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -77,7 +77,7 @@
 
     /**
      * Writes the XML declaration followed by GPX metadata.
-     * This method shall be invoked exactly once before {@link #write(Feature)}.
+     * This method shall be invoked exactly once before {@code write(Feature)}.
      *
      * @throws Exception if an error occurred while writing to the XML file.
      */
@@ -141,10 +141,10 @@
      * @throws JAXBException if underlying JAXB marshaller encounter an error.
      */
     @Override
-    public void write(final Feature feature) throws DataStoreException, XMLStreamException, JAXBException {
+    public void write(final AbstractFeature feature) throws DataStoreException, XMLStreamException, JAXBException {
         if (feature != null) {
             final Types types = ((WritableStore) owner).types;
-            final FeatureType type = feature.getType();
+            final DefaultFeatureType type = feature.getType();
             if (types.wayPoint.isAssignableFrom(type)) {
                 writeWayPoint(feature, Tags.WAY_POINT);
             } else {
@@ -164,14 +164,14 @@
                 }
                 if (isRoute) {
                     for (Object prop : (Collection<?>) feature.getPropertyValue(Tags.ROUTE_POINTS)) {
-                        writeWayPoint((Feature) prop, Tags.ROUTE_POINTS);
+                        writeWayPoint((AbstractFeature) prop, Tags.ROUTE_POINTS);
                     }
                 } else {
                     for (Object segment : (Collection<?>) feature.getPropertyValue(Tags.TRACK_SEGMENTS)) {
                         if (segment != null) {
                             writer.writeStartElement(Tags.TRACK_SEGMENTS);
-                            for (Object prop : (Collection<?>) ((Feature) segment).getPropertyValue(Tags.TRACK_POINTS)) {
-                                writeWayPoint((Feature) prop, Tags.TRACK_POINTS);
+                            for (Object prop : (Collection<?>) ((AbstractFeature) segment).getPropertyValue(Tags.TRACK_POINTS)) {
+                                writeWayPoint((AbstractFeature) prop, Tags.TRACK_POINTS);
                             }
                             writer.writeEndElement();
                         }
@@ -190,7 +190,7 @@
      * @throws XMLStreamException if underlying STAX writer encounter an error.
      * @throws JAXBException if underlying JAXB marshaller encounter an error.
      */
-    private void writeWayPoint(final Feature feature, final String tagName) throws XMLStreamException, JAXBException {
+    private void writeWayPoint(final AbstractFeature feature, final String tagName) throws XMLStreamException, JAXBException {
         if (feature != null) {
             final double[] pt = Geometries.wrap(feature.getPropertyValue(AttributeConvention.GEOMETRY))
                                            .map(GeometryWrapper::getPointCoordinates).orElse(null);
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/RewriteOnUpdate.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/RewriteOnUpdate.java
index 1a1839b..2841975 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/RewriteOnUpdate.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/RewriteOnUpdate.java
@@ -36,8 +36,8 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.collection.BackingStoreException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -76,7 +76,7 @@
      *
      * @see #filtered()
      */
-    private Stream<? extends Feature> filtered;
+    private Stream<? extends AbstractFeature> filtered;
 
     /**
      * Creates an updater for the given source of features.
@@ -119,7 +119,7 @@
      *
      * @throws DataStoreException if the feature stream cannot be obtained.
      */
-    private Stream<? extends Feature> filtered() throws DataStoreException {
+    private Stream<? extends AbstractFeature> filtered() throws DataStoreException {
         if (filtered == null) {
             filtered = features();
         }
@@ -133,7 +133,7 @@
      * @return all features contained in the dataset.
      * @throws DataStoreException if an error occurred while fetching the features.
      */
-    protected Stream<? extends Feature> features() throws DataStoreException {
+    protected Stream<? extends AbstractFeature> features() throws DataStoreException {
         return source.features(false);
     }
 
@@ -144,9 +144,9 @@
      * @param  features  feature instances to append in the {@code FeatureSet}.
      * @throws DataStoreException if the feature stream cannot be obtained or updated.
      */
-    public void add(final Iterator<? extends Feature> features) throws DataStoreException {
+    public void add(final Iterator<? extends AbstractFeature> features) throws DataStoreException {
         ArgumentChecks.ensureNonNull("features", features);
-        final Stream<? extends Feature> toAdd = StreamSupport.stream(
+        final Stream<? extends AbstractFeature> toAdd = StreamSupport.stream(
                 Spliterators.spliteratorUnknownSize(features, Spliterator.ORDERED), false);
         if (isEmpty()) {
             filtered = toAdd;
@@ -161,7 +161,7 @@
      * @param  filter  a predicate which returns {@code true} for feature instances to be removed.
      * @throws DataStoreException if the feature stream cannot be obtained or updated.
      */
-    public void removeIf(final Predicate<? super Feature> filter) throws DataStoreException {
+    public void removeIf(final Predicate<? super AbstractFeature> filter) throws DataStoreException {
         ArgumentChecks.ensureNonNull("filter", filter);
         if (!isEmpty()) {
             filtered = filtered().filter((feature) -> {
@@ -175,10 +175,10 @@
      * If the given operator returns {@code null}, then the filtered feature is removed.
      *
      * @param  filter   a predicate which returns {@code true} for feature instances to be updated.
-     * @param  updater  operation called for each matching {@link Feature} instance. May return {@code null}.
+     * @param  updater  operation called for each matching {@code Feature} instance. May return {@code null}.
      * @throws DataStoreException if the feature stream cannot be obtained or updated.
      */
-    public void replaceIf(final Predicate<? super Feature> filter, final UnaryOperator<Feature> updater) throws DataStoreException {
+    public void replaceIf(final Predicate<? super AbstractFeature> filter, final UnaryOperator<AbstractFeature> updater) throws DataStoreException {
         ArgumentChecks.ensureNonNull("filter",  filter);
         ArgumentChecks.ensureNonNull("updater", updater);
         if (!isEmpty()) {
@@ -212,7 +212,7 @@
      * @throws DataStoreException if an error occurred.
      */
     public void flush() throws DataStoreException {
-        try (Stream<? extends Feature> content = filtered) {
+        try (Stream<? extends AbstractFeature> content = filtered) {
             if (content != null) {
                 filtered = null;
                 OutputStream temporary = null;
@@ -259,7 +259,7 @@
      */
     @Override
     public void close() {
-        final Stream<? extends Feature> content = filtered;
+        final Stream<? extends AbstractFeature> content = filtered;
         if (content != null) {
             filtered = null;
             content.close();
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java
index 9186fcc..ca5b904 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java
@@ -47,8 +47,8 @@
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -103,7 +103,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants, Spliterator<Feature>, Runnable {
+public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants, Spliterator<AbstractFeature>, Runnable {
     /**
      * The XML stream reader.
      */
@@ -163,7 +163,7 @@
      *         or various {@link RuntimeException} among others.
      */
     @Override
-    public abstract boolean tryAdvance(Consumer<? super Feature> action) throws BackingStoreException;
+    public abstract boolean tryAdvance(Consumer<? super AbstractFeature> action) throws BackingStoreException;
 
     /**
      * Returns {@code null} by default since non-binary XML files are hard to split.
@@ -171,7 +171,7 @@
      * @return {@code null}.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         return null;
     }
 
diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamWriter.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamWriter.java
index 8b1f55a..41c4c33 100644
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamWriter.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamWriter.java
@@ -34,8 +34,8 @@
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -87,7 +87,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public abstract class StaxStreamWriter extends StaxStreamIO implements Consumer<Feature> {
+public abstract class StaxStreamWriter extends StaxStreamIO implements Consumer<AbstractFeature> {
     /**
      * The XML stream writer.
      */
@@ -169,16 +169,16 @@
      *         but also {@link JAXBException} if JAXB is used for marshalling metadata objects,
      *         {@link DataStoreException}, {@link ClassCastException}, <i>etc.</i>
      */
-    public abstract void write(Feature feature) throws Exception;
+    public abstract void write(AbstractFeature feature) throws Exception;
 
     /**
-     * Delegates to {@link #write(Feature)}, wrapping {@code Exception} into unchecked {@code BackingStoreException}.
+     * Delegates to {@code write(Feature)}, wrapping {@code Exception} into unchecked {@code BackingStoreException}.
      *
      * @param  feature  the feature to write.
      * @throws BackingStoreException if an error occurred while writing to the XML file.
      */
     @Override
-    public void accept(final Feature feature) throws BackingStoreException {
+    public void accept(final AbstractFeature feature) throws BackingStoreException {
         try {
             write(feature);
         } catch (BackingStoreException e) {
diff --git a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/MetadataTest.java b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/MetadataTest.java
index 6ea920a..217f8c4 100644
--- a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/MetadataTest.java
+++ b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/MetadataTest.java
@@ -26,6 +26,9 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.TestUtilities.date;
 
+// Specific to the main branch:
+import org.junit.jupiter.api.Disabled;
+
 
 /**
  * Tests the {@link Metadata} class.
@@ -62,6 +65,11 @@
      * @throws URISyntaxException if a {@link Link} element is constructed with an invalid URI.
      */
     @Test
+    @Disabled("Can not execute this test on this branch because it depends on Citation.getOnlineResources() "
+          + "and Identification.getExtents() methods, which are not present in GeoAPI 3.0 interfaces. "
+          + "Despite this test failure, the copy constructor should nevertheless works in practice "
+          + "if the Citation and Identification objects are instances of DefaultCitation or AbstractExtent "
+          + "(the SIS implementations of GeoAPI interfaces).")
     public void testCopyConstructor() throws URISyntaxException {
         final Metadata md1 = create();
         final Metadata md2 = new Metadata(md1, null);
diff --git a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/ReaderTest.java b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/ReaderTest.java
index 79c3efa..5e668db 100644
--- a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/ReaderTest.java
+++ b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/ReaderTest.java
@@ -38,9 +38,9 @@
 import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.FeatureTypeInfo;
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.opengis.util.GenericName;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -194,10 +194,10 @@
          */
         final var content = assertInstanceOf(FeatureCatalogueDescription.class, getSingleton(md.getContentInfo()));
         assertTrue(content.isIncludedWithDataset());
-        final Iterator<? extends FeatureTypeInfo> it = content.getFeatureTypeInfo().iterator();
-        assertStringEquals("Route",    it.next().getFeatureTypeName().tip());
-        assertStringEquals("Track",    it.next().getFeatureTypeName().tip());
-        assertStringEquals("WayPoint", it.next().getFeatureTypeName().tip());
+        final Iterator<? extends GenericName> it = content.getFeatureTypes().iterator();
+        assertStringEquals("Route",    it.next().tip());
+        assertStringEquals("Track",    it.next().tip());
+        assertStringEquals("WayPoint", it.next().tip());
         assertFalse(it.hasNext());
     }
 
@@ -211,8 +211,8 @@
         try (Store reader = create(TestData.V1_0, TestData.WAYPOINT)) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals(StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyPoint(it.next(), 0, false);
                 verifyPoint(it.next(), 1, false);
                 verifyPoint(it.next(), 2, false);
@@ -231,8 +231,8 @@
         try (Store reader = create(TestData.V1_1, TestData.WAYPOINT)) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals(StoreProvider.V1_1, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyPoint(it.next(), 0, true);
                 verifyPoint(it.next(), 1, true);
                 verifyPoint(it.next(), 2, true);
@@ -251,8 +251,8 @@
         try (Store reader = create(TestData.V1_0, TestData.ROUTE)) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals(StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyRoute(it.next(), false, 1);
                 verifyEmpty(it.next(), "rtept");
                 assertFalse(it.hasNext());
@@ -279,8 +279,8 @@
      * This verification is shared by {@link #testRoute110()} and {@link #testSequentialReads()}.
      */
     static void verifyRoute110(final Store reader) throws DataStoreException {
-        try (Stream<Feature> features = reader.features(false)) {
-            final Iterator<Feature> it = features.iterator();
+        try (Stream<AbstractFeature> features = reader.features(false)) {
+            final Iterator<AbstractFeature> it = features.iterator();
             verifyRoute(it.next(), true, 3);
             verifyEmpty(it.next(), "rtept");
             assertFalse(it.hasNext());
@@ -295,7 +295,7 @@
      * @param  numLinks  expected number of links.
      */
     @SuppressWarnings("fallthrough")
-    private static void verifyRoute(final Feature f, final boolean v11, final int numLinks) {
+    private static void verifyRoute(final AbstractFeature f, final boolean v11, final int numLinks) {
         assertEquals("Route name",              f.getPropertyValue("name"));
         assertEquals("Route comment",           f.getPropertyValue("cmt"));
         assertEquals("Route description",       f.getPropertyValue("desc"));
@@ -315,9 +315,9 @@
 
         final List<?> points = assertInstanceOf(List.class, f.getPropertyValue("rtept"));
         assertEquals(3, points.size());
-        verifyPoint((Feature) points.get(0), 0, v11);
-        verifyPoint((Feature) points.get(1), 1, v11);
-        verifyPoint((Feature) points.get(2), 2, v11);
+        verifyPoint((AbstractFeature) points.get(0), 0, v11);
+        verifyPoint((AbstractFeature) points.get(1), 1, v11);
+        verifyPoint((AbstractFeature) points.get(2), 2, v11);
 
         final Polyline p = assertInstanceOf(Polyline.class, f.getPropertyValue("sis:geometry"));
         assertEquals(3, p.getPointCount());
@@ -333,7 +333,7 @@
      * @param  f    the route or track to verify.
      * @param  dep  {@code "rtept"} if verifying a route, or {@code "trkseg"} if verifying a track.
      */
-    private static void verifyEmpty(final Feature f, final String dep) {
+    private static void verifyEmpty(final AbstractFeature f, final String dep) {
         assertNull(f.getPropertyValue("name"));
         assertNull(f.getPropertyValue("cmt"));
         assertNull(f.getPropertyValue("desc"));
@@ -356,8 +356,8 @@
         try (Store reader = create(TestData.V1_0, TestData.TRACK)) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals(StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyTrack(it.next(), false, 1);
                 verifyEmpty(it.next(), "trkseg");
                 assertFalse(it.hasNext());
@@ -375,8 +375,8 @@
         try (Store reader = create(TestData.V1_1, TestData.TRACK)) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals(StoreProvider.V1_1, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyTrack(it.next(), true, 3);
                 verifyEmpty(it.next(), "trkseg");
                 assertFalse(it.hasNext());
@@ -392,7 +392,7 @@
      * @param  numLinks  expected number of links.
      */
     @SuppressWarnings("fallthrough")
-    private static void verifyTrack(final Feature f, final boolean v11, final int numLinks) {
+    private static void verifyTrack(final AbstractFeature f, final boolean v11, final int numLinks) {
         assertEquals("Track name",              f.getPropertyValue("name"));
         assertEquals("Track comment",           f.getPropertyValue("cmt"));
         assertEquals("Track description",       f.getPropertyValue("desc"));
@@ -412,13 +412,13 @@
 
         final List<?> segments = assertInstanceOf(List.class, f.getPropertyValue("trkseg"));
         assertEquals(2, segments.size());
-        final Feature seg1 = (Feature) segments.get(0);
-        final Feature seg2 = (Feature) segments.get(1);
+        final AbstractFeature seg1 = (AbstractFeature) segments.get(0);
+        final AbstractFeature seg2 = (AbstractFeature) segments.get(1);
         final List<?> points = assertInstanceOf(List.class, seg1.getPropertyValue("trkpt"));
         assertEquals(3, points.size());
-        verifyPoint((Feature) points.get(0), 0, v11);
-        verifyPoint((Feature) points.get(1), 1, v11);
-        verifyPoint((Feature) points.get(2), 2, v11);
+        verifyPoint((AbstractFeature) points.get(0), 0, v11);
+        verifyPoint((AbstractFeature) points.get(1), 1, v11);
+        verifyPoint((AbstractFeature) points.get(2), 2, v11);
         assertTrue(assertInstanceOf(Collection.class, seg2.getPropertyValue("trkpt")).isEmpty());
 
         final Polyline p = assertInstanceOf(Polyline.class, f.getPropertyValue("sis:geometry"));
@@ -436,7 +436,7 @@
      * @param  index  index of the point being verified: 0, 1 or 2.
      * @param  v11    {@code true} for GPX 1.1, or {@code false} for GPX 1.0.
      */
-    private static void verifyPoint(final Feature f, final int index, final boolean v11) {
+    private static void verifyPoint(final AbstractFeature f, final int index, final boolean v11) {
         assertEquals(index + 1, f.getPropertyValue("sis:identifier"));
         switch (index) {
             case 0: {
@@ -590,14 +590,14 @@
     @Test
     public void testConcurrentReads() throws DataStoreException {
         try (Store reader = createFromURL()) {
-            final Stream<Feature>   f1 = reader.features(false);
-            final Iterator<Feature> i1 = f1.iterator();
+            final Stream<AbstractFeature>   f1 = reader.features(false);
+            final Iterator<AbstractFeature> i1 = f1.iterator();
             verifyRoute(i1.next(), true, 3);
-            final Stream<Feature>   f2 = reader.features(false);
-            final Iterator<Feature> i2 = f2.iterator();
+            final Stream<AbstractFeature>   f2 = reader.features(false);
+            final Iterator<AbstractFeature> i2 = f2.iterator();
             verifyEmpty(i1.next(), "rtept");
-            final Stream<Feature>   f3 = reader.features(false);
-            final Iterator<Feature> i3 = f3.iterator();
+            final Stream<AbstractFeature>   f3 = reader.features(false);
+            final Iterator<AbstractFeature> i3 = f3.iterator();
             verifyRoute(i2.next(), true, 3);
             verifyRoute(i3.next(), true, 3);
             verifyEmpty(i3.next(), "rtept");
diff --git a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java
index 589e94d..24db12e 100644
--- a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java
+++ b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java
@@ -27,9 +27,9 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -62,8 +62,8 @@
     /**
      * Verifies that all designations and definitions can be read from the resources.
      */
-    private static void testResources(final FeatureType type) {
-        for (final PropertyType p : type.getProperties(false)) {
+    private static void testResources(final DefaultFeatureType type) {
+        for (final AbstractIdentifiedType p : type.getProperties(false)) {
             final GenericName name = p.getName();
             if (!AttributeConvention.contains(name)) {
                 final String label = name.toString();
diff --git a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/UpdaterTest.java b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/UpdaterTest.java
index 02ee8f6..4d4a02a 100644
--- a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/UpdaterTest.java
+++ b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/UpdaterTest.java
@@ -38,8 +38,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -110,9 +110,9 @@
     public void testWriteEmpty() throws DataStoreException, IOException {
         try (final WritableStore store = create()) {
             final Types types = store.types;
-            final Feature point1 = types.wayPoint.newInstance();
-            final Feature point2 = types.wayPoint.newInstance();
-            final Feature point3 = types.wayPoint.newInstance();
+            final AbstractFeature point1 = types.wayPoint.newInstance();
+            final AbstractFeature point2 = types.wayPoint.newInstance();
+            final AbstractFeature point3 = types.wayPoint.newInstance();
             point1.setPropertyValue("sis:geometry", new Point(15, 10));
             point2.setPropertyValue("sis:geometry", new Point(25, 20));
             point3.setPropertyValue("sis:geometry", new Point(35, 30));
diff --git a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/WriterTest.java b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/WriterTest.java
index 0f3c9ac..4e1e104 100644
--- a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/WriterTest.java
+++ b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/WriterTest.java
@@ -36,8 +36,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -233,7 +233,7 @@
          * Way Points as defined in "waypoint.xml" test file.
          * Appear also in "route.xml" and "track.xml" files.
          */
-        final Feature point1 = types.wayPoint.newInstance();
+        final AbstractFeature point1 = types.wayPoint.newInstance();
         point1.setPropertyValue("sis:geometry",  new Point(15, 10));
         point1.setPropertyValue("time",          Instant.parse("2010-01-10T00:00:00Z"));
         point1.setPropertyValue("name",          "first point");
@@ -255,7 +255,7 @@
         point1.setPropertyValue("link",          List.of(new Link(new URI("http://first-address1.org")),
                                                          new Link(new URI("http://first-address2.org")),
                                                          new Link(new URI("http://first-address3.org"))));
-        final Feature point3 = types.wayPoint.newInstance();
+        final AbstractFeature point3 = types.wayPoint.newInstance();
         point3.setPropertyValue("sis:geometry",  new Point(35, 30));
         point3.setPropertyValue("time",          Instant.parse("2010-01-30T00:00:00Z"));
         point3.setPropertyValue("name",          "third point");
@@ -276,17 +276,17 @@
         point3.setPropertyValue("fix",           Fix.THREE_DIMENSIONAL);
         point3.setPropertyValue("link",          List.of(new Link(new URI("http://third-address1.org")),
                                                          new Link(new URI("http://third-address2.org"))));
-        final Feature point2 = types.wayPoint.newInstance();
+        final AbstractFeature point2 = types.wayPoint.newInstance();
         point2.setPropertyValue("sis:geometry", new Point(25, 20));
-        final List<Feature> wayPoints = List.of(point1, point2, point3);
-        final List<Feature> features;
+        final List<AbstractFeature> wayPoints = List.of(point1, point2, point3);
+        final List<AbstractFeature> features;
         switch (type) {
             case WAY_POINT: {
                 features = wayPoints;
                 break;
             }
             case ROUTE: {
-                final Feature route1 = types.route.newInstance();
+                final AbstractFeature route1 = types.route.newInstance();
                 route1.setPropertyValue("name",   "Route name");
                 route1.setPropertyValue("cmt",    "Route comment");
                 route1.setPropertyValue("desc",   "Route description");
@@ -297,16 +297,16 @@
                 route1.setPropertyValue("link",   List.of(new Link(new URI("http://route-address1.org")),
                                                           new Link(new URI("http://route-address2.org")),
                                                           new Link(new URI("http://route-address3.org"))));
-                final Feature route2 = types.route.newInstance();
+                final AbstractFeature route2 = types.route.newInstance();
                 features = List.of(route1, route2);
                 break;
             }
             case TRACK: {
-                final Feature seg1 = types.trackSegment.newInstance();
-                final Feature seg2 = types.trackSegment.newInstance();
+                final AbstractFeature seg1 = types.trackSegment.newInstance();
+                final AbstractFeature seg2 = types.trackSegment.newInstance();
                 seg1.setPropertyValue("trkpt", wayPoints);
 
-                final Feature track1 = types.track.newInstance();
+                final AbstractFeature track1 = types.track.newInstance();
                 track1.setPropertyValue("name",   "Track name");
                 track1.setPropertyValue("cmt",    "Track comment");
                 track1.setPropertyValue("desc",   "Track description");
@@ -317,7 +317,7 @@
                 track1.setPropertyValue("link",   List.of(new Link(new URI("http://track-address1.org")),
                                                           new Link(new URI("http://track-address2.org")),
                                                           new Link(new URI("http://track-address3.org"))));
-                final Feature track2 = types.track.newInstance();
+                final AbstractFeature track2 = types.track.newInstance();
                 features = List.of(track1, track2);
                 break;
             }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractFeatureSet.java
index 56ca921..d80d8d7 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractFeatureSet.java
@@ -23,8 +23,8 @@
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.base.MetadataBuilder;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -93,7 +93,7 @@
      */
     @Override
     public Optional<GenericName> getIdentifier() throws DataStoreException {
-        final FeatureType type = getType();
+        final DefaultFeatureType type = getType();
         return (type != null) ? Optional.of(type.getName()) : Optional.empty();
     }
 
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Aggregate.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Aggregate.java
index d864d97..5890799 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Aggregate.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Aggregate.java
@@ -49,10 +49,10 @@
  * {@link org.apache.sis.metadata.iso.DefaultMetadata#getMetadataScopes() metadataScope} /
  * {@link org.apache.sis.metadata.iso.DefaultMetadataScope#getResourceScope() resourceScope} sets to
  * {@link org.opengis.metadata.maintenance.ScopeCode#SERIES} or
- * {@link org.opengis.metadata.maintenance.ScopeCode#INITIATIVE} if applicable.
+ * {@code ScopeCode.INITIATIVE} if applicable.
  * If not too expensive to compute, the names of all components should be listed as
  * {@linkplain org.apache.sis.metadata.iso.identification.AbstractIdentification#getAssociatedResources()
- * associated resources} with an {@link org.opengis.metadata.identification.AssociationType#IS_COMPOSED_OF} relation.
+ * associated resources} with an {@code AssociationType.IS_COMPOSED_OF} relation.
  *
  * @author  Johann Sorel (Geomatys)
  * @version 1.0
@@ -68,7 +68,7 @@
      * <blockquote><code><b>this</b>.metadata</code> /
      * {@link org.apache.sis.metadata.iso.DefaultMetadata#getIdentificationInfo() identificationInfo} /
      * {@link org.apache.sis.metadata.iso.identification.AbstractIdentification#getAssociatedResources() associatedResource}
-     * with {@link org.opengis.metadata.identification.AssociationType#IS_COMPOSED_OF}</blockquote>
+     * with {@code AssociationType.IS_COMPOSED_OF}</blockquote>
      *
      * The name of each child resource in the returned collection is given by the following metadata element:
      *
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
index e6aa5a8..c6bc6ce 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
@@ -145,7 +145,7 @@
         List<double[]> resolutions = source.getResolutions();
         if (reduction != null) {
             JDK16.toList(resolutions.stream()
-                    .map((resolution) -> reduction.apply(new DirectPositionView.Double(resolution)).getCoordinates()));
+                    .map((resolution) -> reduction.apply(new DirectPositionView.Double(resolution)).getCoordinate()));
         }
         return resolutions;
     }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java
index db8b5ba..0515eea 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java
@@ -41,6 +41,9 @@
 import org.apache.sis.storage.event.StoreListener;
 import org.apache.sis.storage.event.StoreListeners;
 
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Manages a series of features, coverages or sensor data.
@@ -337,12 +340,12 @@
                 }
             }
             if (citation != null) {
-                Identifier first = null;
+                ReferenceIdentifier first = null;
                 for (final Identifier c : citation.getIdentifiers()) {
                     if (c instanceof GenericName) {
                         return Optional.of((GenericName) c);
-                    } else if (first == null) {
-                        first = c;
+                    } else if (first == null && c instanceof ReferenceIdentifier) {
+                        first = (ReferenceIdentifier) c;
                     }
                 }
                 if (first != null) {
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureNaming.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureNaming.java
index 92d0951..d43ad2c 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureNaming.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureNaming.java
@@ -31,8 +31,8 @@
 
 /**
  * Helper class for mapping {@link GenericName} instances and their shortened names to features.
- * The features are typically represented by instances of {@link org.opengis.feature.FeatureType}
- * or {@link org.opengis.coverage.Coverage} (sometimes seen as a kind of features), but this class
+ * The features are typically represented by instances of {@code FeatureType}
+ * or {@code Coverage} (sometimes seen as a kind of features), but this class
  * actually puts no restriction on the kind of object associated to {@code GenericName}s;
  * {@link DataStore} implementations are free to choose their internal object.
  * Those objects can be stored and fetched using the {@code String} representation of their name
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
index 03c96f1..70896c5 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
@@ -46,20 +46,17 @@
 import org.apache.sis.util.iso.Names;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Operation;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.SortBy;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
+import org.apache.sis.pending.geoapi.filter.SortBy;
+import org.apache.sis.pending.geoapi.filter.SortProperty;
 
 
 /**
@@ -116,7 +113,7 @@
      * @see #setSelection(Filter)
      */
     @SuppressWarnings("serial")                 // Most SIS implementations are serializable.
-    private Filter<? super Feature> selection;
+    private Filter<? super AbstractFeature> selection;
 
     /**
      * The number of feature instances to skip from the beginning.
@@ -145,7 +142,7 @@
      * @see #setSortBy(SortBy)
      */
     @SuppressWarnings("serial")                 // Most SIS implementations are serializable.
-    private SortBy<Feature> sortBy;
+    private SortBy<AbstractFeature> sortBy;
 
     /**
      * Hint used by resources to optimize returned features.
@@ -177,7 +174,7 @@
     public void setProjection(final String... properties) {
         NamedExpression[] wrappers = null;
         if (properties != null) {
-            final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+            final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
             wrappers = new NamedExpression[properties.length];
             for (int i=0; i<wrappers.length; i++) {
                 final String p = properties[i];
@@ -197,12 +194,12 @@
      * @throws IllegalArgumentException if a property is duplicated.
      */
     @SafeVarargs
-    public final void setProjection(final Expression<? super Feature, ?>... properties) {
+    public final void setProjection(final Expression<? super AbstractFeature, ?>... 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<? super AbstractFeature, ?> e = properties[i];
                 ArgumentChecks.ensureNonNullElement("properties", i, e);
                 wrappers[i] = new NamedExpression(e);
             }
@@ -282,9 +279,9 @@
      */
     @Override
     public void setSelection(final Envelope domain) {
-        Filter<Feature> filter = null;
+        Filter<AbstractFeature> filter = null;
         if (domain != null) {
-            final FilterFactory<Feature,Object,?> ff = DefaultFilterFactory.forFeatures();
+            final DefaultFilterFactory<AbstractFeature,Object,?> ff = DefaultFilterFactory.forFeatures();
             filter = ff.bbox(ff.property(AttributeConvention.GEOMETRY), domain);
         }
         setSelection(filter);
@@ -297,7 +294,7 @@
      *
      * @param  selection  the filter, or {@code null} if none.
      */
-    public void setSelection(final Filter<? super Feature> selection) {
+    public void setSelection(final Filter<? super AbstractFeature> selection) {
         this.selection = selection;
     }
 
@@ -308,7 +305,7 @@
      *
      * @return the filter, or {@code null} if none.
      */
-    public Filter<? super Feature> getSelection() {
+    public Filter<? super AbstractFeature> getSelection() {
         return selection;
     }
 
@@ -371,16 +368,18 @@
 
     /**
      * Sets the expressions to use for sorting the feature instances.
-     * {@code SortBy} objects are used to order the {@link Feature} instances returned by the {@link FeatureSet}.
+     * {@code SortBy} objects are used to order the {@code Feature} instances returned by the {@link FeatureSet}.
      * {@code SortBy} clauses are applied in declaration order, like SQL.
      *
      * @param  properties  expressions to use for sorting the feature instances,
      *                     or {@code null} or an empty array if none.
+     *
+     * @todo Not yet in public API. Pending publication of {@link SortProperty} interface.
      */
     @SafeVarargs
     @SuppressWarnings("varargs")
-    public final void setSortBy(final SortProperty<Feature>... properties) {
-        SortBy<Feature> sortBy = null;
+    final void setSortBy(final SortProperty<AbstractFeature>... properties) {
+        SortBy<AbstractFeature> sortBy = null;
         if (properties != null) {
             sortBy = SortByComparator.create(properties);
         }
@@ -389,11 +388,13 @@
 
     /**
      * Sets the expressions to use for sorting the feature instances.
-     * {@code SortBy} objects are used to order the {@link Feature} instances returned by the {@link FeatureSet}.
+     * {@code SortBy} objects are used to order the {@code Feature} instances returned by the {@link FeatureSet}.
      *
      * @param  sortBy  expressions to use for sorting the feature instances, or {@code null} if none.
+     *
+     * @todo Not yet in public API. Pending publication of {@link SortProperty} interface.
      */
-    public void setSortBy(final SortBy<Feature> sortBy) {
+    final void setSortBy(final SortBy<AbstractFeature> sortBy) {
         this.sortBy = sortBy;
     }
 
@@ -402,8 +403,10 @@
      * This is the value specified in the last call to {@link #setSortBy(SortBy)}.
      *
      * @return expressions to use for sorting the feature instances, or {@code null} if none.
+     *
+     * @todo Not yet in public API. Pending publication of {@link SortProperty} interface.
      */
-    public SortBy<Feature> getSortBy() {
+    final SortBy<AbstractFeature> getSortBy() {
         return sortBy;
     }
 
@@ -430,8 +433,8 @@
     /**
      * Whether a property evaluated by a query is computed on the fly or stored.
      * By default, an expression is evaluated only once for each feature instance,
-     * then the result is stored as a feature {@link Attribute} value.
-     * But the same expression can also be wrapped in a feature {@link Operation}
+     * then the result is stored as a feature {@link AbstractAttribute} value.
+     * But the same expression can also be wrapped in a feature {@link AbstractOperation}
      * and evaluated every times that the value is requested.
      *
      * <h2>Analogy with relational databases</h2>
@@ -448,7 +451,7 @@
         /**
          * The expression is evaluated exactly once when a feature instance is created,
          * and the result is stored as a feature attribute.
-         * The feature property type will be {@link Attribute} and its value will be modifiable.
+         * The feature property type will be {@link AbstractAttribute} and its value will be modifiable.
          * This is the default projection type.
          *
          * <h4>Feature instances in expression evaluation</h4>
@@ -479,7 +482,7 @@
 
         /**
          * The expression is evaluated every times that the property value is requested.
-         * The feature property type will be {@link Operation}.
+         * The feature property type will be {@link AbstractOperation}.
          * This projection type may be preferable to {@link #STORED} in the following circumstances:
          *
          * <ul>
@@ -532,7 +535,7 @@
          * Never {@code null}.
          */
         @SuppressWarnings("serial")
-        public final Expression<? super Feature, ?> expression;
+        public final Expression<? super AbstractFeature, ?> expression;
 
         /**
          * The name to assign to the expression result, or {@code null} if unspecified.
@@ -542,8 +545,8 @@
 
         /**
          * Whether the expression result should be stored or evaluated every times that it is requested.
-         * A stored value will exist as a feature {@link Attribute}, while a virtual value will exist as
-         * a feature {@link Operation}. The latter are commonly called "computed fields" and are equivalent
+         * A stored value will exist as a feature {@link AbstractAttribute}, while a virtual value will exist as
+         * a feature {@link AbstractOperation}. The latter are commonly called "computed fields" and are equivalent
          * to SQL {@code GENERATED ALWAYS} keyword for columns.
          *
          * @since 1.4
@@ -555,7 +558,7 @@
          *
          * @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<? super AbstractFeature, ?> expression) {
             this(expression, (GenericName) null);
         }
 
@@ -565,7 +568,7 @@
          * @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<? super AbstractFeature,?> expression, final GenericName alias) {
             this(expression, alias, ProjectionType.STORED);
         }
 
@@ -576,7 +579,7 @@
          * @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<? super AbstractFeature,?> expression, final String alias) {
             this.expression = Objects.requireNonNull(expression);
             this.alias = (alias != null) ? Names.createLocalName(null, null, alias) : null;
             this.type = ProjectionType.STORED;
@@ -587,11 +590,11 @@
          *
          * @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.
-         * @param type        whether to create a feature {@link Attribute} or a feature {@link Operation}.
+         * @param type        whether to create a feature {@link AbstractAttribute} or a feature {@link AbstractOperation}.
          *
          * @since 1.4
          */
-        public NamedExpression(final Expression<? super Feature,?> expression, final GenericName alias, ProjectionType type) {
+        public NamedExpression(final Expression<? super AbstractFeature,?> expression, final GenericName alias, ProjectionType type) {
             this.expression = Objects.requireNonNull(expression);
             this.type       = Objects.requireNonNull(type);
             this.alias      = alias;
@@ -719,10 +722,10 @@
      * @return type resulting from expressions evaluation (never null).
      * @throws IllegalArgumentException if this method can operate only on some feature types
      *         and the given type is not one of them.
-     * @throws InvalidFilterValueException if this method cannot determine the result type of an expression
+     * @throws IllegalArgumentException if this method cannot determine the result type of an expression
      *         in this query. It may be because that expression is backed by an unsupported implementation.
      */
-    final FeatureType expectedType(final FeatureType valueType) {
+    final DefaultFeatureType expectedType(final DefaultFeatureType valueType) {
         if (projection == null) {
             return valueType;           // All columns included: result is of the same type.
         }
@@ -735,11 +738,11 @@
              * For each property, get the expected type (mandatory) and its name (optional).
              * A default name will be computed if no alias was explicitly given by the user.
              */
-            final Expression<? super Feature,?> expression = item.expression;
+            final Expression<? super AbstractFeature,?> expression = item.expression;
             final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(expression);
             final PropertyTypeBuilder resultType;
             if (fex == null || (resultType = fex.expectedType(valueType, ftb)) == null) {
-                throw new InvalidFilterValueException(Resources.format(Resources.Keys.InvalidExpression_2,
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.InvalidExpression_2,
                             expression.getFunctionName().toInternationalString(), column));
             }
             GenericName name = item.alias;
@@ -798,7 +801,7 @@
              */
             if (item.type == ProjectionType.COMPUTING && resultType instanceof AttributeTypeBuilder<?>) {
                 final var ab = (AttributeTypeBuilder<?>) resultType;
-                final AttributeType<?> storedType = ab.build();
+                final DefaultAttributeType<?> storedType = ab.build();
                 if (ftb.properties().remove(resultType)) {
                     final var properties = Map.of(AbstractOperation.NAME_KEY, name);
                     ftb.addProperty(FeatureOperations.expression(properties, expression, storedType));
@@ -886,7 +889,7 @@
         }
         if (sortBy != null) {
             String separator = " ORDER BY ";
-            for (final SortProperty<Feature> p : sortBy.getSortProperties()) {
+            for (final SortProperty<AbstractFeature> p : sortBy.getSortProperties()) {
                 sb.append(separator);
                 separator = ", ";
                 sb.append(p.getValueReference().getXPath()).append(' ').append(p.getSortOrder());
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSet.java
index b500df4..df29fd1 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSet.java
@@ -19,9 +19,9 @@
 import java.util.Objects;
 import java.util.stream.Stream;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -67,14 +67,14 @@
      * @return description of common properties (never {@code null}).
      * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
      */
-    FeatureType getType() throws DataStoreException;
+    DefaultFeatureType getType() throws DataStoreException;
 
     /**
      * Requests a subset of features and/or feature properties from this resource.
      * The filtering can be applied in two domains:
      *
      * <ul>
-     *   <li>The returned {@code FeatureSet} may contain a smaller number of {@link Feature} instances.</li>
+     *   <li>The returned {@code FeatureSet} may contain a smaller number of {@code Feature} instances.</li>
      *   <li>In each {@code Feature} instance of the returned set, the number of
      *       {@linkplain org.apache.sis.feature.DefaultFeatureType#getProperty properties} may be smaller.</li>
      * </ul>
@@ -146,5 +146,5 @@
      * @return all features contained in this dataset.
      * @throws DataStoreException if an error occurred while creating the stream.
      */
-    Stream<Feature> features(boolean parallel) throws DataStoreException;
+    Stream<AbstractFeature> features(boolean parallel) throws DataStoreException;
 }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSubset.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSubset.java
index ce6e956..a01e984 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSubset.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSubset.java
@@ -24,12 +24,12 @@
 import org.apache.sis.storage.base.StoreUtilities;
 import org.apache.sis.storage.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.SortBy;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.pending.geoapi.filter.SortBy;
 
 
 /**
@@ -56,7 +56,7 @@
      * The type of features in this set. May or may not be the same as {@link #source}.
      * This is computed when first needed.
      */
-    private FeatureType resultType;
+    private DefaultFeatureType resultType;
 
     /**
      * Creates a new set of features by filtering the given set using the given query.
@@ -86,9 +86,9 @@
      * Returns a description of properties that are common to all features in this dataset.
      */
     @Override
-    public synchronized FeatureType getType() throws DataStoreException {
+    public synchronized DefaultFeatureType getType() throws DataStoreException {
         if (resultType == null) {
-            final FeatureType type = source.getType();
+            final DefaultFeatureType type = source.getType();
             try {
                 resultType = query.expectedType(type);
             } catch (IllegalArgumentException e) {
@@ -103,19 +103,19 @@
      * Returns a stream of all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
-        Stream<Feature> stream = source.features(parallel);
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
+        Stream<AbstractFeature> stream = source.features(parallel);
         /*
          * Apply filter.
          */
-        final Filter<? super Feature> selection = query.getSelection();
+        final Filter<? super AbstractFeature> selection = query.getSelection();
         if (selection != null && !selection.equals(Filter.include())) {
             stream = stream.filter(selection);
         }
         /*
          * Apply sorting.
          */
-        final SortBy<Feature> sortBy = query.getSortBy();
+        final SortBy<AbstractFeature> sortBy = query.getSortBy();
         if (sortBy != null) {
             stream = stream.sorted(sortBy);
         }
@@ -140,14 +140,14 @@
         final FeatureQuery.NamedExpression[] projection = query.getStoredProjection();
         if (projection != null) {
             @SuppressWarnings({"unchecked", "rawtypes"})
-            final Expression<? super Feature,?>[] expressions = new Expression[projection.length];
+            final Expression<? super AbstractFeature,?>[] expressions = new Expression[projection.length];
             for (int i=0; i<expressions.length; i++) {
                 expressions[i] = projection[i].expression;
             }
-            final FeatureType type = getType();
+            final DefaultFeatureType type = getType();
             final String[] names = FeatureUtilities.getNames(type.getProperties(false));
             stream = stream.map(t -> {
-                final Feature f = type.newInstance();
+                final AbstractFeature f = type.newInstance();
                 for (int i=0; i < expressions.length; i++) {
                     f.setPropertyValue(names[i], expressions[i].apply(t));
                 }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IllegalFeatureTypeException.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IllegalFeatureTypeException.java
index e477d4d..9cebd43 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IllegalFeatureTypeException.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IllegalFeatureTypeException.java
@@ -23,7 +23,7 @@
 
 /**
  * Thrown when a store cannot write the given feature because its type is not one of the supported types.
- * The {@link org.opengis.feature.FeatureType} is given by {@link org.opengis.feature.Feature#getType()},
+ * The {@code FeatureType} is given by {@code Feature.getType()},
  * and the type expected by the data store is given by {@link FeatureSet#getType()}. Those two values must
  * match, except when the type of the feature set is {@linkplain WritableFeatureSet#updateType updated}.
  *
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Query.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Query.java
index 05b3936..1b16f94 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Query.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Query.java
@@ -18,9 +18,6 @@
 
 import org.opengis.geometry.Envelope;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.QueryExpression;
-
 
 /**
  * Definition of filtering to apply for fetching a resource subset.
@@ -52,7 +49,7 @@
  *
  * @since 0.8
  */
-public abstract class Query implements QueryExpression {
+public abstract class Query {
     /**
      * Creates a new, initially empty, query.
      */
@@ -61,7 +58,7 @@
 
     /**
      * Sets the approximate area of feature instances or pixels to include in the subset.
-     * For feature set, the domain is materialized by a {@link org.opengis.filter.Filter}.
+     * For feature set, the domain is materialized by a {@link org.apache.sis.filter.Filter}.
      * For grid coverage resource, the given envelope specifies the coverage domain.
      *
      * <p>The given envelope is approximate.
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/RasterLoadingStrategy.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/RasterLoadingStrategy.java
index 7fc0019..37ad65b 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/RasterLoadingStrategy.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/RasterLoadingStrategy.java
@@ -23,8 +23,8 @@
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridCoverage;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java
index ab15572..8c753dc 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java
@@ -109,7 +109,7 @@
      *       {@link org.opengis.metadata.maintenance.ScopeCode#DATASET} if the resource is a {@link DataSet}, or
      *       {@link org.opengis.metadata.maintenance.ScopeCode#SERVICE} if the resource is a web service, or
      *       {@link org.opengis.metadata.maintenance.ScopeCode#SERIES} or
-     *       {@link org.opengis.metadata.maintenance.ScopeCode#INITIATIVE}
+     *       {@code ScopeCode.INITIATIVE}
      *       if the resource is an {@link Aggregate} other than a transfer aggregate.</li>
      *   <li>{@code metadata} /
      *       {@link org.apache.sis.metadata.iso.DefaultMetadata#getContentInfo() contentInfo} /
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/WritableFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/WritableFeatureSet.java
index 06ce25d..ee46e96 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/WritableFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/WritableFeatureSet.java
@@ -21,9 +21,9 @@
 import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -50,12 +50,12 @@
      * @throws IllegalFeatureTypeException if the given type is not compatible with the types supported by the store.
      * @throws DataStoreException if another error occurred while changing the feature type.
      */
-    void updateType(FeatureType newType) throws DataStoreException;
+    void updateType(DefaultFeatureType newType) throws DataStoreException;
 
     /**
      * Inserts new feature instances in this {@code FeatureSet}.
      * Any feature already present in this {@link FeatureSet} will remain unmodified.
-     * If a {@linkplain Feature#getProperty feature property} is used as unique identifier, then:
+     * If a {@linkplain AbstractFeature#getProperty feature property} is used as unique identifier, then:
      *
      * <ul>
      *   <li>If a given feature assigns to that property a value already in use, an exception will be thrown.</li>
@@ -75,7 +75,7 @@
      * @throws IllegalFeatureTypeException if a feature given by the iterator is not of the type expected by this {@code FeatureSet}.
      * @throws DataStoreException if another error occurred while storing new features.
      */
-    void add(Iterator<? extends Feature> features) throws DataStoreException;
+    void add(Iterator<? extends AbstractFeature> features) throws DataStoreException;
 
     /**
      * Removes all feature instances from this {@code FeatureSet} which matches the given predicate.
@@ -83,25 +83,25 @@
      * @param  filter  a predicate which returns {@code true} for feature instances to be removed.
      * @throws DataStoreException if an error occurred while removing features.
      */
-    void removeIf(Predicate<? super Feature> filter) throws DataStoreException;
+    void removeIf(Predicate<? super AbstractFeature> filter) throws DataStoreException;
 
     /**
      * Updates all feature instances from this {@code FeatureSet} which match the given predicate.
-     * For each {@link Feature} instance matching the given {@link Predicate},
+     * For each {@code Feature} instance matching the given {@link Predicate},
      * the <code>{@linkplain UnaryOperator#apply UnaryOperator.apply(Feature)}</code> method will be invoked.
      * {@code UnaryOperator}s are free to modify the given {@code Feature} <i>in-place</i>
      * or to return a different feature instance. Two behaviors are possible:
      *
      * <ul>
-     *   <li>If the operator returns a non-null {@link Feature}, then the modified feature is stored
+     *   <li>If the operator returns a non-null {@code Feature}, then the modified feature is stored
      *       in replacement of the previous feature (not necessarily at the same location).</li>
      *   <li>If the operator returns {@code null}, then the feature will be removed from the {@code FeatureSet}.</li>
      * </ul>
      *
      * @param  filter   a predicate which returns {@code true} for feature instances to be updated.
-     * @param  updater  operation called for each matching {@link Feature} instance.
+     * @param  updater  operation called for each matching {@code Feature} instance.
      * @throws IllegalFeatureTypeException if a feature given by the operator is not of the type expected by this {@code FeatureSet}.
      * @throws DataStoreException if another error occurred while replacing features.
      */
-    void replaceIf(Predicate<? super Feature> filter, UnaryOperator<Feature> updater) throws DataStoreException;
+    void replaceIf(Predicate<? super AbstractFeature> filter, UnaryOperator<AbstractFeature> updater) throws DataStoreException;
 }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java
index 63b180e..b2e4e99 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java
@@ -33,8 +33,8 @@
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.base.MetadataBuilder;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -158,7 +158,7 @@
         final MetadataBuilder metadata = new MetadataBuilder();
         metadata.addDefaultMetadata(this, listeners);
         for (final FeatureSet fs : dependencies()) {
-            final FeatureType type = fs.getType();
+            final DefaultFeatureType type = fs.getType();
             metadata.addSource(fs.getMetadata(), ScopeCode.FEATURE_TYPE,
                     (type == null) ? null : new CharSequence[] {type.getName().toInternationalString()});
         }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
index f7a5d84..e2adb3b 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
@@ -35,9 +35,9 @@
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 import org.apache.sis.storage.internal.Resources;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -69,7 +69,7 @@
     /**
      * The most specific feature type common to all feature sets in the {@linkplain #sources} list.
      */
-    private final FeatureType commonType;
+    private final DefaultFeatureType commonType;
 
     /**
      * Creates a new concatenated feature set with the same types as the given feature set,
@@ -98,7 +98,7 @@
             ArgumentChecks.ensureNonNullElement("sources", i, sources[i]);
         }
         this.sources = UnmodifiableArrayList.wrap(sources);
-        final FeatureType[] types = new FeatureType[sources.length];
+        final DefaultFeatureType[] types = new DefaultFeatureType[sources.length];
         for (int i=0; i<types.length; i++) {
             types[i] = sources[i].getType();
         }
@@ -169,7 +169,7 @@
      * @return the common type of all features returned by this set.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return commonType;
     }
 
@@ -213,7 +213,7 @@
      * @return all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) {
+    public Stream<AbstractFeature> features(final boolean parallel) {
         final Stream<FeatureSet> sets = parallel ? sources.parallelStream() : sources.stream();
         return sets.flatMap(set -> {
             try {
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
index 7ce1c79..ae0591a 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
@@ -34,8 +34,8 @@
 import org.apache.sis.util.logging.Logging;
 import static org.apache.sis.coverage.privy.ImageUtilities.LOGGER;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/DimensionAppender.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/DimensionAppender.java
index 831436a..41ac261 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/DimensionAppender.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/DimensionAppender.java
@@ -42,9 +42,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.apache.sis.util.ArraysExt;
-
 
 /**
  * A wrapper over an existing grid coverage resource with dimensions appended.
@@ -228,7 +225,10 @@
         final var sb = new StringBuilder(40);
         sb.append(source).append(" + dimensions[");
         final GridExtent extent = dimToAdd.getExtent();
-        final double[] coordinates = ArraysExt.copyAsDoubles(extent.getLow().getCoordinateValues());
+        final double[] coordinates = new double[extent.getDimension()];
+        for (int i=0; i<coordinates.length; i++) {
+            coordinates[i] = extent.getLow(i);
+        }
         try {
             dimToAdd.getGridToCRS(PixelInCell.CELL_CORNER).transform(coordinates, 0, coordinates, 0, 1);
         } catch (RuntimeException | TransformException e) {
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java
index 4c53391..93c94f9 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java
@@ -37,15 +37,13 @@
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.filter.DefaultFilterFactory;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Expression;
-import org.opengis.filter.BinaryComparisonOperator;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.BinaryComparisonOperator;
 
 
 /**
@@ -135,7 +133,7 @@
     /**
      * The type of features included in this set. Contains two associations as described in class javadoc.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * The first source of features.
@@ -176,12 +174,12 @@
      * 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<Feature> condition;
+    public final BinaryComparisonOperator<AbstractFeature> condition;
 
     /**
      * The factory to use for creating {@code Query} expressions for retrieving subsets of feature sets.
      */
-    private final FilterFactory<Feature,?,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,?,?> factory;
 
     /**
      * Creates a new feature set joining the two given sets. The {@code featureInfo} map defines the name,
@@ -202,19 +200,19 @@
      * @param  rightAlias   name of the associations to the {@code right} features, or {@code null} for a default name.
      * @param  joinType     whether values on both sides are required (inner join), or only one side (outer join).
      * @param  condition    join condition as <var>property from left feature</var> = <var>property from right feature</var>.
-     * @param  featureInfo  information about the {@link FeatureType} of this feature set.
+     * @param  featureInfo  information about the {@code FeatureType} of this feature set.
      * @throws DataStoreException if an error occurred while creating the feature set.
      */
     public JoinFeatureSet(final Resource parent,
                           final FeatureSet left,  String leftAlias,
                           final FeatureSet right, String rightAlias,
-                          final Type joinType, final BinaryComparisonOperator<Feature> condition,
+                          final Type joinType, final BinaryComparisonOperator<AbstractFeature> condition,
                           Map<String,?> featureInfo)
             throws DataStoreException
     {
         super(parent);
-        final FeatureType leftType  = left.getType();
-        final FeatureType rightType = right.getType();
+        final DefaultFeatureType leftType  = left.getType();
+        final DefaultFeatureType rightType = right.getType();
         final GenericName leftName  = leftType.getName();
         final GenericName rightName = rightType.getName();
         if (leftAlias  == null) leftAlias  = leftName.toString();
@@ -231,7 +229,7 @@
          * We could build the FeatureType only when first needed, but the type is required by the iterators.
          * Since we are going to need the type for any use of this JoinFeatureSet, better to create it now.
          */
-        PropertyType[] properties = new PropertyType[] {
+        AbstractIdentifiedType[] properties = new AbstractIdentifiedType[] {
             new DefaultAssociationRole(properties(leftAlias),  leftType,  joinType.minimumOccurs(false), 1),
             new DefaultAssociationRole(properties(rightAlias), rightType, joinType.minimumOccurs(true),  1)
         };
@@ -239,7 +237,7 @@
         if (identifierDelimiter != null && AttributeConvention.hasIdentifier(leftType)
                                         && AttributeConvention.hasIdentifier(rightType))
         {
-            final Operation identifier = FeatureOperations.compound(
+            final AbstractOperation identifier = FeatureOperations.compound(
                     properties(AttributeConvention.IDENTIFIER_PROPERTY), identifierDelimiter,
                     Containers.property(featureInfo, "identifierPrefix", String.class),
                     Containers.property(featureInfo, "identifierSuffix", String.class), properties);
@@ -290,7 +288,7 @@
      * @return a description of properties that are common to all features in this dataset.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -302,7 +300,7 @@
      * @throws DataStoreException if an error occurred while creating the stream.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         final Iterator it = new Iterator();
         return StreamSupport.stream(it, parallel).onClose(it);
     }
@@ -311,13 +309,13 @@
      * Creates a new features containing an association to the two given features.
      * The {@code main} feature cannot be null (this is not verified).
      */
-    private Feature join(Feature main, Feature filtered) {
+    private AbstractFeature join(AbstractFeature main, AbstractFeature filtered) {
         if (swapSides) {
-            final Feature t = main;
+            final AbstractFeature t = main;
             main = filtered;
             filtered = t;
         }
-        final Feature f = type.newInstance();
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue(leftName,  main);
         f.setPropertyValue(rightName, filtered);
         return f;
@@ -327,7 +325,7 @@
      * Iterator over the features resulting from the inner or outer join operation.
      * The {@link #run()} method disposes the resources.
      */
-    private final class Iterator implements Spliterator<Feature>, Consumer<Feature>, Runnable {
+    private final class Iterator implements Spliterator<AbstractFeature>, Consumer<AbstractFeature>, Runnable {
         /**
          * The main stream or a split iterator to close when the {@link #run()} method will be invoked.
          * This is initially the stream from which {@link #mainIterator} has been created. However, if
@@ -345,31 +343,31 @@
          * <p>Only one iteration will be performed on those features, contrarily to the other side where we may
          * iterate over the same elements many times.</p>
          */
-        private final Spliterator<Feature> mainIterator;
+        private final Spliterator<AbstractFeature> mainIterator;
 
         /**
          * A feature fetched from the {@link #mainIterator}. The join operation will match this feature with
          * zero, one or more features from the other side. A {@code null} value means that this feature needs
          * to be retrieved with {@code mainIterator.tryAdvance(…)}.
          */
-        private Feature mainFeature;
+        private AbstractFeature mainFeature;
 
         /**
          * The stream over features in the other (usually right) side. A new stream will be created every time a new
          * feature from the main side is processed. For this reason, it should be the cheapest stream if possible.
          */
-        private Stream<Feature> filteredStream;
+        private Stream<AbstractFeature> filteredStream;
 
         /**
          * Iterator for the {@link #filteredStream}. A new iterator will be recreated every time a new feature
          * from the main side is processed.
          */
-        private Spliterator<Feature> filteredIterator;
+        private Spliterator<AbstractFeature> filteredIterator;
 
         /**
          * A feature fetched from the {@link #filteredIterator}, or {@code null} if none.
          */
-        private Feature filteredFeature;
+        private AbstractFeature filteredFeature;
 
         /**
          * Creates a new iterator. We do not use parallelized {@code mainStream} here because the {@code accept(…)}
@@ -378,7 +376,7 @@
          * so the {@link Stream} wrapping it can use parallelization.
          */
         Iterator() throws DataStoreException {
-            final Stream<Feature> mainStream = (swapSides ? right : left).features(false);
+            final Stream<AbstractFeature> mainStream = (swapSides ? right : left).features(false);
             mainCloseHandler = mainStream::close;
             mainIterator = mainStream.spliterator();
         }
@@ -386,7 +384,7 @@
         /**
          * Creates an iterator resulting from the call to {@link #trySplit()}.
          */
-        private Iterator(final Spliterator<Feature> it) {
+        private Iterator(final Spliterator<AbstractFeature> it) {
             mainIterator = it;
         }
 
@@ -396,8 +394,8 @@
          * Returns {@code null} if this iterator cannot be partitioned.
          */
         @Override
-        public Spliterator<Feature> trySplit() {
-            final Spliterator<Feature> s = mainIterator.trySplit();
+        public Spliterator<AbstractFeature> trySplit() {
+            final Spliterator<AbstractFeature> s = mainIterator.trySplit();
             if (s == null) {
                 return null;
             }
@@ -449,7 +447,7 @@
          * This method is idempotent: it has no effect if the stream is already closed.
          */
         private void closeFilteredIterator() {
-            final Stream<Feature> stream = filteredStream;
+            final Stream<AbstractFeature> stream = filteredStream;
             filteredStream   = null;                // Cleared before call to close() in case of error.
             filteredIterator = null;
             filteredFeature  = null;                // Used as a sentinel value by this.forEachRemaining(…).
@@ -464,7 +462,7 @@
          * The filtering condition is determined by the current {@link #mainFeature}.
          */
         private void createFilteredIterator() {
-            final Expression<Feature,?> expression1, expression2;
+            final Expression<AbstractFeature,?> expression1, expression2;
             final FeatureSet filteredSet;
             if (swapSides) {
                 expression1 = condition.getOperand2();
@@ -476,10 +474,9 @@
                 filteredSet = right;
             }
             final Object mainValue = expression1.apply(mainFeature);
-            final Filter<Feature> filter;
+            final Filter<AbstractFeature> filter;
             if (mainValue != null) {
-                filter = factory.equal(expression2, factory.literal(mainValue),
-                            condition.isMatchingCase(), condition.getMatchAction());
+                filter = factory.equal(expression2, factory.literal(mainValue));
             } else {
                 filter = factory.isNull(expression2);
             }
@@ -497,13 +494,13 @@
          * Executes the given action on all remaining features in the {@code JoinFeatureSet}.
          */
         @Override
-        public void forEachRemaining(final Consumer<? super Feature> action) {
-            final Consumer<Feature> forFiltered = (final Feature feature) -> {
+        public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
+            final Consumer<AbstractFeature> forFiltered = (final AbstractFeature feature) -> {
                 if (feature != null) {
                     action.accept(join(mainFeature, filteredFeature = feature));
                 }
             };
-            final Consumer<Feature> forMain = (final Feature feature) -> {
+            final Consumer<AbstractFeature> forMain = (final AbstractFeature feature) -> {
                 if (feature != null) {
                     mainFeature = feature;
                     createFilteredIterator();
@@ -525,7 +522,7 @@
          * Used by {@link #tryAdvance(Consumer)} implementation only.
          */
         @Override
-        public void accept(final Feature feature) {
+        public void accept(final AbstractFeature feature) {
             filteredFeature = feature;
         }
 
@@ -533,7 +530,7 @@
          * Executes the given action on the next feature in the {@code JoinFeatureSet}.
          */
         @Override
-        public boolean tryAdvance(final Consumer<? super Feature> action) {
+        public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
             for (;;) {
                 if (mainFeature == null) {
                     do if (!mainIterator.tryAdvance(this)) {
@@ -552,7 +549,7 @@
                         return true;
                     }
                 }
-                final Feature feature = mainFeature;
+                final AbstractFeature feature = mainFeature;
                 closeFilteredIterator();
                 if (none && isOuterJoin) {
                     action.accept(join(feature, null));
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/Capability.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/Capability.java
index 36f7cc4..7a067c4 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/Capability.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/Capability.java
@@ -24,9 +24,9 @@
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.BackingStoreException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.citation.Citation;
+// Specific to the main branch:
+import org.opengis.metadata.distribution.Format;
+import org.apache.sis.util.iso.Types;
 
 
 /**
@@ -102,9 +102,13 @@
              * Get a title for the format, followed by the short name between parenthesis
              * if it does not repeat the main title.
              */
+            final Format format = provider.getFormat();
             String title, complement;
             try {
-                title = title(provider.getFormat().getFormatSpecificationCitation()).toString(locale);
+                title = Types.toString(format.getSpecification(), locale);
+                if (title == null) {
+                    title = Types.toString(format.getName(), locale);
+                }
                 complement = provider.getShortName();
             } catch (BackingStoreException e) {
                 title = provider.getShortName();
@@ -118,16 +122,4 @@
         }
         return list;
     }
-
-    /**
-     * Returns the title or alternate title of the given citation, or "untitled" if none.
-     */
-    private static InternationalString title(final Citation specification) {
-        final InternationalString title = specification.getTitle();
-        if (title != null) return title;
-        for (final InternationalString t : specification.getAlternateTitles()) {
-            if (t != null) return t;
-        }
-        return Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/FeatureCatalogBuilder.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/FeatureCatalogBuilder.java
index 15b6d50..0c20456 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/FeatureCatalogBuilder.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/FeatureCatalogBuilder.java
@@ -22,13 +22,13 @@
 import org.apache.sis.storage.IllegalNameException;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
  * Helper methods for the feature metadata created by {@code DataStore} implementations.
- * This is a convenience class for chaining {@link #addFeatureType(FeatureType, long)}
+ * This is a convenience class for chaining {@code addFeatureType(FeatureType, long)}
  * method calls with {@link FeatureNaming#add(DataStore, GenericName, Object)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -47,7 +47,7 @@
      * {@code DataStore} implementations can keep the reference to this {@code FeatureNaming}
      * after the {@link #build()} method has been invoked.
      */
-    public final FeatureNaming<FeatureType> features;
+    public final FeatureNaming<DefaultFeatureType> features;
 
     /**
      * Creates a new builder for the given data store.
@@ -70,9 +70,9 @@
      * @param  type  the feature type to add, or {@code null}.
      * @throws IllegalNameException if a feature of the same name has already been added.
      *
-     * @see #addFeatureType(FeatureType, long)
+     * @see #addFeatureType(DefaultFeatureType, long)
      */
-    public final void define(final FeatureType type) throws IllegalNameException {
+    public final void define(final DefaultFeatureType type) throws IllegalNameException {
         final GenericName name = addFeatureType(type, -1);
         if (name != null) {
             features.add(store, name, type);
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java
index b71b7cc..93dbbdb 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java
@@ -208,7 +208,7 @@
         final var c = new DefaultCitation(notice);
         if (year != 0) {
             final Date date = new Date(LocalDate.of(year, 1, 1).toEpochDay() * MILLISECONDS_PER_DAY);
-            c.setDates(Collections.singleton(new DefaultCitationDate(date, DateType.IN_FORCE)));
+            c.setDates(Collections.singleton(new DefaultCitationDate(date, DateType.valueOf("IN_FORCE"))));
         }
         if (i != 0) {
             buffer.setLength(i);
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryFeatureSet.java
index 8b25894..c2b08a2 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryFeatureSet.java
@@ -23,9 +23,9 @@
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.AbstractFeatureSet;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -39,24 +39,24 @@
     /**
      * The type specified at construction time and returned by {@link #getType()}.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * The features specified at construction time, potentially as a modifiable collection.
-     * For all features in this collection, {@link Feature#getType()} shall be {@link #type}.
+     * For all features in this collection, {@link AbstractFeature#getType()} shall be {@link #type}.
      */
-    private final Collection<Feature> features;
+    private final Collection<AbstractFeature> features;
 
     /**
      * Creates a new set of features stored in memory. It is caller responsibility to ensure that
-     * <code>{@linkplain Feature#getType()} == type</code> for all elements in the given collection
+     * <code>{@linkplain AbstractFeature#getType()} == type</code> for all elements in the given collection
      * (this is not verified).
      *
      * @param parent     the parent resource, or {@code null} if none.
      * @param type       the type of all features in the given collection.
      * @param features   collection of stored features. This collection will not be copied.
      */
-    public MemoryFeatureSet(final Resource parent, final FeatureType type, final Collection<Feature> features) {
+    public MemoryFeatureSet(final Resource parent, final DefaultFeatureType type, final Collection<AbstractFeature> features) {
         super(parent);
         this.type     = Objects.requireNonNull(type);
         this.features = Objects.requireNonNull(features);
@@ -68,7 +68,7 @@
      * @return a description of properties that are common to all features in this dataset.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -89,7 +89,7 @@
      * @return all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) {
+    public Stream<AbstractFeature> features(final boolean parallel) {
         return parallel ? features.parallelStream() : features.stream();
     }
 }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
index 2f5f1ce..20cac18 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
@@ -105,8 +105,8 @@
 import org.apache.sis.pending.jdk.JDK21;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -1053,6 +1053,11 @@
         if (abbreviation != null && abbreviation.length() != 0) {
             if (format == null) {
                 format = MetadataSource.getProvided().lookup(Format.class, abbreviation);
+                /*
+                 * Additional step for converting deprecated "name" and "specification" into non-deprecated properties.
+                 * This step is not required on SIS branches that depend on development branches of GeoAPI 3.1 or 4.0.
+                 */
+                format = DefaultFormat.castOrCopy(format);
             } else {
                 addFormatName(abbreviation);
             }
@@ -1082,7 +1087,7 @@
 
     /**
      * Adds information about the scope of the resource.
-     * The scope is typically (but not restricted to) {@link ScopeCode#COVERAGE},
+     * The scope is typically (but not restricted to) {@code ScopeCode.COVERAGE},
      * {@link ScopeCode#FEATURE} or the more generic {@link ScopeCode#DATASET}.
      * Storage locations are:
      *
@@ -1113,7 +1118,7 @@
      * </ul>
      *
      * If a date already exists for the given type, then the earliest date is retained (oldest date are discarded)
-     * except for {@link DateType#LAST_REVISION}, {@link DateType#LAST_UPDATE LAST_UPDATE} or any other date type
+     * except for {@code DateType.LAST_REVISION}, {@code DateType.LAST_UPDATE LAST_UPDATE} or any other date type
      * prefixed by {@code "LATE_"}, where only the latest date is kept.
      *
      * @param  date   the date to add, or {@code null} for no-operation..
@@ -1823,9 +1828,9 @@
      *         Note that ISO-19115 considers 0 as an invalid value. Consequently, if 0, the feature is not added.
      * @return the name of the added feature (even if not added to the metadata), or {@code null} if none.
      *
-     * @see FeatureCatalogBuilder#define(FeatureType)
+     * @see FeatureCatalogBuilder#define(DefaultFeatureType)
      */
-    public final GenericName addFeatureType(final FeatureType type, final long occurrences) {
+    public final GenericName addFeatureType(final DefaultFeatureType type, final long occurrences) {
         if (type == null) {
             return null;
         }
@@ -2833,6 +2838,7 @@
      * @see #addProcessing(CharSequence, String)
      * @see #addProcessDescription(CharSequence)
      */
+    @SuppressWarnings("deprecation")
     public final void addSource(final CharSequence description, final ScopeCode level, final CharSequence feature) {
         final InternationalString i18n = trim(description);
         if (i18n != null) {
@@ -2880,8 +2886,11 @@
             source.setSourceReferenceSystem(CollectionsExt.first(metadata.getReferenceSystemInfo()));
             for (final Identification id : metadata.getIdentificationInfo()) {
                 source.setSourceCitation(id.getCitation());
-                source.setSourceSpatialResolution(CollectionsExt.first(id.getSpatialResolutions()));
-                scope.setExtents(id.getExtents());
+                if (id instanceof AbstractIdentification) {
+                    final AbstractIdentification aid = (AbstractIdentification) id;
+                    source.setSourceSpatialResolution(CollectionsExt.first(aid.getSpatialResolutions()));
+                    scope.setExtents(aid.getExtents());
+                }
                 if (features != null && features.length != 0) {
                     /*
                      * Note: the same ScopeDescription may be shared by many Source instances
@@ -3095,7 +3104,7 @@
      *
      * <ul>
      *   <li>{@code metadata/metadataLinkage/linkage}
-     *     with {@code function} set to {@link OnLineFunction#COMPLETE_METADATA}</li>
+     *     with {@code function} set to {@code OnLineFunction.COMPLETE_METADATA}</li>
      * </ul>
      *
      * @param  link  URL to a more complete description of the metadata, or {@code null}.
@@ -3103,7 +3112,7 @@
     public final void addCompleteMetadata(final URI link) {
         if (link != null) {
             final var ln = new DefaultOnlineResource(link);
-            ln.setFunction(OnLineFunction.COMPLETE_METADATA);
+            ln.setFunction(OnLineFunction.valueOf("COMPLETE_METADATA"));
             ln.setProtocol(link.getScheme());
             addIfNotPresent(metadata().getMetadataLinkages(), ln);
         }
@@ -3180,22 +3189,10 @@
                 for (ResponsibleParty r : c.getCitedResponsibleParties()) {
                     addIfNotPresent(citation.getCitedResponsibleParties(), r);
                 }
-                for (OnlineResource r : c.getOnlineResources()) {
-                    addIfNotPresent(citation.getOnlineResources(), r);
-                }
                 citation.getPresentationForms().addAll(c.getPresentationForms());
             }
             @SuppressWarnings("LocalVariableHidesMemberVariable")
             final DefaultDataIdentification identification = identification();
-            for (Extent e : info.getExtents()) {
-                addIfNotPresent(identification.getExtents(), e);
-            }
-            for (Resolution r : info.getSpatialResolutions()) {
-                addIfNotPresent(identification.getSpatialResolutions(), r);
-            }
-            for (TemporalAmount r : info.getTemporalResolutions()) {
-                addIfNotPresent(identification.getTemporalResolutions(), r);
-            }
             for (Format r : info.getResourceFormats()) {
                 addCompression(r.getFileDecompressionTechnique());
                 // Ignore format name (see Javadoc).
@@ -3203,8 +3200,23 @@
             for (Constraints r : info.getResourceConstraints()) {
                 addIfNotPresent(identification.getResourceConstraints(), r);
             }
-            identification.getTopicCategories().addAll(info.getTopicCategories());
-            identification.getSpatialRepresentationTypes().addAll(info.getSpatialRepresentationTypes());
+            if (info instanceof DataIdentification) {
+                final var di = (DataIdentification) info;
+                for (Extent e : di.getExtents()) {
+                    addIfNotPresent(identification.getExtents(), e);
+                }
+                for (Resolution r : di.getSpatialResolutions()) {
+                    addIfNotPresent(identification.getSpatialResolutions(), r);
+                }
+                identification.getTopicCategories().addAll(di.getTopicCategories());
+                identification.getSpatialRepresentationTypes().addAll(di.getSpatialRepresentationTypes());
+            }
+            if (info instanceof AbstractIdentification) {
+                final var di = (AbstractIdentification) info;
+                for (TemporalAmount r : di.getTemporalResolutions()) {
+                    addIfNotPresent(identification.getTemporalResolutions(), r);
+                }
+            }
         }
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         final DefaultMetadata metadata = metadata();
@@ -3227,9 +3239,6 @@
                 addIfNotPresent(distribution().getDistributors(), r);
             }
         }
-        for (Lineage info : component.getResourceLineages()) {
-            addIfNotPresent(metadata.getResourceLineages(), info);
-        }
     }
 
     /**
@@ -3274,9 +3283,9 @@
         else if (source instanceof Extent)                      target = extent();
         else if (source instanceof LegalConstraints)            target = constraints();
         else if (source instanceof Series)                      target = series();
-        else if (source instanceof Responsibility)              target = responsibility();
-        else if (source instanceof Party)                       target = party();
-        else if (source instanceof AttributeGroup)              target = attributeGroup();
+        else if (source instanceof DefaultResponsibleParty)     target = responsibility();
+        else if (source instanceof AbstractParty)               target = party();
+        else if (source instanceof DefaultAttributeGroup)       target = attributeGroup();
         else if (source instanceof SampleDimension)             target = sampleDimension();
         else if (source instanceof GCPCollection)               target = groundControlPoints();
         else if (source instanceof Format)                      target = format();
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
index f8dbad4..5d3028e 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
@@ -42,9 +42,9 @@
 import org.opengis.metadata.spatial.GridSpatialRepresentation;
 import org.apache.sis.util.collection.CodeListSet;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main branch:
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.metadata.iso.DefaultMetadata;
 
 
 /**
@@ -170,9 +170,11 @@
     public void accept(final Metadata info) {
         if (info != null) {
             forEach(MetadataFetcher::accept, info.getIdentificationInfo());
-            forEach(MetadataFetcher::accept, info.getResourceLineages());
             forEach(MetadataFetcher::accept, info.getAcquisitionInformation());
             forEach(MetadataFetcher::accept, info.getSpatialRepresentationInfo());
+            if (info instanceof DefaultMetadata) {
+                forEach(MetadataFetcher::accept, ((DefaultMetadata) info).getResourceLineages());
+            }
         }
     }
 
@@ -206,8 +208,9 @@
      * @param  info  the responsible party (not null).
      * @return whether to stop iteration after the given object.
      */
-    protected boolean accept(final Responsibility info) {
-        party = addStrings(party, info.getParties(), Party::getName);
+    protected boolean accept(final ResponsibleParty info) {
+        party = addString(party, info.getIndividualName());
+        party = addString(party, info.getOrganisationName());
         return false;
     }
 
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/ResourceLineage.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/ResourceLineage.java
index 9abc7f6..709decc 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/ResourceLineage.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/ResourceLineage.java
@@ -31,8 +31,8 @@
 import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 import static org.apache.sis.util.privy.CollectionsExt.nonNull;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.MetadataScope;
+// Specific to the main branch:
+import org.opengis.metadata.identification.DataIdentification;
 
 
 /**
@@ -98,8 +98,8 @@
                     description = citation.getTitle();
                 }
             }
-            if (resolution == null) {
-                for (final Resolution candidate : nonNull(info.getSpatialResolutions())) {
+            if (resolution == null && info instanceof DataIdentification) {
+                for (final Resolution candidate : nonNull(((DataIdentification) info).getSpatialResolutions())) {
                     if (candidate != null) {
                         resolution = candidate;
                     }
@@ -121,8 +121,7 @@
      */
     private static ScopeCode getScopeLevel(final Metadata source) {
         ScopeCode level = null;
-        for (final MetadataScope ms : nonNull(source.getMetadataScopes())) {
-            final ScopeCode c = ms.getResourceScope();
+        for (final ScopeCode c : nonNull(source.getHierarchyLevels())) {
             if (c != null) {
                 if (level == null) {
                     level = c;
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/StoreUtilities.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/StoreUtilities.java
index 3b3a539..42e9aa2 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/StoreUtilities.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/StoreUtilities.java
@@ -54,8 +54,9 @@
 import org.apache.sis.system.Modules;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 
 
 /**
@@ -197,7 +198,10 @@
         GeneralEnvelope bounds = null;
         if (metadata != null) {
             for (final Identification identification : metadata.getIdentificationInfo()) {
-                for (final Extent extent : identification.getExtents()) {
+                if (!(identification instanceof AbstractIdentification)) {
+                    continue;       // Following cast is specific to GeoAPI 3.0 branch.
+                }
+                for (final Extent extent : ((AbstractIdentification) identification).getExtents()) {
                     for (final GeographicExtent ge : extent.getGeographicElements()) {
                         if (ge instanceof GeographicBoundingBox) {
                             final GeneralEnvelope env = new GeneralEnvelope((GeographicBoundingBox) ge);
@@ -372,7 +376,7 @@
      */
     public static void copy(final FeatureSet source, final WritableFeatureSet target) throws DataStoreException {
         target.updateType(source.getType());
-        try (Stream<Feature> stream = source.features(false)) {
+        try (Stream<AbstractFeature> stream = source.features(false)) {
             target.add(stream.iterator());
         }
     }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java
index e9a9304..6b7fd83 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java
@@ -50,8 +50,8 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.geometry.MismatchedDimensionException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java
index b813562..1adbd95 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java
@@ -43,8 +43,8 @@
 import static org.apache.sis.storage.base.TiledGridCoverage.X_DIMENSION;
 import static org.apache.sis.storage.base.TiledGridCoverage.Y_DIMENSION;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java
index 37ad5a3..52ba72d 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java
@@ -43,8 +43,8 @@
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/FeatureIterator.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/FeatureIterator.java
index b3044e7..3a7a1e7 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/FeatureIterator.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/FeatureIterator.java
@@ -26,10 +26,10 @@
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.collection.BackingStoreException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -50,7 +50,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-class FeatureIterator implements Spliterator<Feature> {
+class FeatureIterator implements Spliterator<AbstractFeature> {
     /**
      * Index of the column containing trajectory coordinates.
      * Columns before the trajectory are Moving Feature identifier {@code mfIdRef}, start time and end time.
@@ -92,12 +92,12 @@
     @SuppressWarnings({"unchecked", "rawtypes", "fallthrough"})
     FeatureIterator(final Store store) {
         this.store = store;
-        final Collection<? extends PropertyType> properties = store.featureType.getProperties(true);
+        final Collection<? extends AbstractIdentifiedType> properties = store.featureType.getProperties(true);
         converters    = new ObjectConverter[properties.size()];
         values        = new Object[converters.length];
         propertyNames = new String[converters.length];
         int i = -1;
-        for (final PropertyType p : properties) {
+        for (final AbstractIdentifiedType p : properties) {
             propertyNames[++i] = p.getName().tip().toString();
             /*
              * According Moving Features specification:
@@ -135,7 +135,7 @@
                      */
                 }
                 default: {
-                    c = ObjectConverters.find(String.class, ((AttributeType) p).getValueClass());
+                    c = ObjectConverters.find(String.class, ((DefaultAttributeType) p).getValueClass());
                     break;
                 }
             }
@@ -161,7 +161,7 @@
      * is not guaranteed to cover a strict prefix of the elements.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         if (splitCount == null) {
             splitCount = new AtomicInteger();
         }
@@ -175,7 +175,7 @@
      * Executes the given action only on the next feature, if any.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
         try {
             return read(action, false);
         } catch (IOException | IllegalArgumentException | DateTimeException e) {
@@ -187,7 +187,7 @@
      * Executes the given action on all remaining features.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
         try {
             read(action, true);
         } catch (IOException | IllegalArgumentException | DateTimeException e) {
@@ -213,12 +213,12 @@
      * @throws IllegalArgumentException if parsing of a number failed, or other error.
      * @throws DateTimeException if parsing of a date failed.
      */
-    private boolean read(final Consumer<? super Feature> action, final boolean all) throws IOException {
+    private boolean read(final Consumer<? super AbstractFeature> action, final boolean all) throws IOException {
         final FixedSizeList elements = new FixedSizeList(values);
         String line;
         while ((line = store.readLine()) != null) {
             Store.split(line, elements);
-            final Feature feature = store.featureType.newInstance();
+            final AbstractFeature feature = store.featureType.newInstance();
             int i, n = elements.size();
             for (i=0; i<n; i++) {
                 values[i] = converters[i].apply((String) values[i]);
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureBuilder.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureBuilder.java
index aa6ba21..94b8d61 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureBuilder.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureBuilder.java
@@ -30,9 +30,9 @@
 import org.apache.sis.util.CorruptedObjectException;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -111,7 +111,7 @@
 
     /**
      * Adds a time range.
-     * The minimal and maximal values will be used by {@link #storeTimeRange(String, String, Feature)}.
+     * The minimal and maximal values will be used by {@link #storeTimeRange(String, String, AbstractFeature)}.
      *
      * @param  startTime  beginning in milliseconds since Java epoch of the period when the property value is valid.
      * @param  endTime    end in milliseconds since Java epoch of the period when the property value is valid.
@@ -146,7 +146,7 @@
      * @param  endTime    name of the property where to store the end time.
      * @param  dest       feature where to store the start time and end time.
      */
-    public final void storeTimeRange(final String startTime, final String endTime, final Feature dest) {
+    public final void storeTimeRange(final String startTime, final String endTime, final AbstractFeature dest) {
         if (tmin < tmax) {
             final Instant t = Instant.ofEpochMilli(tmin);
             dest.setPropertyValue(startTime, t);
@@ -163,7 +163,7 @@
      * @param  dest   attribute where to store the value.
      */
     @SuppressWarnings("unchecked")
-    public final <V> void storeAttribute(final int index, final Attribute<V> dest) {
+    public final <V> void storeAttribute(final int index, final AbstractAttribute<V> dest) {
         int n = count[index];
         final long[] times  = new long[n];
         final V[]    values = (V[]) Array.newInstance(dest.getType().getValueClass(), n);
@@ -193,7 +193,7 @@
      *                          source method name and logger name, then forward to a {@code WarningListener}.
      */
     public final <G> void storeGeometry(final String featureName, final int index, final int dimension,
-            final Geometries<G> factory, final Attribute<G> dest, final Consumer<LogRecord> warningListener)
+            final Geometries<G> factory, final AbstractAttribute<G> dest, final Consumer<LogRecord> warningListener)
     {
         int n = count[index];
         final Vector[] vectors = new Vector[n];
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureIterator.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureIterator.java
index aa6ff8e..03efe5c 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureIterator.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/MovingFeatureIterator.java
@@ -24,9 +24,9 @@
 import java.time.DateTimeException;
 import java.io.IOException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -74,10 +74,10 @@
      * This method can only be invoked after {@link #readMoving(Consumer, boolean)} completion.
      * This method is ignored if the CSV file contains only static features.
      */
-    Feature[] createMovingFeatures() {
+    AbstractFeature[] createMovingFeatures() {
         int n = 0;
         final int np = values.length - TRAJECTORY_COLUMN;
-        final Feature[] features = new Feature[builders.size()];
+        final AbstractFeature[] features = new AbstractFeature[builders.size()];
         for (final Map.Entry<String,MovingFeatureBuilder> entry : builders.entrySet()) {
             features[n++] = createMovingFeature(entry.getKey(), entry.getValue(), np);
         }
@@ -92,18 +92,18 @@
      * @param  np           number of properties, ignoring the ones before the trajectory column.
      */
     @SuppressWarnings("unchecked")
-    private Feature createMovingFeature(final String featureName, final MovingFeatureBuilder mf, final int np) {
-        final Feature feature = store.featureType.newInstance();
+    private AbstractFeature createMovingFeature(final String featureName, final MovingFeatureBuilder mf, final int np) {
+        final AbstractFeature feature = store.featureType.newInstance();
         feature.setPropertyValue(propertyNames[0], featureName);
         mf.storeTimeRange(propertyNames[1], propertyNames[2], feature);
         int column = 0;
         if (store.hasTrajectories()) {
             mf.storeGeometry(featureName, column, store.spatialDimensionCount(), store.geometries,
-                    (Attribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]), this);
+                    (AbstractAttribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]), this);
             column++;
         }
         while (column < np) {
-            mf.storeAttribute(column, (Attribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN + column]));
+            mf.storeAttribute(column, (AbstractAttribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN + column]));
             column++;
         }
         return feature;
@@ -121,7 +121,7 @@
      * @throws IllegalArgumentException if parsing of a number failed, or other error.
      * @throws DateTimeException if parsing of a date failed.
      */
-    boolean readMoving(final Consumer<? super Feature> action, final boolean all) throws IOException {
+    boolean readMoving(final Consumer<? super AbstractFeature> action, final boolean all) throws IOException {
         final FixedSizeList elements = new FixedSizeList(values);
         final int np = values.length - TRAJECTORY_COLUMN;
         String line;
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
index 3c26854..1b818fb 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
@@ -74,11 +74,9 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -166,7 +164,7 @@
      *
      * @see #parseFeatureType(List)
      */
-    final FeatureType featureType;
+    final DefaultFeatureType featureType;
 
     /**
      * {@code true} if {@link #featureType} contains a trajectory column.
@@ -216,7 +214,7 @@
      * All parsed moving features, or {@code null} if none or if not yet parsed. If {@link #dissociate}
      * is {@code false}, then this list will be created by {@link #features(boolean)} when first needed.
      */
-    private transient List<Feature> movingFeatures;
+    private transient List<AbstractFeature> movingFeatures;
 
     /**
      * Creates a new CSV store from the given file, URL or stream.
@@ -235,7 +233,7 @@
         geometries = Geometries.factory(connector.getOption(OptionKey.GEOMETRY_LIBRARY));
         dissociate = connector.getOption(DataOptionKey.FOLIATION_REPRESENTATION) == FoliationRepresentation.FRAGMENTED;
         GeneralEnvelope envelope    = null;
-        FeatureType     featureType = null;
+        DefaultFeatureType featureType = null;
         Foliation       foliation   = null;
         try {
             final List<String> elements = new ArrayList<>();
@@ -504,10 +502,10 @@
      * @return the column metadata, or {@code null} if the given list does not contain enough elements.
      */
     @SuppressWarnings("rawtypes")               // "rawtypes" because of generic array creation.
-    private FeatureType parseFeatureType(final List<String> elements) throws DataStoreException {
-        AttributeType[] characteristics = null;
+    private DefaultFeatureType parseFeatureType(final List<String> elements) throws DataStoreException {
+        DefaultAttributeType[] characteristics = null;
         final int size = elements.size();
-        final List<PropertyType> properties = new ArrayList<>();
+        final List<AbstractIdentifiedType> properties = new ArrayList<>();
         for (int i=1; i<size; i++) {
             final String name = elements.get(i);
             Class<?> type = null;
@@ -562,7 +560,7 @@
                                 type = double[].class;
                             } else {
                                 type = geometries.polylineClass;
-                                characteristics = new AttributeType[] {MovingFeatureBuilder.TIME_AS_INSTANTS};
+                                characteristics = new DefaultAttributeType[] {MovingFeatureBuilder.TIME_AS_INSTANTS};
                             }
                             minOccurrence = 1;
                             maxOccurrence = 1;
@@ -576,15 +574,15 @@
         // Do not use Map.of(…) because `name` may be null. Let constructor throw the exception.
         final String name = IOUtilities.filenameWithoutExtension(super.getDisplayName());
         return new DefaultFeatureType(Collections.singletonMap(DefaultFeatureType.NAME_KEY, name),
-                                      false, null, properties.toArray(PropertyType[]::new));
+                                      false, null, properties.toArray(AbstractIdentifiedType[]::new));
     }
 
     /**
      * Creates a property type for the given name and type.
      * This is a helper method for {@link #parseFeatureType(List)}.
      */
-    private static PropertyType createProperty(final String name, final Class<?> type,
-            final int minOccurrence, final int maxOccurrence, final AttributeType<?>[] characteristics)
+    private static AbstractIdentifiedType createProperty(final String name, final Class<?> type,
+            final int minOccurrence, final int maxOccurrence, final DefaultAttributeType<?>[] characteristics)
     {
         return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),
                                           type, minOccurrence, maxOccurrence, null, characteristics);
@@ -671,7 +669,7 @@
      * @return type of features in the CSV file.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return featureType;
     }
 
@@ -686,7 +684,7 @@
      * @todo If sequential order, publish Feature as soon as identifier changed.
      */
     @Override
-    public final synchronized Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public final synchronized Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         /*
          * If the user asks for one feature instance per line, then we can return a FeatureIter instance directly.
          * Since each feature is fully constructed from a single line and each line are read atomically, we can
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
index 1d35e79..3674106 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
@@ -156,7 +156,7 @@
             builder.addFormatName(formatName);
             listeners.warning(e);
         }
-        builder.addResourceScope(ScopeCode.COVERAGE, null);
+        builder.addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
         builder.addLanguage(Locale.ENGLISH, encoding, MetadataBuilder.Scope.METADATA);
         builder.addSpatialRepresentation(null, gridGeometry, true);
         builder.addExtent(gridGeometry.getEnvelope(), listeners);
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java
index 7592ce1..3c0616a 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java
@@ -41,8 +41,8 @@
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.StringBuilders;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
+// Specific to the main branch:
+import org.apache.sis.image.SequenceType;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java
index f59400c..57ec403 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java
@@ -259,7 +259,7 @@
     public synchronized Metadata getMetadata() {
         if (metadata == null) {
             final MetadataBuilder mb = new MetadataBuilder();
-            mb.addResourceScope(ScopeCode.COLLECTION, Resources.formatInternational(Resources.Keys.DirectoryContent_1, getDisplayName()));
+            mb.addResourceScope(ScopeCode.valueOf("COLLECTION"), Resources.formatInternational(Resources.Keys.DirectoryContent_1, getDisplayName()));
             mb.addLanguage(configuration.getOption(OptionKey.LOCALE),
                            configuration.getOption(OptionKey.ENCODING),
                            MetadataBuilder.Scope.RESOURCE);
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
index dd72fe7..863f6e3 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
@@ -50,6 +50,9 @@
 import org.apache.sis.storage.base.StoreUtilities;
 import org.apache.sis.setup.OptionKey;
 
+// Specific to the main branch:
+import org.apache.sis.parameter.DefaultParameterDescriptor;
+
 
 /**
  * The provider of {@link Store} instances. This provider is intentionally registered with lowest priority
@@ -117,7 +120,7 @@
      * Creates a parameter descriptor equals to the given one except for the remarks which are set to the given value.
      */
     private static <T> ParameterDescriptor<T> annotate(ParameterBuilder builder, ParameterDescriptor<T> e, InternationalString remark) {
-        return builder.addName(e.getName()).setDescription(e.getDescription().orElse(null)).setRemarks(remark).create(e.getValueClass(), null);
+        return builder.addName(e.getName()).setDescription(((DefaultParameterDescriptor) e).getDescription().orElse(null)).setRemarks(remark).create(e.getValueClass(), null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
index 6b041d6..455035a 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
@@ -538,7 +538,7 @@
                 }
             }
             builder.addFormatName(format);                          // Does nothing if `format` is null.
-            builder.addResourceScope(ScopeCode.COVERAGE, null);
+            builder.addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
             builder.addSpatialRepresentation(null, getGridGeometry(MAIN_IMAGE), true);
             if (gridGeometry.isDefined(GridGeometry.ENVELOPE)) {
                 builder.addExtent(gridGeometry.getEnvelope(), listeners);
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/GeographicEnvelope.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/GeographicEnvelope.java
index 1b21789..ba43baa 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/GeographicEnvelope.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/xml/GeographicEnvelope.java
@@ -26,6 +26,11 @@
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.referencing.CommonCRS;
 
+// Specific to the main branch:
+import org.opengis.metadata.extent.TemporalExtent;
+import org.opengis.metadata.extent.VerticalExtent;
+import org.opengis.util.InternationalString;
+
 
 /**
  * Base class of geographic bounding boxes to expose also as an envelope and an ISO 19115 extent.
@@ -109,6 +114,17 @@
     }
 
     /**
+     * Returns the spatial and temporal extent for the referring object.
+     * The default implementation unconditionally returns {@code null}.
+     *
+     * @return the spatial and temporal extent, or {@code null} in none.
+     */
+    @Override
+    public InternationalString getDescription() {
+        return null;
+    }
+
+    /**
      * Provides geographic component of the extent of the referring object.
      * The default implementation returns a singleton containing only this
      * geographic bounding box.
@@ -121,6 +137,28 @@
     }
 
     /**
+     * Provides temporal component of the extent of the referring object.
+     * The default implementation unconditionally returns an empty set.
+     *
+     * @return the temporal extent, or an empty set if none.
+     */
+    @Override
+    public Collection<? extends TemporalExtent> getTemporalElements() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Provides vertical component of the extent of the referring object.
+     * The default implementation unconditionally returns an empty set.
+     *
+     * @return the vertical extent, or an empty set if none.
+     */
+    @Override
+    public Collection<? extends VerticalExtent> getVerticalElements() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Indication of whether the bounding box encompasses an area covered by the data
      * (<dfn>inclusion</dfn>) or an area where data is not present (<dfn>exclusion</dfn>).
      * The default implementation unconditionally returns {@link Boolean#TRUE}.
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
index c57fb68..36958d1 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
@@ -35,19 +35,13 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-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;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.MatchAction;
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -61,7 +55,7 @@
     /**
      * An arbitrary number of features, all of the same type.
      */
-    private Feature[] features;
+    private AbstractFeature[] features;
 
     /**
      * The {@link #features} array wrapped in a in-memory feature set.
@@ -86,8 +80,8 @@
     private void createFeatureWithIdentifier() {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder().setName("Test");
         ftb.addAttribute(String.class).setName("id").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final FeatureType type = ftb.build();
-        features = new Feature[] {
+        final DefaultFeatureType type = ftb.build();
+        features = new AbstractFeature[] {
             type.newInstance()
         };
         features[0].setPropertyValue("id", "id-0");
@@ -104,15 +98,15 @@
         // A dependency of the test feature type.
         ftb = new FeatureTypeBuilder().setName("Dependency");
         ftb.addAttribute(Integer.class).setName("value3");
-        final FeatureType dependency = ftb.build();
+        final DefaultFeatureType dependency = ftb.build();
 
         // Test feature type with attributes and association.
         ftb = new FeatureTypeBuilder().setName("Test");
         ftb.addAttribute(Integer.class).setName("value1");
         ftb.addAttribute(Integer.class).setName("value2");
         ftb.addAssociation(dependency).setName("dependency");
-        final FeatureType type = ftb.build();
-        features = new Feature[] {
+        final DefaultFeatureType type = ftb.build();
+        features = new AbstractFeature[] {
             feature(type, null,       3, 1,  0),
             feature(type, null,       2, 2,  0),
             feature(type, dependency, 2, 1, 25),
@@ -126,14 +120,14 @@
      * Creates an instance of the test feature type with the given values.
      * The {@code value3} is stored only if {@code dependency} is non-null.
      */
-    private static Feature feature(final FeatureType type, final FeatureType dependency,
+    private static AbstractFeature feature(final DefaultFeatureType type, final DefaultFeatureType dependency,
                                    final int value1, final int value2, final int value3)
     {
-        final Feature f = type.newInstance();
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue("value1", value1);
         f.setPropertyValue("value2", value2);
         if (dependency != null) {
-            final Feature d = dependency.newInstance();
+            final AbstractFeature d = dependency.newInstance();
             d.setPropertyValue("value3", value3);
             f.setPropertyValue("dependency", d);
         }
@@ -143,7 +137,7 @@
     /**
      * Configures the query for returning a single instance and returns that instance.
      */
-    private Feature executeAndGetFirst() throws DataStoreException {
+    private AbstractFeature executeAndGetFirst() throws DataStoreException {
         query.setLimit(1);
         final FeatureSet subset = query.execute(featureSet);
         return TestUtilities.getSingleton(subset.features(false).collect(Collectors.toList()));
@@ -157,11 +151,11 @@
      */
     private void verifyQueryResult(final int... indices) throws DataStoreException {
         final FeatureSet fs = query.execute(featureSet);
-        final List<Feature> result = fs.features(false).collect(Collectors.toList());
+        final List<AbstractFeature> result = fs.features(false).collect(Collectors.toList());
         assertEquals(indices.length, result.size());
         for (int i=0; i<indices.length; i++) {
-            final Feature expected = features[indices[i]];
-            final Feature actual   = result.get(i);
+            final AbstractFeature expected = features[indices[i]];
+            final AbstractFeature actual   = result.get(i);
             if (!expected.equals(actual)) {
                 fail(String.format("Unexpected feature at index %d%n"
                                  + "Expected:%n%s%n"
@@ -195,20 +189,6 @@
     }
 
     /**
-     * Verifies the effect of {@link FeatureQuery#setSortBy(SortProperty[])}.
-     *
-     * @throws DataStoreException if an error occurred while executing the query.
-     */
-    @Test
-    public void testSortBy() throws DataStoreException {
-        createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
-        query.setSortBy(ff.sort(ff.property("value1", Integer.class), SortOrder.ASCENDING),
-                        ff.sort(ff.property("value2", Integer.class), SortOrder.DESCENDING));
-        verifyQueryResult(3, 1, 2, 0, 4);
-    }
-
-    /**
      * Verifies the effect of {@link FeatureQuery#setSelection(Filter)}.
      *
      * @throws DataStoreException if an error occurred while executing the query.
@@ -216,9 +196,9 @@
     @Test
     public void testSelection() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setSelection(ff.equal(ff.property("value1", Integer.class),
-                                    ff.literal(2), true, MatchAction.ALL));
+                                    ff.literal(2)));
         verifyQueryResult(1, 2);
     }
 
@@ -231,7 +211,7 @@
     @Test
     public void testSelectionThroughAssociation() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setSelection(ff.equal(ff.property("dependency/value3"), ff.literal(18)));
         verifyQueryResult(3);
     }
@@ -244,25 +224,25 @@
     @Test
     public void testProjection() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) null),
                             new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), "renamed1"),
                             new FeatureQuery.NamedExpression(ff.literal("a literal"), "computed"));
 
         // Check result type.
-        final Feature instance = executeAndGetFirst();
-        final FeatureType resultType = instance.getType();
+        final AbstractFeature instance = executeAndGetFirst();
+        final DefaultFeatureType resultType = instance.getType();
         assertEquals("Test", resultType.getName().toString());
         assertEquals(3, resultType.getProperties(true).size());
-        final PropertyType pt1 = resultType.getProperty("value1");
-        final PropertyType pt2 = resultType.getProperty("renamed1");
-        final PropertyType pt3 = resultType.getProperty("computed");
-        assertTrue(pt1 instanceof AttributeType);
-        assertTrue(pt2 instanceof AttributeType);
-        assertTrue(pt3 instanceof AttributeType);
-        assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
-        assertEquals(Integer.class, ((AttributeType) pt2).getValueClass());
-        assertEquals(String.class,  ((AttributeType) pt3).getValueClass());
+        final AbstractIdentifiedType pt1 = resultType.getProperty("value1");
+        final AbstractIdentifiedType pt2 = resultType.getProperty("renamed1");
+        final AbstractIdentifiedType pt3 = resultType.getProperty("computed");
+        assertTrue(pt1 instanceof DefaultAttributeType);
+        assertTrue(pt2 instanceof DefaultAttributeType);
+        assertTrue(pt3 instanceof DefaultAttributeType);
+        assertEquals(Integer.class, ((DefaultAttributeType) pt1).getValueClass());
+        assertEquals(Integer.class, ((DefaultAttributeType) pt2).getValueClass());
+        assertEquals(String.class,  ((DefaultAttributeType) pt3).getValueClass());
 
         // Check feature instance.
         assertEquals(3, instance.getPropertyValue("value1"));
@@ -279,8 +259,8 @@
     public void testProjectionByNames() throws DataStoreException {
         createFeaturesWithAssociation();
         query.setProjection("value2");
-        final Feature instance = executeAndGetFirst();
-        final PropertyType p = TestUtilities.getSingleton(instance.getType().getProperties(true));
+        final AbstractFeature instance = executeAndGetFirst();
+        final AbstractIdentifiedType p = TestUtilities.getSingleton(instance.getType().getProperties(true));
         assertEquals("value2", p.getName().toString());
     }
 
@@ -293,19 +273,19 @@
     @Test
     public void testDefaultColumnName() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setLimit(1);
         query.setProjection(
                 ff.add(ff.property("value1", Number.class), ff.literal(1)),
                 ff.add(ff.property("value2", Number.class), ff.literal(1)));
         final FeatureSet subset = featureSet.subset(query);
-        final FeatureType type = subset.getType();
-        final Iterator<? extends PropertyType> properties = type.getProperties(true).iterator();
+        final DefaultFeatureType type = subset.getType();
+        final Iterator<? extends AbstractIdentifiedType> properties = type.getProperties(true).iterator();
         assertEquals("Unnamed #1", properties.next().getName().toString());
         assertEquals("Unnamed #2", properties.next().getName().toString());
         assertFalse(properties.hasNext());
 
-        final Feature instance = TestUtilities.getSingleton(subset.features(false).collect(Collectors.toList()));
+        final AbstractFeature instance = TestUtilities.getSingleton(subset.features(false).collect(Collectors.toList()));
         assertSame(type, instance.getType());
     }
 
@@ -319,21 +299,21 @@
     @Test
     public void testProjectionOfAbstractType() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1"),  (String) null),
                             new FeatureQuery.NamedExpression(ff.property("/*/unknown"), "unexpected"));
 
         // Check result type.
-        final Feature instance = executeAndGetFirst();
-        final FeatureType resultType = instance.getType();
+        final AbstractFeature instance = executeAndGetFirst();
+        final DefaultFeatureType resultType = instance.getType();
         assertEquals("Test", resultType.getName().toString());
         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());
+        final AbstractIdentifiedType pt1 = resultType.getProperty("value1");
+        final AbstractIdentifiedType pt2 = resultType.getProperty("unexpected");
+        assertTrue(pt1 instanceof DefaultAttributeType<?>);
+        assertTrue(pt2 instanceof DefaultAttributeType<?>);
+        assertEquals(Integer.class, ((DefaultAttributeType<?>) pt1).getValueClass());
+        assertEquals(Object.class,  ((DefaultAttributeType<?>) pt2).getValueClass());
 
         // Check feature property values.
         assertEquals(3,    instance.getPropertyValue("value1"));
@@ -349,11 +329,11 @@
     @Test
     public void testProjectionThroughAssociation() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1"),  (String) null),
                             new FeatureQuery.NamedExpression(ff.property("dependency/value3"), "value3"));
         query.setOffset(2);
-        final Feature instance = executeAndGetFirst();
+        final AbstractFeature instance = executeAndGetFirst();
         assertEquals( 2, instance.getPropertyValue("value1"));
         assertEquals(25, instance.getPropertyValue("value3"));
     }
@@ -368,14 +348,14 @@
     public void testProjectionOfLink() throws DataStoreException {
         createFeatureWithIdentifier();
         query.setProjection(AttributeConvention.IDENTIFIER);
-        final Feature instance = executeAndGetFirst();
+        final AbstractFeature instance = executeAndGetFirst();
         assertEquals("id-0", instance.getPropertyValue(AttributeConvention.IDENTIFIER));
     }
 
     /**
      * Shortcut for creating expression for a projection computed on-the-fly.
      */
-    private static FeatureQuery.NamedExpression virtualProjection(final Expression<Feature, ?> expression, final String alias) {
+    private static FeatureQuery.NamedExpression virtualProjection(final Expression<AbstractFeature, ?> expression, final String alias) {
         return new FeatureQuery.NamedExpression(expression, Names.createLocalName(null, null, alias), FeatureQuery.ProjectionType.COMPUTING);
     }
 
@@ -387,30 +367,30 @@
     @Test
     public void testVirtualProjection() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(
                 new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) null),
                 virtualProjection(ff.property("value1", Integer.class), "renamed1"),
                 virtualProjection(ff.literal("a literal"), "computed"));
 
         // Check result type.
-        final Feature instance = executeAndGetFirst();
-        final FeatureType resultType = instance.getType();
+        final AbstractFeature instance = executeAndGetFirst();
+        final DefaultFeatureType resultType = instance.getType();
         assertEquals("Test", resultType.getName().toString());
         assertEquals(3, resultType.getProperties(true).size());
-        final PropertyType pt1 = resultType.getProperty("value1");
-        final PropertyType pt2 = resultType.getProperty("renamed1");
-        final PropertyType pt3 = resultType.getProperty("computed");
-        assertTrue(pt1 instanceof AttributeType<?>);
-        assertTrue(pt2 instanceof Operation);
-        assertTrue(pt3 instanceof Operation);
-        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());
+        final AbstractIdentifiedType pt1 = resultType.getProperty("value1");
+        final AbstractIdentifiedType pt2 = resultType.getProperty("renamed1");
+        final AbstractIdentifiedType pt3 = resultType.getProperty("computed");
+        assertTrue(pt1 instanceof DefaultAttributeType<?>);
+        assertTrue(pt2 instanceof AbstractOperation);
+        assertTrue(pt3 instanceof AbstractOperation);
+        final AbstractIdentifiedType result2 = ((AbstractOperation) pt2).getResult();
+        final AbstractIdentifiedType result3 = ((AbstractOperation) pt3).getResult();
+        assertEquals(Integer.class, ((DefaultAttributeType<?>) pt1).getValueClass());
+        assertTrue(result2 instanceof DefaultAttributeType<?>);
+        assertTrue(result3 instanceof DefaultAttributeType<?>);
+        assertEquals(Integer.class, ((DefaultAttributeType<?>) result2).getValueClass());
+        assertEquals(String.class,  ((DefaultAttributeType<?>) result3).getValueClass());
 
         // Check feature instance.
         assertEquals(3, instance.getPropertyValue("value1"));
@@ -431,7 +411,7 @@
     @Test
     public void testIncorrectVirtualProjection() throws DataStoreException {
         createFeaturesWithAssociation();
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) null),
                             virtualProjection(ff.property("valueMissing", Integer.class), "renamed1"));
 
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java
index d3ef537..b0961db 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java
@@ -34,9 +34,10 @@
 import static org.apache.sis.metadata.Assertions.assertFeatureSourceEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.metadata.iso.DefaultMetadata;
 
 
 /**
@@ -63,7 +64,7 @@
         builder.addAttribute(String.class).setName("name");
         builder.addAttribute(Integer.class).setName("population");
 
-        final FeatureType ft = builder.build();
+        final DefaultFeatureType ft = builder.build();
         final FeatureSet cfs = ConcatenatedFeatureSet.create(
                 new MemoryFeatureSet(null, ft, List.of(ft.newInstance(), ft.newInstance())),
                 new MemoryFeatureSet(null, ft, List.of(ft.newInstance())));
@@ -74,7 +75,7 @@
         final Metadata md = cfs.getMetadata();
         assertNotNull(md);
         assertContentInfoEquals("City", 3, (FeatureCatalogueDescription) getSingleton(md.getContentInfo()));
-        final Lineage lineage = getSingleton(md.getResourceLineages());
+        final Lineage lineage = getSingleton(((DefaultMetadata) md).getResourceLineages());
         assertFeatureSourceEquals("City", new String[] {"City"}, getSingleton(lineage.getSources()));
     }
 
@@ -93,32 +94,32 @@
         builder.addAttribute(Integer.class).setName("value");
         builder.setName("parent");
 
-        final FeatureType superType = builder.build();
+        final DefaultFeatureType superType = builder.build();
 
         builder.clear();
         builder.setSuperTypes(superType);
         builder.addAttribute(String.class).setName("label");
 
-        final FeatureType t1 = builder.setName("t1").build();
-        final FeatureType t2 = builder.setName("t2").build();
+        final DefaultFeatureType t1 = builder.setName("t1").build();
+        final DefaultFeatureType t2 = builder.setName("t2").build();
 
         // Populate a feature set for first type.
-        final Feature t1f1 = t1.newInstance();
+        final AbstractFeature t1f1 = t1.newInstance();
         t1f1.setPropertyValue("value", 2);
         t1f1.setPropertyValue("label", "first-first");
 
-        final Feature t1f2 = t1.newInstance();
+        final AbstractFeature t1f2 = t1.newInstance();
         t1f2.setPropertyValue("value", 3);
         t1f2.setPropertyValue("label", "first-second");
 
         final FeatureSet t1fs = new MemoryFeatureSet(null, t1, List.of(t1f1, t1f2));
 
         // Populate a feature set for second type.
-        final Feature t2f1 = t2.newInstance();
+        final AbstractFeature t2f1 = t2.newInstance();
         t2f1.setPropertyValue("value", 3);
         t2f1.setPropertyValue("label", "second-first");
 
-        final Feature t2f2 = t2.newInstance();
+        final AbstractFeature t2f2 = t2.newInstance();
         t2f2.setPropertyValue("value", 4);
         t2f2.setPropertyValue("label", "second-second");
 
@@ -154,9 +155,9 @@
     public void noCommonType() {
         final FeatureTypeBuilder builder = new FeatureTypeBuilder();
         builder.setName("super");
-        final FeatureType mockSuperType = builder.build();
-        final FeatureType firstType  = builder.setSuperTypes(mockSuperType).setName("first").build();
-        final FeatureType secondType = builder.clear().setName("second").build();
+        final DefaultFeatureType mockSuperType = builder.build();
+        final DefaultFeatureType firstType  = builder.setSuperTypes(mockSuperType).setName("first").build();
+        final DefaultFeatureType secondType = builder.clear().setName("second").build();
         final FeatureSet fs1 = new MemoryFeatureSet(null, firstType,  List.of());
         final FeatureSet fs2 = new MemoryFeatureSet(null, secondType, List.of());
         var e = assertThrows(DataStoreContentException.class, () -> ConcatenatedFeatureSet.create(fs1, fs2),
@@ -175,7 +176,7 @@
                              "An empty concatenation has been created.");
         assertNotNull(e);
         final FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("mock");
-        final FeatureType mockType = builder.build();
+        final DefaultFeatureType mockType = builder.build();
         final FeatureSet fs1 = new MemoryFeatureSet(null, mockType, List.of());
         final FeatureSet set = ConcatenatedFeatureSet.create(fs1);
         assertSame(fs1, set, "A concatenation has been created from a single type.");
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
index 29151b1..2fabfc1 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
@@ -34,12 +34,12 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.MatchAction;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.BinaryComparisonOperator;
 
 
 /**
@@ -68,7 +68,7 @@
         builder.addAttribute( String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
         builder.addAttribute( String.class).setName("myNameSpace", "att1");
         builder.addAttribute(Integer.class).setName("myNameSpace", "att2");
-        final FeatureType type1 = builder.build();
+        final DefaultFeatureType type1 = builder.build();
         featureSet1 = new MemoryFeatureSet(null, type1, List.of(
                 newFeature1(type1, "fid_1_0", "str1",   1),
                 newFeature1(type1, "fid_1_1", "str2",   2),
@@ -80,7 +80,7 @@
         builder.addAttribute( String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
         builder.addAttribute(Integer.class).setName("otherNameSpace", "att3");
         builder.addAttribute( Double.class).setName("otherNameSpace", "att4");
-        final FeatureType type2 = builder.build();
+        final DefaultFeatureType type2 = builder.build();
         featureSet2 = new MemoryFeatureSet(null, type2, List.of(
                 newFeature2(type2, "fid_2_0",  1, 10),
                 newFeature2(type2, "fid_2_1",  2, 20),
@@ -94,8 +94,8 @@
      * Creates a new feature of type 1 with the given identifier and attribute values.
      * This is a helper method for the constructor only.
      */
-    private static Feature newFeature1(final FeatureType type, final String id, final String att1, final int att2) {
-        final Feature f = type.newInstance();
+    private static AbstractFeature newFeature1(final DefaultFeatureType type, final String id, final String att1, final int att2) {
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue(AttributeConvention.IDENTIFIER, id);
         f.setPropertyValue("att1", att1);
         f.setPropertyValue("att2", att2);
@@ -106,8 +106,8 @@
      * Creates a new feature of type 2 with the given identifier and attribute values.
      * This is a helper method for the constructor only.
      */
-    private static Feature newFeature2(final FeatureType type, final String id, final int att3, final double att4) {
-        final Feature f = type.newInstance();
+    private static AbstractFeature newFeature2(final DefaultFeatureType type, final String id, final int att3, final double att4) {
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue(AttributeConvention.IDENTIFIER, id);
         f.setPropertyValue("att3", att3);
         f.setPropertyValue("att4", att4);
@@ -118,15 +118,15 @@
      * Creates a new join feature set of the given type using the {@link #featureSet1} and {@link #featureSet2}.
      */
     private FeatureSet create(final JoinFeatureSet.Type type) throws DataStoreException {
-        final FilterFactory<Feature, Object, ?> factory = DefaultFilterFactory.forFeatures();
-        final BinaryComparisonOperator<Feature> condition = factory.equal(
+        final DefaultFilterFactory<AbstractFeature, Object, ?> factory = DefaultFilterFactory.forFeatures();
+        final Filter<AbstractFeature> condition = factory.equal(
                 factory.property("att2", String.class),
-                factory.property("att3", String.class),
-                true, MatchAction.ANY);
+                factory.property("att3", String.class));
         final Map<String,Object> properties = new HashMap<>(4);
         assertNull(properties.put("name", "JoinSet"));
         assertNull(properties.put("identifierDelimiter", " "));
-        return new JoinFeatureSet(null, featureSet1, "s1", featureSet2, "s2", type, condition, properties);
+        return new JoinFeatureSet(null, featureSet1, "s1", featureSet2, "s2", type,
+                (BinaryComparisonOperator<AbstractFeature>) condition, properties);
     }
 
     /**
@@ -134,7 +134,7 @@
      * then this method copies the features in a temporary list using parallelized paths
      * before to return the stream of that list.
      */
-    private Stream<Feature> stream(final FeatureSet col) throws DataStoreException {
+    private Stream<AbstractFeature> stream(final FeatureSet col) throws DataStoreException {
         if (parallel) {
             return col.features(true).collect(Collectors.toList()).stream();
         } else {
@@ -145,7 +145,7 @@
     /**
      * Returns the identifier of the given feature.
      */
-    private static String getId(final Feature feature) {
+    private static String getId(final AbstractFeature feature) {
         return String.valueOf(feature.getPropertyValue(AttributeConvention.IDENTIFIER));
     }
 
@@ -157,12 +157,12 @@
     @Test
     public void testInnerJoin() throws DataStoreException {
         final FeatureSet col = create(JoinFeatureSet.Type.INNER);
-        try (Stream<Feature> stream = stream(col)) {
-            final Iterator<Feature> ite = stream.iterator();
+        try (Stream<AbstractFeature> stream = stream(col)) {
+            final Iterator<AbstractFeature> ite = stream.iterator();
             int count = 0;
             while (ite.hasNext()) {
                 count++;
-                final Feature f = ite.next();
+                final AbstractFeature f = ite.next();
                 final String att1;          // Expected value of "att1".
                 final int    join;          // Expected value of "att2" and "att3", on which the join operation is done.
                 final double att4;          // Expected value of "att4".
@@ -173,12 +173,12 @@
                     case "fid_1_2 fid_2_3":  att1 = "str3"; join = 3; att4 = 40; break;
                     default: fail("unexpected feature"); continue;
                 }
-                final Feature c1 = (Feature) f.getPropertyValue("s1");
-                final Feature c2 = (Feature) f.getPropertyValue("s2");
-                assertEquals(att1, c1.getProperty("att1").getValue());
-                assertEquals(join, c1.getProperty("att2").getValue());
-                assertEquals(join, c2.getProperty("att3").getValue());
-                assertEquals(att4, c2.getProperty("att4").getValue());
+                final AbstractFeature c1 = (AbstractFeature) f.getPropertyValue("s1");
+                final AbstractFeature c2 = (AbstractFeature) f.getPropertyValue("s2");
+                assertEquals(att1, ((AbstractAttribute) c1.getProperty("att1")).getValue());
+                assertEquals(join, ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                assertEquals(join, ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                assertEquals(att4, ((AbstractAttribute) c2.getProperty("att4")).getValue());
             }
             assertEquals(4, count, "Unexpected number of features.");
         }
@@ -213,46 +213,46 @@
      * @param  nr  1 if testing outer right, 0 otherwise.
      */
     private void testOuter(final FeatureSet col, final int nl, final int nr) throws DataStoreException {
-        try (Stream<Feature> stream = stream(col)) {
-            final Iterator<Feature> ite = stream.iterator();
+        try (Stream<AbstractFeature> stream = stream(col)) {
+            final Iterator<AbstractFeature> ite = stream.iterator();
             int foundStr1 = 0, foundStr20 = 0, foundStr50 = 0, foundStr60 = 0,
                 foundStr3 = 0, foundStr21 = 0, foundStr51 = 0, foundStr61 = 0, count = 0;
             while (ite.hasNext()) {
-                final Feature f  = ite.next();
-                final Feature c1 = (Feature) f.getPropertyValue("s1");
-                final Feature c2 = (Feature) f.getPropertyValue("s2");
+                final AbstractFeature f  = ite.next();
+                final AbstractFeature c1 = (AbstractFeature) f.getPropertyValue("s1");
+                final AbstractFeature c2 = (AbstractFeature) f.getPropertyValue("s2");
                 if (c1 != null) {
-                    switch ((String) c1.getProperty("att1").getValue()) {
+                    switch ((String) ((AbstractAttribute) c1.getProperty("att1")).getValue()) {
                         case "str1": {
-                            assertEquals( 1,  c1.getProperty("att2").getValue());
-                            assertEquals( 1,  c2.getProperty("att3").getValue());
-                            assertEquals(10d, c2.getProperty("att4").getValue());
+                            assertEquals( 1,  ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                            assertEquals( 1,  ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                            assertEquals(10d, ((AbstractAttribute) c2.getProperty("att4")).getValue());
                             foundStr1++;
                             break;
                         }
                         case "str2": {
-                            assertEquals(2, c1.getProperty("att2").getValue());
-                            assertEquals(2, c2.getProperty("att3").getValue());
-                            double att4 = (Double)  c2.getProperty("att4").getValue();
+                            assertEquals(2, ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                            assertEquals(2, ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                            double att4 = (Double)  ((AbstractAttribute) c2.getProperty("att4")).getValue();
                             if (att4 == 20) foundStr20++;
                             if (att4 == 30) foundStr21++;
                             break;
                         }
                         case "str3": {
-                            assertEquals( 3,  c1.getProperty("att2").getValue());
-                            assertEquals( 3,  c2.getProperty("att3").getValue());
-                            assertEquals(40d, c2.getProperty("att4").getValue());
+                            assertEquals( 3,  ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                            assertEquals( 3,  ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                            assertEquals(40d, ((AbstractAttribute) c2.getProperty("att4")).getValue());
                             foundStr3++;
                             break;
                         }
                         case "str50": {
-                            assertEquals(50, c1.getProperty("att2").getValue());
+                            assertEquals(50, ((AbstractAttribute) c1.getProperty("att2")).getValue());
                             assertNull(c2);
                             foundStr50++;
                             break;
                         }
                         case "str51": {
-                            assertEquals(51, c1.getProperty("att2").getValue());
+                            assertEquals(51, ((AbstractAttribute) c1.getProperty("att2")).getValue());
                             assertNull(c2);
                             foundStr51++;
                             break;
@@ -263,14 +263,14 @@
                         }
                     }
                 } else {
-                    switch ((Integer) c2.getProperty("att3").getValue()) {
+                    switch ((Integer) ((AbstractAttribute) c2.getProperty("att3")).getValue()) {
                         case 60: {
-                            assertEquals(c2.getProperty("att4").getValue(), 60d);
+                            assertEquals(((AbstractAttribute) c2.getProperty("att4")).getValue(), 60d);
                             foundStr60++;
                             break;
                         }
                         case 61: {
-                            assertEquals(c2.getProperty("att4").getValue(), 61d);
+                            assertEquals(((AbstractAttribute) c2.getProperty("att4")).getValue(), 61d);
                             foundStr61++;
                             break;
                         }
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java
index a3d8f44..6ef1ccf 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java
@@ -33,11 +33,11 @@
 import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.content.FeatureCatalogueDescription;
-import org.opengis.metadata.content.FeatureTypeInfo;
-import org.opengis.metadata.constraint.LegalConstraints;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
+import org.apache.sis.metadata.iso.content.DefaultFeatureCatalogueDescription;
+import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
 
 
 /**
@@ -88,8 +88,8 @@
     private static void verifyCopyrightParsing(final String notice) {
         final var builder = new MetadataBuilder();
         builder.parseLegalNotice(notice);
-        final var constraints = assertInstanceOf(LegalConstraints.class,
-                getSingleton(getSingleton(builder.build().getIdentificationInfo()).getResourceConstraints()));
+        final var constraints = assertInstanceOf(DefaultLegalConstraints.class, getSingleton(getSingleton(
+                builder.build().getIdentificationInfo()).getResourceConstraints()));
 
         assertEquals(Restriction.COPYRIGHT, getSingleton(constraints.getUseConstraints()));
         final Citation ref = getSingleton(constraints.getReferences());
@@ -99,7 +99,7 @@
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      *
      * @todo Combine the 4 tests in a single one for leveraging the same {@link DefaultFeatureType} instance?
      *       It would be consistent with {@link #testParseLegalNotice()}, and the error message in those tests
@@ -111,7 +111,7 @@
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      */
     @Test
     public void no_overflow_on_feature_count() {
@@ -119,7 +119,7 @@
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      */
     @Test
     public void verify_feature_count_is_written() {
@@ -127,7 +127,7 @@
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      */
     @Test
     public void feature_should_be_ignored_when_count_is_zero() {
@@ -136,7 +136,7 @@
 
     /**
      * Creates a new simple metadata with a single simple feature type and the given
-     * {@linkplain FeatureTypeInfo#getFeatureInstanceCount() feature instance count}.
+     * {@linkplain DefaultFeatureTypeInfo#getFeatureInstanceCount() feature instance count}.
      * Then, asserts that the value in the built metadata is compliant with a given control value.
      *
      * @param expected       the feature instance count value we want to see in the metadata (control value).
@@ -153,8 +153,8 @@
             assertTrue(metadata.getContentInfo().isEmpty());
         } else {
             final ContentInformation content = getSingleton(metadata.getContentInfo());
-            assertInstanceOf(FeatureCatalogueDescription.class, content);
-            final FeatureTypeInfo info = getSingleton(((FeatureCatalogueDescription) content).getFeatureTypeInfo());
+            assertInstanceOf(DefaultFeatureCatalogueDescription.class, content);
+            final DefaultFeatureTypeInfo info = getSingleton(((DefaultFeatureCatalogueDescription) content).getFeatureTypeInfo());
             assertEquals(expected, info.getFeatureInstanceCount(), errorMessage);
         }
     }
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/csv/StoreTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/csv/StoreTest.java
index de9ed14..b4f73f8 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/csv/StoreTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/csv/StoreTest.java
@@ -39,11 +39,12 @@
 import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 
 
 /**
@@ -108,7 +109,7 @@
         try (Store store = open(true)) {
             metadata = store.getMetadata();
         }
-        final Extent extent = getSingleton(getSingleton(metadata.getIdentificationInfo()).getExtents());
+        final Extent extent = getSingleton(((AbstractIdentification) getSingleton(metadata.getIdentificationInfo())).getExtents());
         final GeographicBoundingBox bbox = (GeographicBoundingBox) getSingleton(extent.getGeographicElements());
         assertEquals(50.23, bbox.getWestBoundLongitude());
         assertEquals(50.31, bbox.getEastBoundLongitude());
@@ -127,7 +128,7 @@
         try (Store store = open(true)) {
             verifyFeatureType(store.featureType, double[].class, 1);
             assertEquals(Foliation.TIME, store.foliation);
-            final Iterator<Feature> it = store.features(false).iterator();
+            final Iterator<AbstractFeature> it = store.features(false).iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:11", new double[] {11, 2, 12, 3},        "walking", 1);
             assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3},        "walking", 2);
             assertPropertyEquals(it.next(), "a", "12:36:11", "12:36:51", new double[] {12, 3, 10, 3},        "walking", 2);
@@ -156,7 +157,7 @@
         try (Store store = open(false)) {
             verifyFeatureType(store.featureType, Polyline.class, Integer.MAX_VALUE);
             assertEquals(Foliation.TIME, store.foliation);
-            final Iterator<Feature> it = store.features(false).iterator();
+            final Iterator<AbstractFeature> it = store.features(false).iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:51", new double[] {11, 2, 12, 3, 10, 3}, List.of("walking"), List.of(1, 2));
             assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3},        List.of("walking"), List.of(2));
             assertPropertyEquals(it.next(), "c", "12:33:51", "12:36:51", new double[] {12, 1, 10, 2, 11, 3}, List.of("vehicle"), List.of(1));
@@ -167,21 +168,21 @@
     /**
      * Verifies that the feature type is equal to the expected one.
      */
-    private static void verifyFeatureType(final FeatureType type, final Class<?> geometryType, final int maxOccurs) {
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "mfidref",       String.class,   1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "startTime",     Instant.class,  1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "endTime",       Instant.class,  1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "trajectory",    geometryType,   1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "state",         String.class,   0, maxOccurs);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "\"type\" code", Integer.class,  0, maxOccurs);
+    private static void verifyFeatureType(final DefaultFeatureType type, final Class<?> geometryType, final int maxOccurs) {
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "mfidref",       String.class,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "startTime",     Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "endTime",       Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "trajectory",    geometryType,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "state",         String.class,   0, maxOccurs);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "\"type\" code", Integer.class,  0, maxOccurs);
         assertFalse(it.hasNext());
     }
 
     /**
      * Asserts that the given property type has the given information.
      */
-    private static void assertPropertyTypeEquals(final AttributeType<?> p,
+    private static void assertPropertyTypeEquals(final DefaultAttributeType<?> p,
             final String name, final Class<?> valueClass, final int minOccurs, final int maxOccurs)
     {
         assertEquals(name,       p.getName().toString());
@@ -193,7 +194,7 @@
     /**
      * Asserts that the property of the given name in the given feature has expected information.
      */
-    private void assertPropertyEquals(final Feature f, final String mfidref,
+    private void assertPropertyEquals(final AbstractFeature f, final String mfidref,
             final String startTime, final String endTime, final double[] trajectory,
             final Object state, final Object typeCode)
     {
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/esri/AsciiGridStoreTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/esri/AsciiGridStoreTest.java
index 67a74ab..d6486c4 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/esri/AsciiGridStoreTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/esri/AsciiGridStoreTest.java
@@ -33,8 +33,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.Identification;
+// Specific to the main branch:
+import org.opengis.metadata.identification.DataIdentification;
 
 
 /**
@@ -85,9 +85,7 @@
              * Format information is hard-coded in "SpatialMetadata" database. Complete string should
              * be "ESRI ArcInfo ASCII Grid format" but it depends on the presence of Derby dependency.
              */
-            final Identification id = getSingleton(metadata.getIdentificationInfo());
-            final String format = getSingleton(id.getResourceFormats()).getFormatSpecificationCitation().getTitle().toString();
-            assertTrue(format.contains("ASCII Grid"), format);
+            final DataIdentification id = (DataIdentification) getSingleton(metadata.getIdentificationInfo());
             /*
              * This information should have been read from the PRJ file.
              */
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/image/WorldFileStoreTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/image/WorldFileStoreTest.java
index 9027bd7..a5c8cdc 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/image/WorldFileStoreTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/image/WorldFileStoreTest.java
@@ -39,8 +39,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.identification.Identification;
+// Specific to the main branch:
+import org.opengis.metadata.identification.DataIdentification;
 
 
 /**
@@ -100,9 +100,7 @@
              */
             assertEquals("gradient", store.getIdentifier().get().toString());
             final Metadata metadata = store.getMetadata();
-            final Identification id = getSingleton(metadata.getIdentificationInfo());
-            final String format = getSingleton(id.getResourceFormats()).getFormatSpecificationCitation().getTitle().toString();
-            assertTrue(format.contains("PNG"), format);
+            final DataIdentification id = (DataIdentification) getSingleton(metadata.getIdentificationInfo());
             assertEquals("WGS 84", getSingleton(metadata.getReferenceSystemInfo()).getName().getCode());
             final GeographicBoundingBox bbox = (GeographicBoundingBox)
                     getSingleton(getSingleton(id.getExtents()).getGeographicElements());
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/test/CoverageReadConsistency.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/test/CoverageReadConsistency.java
index 1f92a0b..3003ead 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/test/CoverageReadConsistency.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/test/CoverageReadConsistency.java
@@ -401,8 +401,13 @@
              * two-dimensional image, the following loop will be executed only once. If reading a 3D
              * or 4D image, the loop is executed for all possible two-dimensional slices in the cube.
              */
-            final long[] sliceMin = actualReadExtent.getLow() .getCoordinateValues();
-            final long[] sliceMax = actualReadExtent.getHigh().getCoordinateValues();
+            final int sd = actualReadExtent.getDimension();
+            final long[] sliceMin = new long[sd];
+            final long[] sliceMax = new long[sd];
+            for (int i=0; i<sd; i++) {
+                sliceMin[i] = actualReadExtent.getLow(i);
+                sliceMax[i] = actualReadExtent.getHigh(i);
+            }
 nextSlice:  for (;;) {
                 System.arraycopy(sliceMin, BIDIMENSIONAL, sliceMax, BIDIMENSIONAL, dimension - BIDIMENSIONAL);
                 final PixelIterator itr = iterator(full,   sliceMin, sliceMax, subsampling, subOffsets, allowSubsampling);
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/wkt/StoreTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/wkt/StoreTest.java
index 12b30d1..1aa0527 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/wkt/StoreTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/wkt/StoreTest.java
@@ -31,8 +31,8 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+// Specific to the main branch:
+import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/xml/StoreTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/xml/StoreTest.java
index 15ab93b..aa01302 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/xml/StoreTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/xml/StoreTest.java
@@ -32,8 +32,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.nio.charset.StandardCharsets;
+// Specific to the main branch:
+import org.opengis.metadata.identification.CharacterSet;
 
 
 /**
@@ -73,7 +73,7 @@
             "          <gmd:onlineResource>\n" +
             "            <gmd:CI_OnlineResource>\n" +
             "              <gmd:linkage>\n" +
-            "                <gmd:URL>https://sis.apache.org</gmd:URL>\n" +
+            "                <gmd:URL>http://sis.apache.org</gmd:URL>\n" +
             "              </gmd:linkage>\n" +
             "              <gmd:function>\n" +
             "                <gmd:CI_OnLineFunctionCode codeListValue=\"information\" codeSpace=\"fra\">Information</gmd:CI_OnLineFunctionCode>\n" +
@@ -102,17 +102,15 @@
             metadata = store.getMetadata();
             assertSame(metadata, store.getMetadata(), "Expected cached value.");
         }
-        final Responsibility resp     = getSingleton(metadata.getContacts());
-        final Party          party    = getSingleton(resp.getParties());
-        final Contact        contact  = getSingleton(party.getContactInfo());
-        final OnlineResource resource = getSingleton(contact.getOnlineResources());
+        final ResponsibleParty resp     = getSingleton(metadata.getContacts());
+        final Contact          contact  = resp.getContactInfo();
+        final OnlineResource   resource = contact.getOnlineResource();
 
-        assertInstanceOf(Organisation.class, party, "party");
-        assertEquals(Locale.ENGLISH,              getSingleton(metadata.getLocalesAndCharsets().keySet()));
-        assertEquals(StandardCharsets.UTF_8,      getSingleton(metadata.getLocalesAndCharsets().values()));
+        assertEquals(Locale.ENGLISH,              metadata.getLanguage());
+        assertEquals(CharacterSet.UTF_8,          metadata.getCharacterSet());
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, resp.getRole());
-        assertEquals("Apache SIS",                String.valueOf(party.getName()));
-        assertEquals("https://sis.apache.org",    String.valueOf(resource.getLinkage()));
+        assertEquals("Apache SIS",                String.valueOf(resp.getOrganisationName()));
+        assertEquals("http://sis.apache.org",     String.valueOf(resource.getLinkage()));
         assertEquals(OnLineFunction.INFORMATION,  resource.getFunction());
     }
 }
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java b/endorsed/src/org.apache.sis.util/main/module-info.java
index 6391f7b..de0142c 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -30,7 +30,7 @@
     requires transitive java.sql;
     requires transitive java.logging;
     requires transitive java.measure;
-    requires transitive org.opengis.geoapi.pending;
+    requires transitive org.opengis.geoapi;
 
     provides javax.measure.spi.ServiceProvider
         with org.apache.sis.measure.UnitServices;
@@ -119,8 +119,6 @@
             org.apache.sis.storage.netcdf,
             org.apache.sis.storage.geotiff,
             org.apache.sis.storage.earthobservation,
-            org.apache.sis.cql,                         // In the "incubator" sub-project.
-            org.apache.sis.portrayal.map,               // In the "incubator" sub-project.
             org.apache.sis.portrayal,
             org.apache.sis.cloud.aws,
             org.apache.sis.console,
@@ -128,6 +126,10 @@
             org.apache.sis.referencing.epsg,            // In the "non-free" sub-project.
             org.apache.sis.referencing.database;        // In the "non-free" sub-project.
 
+    exports org.apache.sis.pending.geoapi.temporal to
+            org.apache.sis.metadata,
+            org.apache.sis.feature;
+
     exports org.apache.sis.converter to
             org.apache.sis.metadata,
             org.apache.sis.referencing,
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/IdentifiedObjectFormat.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/IdentifiedObjectFormat.java
index 66f04ba..6f7b5f8 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/IdentifiedObjectFormat.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/IdentifiedObjectFormat.java
@@ -27,8 +27,8 @@
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.MetadataServices;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -60,7 +60,7 @@
      */
     @Override
     public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
-        final Identifier identifier = ((IdentifiedObject) obj).getName();
+        final ReferenceIdentifier identifier = ((IdentifiedObject) obj).getName();
         if (identifier == null) {
             return toAppendTo.append(Vocabulary.forLocale(locale).getString(Vocabulary.Keys.Unnamed));
         }
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java
index e59669e..77705e2 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java
@@ -306,7 +306,6 @@
      *
      * @param  lineSeparator  the new line separator, or {@code null} for forwarding EOL <i>as-is</i>.
      *
-     * @see System#lineSeparator()
      * @see Characters#isLineOrParagraphSeparator(int)
      */
     public void setLineSeparator(final String lineSeparator) {
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java
index c3e7738..0605b1d 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java
@@ -213,7 +213,7 @@
      *   <li>If present, {@code '?'} shall be the first character in the pattern.</li>
      *   <li>The repeated character (specified inside the pair of brackets) is mandatory.</li>
      *   <li>In the current implementation, the repeated character must be in the
-     *       {@linkplain Character#isBmpCodePoint(int) Basic Multilanguage Plane}.</li>
+     *       Basic Multilanguage Plane.</li>
      *   <li>If {@code '/'} is present, anything on its right side shall be compliant
      *       with the {@link Pattern} syntax.</li>
      * </ul>
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/CompoundDirectPositions.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/CompoundDirectPositions.java
index 02a0cab..d20a906 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/CompoundDirectPositions.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/CompoundDirectPositions.java
@@ -20,6 +20,9 @@
 import org.opengis.geometry.DirectPosition;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the main branch:
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
 
 /**
  * A sequence of {@code DirectPosition}s which is a view over arrays of coordinate values.
@@ -99,6 +102,26 @@
     }
 
     /**
+     * Returns {@code this} since this object is already a direct position.
+     *
+     * @return always {@code this}.
+     */
+    @Override
+    public DirectPosition getDirectPosition() {
+        return this;
+    }
+
+    /**
+     * Returns {@code null} since there is no CRS associated to this object.
+     *
+     * @return always {@code null}.
+     */
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return null;
+    }
+
+    /**
      * Returns the number of dimensions.
      */
     @Override
@@ -110,7 +133,23 @@
      * Return the coordinate value at the given dimension.
      */
     @Override
-    public double getCoordinate(final int dimension) {
+    public double getOrdinate(final int dimension) {
         return coordinates[dimension].doubleValue(index);
     }
+
+    /**
+     * Not needed.
+     */
+    @Override
+    public double[] getCoordinate() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Not needed.
+     */
+    @Override
+    public void setOrdinate(int dimension, double value) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Line.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Line.java
index 0b1c196..51246ba 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Line.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Line.java
@@ -26,8 +26,8 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -310,14 +310,13 @@
         for (final DirectPosition p : points) {
             final int dimension = p.getDimension();
             if (dimension != DIMENSION) {
-                throw new org.opengis.geometry.MismatchedDimensionException(
-                        Errors.format(Errors.Keys.MismatchedDimension_3,
-                        Strings.toIndexed("points", i), DIMENSION, dimension));
+                throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
+                            Strings.toIndexed("points", i), DIMENSION, dimension));
             }
             i++;
             final double x,y;
-            if (!isNaN(y = p.getCoordinate(1)) &&     // Test first the dimension which is most likely to contain NaN.
-                !isNaN(x = p.getCoordinate(0)))
+            if (!isNaN(y = p.getOrdinate(1)) &&     // Test first the dimension which is most likely to contain NaN.
+                !isNaN(x = p.getOrdinate(0)))
             {
                 mean_x = mean_x.add(x, false);
                 mean_y = mean_y.add(y, false);
@@ -344,8 +343,8 @@
         DoubleDouble mean_xy = DoubleDouble.ZERO;
         for (final DirectPosition p : points) {
             final double y, x;
-            if (!isNaN(y = p.getCoordinate(1)) &&     // Test first the dimension which is most likely to contain NaN.
-                !isNaN(x = p.getCoordinate(0)))
+            if (!isNaN(y = p.getOrdinate(1)) &&     // Test first the dimension which is most likely to contain NaN.
+                !isNaN(x = p.getOrdinate(0)))
             {
                 var  dx = DoubleDouble.of(x, true).subtract(mean_x);    // Δx = x - mean_x
                 mean_x2 = mean_x2.add(dx.square());                     // mean_x² += (Δx)²
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/MathFunctions.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/MathFunctions.java
index 9327ed7..e8d3ad0 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/MathFunctions.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/MathFunctions.java
@@ -240,7 +240,7 @@
      * returns directly the {@linkplain Math#abs(double) absolute value} of that element
      * without computing {@code sqrt(v²)}, in order to avoid rounding error. This special case
      * has been implemented because this method is often invoked for computing the length of
-     * {@linkplain org.opengis.coverage.grid.RectifiedGrid#getOffsetVectors() offset vectors},
+     * offset vectors,
      * typically aligned with the axes of a {@linkplain org.opengis.referencing.cs.CartesianCS
      * Cartesian coordinate system}.
      *
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Plane.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Plane.java
index dedfb85..aa1ee48 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Plane.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Plane.java
@@ -30,8 +30,8 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -403,14 +403,13 @@
             for (final DirectPosition p : points) {
                 final int dimension = p.getDimension();
                 if (dimension != DIMENSION) {
-                    throw new org.opengis.geometry.MismatchedDimensionException(
-                            Errors.format(Errors.Keys.MismatchedDimension_3,
-                            Strings.toIndexed("points", i), DIMENSION, dimension));
+                    throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
+                                Strings.toIndexed("points", i), DIMENSION, dimension));
                 }
                 i++;
-                final double x = p.getCoordinate(0); if (Double.isNaN(x)) continue;
-                final double y = p.getCoordinate(1); if (Double.isNaN(y)) continue;
-                final double z = p.getCoordinate(2); if (Double.isNaN(z)) continue;
+                final double x = p.getOrdinate(0); if (Double.isNaN(x)) continue;
+                final double y = p.getOrdinate(1); if (Double.isNaN(y)) continue;
+                final double z = p.getOrdinate(2); if (Double.isNaN(z)) continue;
                 sum_x  = sum_x .add(x, false);
                 sum_y  = sum_y .add(y, false);
                 sum_z  = sum_z .add(z, false);
@@ -518,9 +517,9 @@
                 } else {
                     if (!points.hasNext()) break;
                     final DirectPosition p = points.next();
-                    x = p.getCoordinate(0);
-                    y = p.getCoordinate(1);
-                    z = p.getCoordinate(2);
+                    x = p.getOrdinate(0);
+                    y = p.getOrdinate(1);
+                    z = p.getOrdinate(2);
                 }
                 x = (x - mean_x) * sx;
                 y = (y - mean_y) * sy;
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Angle.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Angle.java
index bb3bc61..a53bc95 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Angle.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Angle.java
@@ -164,7 +164,7 @@
             final AxisDirection dir = axis.getDirection();
             final boolean isPositive = dir.equals(positive);
             if (isPositive || dir.equals(negative)) {
-                double value = position.getCoordinate(i);
+                double value = position.getOrdinate(i);
                 if (!isPositive) value = -value;
                 final Unit<?> unit = axis.getUnit();
                 if (unit != Units.DEGREE) try {
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/MeasurementRange.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/MeasurementRange.java
index 177bdcf..7a61a9a 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/MeasurementRange.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/MeasurementRange.java
@@ -258,13 +258,6 @@
      * instead of {@code MeasurementRange}. Nevertheless this method may return {@code null} if a unit
      * <em>should</em> exist but for some reason is unavailable.
      *
-     * <h4>Example</h4>
-     * ISO 19115-1 {@code SampleDimension} specifies that its
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getUnits() unit} property is mandatory if the
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getMinValue() minimum value} or
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getMaxValue() maximum value} are provided.
-     * Nevertheless it happens sometimes that this information is missing in metadata.
-     *
      * @return the unit of measurement, or {@code null}.
      */
     @Override
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/NumberRange.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/NumberRange.java
index 6cee110..0961800 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/NumberRange.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/NumberRange.java
@@ -64,8 +64,8 @@
  * <a href="https://en.wikipedia.org/wiki/Interval_%28mathematics%29">mathematical definition of interval</a>.
  * It is closely related, while not identical, to the ISO 19123 (<cite>Coverage geometry and functions</cite>)
  * definition of "ranges". At the difference of the parent {@link Range} class, which can be used only with
- * {@linkplain org.opengis.coverage.DiscreteCoverage discrete coverages}, the {@code NumberRange} class can
- * also be used with {@linkplain org.opengis.coverage.ContinuousCoverage continuous coverages}.
+ * discrete coverages, the {@code NumberRange} class can
+ * also be used with continuous coverages.
  *
  * <h2>Immutability and thread safety</h2>
  * This class and the {@link MeasurementRange} subclasses are immutable, and thus inherently thread-safe.
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Range.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Range.java
index 0ee5fbb..d6ae2f8 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Range.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/Range.java
@@ -57,8 +57,8 @@
  *
  * <h2>Relationship with ISO 19123 definition of range</h2>
  * The ISO 19123 standard (<cite>Coverage geometry and functions</cite>) defines the range as the set
- * (either finite or {@linkplain org.opengis.geometry.TransfiniteSet transfinite}) of feature attribute
- * values associated by a function (the {@linkplain org.opengis.coverage.Coverage coverage}) with the
+ * (either finite or transfinite) of feature attribute
+ * values associated by a function (the coverage) with the
  * elements of the coverage domain. In other words, if we see a coverage as a function, then a range
  * is the set of possible return values.
  *
@@ -69,9 +69,8 @@
  * is closely related, but not identical, to the ISO 19123 definition or range.</p>
  *
  * <p>Ranges are not necessarily numeric. Numeric and non-numeric ranges can be associated to
- * {@linkplain org.opengis.coverage.DiscreteCoverage discrete coverages}, while typically only
- * numeric ranges can be associated to {@linkplain org.opengis.coverage.ContinuousCoverage
- * continuous coverages}.</p>
+ * discrete coverages, while typically only
+ * numeric ranges can be associated to continuous coverages.</p>
  *
  * <h2>Immutability and thread safety</h2>
  * This class and the {@link NumberRange} / {@link MeasurementRange} subclasses are immutable,
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/Period.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/Period.java
new file mode 100644
index 0000000..f8ccb86
--- /dev/null
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/Period.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.geoapi.temporal;
+
+import java.time.temporal.Temporal;
+import org.opengis.temporal.TemporalPrimitive;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 1.5
+ */
+public interface Period extends TemporalPrimitive {
+    /**
+     * Links this period to the instant at which it ends.
+     *
+     * @return The beginning instant.
+     */
+    Temporal getBeginning();
+
+    /**
+     * Links this period to the instant at which it ends.
+     *
+     * @return The end instant.
+     */
+    Temporal getEnding();
+}
diff --git a/incubator/src/org.apache.sis.cql/main/module-info.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/package-info.java
similarity index 68%
copy from incubator/src/org.apache.sis.cql/main/module-info.java
copy to endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/package-info.java
index 4307e9c..f5965ef 100644
--- a/incubator/src/org.apache.sis.cql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/package-info.java
@@ -16,12 +16,15 @@
  */
 
 /**
- * CQL parser.
+ * Placeholder for GeoAPI interfaces not present in GeoAPI 3.0.
  *
- * @author  Johann Sorel (Geomatys)
+ * <STRONG>Do not use!</STRONG>
+ *
+ * This package is for internal use by SIS only. Classes in this package
+ * may change in incompatible ways in any future version without notice.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 1.5
  */
-module org.apache.sis.cql {
-    requires transitive org.apache.sis.feature;
-    requires org.locationtech.jts;
-    requires org.antlr.antlr4.runtime;
-}
+package org.apache.sis.pending.geoapi.temporal;
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Modules.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Modules.java
index 4c9a7d9..f4c1c8c 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Modules.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Modules.java
@@ -116,7 +116,7 @@
      *
      * @see org.apache.sis.util.Version
      */
-    public static final int MINOR_VERSION = 9;
+    public static final int MINOR_VERSION = 5;
 
     /**
      * The prefix of all classnames in Apache SIS, including a trailing dot.
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Supervisor.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Supervisor.java
index 0c006b1..5247500 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Supervisor.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Supervisor.java
@@ -56,7 +56,7 @@
      * Java compiler to omit any dependency to this {@code Supervisor} class.
      */
     @Configuration
-    static final boolean ENABLED = true;
+    static final boolean ENABLED = false;
 
     /**
      * The JMX object name for the {@code Supervisor} service.
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/AbstractInternationalString.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/AbstractInternationalString.java
index 72d0bff..a546c1c 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/AbstractInternationalString.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/AbstractInternationalString.java
@@ -38,7 +38,7 @@
  * by a {@link org.opengis.util.CodeList} value. This can be done with:
  *
  * <ul>
- *   <li>{@link org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)} for getting
+ *   <li>{@link org.apache.sis.util.iso.Types#getCodeTitle(CodeList)} for getting
  *       the {@link InternationalString} instance to store in a metadata property.</li>
  *   <li>{@link org.apache.sis.util.iso.Types#forCodeTitle(CharSequence)} for retrieving the
  *       {@link org.opengis.util.CodeList} previously stored as an {@code InternationalString}.</li>
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java
index 391a52b..42b3731 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java
@@ -27,9 +27,8 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.coverage.grid.GridEnvelope;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -771,7 +770,7 @@
             if (cs != null) {                                       // Should never be null, but let be safe.
                 final int dimension = cs.getDimension();
                 if (dimension != expected) {
-                    throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                    throw new MismatchedDimensionException(Errors.format(
                             Errors.Keys.MismatchedDimension_3, name, expected, dimension));
                 }
             }
@@ -796,7 +795,7 @@
         if (cs != null) {
             final int dimension = cs.getDimension();
             if (dimension != expected) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -820,7 +819,7 @@
         if (indices != null) {
             final int dimension = indices.length;
             if (dimension != expected) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -842,7 +841,7 @@
         if (vector != null) {
             final int dimension = vector.length;
             if (dimension != expected) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -864,7 +863,7 @@
         if (position != null) {
             final int dimension = position.getDimension();
             if (dimension != expected) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -886,31 +885,7 @@
         if (envelope != null) {
             final int dimension = envelope.getDimension();
             if (dimension != expected) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
-                        Errors.Keys.MismatchedDimension_3, name, expected, dimension));
-            }
-        }
-    }
-
-    /**
-     * Ensures that the given grid envelope, if non-null, has the expected number of dimensions.
-     * This method does nothing if the given grid envelope is null.
-     *
-     * @param  name      the name of the argument to be checked. Used only if an exception is thrown.
-     * @param  expected  the expected number of dimensions.
-     * @param  envelope  the grid envelope to check for its dimension, or {@code null}.
-     * @throws MismatchedDimensionException if the given envelope is non-null and does
-     *         not have the expected number of dimensions.
-     *
-     * @since 1.3
-     */
-    public static void ensureDimensionMatches(final String name, final int expected, final GridEnvelope envelope)
-            throws MismatchedDimensionException
-    {
-        if (envelope != null) {
-            final int dimension = envelope.getDimension();
-            if (dimension != expected) {
-                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
+                throw new MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -943,9 +918,8 @@
                 expectedSource = expectedTarget;
                 side = 1;
             }
-            throw new org.opengis.geometry.MismatchedDimensionException(
-                    Errors.format(Errors.Keys.MismatchedTransformDimension_4,
-                    name, side, expectedSource, dimension));
+            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedTransformDimension_4,
+                                                                 name, side, expectedSource, dimension));
         }
     }
 }
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java
index 92e0be6..432b03b 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java
@@ -27,6 +27,9 @@
 import org.apache.sis.util.privy.CheckedArrayList;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the main branch:
+import org.apache.sis.util.privy.CodeLists;
+
 
 /**
  * A specialized {@code Set} implementation for use with {@link CodeList} values.
@@ -135,7 +138,7 @@
     public CodeListSet(final Class<E> elementType, final boolean fill) throws IllegalArgumentException {
         this(elementType);
         if (fill) {
-            codes = POOL.unique(CodeList.values(elementType));
+            codes = POOL.unique(CodeLists.values(elementType));
             int n = codes.length;
             if (n < Long.SIZE) {
                 values = (1L << n) - 1;
@@ -166,7 +169,7 @@
     final E valueOf(final int ordinal) {
         E[] array = codes;
         if (array == null || ordinal >= array.length) {
-            codes = array = POOL.unique(CodeList.values(elementType));
+            codes = array = POOL.unique(CodeLists.values(elementType));
         }
         return array[ordinal];
     }
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java
index 323e2cb..271f6bd 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java
@@ -25,29 +25,51 @@
 import org.apache.sis.util.Characters.Filter;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import java.lang.reflect.Array;
 
 
 /**
  * Implementation of some {@link org.apache.sis.util.iso.Types} methods needed by {@code org.apache.sis.util} module.
- * This class opportunistically implements {@link Predicate} interface, but this is an implementation details.
+ * This class opportunistically implements {@code CodeList.Filter} interface, but this is an implementation details.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class CodeLists implements Predicate<CodeList<?>> {
+public final class CodeLists implements CodeList.Filter {
+    /**
+     * The name of bundle resources for code list titles.
+     * Keys are {@link CodeList#identifier()}.
+     */
+    public static final String RESOURCES = "org.apache.sis.metadata.iso.CodeLists";
+
     /**
      * The name to compare during filtering operation.
      */
     private final String codename;
 
     /**
+     * {@code true} if {@link CodeList#valueOf} is allowed to create new code lists.
+     */
+    private final boolean canCreate;
+
+    /**
      * Creates a new filter for the specified code name.
      *
      * @param codename the name to compare during filtering operation.
      */
-    private CodeLists(final String codename) {
+    private CodeLists(final String codename, final boolean canCreate) {
         this.codename  = codename;
+        this.canCreate = canCreate;
+    }
+
+    /**
+     * Returns the name of the code to create, or {@code null} if no new code list shall be created.
+     *
+     * @return the name specified at construction time.
+     */
+    @Override
+    public String codename() {
+        return canCreate ? codename : null;
     }
 
     /**
@@ -56,7 +78,7 @@
      * @param  code  the code list candidate.
      */
     @Override
-    public boolean test(final CodeList<?> code) {
+    public boolean accept(final CodeList<?> code) {
         for (final String candidate : code.names()) {
             if (accept(candidate, codename)) {
                 return true;
@@ -89,15 +111,7 @@
             return Enum.valueOf(enumType, name);
         } catch (IllegalArgumentException e) {
             final T[] values = enumType.getEnumConstants();
-            if (values instanceof ControlledVocabulary[]) {
-                for (final ControlledVocabulary code : (ControlledVocabulary[]) values) {
-                    for (final String candidate : code.names()) {
-                        if (accept(candidate, name)) {
-                            return enumType.cast(code);
-                        }
-                    }
-                }
-            } else if (values != null) {
+            if (values != null) {
                 for (final Enum<?> code : values) {
                     if (accept(code.name(), name)) {
                         return enumType.cast(code);
@@ -119,7 +133,7 @@
      */
     public static <E extends CodeList<E>> E forCodeName(final Class<E> codeType, String name) {
         name = Strings.trimOrNull(name);
-        return (name != null) ? find(codeType, new CodeLists(name)) : null;
+        return (name != null) ? CodeList.valueOf(codeType, new CodeLists(name, false)) : null;
     }
 
     /**
@@ -131,12 +145,10 @@
      * @return a code matching the given name, or {@code null} if none.
      */
     public static <E extends CodeList<E>> E find(final Class<E> codeType, final Predicate<? super CodeList<?>> filter) {
-        for (final E code : CodeList.values(codeType)) {
-            if (filter.test(code)) {
-                return code;
-            }
-        }
-        return null;
+        return CodeList.valueOf(codeType, new CodeList.Filter() {
+            @Override public boolean accept(CodeList<?> code) {return filter.test(code);}
+            @Override public String codename() {return null;}
+        });
     }
 
     /**
@@ -155,9 +167,21 @@
         if (name == null) {
             return null;
         }
-        E code = forCodeName(codeType, name);
-        if (code == null) try {
-            code = codeType.cast(codeType.getMethod("valueOf", String.class).invoke(null, name));
+        return CodeList.valueOf(codeType, new CodeLists(name, true));
+    }
+
+    /**
+     * Returns all known values for the given type of code list or enumeration.
+     *
+     * @param  <T>       the compile-time type given as the {@code codeType} parameter.
+     * @param  codeType  the type of code list or enumeration.
+     * @return the list of values for the given code list or enumeration, or an empty array if none.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends CodeList<?>> T[] values(final Class<T> codeType) {
+        Object values;
+        try {
+            values = codeType.getMethod("values", (Class<?>[]) null).invoke(null, (Object[]) null);
         } catch (InvocationTargetException e) {
             final Throwable cause = e.getCause();
             if (cause instanceof RuntimeException) {
@@ -166,13 +190,10 @@
             if (cause instanceof Error) {
                 throw (Error) cause;
             }
-            // `CodeList.valueOf(String)` methods are not expected to throw checked exceptions.
             throw new UndeclaredThrowableException(cause);
-        } catch (IllegalAccessException e) {
-            throw (InaccessibleObjectException) new InaccessibleObjectException(e.getMessage()).initCause(e);
-        } catch (NoSuchMethodException | NullPointerException e) {
-            throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementNotFound_1, name), e);
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            values = Array.newInstance(codeType, 0);
         }
-        return code;
+        return (T[]) values;
     }
 }
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/MetadataServices.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/MetadataServices.java
index e3684de..5dcd8f5 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/MetadataServices.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/MetadataServices.java
@@ -28,11 +28,8 @@
 import org.apache.sis.system.OptionalDependency;
 import org.apache.sis.util.CharSequences;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.MissingResourceException;
-import org.opengis.annotation.UML;
-import org.opengis.annotation.ResourceBundles;
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -119,25 +116,9 @@
      * @param  locale  desired locale for the title.
      * @return the title.
      *
-     * @see org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)
+     * @see org.apache.sis.util.iso.Types#getCodeTitle(CodeList)
      */
-    public String getCodeTitle(final ControlledVocabulary code, final Locale locale) {
-        /*
-         * Following code reproduces the work done by `org.apache.sis.util.iso.Types.getCodeList(…)` with
-         * less handling of special cases. It is executed only if the `org.apache.sis.metadata` module is
-         * not on the module path, otherwise the `org.apache.sis.metadata` implementation will be used.
-         */
-        final UML uml = code.getClass().getAnnotation(UML.class);
-        if (uml != null) try {
-            return ResourceBundles.codeLists(locale).getString(uml.identifier() + '.' + code.identifier());
-        } catch (MissingResourceException e) {
-            /*
-             * Ignore. The reason for not finding the resource may because of above code not covering enough cases.
-             * Usually the `org.apache.sis.metadata` module will be present on the module path, in which case this
-             * implementation will not be used. We need just enough code for allowing `org.apache.sis.util` tests
-             * to pass.
-             */
-        }
+    public String getCodeTitle(final CodeList<?> code, final Locale locale) {
         return CharSequences.camelCaseToSentence(code.identifier()).toString();
     }
 
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/PropertyFormat.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/PropertyFormat.java
index 16c5849..8a34de6 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/PropertyFormat.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/PropertyFormat.java
@@ -35,8 +35,8 @@
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -99,12 +99,12 @@
             text = freeText(((InternationalString) value).toString(getLocale()));
         } else if (value instanceof CharSequence) {
             text = freeText(value.toString());
-        } else if (value instanceof ControlledVocabulary) {
-            text = MetadataServices.getInstance().getCodeTitle((ControlledVocabulary) value, getLocale());
-        } else if (value instanceof Boolean) {
-            text = Vocabulary.forLocale(getLocale()).getString((Boolean) value ? Vocabulary.Keys.True : Vocabulary.Keys.False);
+        } else if (value instanceof CodeList<?>) {
+            text = MetadataServices.getInstance().getCodeTitle((CodeList<?>) value, getLocale());
         } else if (value instanceof Enum<?>) {
             text = CharSequences.upperCaseToSentence(((Enum<?>) value).name());
+        } else if (value instanceof Boolean) {
+            text = Vocabulary.forLocale(getLocale()).getString((Boolean) value ? Vocabulary.Keys.True : Vocabulary.Keys.False);
         } else if (value instanceof Type) {
             appendName(((Type) value).getTypeName());
             return;
@@ -121,7 +121,7 @@
             final Locale locale = getLocale();
             text = (locale != Locale.ROOT) ? ((Currency) value).getDisplayName(locale) : value.toString();
         } else if (value instanceof Record) {
-            appendCollection(((Record) value).getFields().values(), recursive);
+            appendCollection(((Record) value).getAttributes().values(), recursive);
             return;
         } else if (value instanceof Iterable<?>) {
             appendCollection((Iterable<?>) value, recursive);
@@ -152,7 +152,7 @@
     /**
      * Invoked by {@link PropertyFormat} for formatting a value which has not been recognized as one of the types
      * to be handled in a special way. Some of the types handled in a special way are {@link InternationalString},
-     * {@link ControlledVocabulary}, {@link Enum}, {@link Type}, {@link Locale}, {@link TimeZone}, {@link Charset},
+     * {@link CodeList}, {@link Enum}, {@link Type}, {@link Locale}, {@link TimeZone}, {@link Charset},
      * {@link Currency}, {@link Record}, {@link Iterable} and arrays. Other types should be handled by this method.
      * In particular, {@link Number}, {@link java.util.Date} and {@link org.apache.sis.measure.Angle}
      * are <strong>not</strong> handled by default by this {@link PropertyFormat} class and should be handled here.
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
index 2bb0bb3..3443427 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
@@ -51,9 +51,6 @@
 import org.apache.sis.measure.RangeFormat;
 import org.apache.sis.measure.Range;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
-
 
 /**
  * {@link ResourceBundle} implementation accepting integers instead of strings for resource keys.
@@ -411,8 +408,8 @@
                 replacement = ((URI) element).getSchemeSpecificPart();      // For decoding encoded characters.
             } else if (element instanceof Class<?>) {
                 replacement = Classes.getShortName(getPublicType((Class<?>) element));
-            } else if (element instanceof ControlledVocabulary) {
-                replacement = MetadataServices.getInstance().getCodeTitle((ControlledVocabulary) element, getLocale());
+            } else if (element instanceof CodeList<?>) {
+                replacement = MetadataServices.getInstance().getCodeTitle((CodeList<?>) element, getLocale());
             } else if (element instanceof Range<?>) {
                 final Range<?> range = (Range<?>) element;
                 replacement = new RangeFormat(getLocale(), range.getElementType()).format(range);
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/ContentVerifier.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/ContentVerifier.java
new file mode 100644
index 0000000..d2c9849
--- /dev/null
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/ContentVerifier.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.test;
+
+import org.opengis.metadata.Metadata;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assumptions.abort;
+
+
+/**
+ * Place-holder for a GeoAPI 3.1 class. Used only for allowing the code to compile.
+ * For real test execution, see the development branches on GeoAPI 4.0-SNAPSHOT.
+ */
+@SuppressWarnings("doclint:missing")
+public class ContentVerifier {
+    public void addPropertyToIgnore(Class<?> type, String property) {
+        abort("This test requires GeoAPI 3.1.");
+    }
+
+    public void addMetadataToVerify(Metadata actual) {
+        abort("This test requires GeoAPI 3.1.");
+    }
+
+    public void assertMetadataEquals(final String path, final Object value, final Object... others) {
+        abort("This test requires GeoAPI 3.1.");
+    }
+}
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/GeoapiAssert.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/GeoapiAssert.java
new file mode 100644
index 0000000..e39b5f3
--- /dev/null
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/GeoapiAssert.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.test;
+
+import java.util.Collection;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.Static;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assertions.*;
+import org.opengis.test.Assert;
+
+
+/**
+ * Temporary class for test methods that are expected to be provided in next GeoAPI release.
+ * Those methods are defined in a separated class in order to make easier for us to identify
+ * which methods may be removed from SIS (actually moved to GeoAPI) in a future GeoAPI release.
+ *
+ * <p>This class is needed for Apache SIS main branch, since the later is linked to GeoAPI official release.
+ * But this class can be removed on Apache SIS branches which are linked to a GeoAPI development branch.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class GeoapiAssert extends Static {
+    /**
+     * A flag for code that are pending next GeoAPI release before to be enabled.
+     * This flag is always set to {@code false}, except occasionally just before
+     * a GeoAPI release for testing purpose. It shall be used as below:
+     *
+     * {@snippet lang="java" :
+     *     if (PENDING_NEXT_GEOAPI_RELEASE) {
+     *         // Do some stuff here.
+     *     }
+     *     }
+     *
+     * The intend is to make easier to identify test cases that fail with the current version
+     * of the {@code geoapi-conformance} module, but should pass with the development snapshot.
+     */
+    public static final boolean PENDING_NEXT_GEOAPI_RELEASE = false;
+
+    /**
+     * The keyword for unrestricted value in {@link String} arguments.
+     */
+    private static final String UNRESTRICTED = "##unrestricted";
+
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private GeoapiAssert() {
+    }
+
+    private static String nonNull(final String message) {
+        return (message != null) ? message.trim().concat(" ") : "";
+    }
+
+    /**
+     * Returns the concatenation of the given message with the given extension.
+     * This method returns the given extension if the message is null or empty.
+     *
+     * <p>Invoking this method is equivalent to invoking {@code nonNull(message) + ext},
+     * but avoid the creation of temporary objects in the common case where the message
+     * is null.</p>
+     *
+     * @param  message  the message, or {@code null}.
+     * @param  ext      the extension to append after the message.
+     * @return the concatenated string.
+     */
+    private static String concat(String message, final String ext) {
+        if (message == null || (message = message.trim()).isEmpty()) {
+            return ext;
+        }
+        return message + ' ' + ext;
+    }
+
+    /**
+     * Verifies if we expected a null value, then returns {@code true} if the value is null as expected.
+     */
+    private static boolean isNull(final Object expected, final Object actual, final String message) {
+        final boolean isNull = (actual == null);
+        if (isNull != (expected == null)) {
+            fail(concat(message, isNull ? "Value is null." : "Expected null."));
+        }
+        return isNull;
+    }
+
+    public static void assertPositive(final int value, final String message) {
+        Assert.assertPositive(message, value);
+    }
+
+    public static void assertStrictlyPositive(final int value, final String message) {
+        Assert.assertStrictlyPositive(message, value);
+    }
+
+    public static <T> void assertValidRange(final Comparable<T> minimum, final Comparable<T> maximum, final String message) {
+        Assert.assertValidRange(message, minimum, maximum);
+    }
+
+    public static void assertValidRange(final int minimum, final int maximum, final String message) {
+        Assert.assertValidRange(message, minimum, maximum);
+    }
+
+    public static void assertValidRange(final double minimum, final double maximum, final String message) {
+        Assert.assertValidRange(message, minimum, maximum);
+    }
+
+    public static <T> void assertBetween(final Comparable<T> minimum, final Comparable<T> maximum, T value, final String message) {
+        Assert.assertBetween(message, minimum, maximum, value);
+    }
+
+    public static void assertBetween(final int minimum, final int maximum, final int value, final String message) {
+        Assert.assertBetween(message, minimum, maximum, value);
+    }
+
+    public static void assertBetween(final double minimum, final double maximum, final double value, final String message) {
+        Assert.assertBetween(message, minimum, maximum, value);
+    }
+
+    public static void assertContains(final Collection<?> collection, final Object value, final String message) {
+        Assert.assertContains(message, collection, value);
+    }
+
+    /**
+     * Asserts that the title or an alternate title of the given citation is equal to the given string.
+     * This method is typically used for testing if a citation stands for the OGC, OGP or EPSG authority
+     * for instance. Such abbreviations are often declared as {@linkplain Citation#getAlternateTitles()
+     * alternate titles} rather than the main {@linkplain Citation#getTitle() title}, but this method
+     * tests both for safety.
+     *
+     * @param expected  the expected title or alternate title.
+     * @param actual    the citation to test.
+     * @param message   header of the exception message in case of failure, or {@code null} if none.
+     */
+    public static void assertAnyTitleEquals(final String expected, final Citation actual, final String message) {
+        if (isNull(expected, actual, message)) {
+            return;
+        }
+        InternationalString title = actual.getTitle();
+        if (title != null && expected.equals(title.toString())) {
+            return;
+        }
+        for (final InternationalString t : actual.getAlternateTitles()) {
+            if (expected.equals(t.toString())) {
+                return;
+            }
+        }
+        fail(concat(message, '"' + expected + "\" not found in title or alternate titles."));
+    }
+
+    /**
+     * Asserts that the given identifier is equal to the given authority, code space, version and code.
+     * If any of the above-cited properties is {@code ""##unrestricted"}, then it will not be verified.
+     * This flexibility is useful in the common case where a test accepts any {@code version} value.
+     *
+     * @param authority  the expected authority title or alternate title (may be {@code null}), or {@code "##unrestricted"}.
+     * @param codeSpace  the expected code space (may be {@code null}), or {@code "##unrestricted"}.
+     * @param version    the expected version    (may be {@code null}), or {@code "##unrestricted"}.
+     * @param code       the expected code value (may be {@code null}), or {@code "##unrestricted"}.
+     * @param actual     the identifier to test.
+     * @param message    header of the exception message in case of failure, or {@code null} if none.
+     */
+    public static void assertIdentifierEquals(final String authority, final String codeSpace, final String version,
+            final String code, final ReferenceIdentifier actual, final String message)
+    {
+        if (actual == null) {
+            fail(concat(message, "Identifier is null"));
+        } else {
+            if (!UNRESTRICTED.equals(authority)) assertAnyTitleEquals(authority, actual.getAuthority(), message);
+            if (!UNRESTRICTED.equals(codeSpace)) assertEquals(codeSpace, actual.getCodeSpace(), () -> concat(message, "Wrong code space"));
+            if (!UNRESTRICTED.equals(version))   assertEquals(version,   actual.getVersion(),   () -> concat(message, "Wrong version"));
+            if (!UNRESTRICTED.equals(code)) assertEquals(code, actual.getCode(), () -> concat(message, "Wrong code"));
+        }
+    }
+
+    /**
+     * Asserts that all axes in the given coordinate system are pointing toward the given directions, in the same order.
+     *
+     * @param cs        the coordinate system to test.
+     * @param expected  the expected axis directions.
+     */
+    public static void assertAxisDirectionsEqual(final CoordinateSystem cs, final AxisDirection... expected) {
+        assertAxisDirectionsEqual(cs, expected, null);
+    }
+
+    /**
+     * Asserts that all axes in the given coordinate system are pointing toward the given directions,
+     * in the same order.
+     *
+     * @param cs        the coordinate system to test.
+     * @param expected  the expected axis directions.
+     * @param message   header of the exception message in case of failure, or {@code null} if none.
+     */
+    public static void assertAxisDirectionsEqual(final CoordinateSystem cs, final AxisDirection[] expected, final String message) {
+        assertEquals(expected.length, cs.getDimension(), () -> concat(message, "Wrong coordinate system dimension."));
+        for (int i=0; i<expected.length; i++) {
+            final int ci = i;   // Because lambda expressions require final values.
+            assertEquals(expected[i], cs.getAxis(i).getDirection(),
+                    () -> concat(message, "Wrong axis direction at index" + ci + '.'));
+        }
+    }
+
+    /**
+     * Asserts that the given matrix is equal to the expected one, up to the given tolerance value.
+     *
+     * @param expected   the expected matrix, which may be {@code null}.
+     * @param actual     the matrix to compare, or {@code null}.
+     * @param tolerance  the tolerance threshold.
+     * @param message    header of the exception message in case of failure, or {@code null} if none.
+     *
+     * @see org.opengis.test.referencing.TransformTestCase#assertMatrixEquals(String, Matrix, Matrix, Matrix)
+     */
+    public static void assertMatrixEquals(final Matrix expected, final Matrix actual, final double tolerance, final String message) {
+        if (isNull(expected, actual, message)) {
+            return;
+        }
+        final int numRow = actual.getNumRow();
+        final int numCol = actual.getNumCol();
+        assertEquals(expected.getNumRow(), numRow, "numRow");
+        assertEquals(expected.getNumCol(), numCol, "numCol");
+        for (int j=0; j<numRow; j++) {
+            for (int i=0; i<numCol; i++) {
+                final double e = expected.getElement(j,i);
+                final double a = actual.getElement(j,i);
+                if (!(StrictMath.abs(e - a) <= tolerance) && Double.doubleToLongBits(a) != Double.doubleToLongBits(e)) {
+                    fail(nonNull(message) + "Matrix.getElement(" + j + ", " + i + "): expected " + e + " but got " + a);
+                }
+            }
+        }
+    }
+}
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/TestUtilities.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/TestUtilities.java
index af47237..2a14dbd 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/TestUtilities.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/test/TestUtilities.java
@@ -52,9 +52,11 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-import org.opengis.metadata.extent.GeographicExtent;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceSystem;
+import org.opengis.metadata.extent.Extent;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.operation.CoordinateOperation;
 
 
 /**
@@ -354,7 +356,16 @@
      * @return the single scope of the given object.
      */
     public static String getScope(final IdentifiedObject object) {
-        InternationalString scope = getSingleton(object.getDomains()).getScope();
+        final InternationalString scope;
+        if (object instanceof ReferenceSystem) {
+            scope = ((ReferenceSystem) object).getScope();
+        } else if (object instanceof Datum) {
+            scope = ((Datum) object).getScope();
+        } else if (object instanceof CoordinateOperation) {
+            scope = ((CoordinateOperation) object).getScope();
+        } else {
+            scope = null;
+        }
         assertNotNull(scope, "Missing scope.");
         return scope.toString();
     }
@@ -367,9 +378,18 @@
      * @return the single domain of validity of the given object.
      */
     public static GeographicBoundingBox getDomainOfValidity(final IdentifiedObject object) {
-        ObjectDomain domain = getSingleton(object.getDomains());
-        GeographicExtent extent = getSingleton(domain.getDomainOfValidity().getGeographicElements());
-        return assertInstanceOf(GeographicBoundingBox.class, extent);
+        final Extent extent;
+        if (object instanceof ReferenceSystem) {
+            extent = ((ReferenceSystem) object).getDomainOfValidity();
+        } else if (object instanceof Datum) {
+            extent = ((Datum) object).getDomainOfValidity();
+        } else if (object instanceof CoordinateOperation) {
+            extent = ((CoordinateOperation) object).getDomainOfValidity();
+        } else {
+            extent = null;
+        }
+        assertNotNull(extent, "Missing extent.");
+        return assertInstanceOf(GeographicBoundingBox.class, getSingleton(extent.getGeographicElements()));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/ClassesTest.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/ClassesTest.java
index 90397af..1564f4f 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/ClassesTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/ClassesTest.java
@@ -51,8 +51,6 @@
 import java.awt.geom.Point2D;
 import javax.print.attribute.standard.PrinterStateReason;
 import javax.print.attribute.standard.PrinterStateReasons;
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.cs.EllipsoidalCS;
@@ -147,12 +145,7 @@
     /**
      * Dummy class for {@link #testGetLeafInterfaces()}.
      */
-    @SuppressWarnings("deprecation")
-    private abstract static class T1 implements GeographicCRS {
-        @Override public InternationalString getScope() {return null;}
-        @Override public Extent getDomainOfValidity() {return null;}
-    }
-    @SuppressWarnings("deprecation")
+    private abstract static class T1 implements GeographicCRS {}
     private abstract static class T2 extends T1 implements SingleCRS, CoordinateOperation {}
     private abstract static class T3 extends T2 implements Transformation {}
 
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java
index 6f449ee..5a20b6c 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java
@@ -213,7 +213,7 @@
     public void testFill() {
         final CodeListSet<AxisDirection> c = new CodeListSet<>(AxisDirection.class, true);
         assertTrue(c.size() >= 32, "Expect at least 32 elements as of GeoAPI 3.0.");
-        assertTrue(c.toString().startsWith("[AxisDirection.OTHER, AxisDirection.NORTH, "));
+        assertTrue(c.toString().startsWith("[AxisDirection[OTHER], AxisDirection[NORTH], "));
         /*
          * Testing the full array would be too long and may change in future GeoAPI version
          * anyway. Actually the main interest of this test is to ensure that the toString()
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java
index 17954d3..c3841a0 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java
@@ -21,6 +21,10 @@
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
 
+// Specific to the main branch:
+import java.util.List;
+import java.util.ArrayList;
+
 
 /**
  * A code list containing more than 64 elements. This implementation can be used by tests
@@ -29,23 +33,27 @@
  * @author  Martin Desruisseaux (Geomatys)
  */
 @SuppressWarnings("serial")
-public final class LargeCodeList extends CodeList<LargeCodeList> {
+public final class LargeCodeList  extends CodeList<LargeCodeList> {
+    /**
+     * List of all enumerations of this type.
+     */
+    private static final List<LargeCodeList> VALUES = new ArrayList<>(100);
+
     /**
      * Creates 100 code list elements.
-     * We need to construct values with {@code valueOf(String)} instead of the constructor
-     * because this package is not exported to GeoAPI. See {@link CodeList} class javadoc.
      */
     static {
-        for (int i=0; i<80; i++) {
-            assertEquals(i, valueOf("LC#" + i).ordinal());
+        for (int i=0; i<100; i++) {
+            assertEquals(i, new LargeCodeList(i).ordinal());
         }
     }
 
     /**
-     * Constructs an element.
+     * Constructs an element. The new element is automatically
+     * added to the list to be returned by {@link #values}.
      */
-    private LargeCodeList(String name) {
-        super(name);
+    private LargeCodeList(final int i) {
+        super("LC#" + i, VALUES);
     }
 
     /**
@@ -54,7 +62,9 @@
      * @return the list of codes declared in the current JVM.
      */
     public static LargeCodeList[] values() {
-        return values(LargeCodeList.class);
+        synchronized (VALUES) {
+            return VALUES.toArray(LargeCodeList[]::new);
+        }
     }
 
     /**
@@ -74,6 +84,6 @@
      * @return a code list element matching the given name.
      */
     public static LargeCodeList valueOf(final String code) {
-        return valueOf(LargeCodeList.class, code, LargeCodeList::new).get();
+        return valueOf(LargeCodeList.class, code);
     }
 }
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java
index 040f3f6..b42db52 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java
@@ -252,7 +252,7 @@
         tf = new TreeTableFormat(Locale.FRENCH, null);
         assertMultilinesEquals(
                 "Root\n" +
-                "  ├─CodeList…… Point de contact\n" +
+                "  ├─CodeList…… Point of contact\n" + // Not yet localized.
                 "  ├─Enum……………… Half down\n" +                      // No localization provided.
                 "  └─i18n……………… Une phrase en français\n", tf.format(table));
 
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/resources/IndexedResourceBundleTest.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/resources/IndexedResourceBundleTest.java
index e9a8daa..c0586ca 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/resources/IndexedResourceBundleTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/resources/IndexedResourceBundleTest.java
@@ -158,7 +158,6 @@
 
     /**
      * Tests the {@link IndexedResourceBundle#getString(short, Object)} method with a {@code CodeList} argument.
-     * The intent is to test the code list localization.
      */
     @Test
     public void testGetStringWithCodeList() {
diff --git a/geoapi/README.md b/geoapi/README.md
deleted file mode 100644
index 691b236..0000000
--- a/geoapi/README.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# GeoAPI snapshot
-
-The Apache SIS source code repository has two branches, named `geoapi-3.1` and `geoapi-4.0`,
-which depend on [GeoAPI](https://www.geoapi.org/) versions that are still in development.
-Those GeoAPI versions are not deployed on Maven Central, because they are not yet officially approved OGC releases.
-The Apache SIS branches that use those versions are never deployed on Maven Central neither.
-Official Apache SIS releases are made from the `main` branch, which depends on the standard GeoAPI 3.0.2 release only.
-
-The Apache SIS `geoapi-3.1` and `geoapi-4.0` branches are nevertheless useful for testing latest GeoAPI developments.
-The implementation experience gained is used for adjusting the GeoAPI interfaces before submission as an OGC standard.
-For making possible to compile against GeoAPI 3.1/4.0 without deployment on Maven Central, GeoAPI must be compiled locally.
-This is done in this directory with a Git sub-module, which fetch GeoAPI at a specific commit identified by a SHA1.
-The commit SHA1 is updated when needed for keeping Apache SIS `geoapi-xxx` branches in sync with the GeoAPI snapshot they implement.
-
-## Git commands
-Following command should be executed once after a fresh checkout of SIS `geoapi-3.1` or `geoapi-4.0` branch:
-
-```bash
-git submodule update --init
-```
-
-After above initialization, the usual `git pull` command should upgrade GeoAPI snapshot as well when needed.
-If a Git error message said that the SHA1 is unknown, running `git fetch --recurse-submodules` first may help.
-
-## Prerequisites
-Maven must be available on the classpath.
-The GeoAPI snapshot is built by a call to `mvn clean install`.
-This call is done automatically by the Gradle build.
diff --git a/geoapi/build.gradle.kts b/geoapi/build.gradle.kts
deleted file mode 100644
index 3ce4abe..0000000
--- a/geoapi/build.gradle.kts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-tasks.register<Exec>("rebuild") {
-    setWorkingDir(file("snapshot"))
-    commandLine("mvn", "clean", "install")
-    /*
-     * The following are used by Gradle for deciding if the GeoAPI project needs to be rebuilt.
-     * We declare only the modules of interest to Apache SIS. Changes in other modules will not
-     * trigger a rebuild.
-     */
-    inputs.dir("snapshot/geoapi/src/main")
-    inputs.dir("snapshot/geoapi-pending/src/main")
-    inputs.dir("snapshot/geoapi-conformance/src/main")
-
-    outputs.file("snapshot/geoapi-pending/target/geoapi-pending-3.1-SNAPSHOT.jar")
-    outputs.file("snapshot/geoapi-conformance/target/geoapi-conformance-3.1-SNAPSHOT.jar")
-}
diff --git a/geoapi/snapshot b/geoapi/snapshot
deleted file mode 160000
index a00e026..0000000
--- a/geoapi/snapshot
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a00e026538b2704db1ec82cbad64fef1bef992c0
diff --git a/gradle.properties b/gradle.properties
index e9b032a..2ee99b3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@
 # This file provides a single location where version number
 # and deployment URL can be changed before new tags.
 #
-version=1.x-SNAPSHOT
+version=1.5-SNAPSHOT
 
 # Following identifiers should match <server> elements in the Maven settings.xml file.
 # They are used for fetching the credentials for publishing binaries in a repository.
diff --git a/incubator/build.gradle.kts b/incubator/build.gradle.kts
index c10f193..16547b7 100644
--- a/incubator/build.gradle.kts
+++ b/incubator/build.gradle.kts
@@ -47,7 +47,6 @@
     api(files("../endorsed/build/classes/java/main/org.apache.sis.referencing"))
     api(files("../endorsed/build/classes/java/main/org.apache.sis.feature"))
     api(files("../endorsed/build/classes/java/main/org.apache.sis.storage"))
-    api(files("../endorsed/build/classes/java/main/org.apache.sis.portrayal"))
 
     // Test dependencies
     testImplementation(tests.junit5)
@@ -74,9 +73,6 @@
  * replace ANTLR generated code by hand-written code in a future version.
  */
 var srcDir = file("src")        // Must be the same as the hard-coded value in `BuildHelper.java`.
-tasks.generateGrammarSource {
-    setOutputDirectory(file("${srcDir}/org.apache.sis.cql/main"))
-}
 tasks.compileJava {
     dependsOn(":endorsed:compileJava")
     options.release.set(11)         // The version of both Java source code and compiled byte code.
@@ -87,7 +83,6 @@
     srcDir.list().forEach {
         addRead(options.compilerArgs, it, "org.apache.sis.test.incubator,org.junit.jupiter.api")
     }
-    addExportForTests(options.compilerArgs)
 }
 
 /*
@@ -101,41 +96,10 @@
 }
 
 /*
- * Adds a JVM argument for making an internal package accessible to another module.
- * This is for making internal packages accessible to JUnit or to some test classes
- * defined in other modules.
- */
-fun addExport(args : MutableList<String>, module : String, pkg : String, consumers : String) {
-    args.add("--add-exports")
-    args.add(module + '/' + pkg + '=' + consumers)
-}
-
-/*
- * Add compiler and runtime options for patching the Apache SIS main modules with the test classes.
- * The same options are required for both compiling and executing the tests.
- */
-fun addExportForTests(args : MutableList<String>) {
-    /*
-     * Some test classes need access to more internal packages than requested by the main classes.
-     * The following lines may need to be edited when export statements are added or removed in a
-     * module-info.java file of main source code, or when a test class starts using or stop using
-     * an internal API.
-     */
-    // ――――――――――――― Module name ――――――――――――――――――――――― Package to export ―――――――――――――――
-    addExport(args, "org.apache.sis.feature",           "org.apache.sis.geometry.wrapper.jts",
-                    "org.apache.sis.portrayal.map")
-
-    addExport(args, "org.apache.sis.storage",           "org.apache.sis.storage.base",
-                    "org.apache.sis.portrayal.map")
-}
-
-/*
  * Discover and execute JUnit-based tests.
  */
 tasks.test {
     val args = mutableListOf("-enableassertions")
-    addExportForTests(args)
-    addExport(args, "org.apache.sis.cql", "org.apache.sis.cql", "ALL-UNNAMED")
     setAllJvmArgs(args)
     testLogging {
         events("FAILED", "STANDARD_OUT", "STANDARD_ERROR")
@@ -151,18 +115,6 @@
  */
 publishing {
     publications {
-        create<MavenPublication>("cql") {
-            var module = "org.apache.sis.cql"
-            groupId    = "org.apache.sis.core"
-            artifactId = "sis-cql"
-            artifact(layout.buildDirectory.file("libs/${module}.jar"))
-            artifact(layout.buildDirectory.file("docs/${module}-sources.jar")) {classifier = "sources"}
-            artifact(layout.buildDirectory.file("docs/${module}-javadoc.jar")) {classifier = "javadoc"}
-            pom {
-                name        = "Apache SIS CQL"
-                description = "CQL parser."
-            }
-        }
         create<MavenPublication>("storage.shapefile") {
             var module = "org.apache.sis.storage.shapefile"
             groupId    = "org.apache.sis.storage"
@@ -187,18 +139,6 @@
                 description = "Read and write files in the JSON Coverage format."
             }
         }
-        create<MavenPublication>("portrayal.map") {
-            var module = "org.apache.sis.portrayal.map"
-            groupId    = "org.apache.sis.core"
-            artifactId = "sis-portrayal-map"
-            artifact(layout.buildDirectory.file("libs/${module}.jar"))
-            artifact(layout.buildDirectory.file("docs/${module}-sources.jar")) {classifier = "sources"}
-            artifact(layout.buildDirectory.file("docs/${module}-javadoc.jar")) {classifier = "javadoc"}
-            pom {
-                name        = "Apache SIS portrayal"
-                description = "Symbology and map representations, together with a rendering engine for display."
-            }
-        }
         create<MavenPublication>("webapp") {
             var module = "org.apache.sis.webapp"
             groupId    = "org.apache.sis.application"
diff --git a/incubator/src/main/antlr/org/apache/sis/cql/internal/CQL.g4 b/incubator/src/main/antlr/org/apache/sis/cql/internal/CQL.g4
deleted file mode 100644
index 91197f8..0000000
--- a/incubator/src/main/antlr/org/apache/sis/cql/internal/CQL.g4
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-grammar CQL;
-
-options {
-    language = Java;
-}
-@header {
-    package org.apache.sis.cql.internal;
-}
-
-//-----------------------------------------------------------------//
-// LEXER
-//-----------------------------------------------------------------//
-
-
-// GLOBAL STUFF ---------------------------------------
-
-COMMA 	: ',' ;
-WS  :   ( ' ' | '\t' | '\r'| '\n' ) -> skip;
-UNARY : '+' | '-' ;
-MULT : '*' ;
-DIV : '/' ;
-fragment DIGIT : '0'..'9' ;
-
-// caseinsensitive , possible alternative solution ?
-fragment A: ('a'|'A');
-fragment B: ('b'|'B');
-fragment C: ('c'|'C');
-fragment D: ('d'|'D');
-fragment E: ('e'|'E');
-fragment F: ('f'|'F');
-fragment G: ('g'|'G');
-fragment H: ('h'|'H');
-fragment I: ('i'|'I');
-fragment J: ('j'|'J');
-fragment K: ('k'|'K');
-fragment L: ('l'|'L');
-fragment M: ('m'|'M');
-fragment N: ('n'|'N');
-fragment O: ('o'|'O');
-fragment P: ('p'|'P');
-fragment Q: ('q'|'Q');
-fragment R: ('r'|'R');
-fragment S: ('s'|'S');
-fragment T: ('t'|'T');
-fragment U: ('u'|'U');
-fragment V: ('v'|'V');
-fragment W: ('w'|'W');
-fragment X: ('x'|'X');
-fragment Y: ('y'|'Y');
-fragment Z: ('z'|'Z');
-fragment LETTER : ~('0'..'9' | ' ' | '\t' | '\r'| '\n' | ',' | '-' | '+' | '*' | '/' | '(' | ')' | '=' | '>' | '<');
-
-LPAREN : '(';
-RPAREN : ')';
-
-
-//LITERALS  ----------------------------------------------
-
-TEXT :   '\'' ( ESC_SEQ | ~('\'') )* '\'' ;
-INT : DIGIT+ ;
-
-FLOAT
-    :   ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
-    |   '.' ('0'..'9')+ EXPONENT?
-    |   ('0'..'9')+ EXPONENT
-    ;
-
-
-// FILTERING OPERAND -----------------------------------
-COMPARE
-	: EQUALABOVE
-	| EQUALUNDER
-	| NOTEQUAL
-	| EQUAL
-	| ABOVE
-	| UNDER
-	;
-fragment EQUALABOVE : '>=' ;
-fragment EQUALUNDER : '<=' ;
-fragment NOTEQUAL   : '<>' ;
-fragment EQUAL      : '=' ;
-fragment ABOVE      : '>' ;
-fragment UNDER      : '<' ;
-LIKE    : L I K E;
-ILIKE   : I L I K E;
-
-IS      : I S ;
-NULL    : N U L L ;
-BETWEEN : B E T W E E N;
-IN      : I N;
-
-
-
-// LOGIC ----------------------------------------------
-AND : A N D;
-OR  : O R ;
-NOT : N O T ;
-
-// GEOMETRIC TYPES AND FILTERS ------------------------
-POINT               : P O I N T ;
-LINESTRING          : L I N E S T R I N G ;
-POLYGON             : P O L Y G O N ;
-MPOINT              : M U L T I P O I N T ;
-MLINESTRING         : M U L T I L I N E S T R I N G ;
-MPOLYGON            : M U L T I P O L Y G O N ;
-GEOMETRYCOLLECTION  : G E O M E T R Y C O L L E C T I O N ;
-ENVELOPE            : E N V E L O P E ;
-EMPTY               : E M P T Y ;
-
-BBOX        : B B O X ;
-BEYOND      : B E Y O N D ;
-CONTAINS    : C O N T A I N S ;
-CROSSES     : C R O S S E S;
-DISJOINT    : D I S J O I N T ;
-DWITHIN     : D W I T H I N ;
-EQUALS      : E Q U A L S ;
-INTERSECTS  : I N T E R S E C T S;
-OVERLAPS    : O V E R L A P S;
-TOUCHES     : T O U C H E S;
-WITHIN      : W I T H I N ;
-
-// TEMPORAL TYPES AND FILTERS
-
-DATE : DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT 'T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ('.' DIGIT+)? 'Z';
-DURATION_P : P (INT 'Y')? (INT 'M')? (INT 'D')? (INT 'H')? (INT 'M')? (INT 'S')?;
-DURATION_T : P T (INT 'H')? (INT 'M')? (INT 'S')?;
-
-AFTER		: A F T E R ;
-ANYINTERACTS	: A N Y I N T E R A C T S ;
-BEFORE		: B E F O R E ;
-BEGINS		: B E G I N S ;
-BEGUNBY		: B E G U N B Y ;
-DURING		: D U R I N G ;
-ENDEDBY		: E N D E D B Y ;
-ENDS		: E N D S ;
-MEETS		: M E E T S ;
-METBY		: M E T B Y ;
-OVERLAPPEDBY	: O V E R L A P P E D B Y ;
-TCONTAINS	: T C O N T A I N S ;
-TEQUALS		: T E Q U A L S ;
-TOVERLAPS	: T O V E R L A P S ;
-
-// QUERY ---------------------------------------------
-
-SELECT : S E L E C T ;
-WHERE : W H E R E ;
-LIMIT : L I M I T ;
-OFFSET : O F F S E T ;
-AS : A S ;
-ORDER : O R D E R ;
-BY : B Y ;
-ASC : A S C ;
-DESC : D E S C ;
-
-// PROPERTY NAME -------------------------------------
-PROPERTY_NAME    	:  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'    ;
-NAME   	: LETTER (DIGIT|LETTER)* ;
-
-
-// FRAGMENT -------------------------------------------
-
-fragment EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
-fragment HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
-
-fragment
-ESC_SEQ
-    :   '\\' ('b'|'t'|'n'|'f'|'r'|'"'|'\''|'\\')
-    |   UNICODE_ESC
-    |   OCTAL_ESC
-    ;
-
-fragment
-OCTAL_ESC
-    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
-    |   '\\' ('0'..'7') ('0'..'7')
-    |   '\\' ('0'..'7')
-    ;
-
-fragment
-UNICODE_ESC
-    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
-    ;
-
-
-
-
-//-----------------------------------------------------------------//
-// PARSER
-//-----------------------------------------------------------------//
-
-expressionNum : INT | FLOAT ;
-expressionUnary : UNARY? expressionNum ;
-
-coordinate          : expressionUnary expressionUnary ;
-coordinateSerie    : LPAREN coordinate (COMMA coordinate)*  RPAREN ;
-coordinateSeries   : LPAREN coordinateSerie (COMMA coordinateSerie)* RPAREN;
-
-expressionGeometry
-	: POINT ( EMPTY | coordinateSerie )
-	| LINESTRING ( EMPTY | coordinateSerie )
-	| POLYGON ( EMPTY | coordinateSeries )
-	| MPOINT ( EMPTY | coordinateSerie )
-	| MLINESTRING  ( EMPTY | coordinateSeries )
-	| MPOLYGON ( EMPTY | LPAREN coordinateSeries (COMMA coordinateSeries)* RPAREN )
-        | GEOMETRYCOLLECTION ( EMPTY | (LPAREN expressionGeometry (COMMA expressionGeometry)* RPAREN) )
-        | ENVELOPE ( EMPTY | (LPAREN expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary RPAREN) )
-	;
-
-expressionFctParam
-        : expression (COMMA expression)*
-        ;
-
-expressionTerm
-	: TEXT
-	| expressionUnary
-	| PROPERTY_NAME
-	| DATE
-	| DURATION_P
-	| DURATION_T
-	| NAME (LPAREN expressionFctParam? RPAREN)?
-	| expressionGeometry
-	| LPAREN expression RPAREN
-	;
-
-expression : expression (MULT|DIV) expression
-           | expression UNARY expression
-           | expressionTerm
-           ;
-
-filterGeometry
-        : BBOX LPAREN expression COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary (COMMA TEXT)? RPAREN
-        | BEYOND LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-        | CONTAINS LPAREN expression COMMA expression RPAREN
-        | CROSSES LPAREN expression COMMA expression RPAREN
-        | DISJOINT LPAREN expression COMMA expression RPAREN
-        | DWITHIN LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-        | EQUALS LPAREN expression COMMA expression RPAREN
-        | INTERSECTS LPAREN expression COMMA expression RPAREN
-        | OVERLAPS LPAREN expression COMMA expression RPAREN
-        | TOUCHES LPAREN expression COMMA expression RPAREN
-        | WITHIN LPAREN expression COMMA expression RPAREN
-        ;
-
-filterTerm 	: expression
-                    (
-                              COMPARE  expression
-                            | NOT? IN LPAREN (expressionFctParam )?  RPAREN
-                            | BETWEEN expression AND expression
-                            | NOT? LIKE expression
-                            | NOT? ILIKE expression
-                            | IS NOT? NULL
-                            | AFTER  expression
-                            | ANYINTERACTS expression
-                            | BEFORE expression
-                            | BEGINS expression
-                            | BEGUNBY expression
-                            | DURING expression
-                            | ENDEDBY expression
-                            | ENDS expression
-                            | MEETS expression
-                            | METBY expression
-                            | OVERLAPPEDBY expression
-                            | TCONTAINS expression
-                            | TEQUALS expression
-                            | TOVERLAPS expression
-                    )
-                | filterGeometry
-                ;
-
-filter : filter (AND filter)+
-       | filter (OR filter )+
-       | LPAREN filter RPAREN
-       | NOT (filterTerm | (LPAREN filter RPAREN) )
-       | filterTerm
-       ;
-
-filterOrExpression : filter | expression ;
-
-sortprop : expression (ASC | DESC)? ;
-orderby : ORDER BY sortprop (COMMA sortprop)* ;
-limit : LIMIT INT ;
-offset : OFFSET INT ;
-where : WHERE filter ;
-projection : expression (AS TEXT)? ;
-query : SELECT (MULT | (projection (COMMA projection)*)) where? orderby? offset? limit?;
diff --git a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQL.java b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQL.java
deleted file mode 100644
index 14f8a58..0000000
--- a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQL.java
+++ /dev/null
@@ -1,861 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.time.Duration;
-import java.time.Period;
-import java.util.List;
-import java.util.ArrayList;
-import java.time.temporal.TemporalAccessor;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.CoordinateSequence;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.Polygon;
-import org.antlr.v4.runtime.tree.ParseTree;
-import org.antlr.v4.runtime.tree.TerminalNode;
-import javax.measure.Unit;
-import javax.measure.quantity.Length;
-import org.opengis.util.FactoryException;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.Literal;
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
-import org.apache.sis.measure.Units;
-import org.apache.sis.measure.Quantities;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.cql.internal.AntlrCQL;
-import org.apache.sis.cql.internal.CQLParser.CoordinateContext;
-import org.apache.sis.cql.internal.CQLParser.CoordinateSerieContext;
-import org.apache.sis.cql.internal.CQLParser.CoordinateSeriesContext;
-import org.apache.sis.cql.internal.CQLParser.ExpressionContext;
-import org.apache.sis.cql.internal.CQLParser.ExpressionFctParamContext;
-import org.apache.sis.cql.internal.CQLParser.ExpressionGeometryContext;
-import org.apache.sis.cql.internal.CQLParser.ExpressionNumContext;
-import org.apache.sis.cql.internal.CQLParser.ExpressionTermContext;
-import org.apache.sis.cql.internal.CQLParser.ExpressionUnaryContext;
-import org.apache.sis.cql.internal.CQLParser.FilterContext;
-import org.apache.sis.cql.internal.CQLParser.FilterGeometryContext;
-import org.apache.sis.cql.internal.CQLParser.FilterTermContext;
-import org.apache.sis.util.privy.StandardDateFormat;
-import static org.apache.sis.cql.internal.CQLParser.*;
-
-
-/**
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class CQL {
-
-    private static final GeometryFactory GF = org.apache.sis.geometry.wrapper.jts.Factory.INSTANCE.factory(false);
-
-    private CQL() {
-    }
-
-    public static Expression<Feature,?> parseExpression(String cql) throws CQLException {
-        return parseExpression(cql, null);
-    }
-
-    public static Expression<Feature,?> parseExpression(String cql, FilterFactory<Feature,?,?> factory) throws CQLException {
-        final Object obj = AntlrCQL.compileExpression(cql);
-        Expression<Feature,?> result = null;
-        if (obj instanceof ExpressionContext) {
-            ParseTree tree = (ParseTree) obj;
-            if (factory == null) {
-                factory = DefaultFilterFactory.forFeatures();
-            }
-            result = convertExpression(tree, factory);
-        }
-        return result;
-    }
-
-    public static Filter<Feature> parseFilter(String cql) throws CQLException {
-        return parseFilter(cql, null);
-    }
-
-    public static Filter<Feature> parseFilter(String cql, FilterFactory<Feature,Object,Object> factory) throws CQLException {
-        cql = cql.trim();
-
-        // Bypass parsing for inclusive filter.
-        if (cql.isEmpty() || "*".equals(cql)) {
-            return Filter.include();
-        }
-        final Object obj = AntlrCQL.compileFilter(cql);
-        Filter<Feature> result = null;
-        if (obj instanceof FilterContext) {
-            ParseTree tree = (ParseTree) obj;
-            if (factory == null) {
-                factory = DefaultFilterFactory.forFeatures();
-            }
-            result = convertFilter(tree, factory);
-        }
-        return result;
-    }
-
-    public static Query parseQuery(String cql) throws CQLException {
-        return parseQuery(cql, null);
-    }
-
-    public static Query parseQuery(String cql, FilterFactory<Feature,Object,Object> factory) throws CQLException {
-        final Object obj = AntlrCQL.compileQuery(cql);
-        Query result = null;
-        if (obj instanceof QueryContext) {
-            ParseTree tree = (ParseTree) obj;
-            if (factory == null) {
-                factory = DefaultFilterFactory.forFeatures();
-            }
-            result = convertQuery(tree, factory);
-        }
-        return result;
-    }
-
-    public static String write(final Filter<Feature> filter) {
-        final StringBuilder sb = new StringBuilder();
-        FilterToCQLVisitor.INSTANCE.visit(filter, sb);
-        return sb.toString();
-    }
-
-    public static String write(final Expression<Feature,?> exp) {
-        final StringBuilder sb = new StringBuilder();
-        FilterToCQLVisitor.INSTANCE.visit(exp, sb);
-        return sb.toString();
-    }
-
-    public static String write(final Query query) {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("SELECT ");
-        if (query.projections.isEmpty()) {
-            sb.append('*');
-        } else {
-            for (int i = 0, n = query.projections.size(); i < n; i++) {
-                Query.Projection p = query.projections.get(i);
-                if (i != 0) sb.append(", ");
-                sb.append(write(p.expression));
-                if (p.alias != null && !p.alias.isEmpty()) {
-                    sb.append(" AS '");
-                    sb.append(p.alias);
-                    sb.append('\'');
-                }
-            }
-        }
-        if (query.filter != null) {
-            sb.append(" WHERE ");
-            sb.append(write(query.filter));
-        }
-
-        if (!query.sortby.isEmpty()) {
-            sb.append(" ORDER BY ");
-            for (int i = 0, n = query.sortby.size(); i < n; i++) {
-                SortProperty p = query.sortby.get(i);
-                if (i != 0) sb.append(", ");
-                sb.append(write(p.getValueReference()));
-                switch (p.getSortOrder()) {
-                    case ASCENDING : sb.append(" ASC"); break;
-                    case DESCENDING : sb.append(" DESC"); break;
-                }
-            }
-        }
-
-        if (query.offset != null) {
-            sb.append(" OFFSET ");
-            sb.append(query.offset);
-        }
-        if (query.limit != null) {
-            sb.append(" LIMIT ");
-            sb.append(query.limit);
-        }
-
-        return sb.toString();
-    }
-
-    /**
-     * Convert the given tree in an Expression.
-     */
-    private static Expression<Feature,?> convertExpression(ParseTree tree, FilterFactory<Feature,?,?> ff) throws CQLException {
-        if (tree instanceof ExpressionContext) {
-            //: expression MULT expression
-            //| expression UNARY expression
-            //| expressionTerm
-            if (tree.getChildCount() == 3) {
-                final String operand = tree.getChild(1).getText();
-                // TODO: unsafe cast.
-                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)) {
-                    return ff.divide(left, right);
-                } else if ("+".equals(operand)) {
-                    return ff.add(left, right);
-                } else if ("-".equals(operand)) {
-                    return ff.subtract(left, right);
-                }
-            } else {
-                return convertExpression(tree.getChild(0), ff);
-            }
-        } //        else if(tree instanceof ExpressionStringContext){
-        //            //strip start and end '
-        //            final String text = tree.getText();
-        //            return ff.literal(text.substring(1, text.length()-1));
-        //        }
-        else if (tree instanceof ExpressionTermContext) {
-            //: expressionString
-            //| expressionUnary
-            //| PROPERTY_NAME
-            //| DATE
-            //| DURATION_P
-            //| DURATION_T
-            //| NAME (LPAREN expressionFctParam? RPAREN)?
-            //| expressionGeometry
-            //| LPAREN expression RPAREN
-
-            //: TEXT
-            //| expressionUnary
-            //| PROPERTY_NAME
-            //| DATE
-            //| DURATION_P
-            //| DURATION_T
-            //| expressionGeometry
-            final ExpressionTermContext exp = (ExpressionTermContext) tree;
-            if (exp.getChildCount() == 1) {
-                return convertExpression(tree.getChild(0), ff);
-            }
-            // LPAREN expression RPAREN
-            if (exp.expression() != null) {
-                return convertExpression(exp.expression(), ff);
-            }
-            // NAME (LPAREN expressionFctParam? RPAREN)?
-            if (exp.NAME() != null) {
-                final String name = exp.NAME().getText();
-                final ExpressionFctParamContext prm = exp.expressionFctParam();
-                if (prm == null) {
-                    //handle as property name
-                    return ff.property(name);
-                }
-                // Handle as a function.
-                final List<ExpressionContext> params = prm.expression();
-                final List<Expression<Feature,?>> exps = new ArrayList<>();
-                for (int i = 0, n = params.size(); i < n; i++) {
-                    exps.add(convertExpression(params.get(i), ff));
-                }
-                return ff.function(name, exps.toArray(new Expression[exps.size()]));
-            }
-        } else if (tree instanceof ExpressionUnaryContext) {
-            //: UNARY? expressionNum ;
-            final ExpressionUnaryContext exp = (ExpressionUnaryContext) tree;
-            return ff.literal(unaryAsNumber(exp));
-
-        } else if (tree instanceof ExpressionNumContext) {
-            //: INT | FLOAT ;
-            return convertExpression(tree.getChild(0), ff);
-        } else if (tree instanceof TerminalNode) {
-            final TerminalNode exp = (TerminalNode) tree;
-            switch (exp.getSymbol().getType()) {
-                case PROPERTY_NAME: {
-                    // strip start and end "
-                    final String text = tree.getText();
-                    return ff.property(text.substring(1, text.length() - 1));
-                }
-                case NAME:  return ff.property(tree.getText());
-                case INT:   return ff.literal(Integer.valueOf(tree.getText()));
-                case FLOAT: return ff.literal(Double.valueOf(tree.getText()));
-                case DATE: {
-                    TemporalAccessor ta = StandardDateFormat.FORMAT.parse(tree.getText());
-                    return ff.literal(ta);
-                    // TODO! return ff.literal(TemporalUtilities.getTimeInMillis(tree.getText()));
-                }
-                case TEXT: {
-                    // strip start and end '
-                    String text = tree.getText();
-                    text = text.replaceAll("\\\\'", "'");
-                    return ff.literal(text.substring(1, text.length() - 1));
-                }
-                case DURATION_P :
-                    return ff.literal(Period.parse(tree.getText()));
-                case DURATION_T :
-                    return ff.literal(Duration.parse(tree.getText()));
-            }
-        } else if (tree instanceof ExpressionGeometryContext) {
-            //: POINT ( EMPTY | coordinateSerie )
-            //| LINESTRING ( EMPTY | coordinateSerie )
-            //| POLYGON ( EMPTY | coordinateSeries )
-            //| MPOINT ( EMPTY | coordinateSerie )
-            //| MLINESTRING  ( EMPTY | coordinateSeries )
-            //| MPOLYGON ( EMPTY | LPAREN coordinateSeries (COMMA coordinateSeries)* RPAREN )
-            //| GEOMETRYCOLLECTION ( EMPTY | (LPAREN expressionGeometry (COMMA expressionGeometry)* RPAREN) )
-            //| ENVELOPE ( EMPTY | (LPAREN expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary RPAREN) )
-            final ExpressionGeometryContext exp = (ExpressionGeometryContext) tree;
-            switch (((TerminalNode) exp.getChild(0)).getSymbol().getType()) {
-                case POINT: {
-                    final ParseTree st = tree.getChild(1);
-                    final CoordinateSequence cs;
-                    if (isEmptyToken(st)) {
-                        cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                    } else {
-                        cs = parseSequence(st);
-                    }
-                    final Geometry geom = GF.createPoint(cs);
-                    return ff.literal(geom);
-                }
-                case LINESTRING: {
-                    final ParseTree st = tree.getChild(1);
-                    final CoordinateSequence cs;
-                    if (isEmptyToken(st)) {
-                        cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                    } else {
-                        cs = parseSequence(st);
-                    }
-                    final Geometry geom = GF.createLineString(cs);
-                    return ff.literal(geom);
-                }
-                case POLYGON: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
-                    } else {
-                        final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
-                        final List<CoordinateSerieContext> subs = series.coordinateSerie();
-                        final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
-                        final int n = subs.size();
-                        final LinearRing[] holes = new LinearRing[n - 1];
-                        for (int i = 1; i < n; i++) {
-                            holes[i - 1] = GF.createLinearRing(parseSequence(subs.get(i)));
-                        }
-                        geom = GF.createPolygon(contour, holes);
-                    }
-                    return ff.literal(geom);
-                }
-                case MPOINT: {
-                    final ParseTree st = tree.getChild(1);
-                    final CoordinateSequence cs;
-                    if (isEmptyToken(st)) {
-                        cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                    } else {
-                        cs = parseSequence(st);
-                    }
-                    final Geometry geom = GF.createMultiPoint(cs);
-                    return ff.literal(geom);
-                }
-                case MLINESTRING: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createMultiLineString(new LineString[0]);
-                    } else {
-                        final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
-                        final List<CoordinateSerieContext> subs = series.coordinateSerie();
-                        final int n = subs.size();
-                        final LineString[] strings = new LineString[n];
-                        for (int i = 0; i < n; i++) {
-                            strings[i] = GF.createLineString(parseSequence(subs.get(i)));
-                        }
-                        geom = GF.createMultiLineString(strings);
-                    }
-                    return ff.literal(geom);
-                }
-                case MPOLYGON: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createMultiPolygon(new Polygon[0]);
-                    } else {
-                        final List<CoordinateSeriesContext> eles = exp.coordinateSeries();
-                        final int n = eles.size();
-                        final Polygon[] polygons = new Polygon[n];
-                        for (int i=0; i<n; i++) {
-                            final CoordinateSeriesContext polyTree = eles.get(i);
-                            final List<CoordinateSerieContext> subs = polyTree.coordinateSerie();
-                            final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
-                            final int hn = subs.size();
-                            final LinearRing[] holes = new LinearRing[hn - 1];
-                            for (int j=1; j<hn; j++) {
-                                holes[j-1] = GF.createLinearRing(parseSequence(subs.get(j)));
-                            }
-                            final Polygon poly = GF.createPolygon(contour, holes);
-                            polygons[i] = poly;
-                        }
-                        geom = GF.createMultiPolygon(polygons);
-                    }
-                    return ff.literal(geom);
-                }
-                case GEOMETRYCOLLECTION: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createGeometryCollection(new Geometry[0]);
-                    } else {
-                        final List<ExpressionGeometryContext> eles = exp.expressionGeometry();
-                        final int n = eles.size();
-                        final Geometry[] subs = new Geometry[n];
-                        for (int i=0; i<n; i++) {
-                            final ParseTree subTree = eles.get(i);
-                            final Geometry sub = (Geometry) convertExpression(subTree, ff).apply(null);
-                            subs[i] = sub;
-                        }
-                        geom = GF.createGeometryCollection(subs);
-                    }
-                    return ff.literal(geom);
-                }
-                case ENVELOPE: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
-                    } else {
-                        final List<ExpressionUnaryContext> unaries = exp.expressionUnary();
-                        final double west  = unaryAsNumber(unaries.get(0)).doubleValue();
-                        final double east  = unaryAsNumber(unaries.get(1)).doubleValue();
-                        final double north = unaryAsNumber(unaries.get(2)).doubleValue();
-                        final double south = unaryAsNumber(unaries.get(3)).doubleValue();
-                        final LinearRing contour = GF.createLinearRing(new Coordinate[]{
-                            new Coordinate(west, north),
-                            new Coordinate(east, north),
-                            new Coordinate(east, south),
-                            new Coordinate(west, south),
-                            new Coordinate(west, north)
-                        });
-                        geom = GF.createPolygon(contour, new LinearRing[0]);
-                    }
-                    return ff.literal(geom);
-                }
-            }
-            return convertExpression(tree.getChild(0), ff);
-        }
-        throw new CQLException("Unreconized expression : type=" + tree.getText());
-    }
-
-    private static boolean isEmptyToken(ParseTree tree) {
-        return tree instanceof TerminalNode && ((TerminalNode) tree).getSymbol().getType() == EMPTY;
-    }
-
-    private static Number unaryAsNumber(ExpressionUnaryContext exp) {
-        //: UNARY? expressionNum ;
-        final boolean negate = (exp.UNARY() != null && exp.UNARY().getSymbol().getText().equals("-"));
-        final ExpressionNumContext num = exp.expressionNum();
-        if (num.INT() != null) {
-            int val = Integer.valueOf(num.INT().getText());
-            return negate ? -val : val;
-        } else {
-            double val = Double.valueOf(num.FLOAT().getText());
-            return negate ? -val : val;
-        }
-    }
-
-    private static CoordinateSequence parseSequence(ParseTree tree) {
-        final CoordinateSerieContext exp = (CoordinateSerieContext) tree;
-        final List<CoordinateContext> lst = exp.coordinate();
-        final int size = lst.size();
-        final Coordinate[] coords = new Coordinate[size];
-        for (int i = 0; i < size; i++) {
-            final CoordinateContext cc = lst.get(i);
-            coords[i] = new Coordinate(
-                    unaryAsNumber(cc.expressionUnary(0)).doubleValue(),
-                    unaryAsNumber(cc.expressionUnary(1)).doubleValue());
-        }
-        return GF.getCoordinateSequenceFactory().create(coords);
-    }
-
-    private static Unit<Length> parseLengthUnit(final Expression<Feature,?> unitExp) {
-        Object value = unitExp.apply(null);
-        if (value != null) {
-            return Units.ensureLinear(Units.valueOf(value.toString()));
-        }
-        if (unitExp instanceof ValueReference<?,?>) {
-            value = ((ValueReference<?,?>) unitExp).getXPath();
-        }
-        throw new IllegalArgumentException("Unit `" + value + "` is not a literal.");
-    }
-
-    /**
-     * Convert the given tree in a Filter.
-     */
-    private static Filter<Feature> convertFilter(ParseTree tree, FilterFactory<Feature,Object,Object> ff) throws CQLException {
-        if (tree instanceof FilterContext) {
-            //: filter (AND filter)+
-            //| filter (OR filter)+
-            //| LPAREN filter RPAREN
-            //| NOT filterTerm
-            //| filterTerm
-
-            final FilterContext exp = (FilterContext) tree;
-
-            //| filterTerm
-            if (exp.getChildCount() == 1) {
-                return convertFilter(tree.getChild(0), ff);
-            } else if (exp.NOT() != null) {
-                //| NOT (filterTerm | ( LPAREN filter RPAREN ))
-                if (exp.filterTerm() != null) {
-                    return ff.not(convertFilter(exp.filterTerm(), ff));
-                } else {
-                    return ff.not(convertFilter(exp.filter(0), ff));
-                }
-
-            } else if (!exp.AND().isEmpty()) {
-                //: filter (AND filter)+
-                final List<Filter<Feature>> subs = new ArrayList<>();
-                for (FilterContext f : exp.filter()) {
-                    final Filter<Feature> sub = convertFilter(f, ff);
-                    if (sub.getOperatorType() == LogicalOperatorName.AND) {
-                        subs.addAll(((LogicalOperator<Feature>) sub).getOperands());
-                    } else {
-                        subs.add(sub);
-                    }
-                }
-                return ff.and(subs);
-            } else if (!exp.OR().isEmpty()) {
-                //| filter (OR filter)+
-                final List<Filter<Feature>> subs = new ArrayList<>();
-                for (FilterContext f : exp.filter()) {
-                    final Filter<Feature> sub = convertFilter(f, ff);
-                    if (sub.getOperatorType() == LogicalOperatorName.OR) {
-                        subs.addAll(((LogicalOperator<Feature>) sub).getOperands());
-                    } else {
-                        subs.add(sub);
-                    }
-                }
-                return ff.or(subs);
-            } else if (exp.LPAREN() != null) {
-                //| LPAREN filter RPAREN
-                return convertFilter(exp.filter(0), ff);
-            }
-        } else if (tree instanceof FilterTermContext) {
-            //: expression
-            //    (
-            //              COMPARE  expression
-            //            | NOT? IN LPAREN (expressionFctParam )?  RPAREN
-            //            | BETWEEN expression AND expression
-            //            | NOT? LIKE expression
-            //            | NOT? ILIKE expression
-            //            | IS NOT? NULL
-            //            | AFTER expression
-            //            | ANYINTERACTS expression
-            //            | BEFORE expression
-            //            | BEGINS expression
-            //            | BEGUNBY expression
-            //            | DURING expression
-            //            | ENDEDBY expression
-            //            | ENDS expression
-            //            | MEETS expression
-            //            | METBY expression
-            //            | OVERLAPPEDBY expression
-            //            | TCONTAINS expression
-            //            | TEQUALS expression
-            //            | TOVERLAPS expression
-            //    )
-            //| filterGeometry
-
-            final FilterTermContext exp = (FilterTermContext) tree;
-            final List<ExpressionContext> exps = exp.expression();
-            if (exp.COMPARE() != null) {
-                // expression COMPARE expression
-                final String text = exp.COMPARE().getText();
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                if ("=".equals(text)) {
-                    return ff.equal(left, right);
-                } else if ("<>".equals(text)) {
-                    return ff.notEqual(left, right);
-                } else if (">".equals(text)) {
-                    return ff.greater(left, right);
-                } else if ("<".equals(text)) {
-                    return ff.less(left, right);
-                } else if (">=".equals(text)) {
-                    return ff.greaterOrEqual(left, right);
-                } else if ("<=".equals(text)) {
-                    return ff.lessOrEqual(left, right);
-                }
-            } else if (exp.IN() != null) {
-                // expression NOT? IN LPAREN (expressionFctParam )?  RPAREN
-                final Expression<Feature,?> val = convertExpression(exps.get(0), ff);
-                final ExpressionFctParamContext prm = exp.expressionFctParam();
-                final List<ExpressionContext> params = prm.expression();
-                final List<Expression<Feature,?>> subexps = new ArrayList<>();
-                for (int i = 0, n = params.size(); i < n; i++) {
-                    subexps.add(convertExpression(params.get(i), ff));
-                }
-                final int size = subexps.size();
-                final Filter<Feature> selection;
-                switch (size) {
-                    case 0: {
-                        selection = Filter.exclude();
-                        break;
-                    }
-                    case 1: {
-                        selection = ff.equal(val, subexps.get(0));
-                        break;
-                    }
-                    default: {
-                        final List<Filter<Feature>> filters = new ArrayList<>();
-                        for (Expression<Feature,?> e : subexps) {
-                            filters.add(ff.equal(val, e));
-                        }   selection = ff.or(filters);
-                        break;
-                    }
-                }
-                if (exp.NOT() != null) {
-                    return ff.not(selection);
-                } else {
-                    return selection;
-                }
-            } else if (exp.BETWEEN() != null) {
-                // expression BETWEEN expression AND expression
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                final Expression<Feature,?> exp3 = convertExpression(exps.get(2), ff);
-                return ff.between(exp1, exp2, exp3);
-            } else if (exp.LIKE() != null) {
-                // expression NOT? LIKE expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.like(left, right.apply(null).toString(), '%', '_', '\\', true));
-                } else {
-                    return ff.like(left, right.apply(null).toString(), '%', '_', '\\', true);
-                }
-            } else if (exp.ILIKE() != null) {
-                // expression NOT? LIKE expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.like(left, right.apply(null).toString(), '%', '_', '\\', false));
-                } else {
-                    return ff.like(left, right.apply(null).toString(), '%', '_', '\\', false);
-                }
-            } else if (exp.IS() != null) {
-                // expression IS NOT? NULL
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.isNull(exp1));
-                } else {
-                    return ff.isNull(exp1);
-                }
-            } else if (exp.AFTER() != null) {
-                // expression AFTER expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.after(left, right);
-            } else if (exp.ANYINTERACTS() != null) {
-                // expression ANYINTERACTS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.anyInteracts(left, right);
-            } else if (exp.BEFORE() != null) {
-                // expression BEFORE expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.before(left, right);
-            } else if (exp.BEGINS() != null) {
-                // expression BEGINS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.begins(left, right);
-            } else if (exp.BEGUNBY() != null) {
-                // expression BEGUNBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.begunBy(left, right);
-            } else if (exp.DURING() != null) {
-                // expression DURING expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.during(left, right);
-            } else if (exp.ENDEDBY() != null) {
-                // expression ENDEDBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.endedBy(left, right);
-            } else if (exp.ENDS() != null) {
-                // expression ENDS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.ends(left, right);
-            } else if (exp.MEETS() != null) {
-                // expression MEETS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.meets(left, right);
-            } else if (exp.METBY() != null) {
-                // expression METBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.metBy(left, right);
-            } else if (exp.OVERLAPPEDBY() != null) {
-                // expression OVERLAPPEDBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.overlappedBy(left, right);
-            } else if (exp.TCONTAINS() != null) {
-                // expression TCONTAINS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.tcontains(left, right);
-            } else if (exp.TEQUALS() != null) {
-                // expression TEQUALS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.tequals(left, right);
-            } else if (exp.TOVERLAPS() != null) {
-                // expression TOVERLAPS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.toverlaps(left, right);
-            } else if (exp.filterGeometry() != null) {
-                // expression filterGeometry
-                return convertFilter(exp.filterGeometry(), ff);
-            }
-        } else if (tree instanceof FilterGeometryContext) {
-            //: BBOX LPAREN (PROPERTY_NAME|NAME) COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary (COMMA TEXT)? RPAREN
-            //| BEYOND LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-            //| CONTAINS LPAREN expression COMMA expression RPAREN
-            //| CROSSES LPAREN expression COMMA expression RPAREN
-            //| DISJOINT LPAREN expression COMMA expression RPAREN
-            //| DWITHIN LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-            //| EQUALS LPAREN expression COMMA expression RPAREN
-            //| INTERSECTS LPAREN expression COMMA expression RPAREN
-            //| OVERLAPS LPAREN expression COMMA expression RPAREN
-            //| TOUCHES LPAREN expression COMMA expression RPAREN
-            //| WITHIN LPAREN expression COMMA expression RPAREN
-
-            final FilterGeometryContext exp = (FilterGeometryContext) tree;
-            final List<ExpressionContext> exps = exp.expression();
-            if (exp.BBOX() != null) {
-                final Expression<Feature,?> prop = convertExpression(exps.get(0), ff);
-                final double v1 = unaryAsNumber(exp.expressionUnary(0)).doubleValue();
-                final double v2 = unaryAsNumber(exp.expressionUnary(1)).doubleValue();
-                final double v3 = unaryAsNumber(exp.expressionUnary(2)).doubleValue();
-                final double v4 = unaryAsNumber(exp.expressionUnary(3)).doubleValue();
-                GeneralEnvelope env;
-                if (exp.TEXT() != null) try {
-                    String crs = convertExpression(exp.TEXT(), ff).apply(null).toString();
-                    env = new GeneralEnvelope(CRS.forCode(crs));
-                } catch (FactoryException e) {
-                    throw new CQLException("Cannot parse CRS code.", e);
-                } else {
-                    env = new GeneralEnvelope(2);
-                }
-                env.setRange(0, v1, v3);
-                env.setRange(1, v2, v4);
-                return ff.bbox(prop, env);
-            } else if (exp.BEYOND() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                final double distance = ((Number) convertExpression(exps.get(2), ff).apply(null)).doubleValue();
-                final Unit<Length> unit = parseLengthUnit(convertExpression(exps.get(3), ff));
-                return ff.beyond(exp1, exp2, Quantities.create(distance, unit));
-            } else if (exp.CONTAINS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.contains(exp1, exp2);
-            } else if (exp.CROSSES() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.crosses(exp1, exp2);
-            } else if (exp.DISJOINT() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.disjoint(exp1, exp2);
-            } else if (exp.DWITHIN() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                final double distance = ((Number) convertExpression(exps.get(2), ff).apply(null)).doubleValue();
-                final Unit<Length> unit = parseLengthUnit(convertExpression(exps.get(3), ff));
-                return ff.within(exp1, exp2, Quantities.create(distance, unit));
-            } else if (exp.EQUALS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.equals(exp1, exp2);
-            } else if (exp.INTERSECTS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.intersects(exp1, exp2);
-            } else if (exp.OVERLAPS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.overlaps(exp1, exp2);
-            } else if (exp.TOUCHES() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.touches(exp1, exp2);
-            } else if (exp.WITHIN() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.within(exp1, exp2);
-            }
-        }
-        throw new CQLException("Unreconized filter : type=" + tree.getText());
-    }
-
-    /**
-     * Convert the given tree in a Query.
-     */
-    private static Query convertQuery(ParseTree tree, FilterFactory<Feature,Object,Object> ff) throws CQLException {
-        if (tree instanceof QueryContext) {
-            final QueryContext context = (QueryContext) tree;
-            final List<ProjectionContext> projections = context.projection();
-            final WhereContext where = context.where();
-            final OffsetContext offset = context.offset();
-            final LimitContext limit = context.limit();
-            final OrderbyContext orderby = context.orderby();
-
-            final Query query = new Query();
-            if (context.MULT() == null) {
-                for (ProjectionContext pc : projections) {
-                    final Expression<Feature,?> exp = convertExpression(pc.expression(), ff);
-                    if (pc.AS() != null) {
-                        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));
-                    }
-                }
-            }
-            if (where != null) {
-                query.filter = (Filter<Feature>) convertFilter(where.filter(), ff);
-            }
-            if (offset != null) {
-                query.offset = Integer.valueOf(offset.INT().getText());
-            }
-            if (limit != null) {
-                query.limit = Integer.valueOf(limit.INT().getText());
-            }
-            if (orderby != null) {
-                for (SortpropContext spc : orderby.sortprop()) {
-                    final Expression<Feature,?> exp = convertExpression(spc.expression(), ff);
-                    if (exp instanceof ValueReference) {
-                        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");
-                    }
-                }
-            }
-            return query;
-        }
-        return null;
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQLException.java b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQLException.java
deleted file mode 100644
index 28dd5f4..0000000
--- a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQLException.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-
-/**
- * Thrown when a CQL statement cannot be parsed.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class CQLException extends Exception {
-    /**
-     * Serial number for inter-operability with different versions.
-     */
-    private static final long serialVersionUID = -1494977914551289727L;
-
-    /**
-     * Constructs an exception with the specified detail message.
-     *
-     * @param  message  the detail message.
-     */
-    public CQLException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs an exception with the specified detail message and cause.
-     *
-     * @param  message  the detail message.
-     * @param  cause    the cause for this exception.
-     */
-    public CQLException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/FilterToCQLVisitor.java b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/FilterToCQLVisitor.java
deleted file mode 100644
index b7a24b8..0000000
--- a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/FilterToCQLVisitor.java
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Pattern;
-import java.time.temporal.TemporalAccessor;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.io.WKTWriter;
-import javax.measure.Unit;
-import javax.measure.Quantity;
-import org.opengis.util.CodeList;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
-import org.opengis.filter.TemporalOperatorName;
-import org.opengis.filter.BinarySpatialOperator;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BetweenComparisonOperator;
-import org.opengis.filter.LikeOperator;
-import org.opengis.filter.Expression;
-import org.apache.sis.measure.UnitFormat;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.geometry.wrapper.Geometries;
-import org.apache.sis.geometry.wrapper.GeometryWrapper;
-import org.apache.sis.filter.privy.FunctionNames;
-import org.apache.sis.filter.privy.Visitor;
-import org.apache.sis.util.privy.StandardDateFormat;
-
-
-/**
- * Visitor to convert a Filter in CQL.
- *
- * @author  Johann Sorel (Geomatys)
- */
-final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
-
-    static final FilterToCQLVisitor INSTANCE = new FilterToCQLVisitor();
-
-    /**
-     * Pattern to check for property name to escape against regExp
-     */
-    private final Pattern patternPropertyName;
-
-    /**
-     * Formatter to use for unit symbol. Not thread-safe; usage must be synchronized.
-     */
-    private final UnitFormat unitFormat;
-
-    /**
-     * Creates a new visitor.
-     */
-    private FilterToCQLVisitor() {
-        patternPropertyName = Pattern.compile("[,+\\-/*\\t\\n\\r\\d\\s]");
-        unitFormat = new UnitFormat(Locale.US);
-        unitFormat.setStyle(UnitFormat.Style.NAME);
-
-        constant(Filter.exclude(), "1=0");
-        constant(Filter.include(), "1=1");
-        operatorBetweenValues(LogicalOperatorName.AND, "AND");
-        operatorBetweenValues(LogicalOperatorName.OR,  "OR");
-        setFilterHandler(LogicalOperatorName.NOT, (f,sb) -> {
-            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
-            format(sb.append("NOT "), filter.getOperands().get(0));
-        });
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN), (f,sb) -> {
-            final BetweenComparisonOperator<Feature> filter = (BetweenComparisonOperator<Feature>) f;
-            format(sb, filter.getExpression());
-            format(sb.append(" BETWEEN "), filter.getLowerBoundary());
-            format(sb.append(" AND "), filter.getUpperBoundary());
-        });
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO,                 "=");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO,             "<>");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN,             ">");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, ">=");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,                "<");
-        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<Feature,?>> operands = f.getExpressions();
-            format(sb, operands.get(0));
-            // TODO: ILIKE is not standard SQL.
-            sb.append(filter.isMatchingCase() ? " LIKE " : " ILIKE ");
-            // TODO: convert wildcards and escape to SQL 92.
-            format(sb, operands.get(1));
-        });
-        operatorAfterValue(FunctionNames.PROPERTY_IS_NULL, " IS NULL");
-        operatorAfterValue(FunctionNames.PROPERTY_IS_NIL,  " IS NIL");
-        /*
-         * Spatial filters.
-         */
-        setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
-            final BinarySpatialOperator<Feature> filter = (BinarySpatialOperator<Feature>) f;
-            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<Feature,?>) left
-                    : (Literal<Feature,?>) right).getValue();      // TODO: potential classCastException.
-
-            final GeneralEnvelope e = Geometries.wrap(lit).map(GeometryWrapper::getEnvelope).orElse(null);
-            if (e != null) {
-                if (e.getDimension() > 2) {
-                    throw new UnsupportedOperationException("Only 2D envelopes accepted");
-                }
-                sb.append("BBOX(").append(pName.getXPath()).append(", ");
-                sb.append(e.getMinimum(0)).append(", ")
-                  .append(e.getMaximum(0)).append(", ")
-                  .append(e.getMinimum(1)).append(", ")
-                  .append(e.getMaximum(1)).append(')');
-            } else {
-                // Use writing BBOX(exp1,exp2).
-                format(sb.append("BBOX("), left);
-                format(sb.append(','), right);
-                sb.append(')');
-            }
-        });
-        for (final SpatialOperatorName type : SpatialOperatorName.values()) {
-            if (type != SpatialOperatorName.BBOX) {
-                function(type, type.identifier().toUpperCase(Locale.US));
-                if (type == SpatialOperatorName.OVERLAPS) break;
-            }
-        }
-        function(DistanceOperatorName.WITHIN, "DWITHIN");
-        function(DistanceOperatorName.BEYOND, "BEYOND");
-        for (final TemporalOperatorName type : TemporalOperatorName.values()) {
-            function(type, type.identifier().toUpperCase(Locale.US));
-            if (type == TemporalOperatorName.ANY_INTERACTS) break;
-        }
-        /*
-         * Expressions
-         */
-        setExpressionHandler(FunctionNames.Literal, (e,sb) -> {
-            final Literal<Feature,?> exp = (Literal<Feature,?>) e;
-            final Object value = exp.getValue();
-            if (value instanceof Quantity<?>) {
-                final Quantity<?> q = (Quantity<?>) value;
-                final Unit<?> unit = q.getUnit();
-                sb.append(q.getValue().doubleValue()).append(", '");
-                try {
-                    synchronized (unitFormat) {
-                        unitFormat.format(unit, sb);
-                    }
-                } catch (IOException ex) {
-                    throw new UncheckedIOException(ex);     // Should never happen.
-                }
-                sb.append('\'');
-            } else if (value instanceof Number) {
-                sb.append(value);
-            } else if (value instanceof Date) {
-                final Date date = (Date) value;
-                sb.append(StandardDateFormat.FORMAT.format(date.toInstant()));
-            } else if (value instanceof TemporalAccessor) {
-                final TemporalAccessor date = (TemporalAccessor) value;
-                sb.append(StandardDateFormat.FORMAT.format(date));
-            } else if (value instanceof Geometry) {
-                final Geometry geometry = (Geometry) value;
-                final WKTWriter writer = new WKTWriter();
-                sb.append(writer.write(geometry));
-            } else {
-                sb.append('\'').append(value).append('\'');
-            }
-        });
-        setExpressionHandler(FunctionNames.ValueReference, (e,sb) -> {
-            final ValueReference<Feature,?> exp = (ValueReference<Feature,?>) e;
-            final String name = exp.getXPath();
-            if (patternPropertyName.matcher(name).find()) {
-                // Escape for special chars
-                sb.append('"').append(name).append('"');
-            } else {
-                sb.append(name);
-            }
-        });
-        arithmetic(FunctionNames.Add,      '+');
-        arithmetic(FunctionNames.Divide,   '/');
-        arithmetic(FunctionNames.Multiply, '*');
-        arithmetic(FunctionNames.Subtract, '-');
-    }
-
-    private void constant(final Filter<?> type, final String text) {
-        setFilterHandler(type.getOperatorType(), (f,sb) -> sb.append(text));
-    }
-
-    private void operatorAfterValue(final String type, final String operator) {
-        setFilterHandler(ComparisonOperatorName.valueOf(type), (f,sb) -> {
-            format(sb, f.getExpressions().get(0));
-            sb.append(operator);
-        });
-    }
-
-    private void operatorBetweenValues(final ComparisonOperatorName type, final String operator) {
-        setFilterHandler(type, (f,sb) -> {
-            final List<Expression<Feature,?>> operands = f.getExpressions();
-            format(sb, operands.get(0));
-            final int n = operands.size();
-            for (int i=1; i<n; i++) {
-                // Should execute only once. If n>2, make the problem visible in the CQL.
-                format(sb.append(' ').append(operator).append(' '), operands.get(i));
-            }
-        });
-    }
-
-    private void operatorBetweenValues(final LogicalOperatorName type, final String operator) {
-        setFilterHandler(type, (f,sb) -> {
-            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
-            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++) {
-                format(sb.append(' ').append(operator).append(' '), operands.get(i));
-            }
-            sb.append(')');
-        });
-    }
-
-    private void function(final CodeList<?> type, final String operator) {
-        setFilterHandler(type, (f,sb) -> {
-            final List<Expression<Feature,?>> operands = f.getExpressions();
-            sb.append(operator).append('(');
-            final int n = operands.size();
-            for (int i=0; i<n; i++) {
-                if (i != 0) sb.append(", ");
-                format(sb, operands.get(i));
-            }
-            sb.append(')');
-        });
-    }
-
-    private void arithmetic(final String type, final char operator) {
-        setExpressionHandler(type, (e,sb) -> {
-            final List<Expression<Feature,?>> parameters = e.getParameters();
-            format(sb, parameters.get(0));
-            final int n = parameters.size();
-            for (int i=1; i<n; i++) {
-                format(sb.append(' ').append(operator).append(' '), parameters.get(i));
-            }
-        });
-    }
-
-    /**
-     * Executes the registered action for the given filter.
-     *
-     * <h4>Note on type safety</h4>
-     * This method signature uses {@code <? super R>} for caller's convenience because this is the type that
-     * we get from {@link LogicalOperator#getOperands()}. But the {@link BiConsumer} uses exactly {@code <R>}
-     * type because doing otherwise causes complications with types that cannot be expressed in Java (kinds
-     * of {@code <? super ? super R>}). The cast in this method is okay if we do not invoke any {@code filter}
-     * method with a return value (directly or indirectly as list elements) of exactly {@code <R>} type.
-     * Such methods do not exist in the GeoAPI interfaces, so we are safe if the {@link BiConsumer}
-     * does not invoke implementation-specific methods.
-     *
-     * @param  sb      where to write the result of all actions.
-     * @param  filter  the filter for which to execute an action based on its type.
-     * @throws UnsupportedOperationException if there is no action registered for the given filter.
-     */
-    @SuppressWarnings("unchecked")
-    private void format(final StringBuilder sb, final Filter<Feature> filter) {
-        visit((Filter<Feature>) filter, sb);
-    }
-
-    /**
-     * Executes the registered action for the given expression.
-     * Throws an exception if the expression did not write anything in the buffer.
-     *
-     * <h4>Note on type safety</h4>
-     * This method signature uses {@code <? super R>} for caller's convenience because this is the type that
-     * we get from {@link Expression#getParameters()}. But the {@link BiConsumer} expects exactly {@code <R>}
-     * type because doing otherwise causes complications with types that cannot be expressed in Java (kinds
-     * of {@code <? super ? super R>}). The cast in this method is okay if we do not invoke any {@code exp}
-     * method with a return value (directly or indirectly as list elements) of exactly {@code <R>} type.
-     * Such methods do not exist in the GeoAPI interfaces, so we are safe if the {@link BiConsumer}
-     * does not invoke implementation-specific methods.
-     *
-     * @param  sb   where to write the result of all actions.
-     * @param  exp  the expression for which to execute an action based on its type.
-     * @throws UnsupportedOperationException if there is no action registered for the given expression.
-     */
-    @SuppressWarnings("unchecked")
-    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<Feature,?>> exps = e.getParameters();
-        sb.append(type).append('(');
-        final int n = exps.size();
-        for (int i=0; i<n; i++) {
-            if (i != 0) sb.append(", ");
-            format(sb, exps.get(i));
-        }
-        sb.append(')');
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/Query.java b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/Query.java
deleted file mode 100644
index 6c8794b..0000000
--- a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/Query.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Filter;
-import org.opengis.filter.SortProperty;
-
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class Query {
-
-    public final List<Projection> projections = new ArrayList<>();
-    public Filter<Feature> filter;
-    public Integer offset;
-    public Integer limit;
-    public final List<SortProperty> sortby = new ArrayList<>();
-
-    public Query() {
-    }
-
-    public Query(List<Projection> projections, Filter filter, List<SortProperty> sortby, Integer offset, Integer limit) {
-        if (projections != null) this.projections.addAll(projections);
-        this.filter = filter;
-        if (sortby != null) this.sortby.addAll(sortby);
-        this.offset = offset;
-        this.limit = limit;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 3;
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final Query other = (Query) obj;
-        if (!Objects.equals(this.projections, other.projections)) {
-            return false;
-        }
-        if (!Objects.equals(this.filter, other.filter)) {
-            return false;
-        }
-        if (!Objects.equals(this.sortby, other.sortby)) {
-            return false;
-        }
-        if (!Objects.equals(this.offset, other.offset)) {
-            return false;
-        }
-        if (!Objects.equals(this.limit, other.limit)) {
-            return false;
-        }
-        return true;
-    }
-
-    public static class Projection {
-        public Expression<Feature, ?> expression;
-        public String alias;
-
-        public Projection(Expression<Feature, ?> expression, String alias) {
-            this.expression = expression;
-            this.alias = alias;
-        }
-
-        @Override
-        public int hashCode() {
-            int hash = 7;
-            hash = 19 * hash + Objects.hashCode(this.expression);
-            hash = 19 * hash + Objects.hashCode(this.alias);
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null) {
-                return false;
-            }
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final Projection other = (Projection) obj;
-            if (!Objects.equals(this.alias, other.alias)) {
-                return false;
-            }
-            if (!Objects.equals(this.expression, other.expression)) {
-                return false;
-            }
-            return true;
-        }
-
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/internal/.gitignore b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/internal/.gitignore
deleted file mode 100644
index 4948df6..0000000
--- a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/internal/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by ANTLR
-*.interp
-*.tokens
-CQLLexer.java
-CQLParser.java
-CQLListener.java
-CQLBaseListener.java
diff --git a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/internal/AntlrCQL.java b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/internal/AntlrCQL.java
deleted file mode 100644
index 64c3fa3..0000000
--- a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/internal/AntlrCQL.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql.internal;
-
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CodePointCharStream;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.RecognitionException;
-import org.antlr.v4.runtime.TokenStream;
-import org.antlr.v4.runtime.tree.ParseTree;
-
-
-/**
- * ANTLR CQL parser methods.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class AntlrCQL {
-
-    private AntlrCQL() {
-    }
-
-    public static ParseTree compile(String cql) {
-        final Object obj = compileFilterOrExpression(cql);
-        ParseTree tree = null;
-        if (obj instanceof ParseTree) {
-            tree = (ParseTree) obj;
-        }
-        return tree;
-    }
-
-    public static Object compileExpression(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.ExpressionContext ctx = parser.expression();
-            return ctx;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileFilter(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.FilterContext retfilter = parser.filter();
-
-            return retfilter;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileFilterOrExpression(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.FilterOrExpressionContext retfilter = parser.filterOrExpression();
-
-            return retfilter;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileQuery(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.QueryContext ctx = parser.query();
-            return ctx;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/CQLTestCase.java b/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/CQLTestCase.java
deleted file mode 100644
index a29dc17..0000000
--- a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/CQLTestCase.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import org.locationtech.jts.geom.GeometryFactory;
-import org.opengis.filter.FilterFactory;
-import org.opengis.feature.Feature;
-import org.apache.sis.filter.DefaultFilterFactory;
-
-
-/**
- * Base class of all CQL tests.
- *
- * @author  Johann Sorel (Geomatys)
- */
-abstract class CQLTestCase {
-    /**
-     * The factory to use for creating filter and expressions.
-     */
-    final FilterFactory<Feature,Object,Object> FF;
-
-    /**
-     * The factory to use for creating Java Topology Suite (JTS) objects.
-     */
-    final GeometryFactory GF;
-
-    /**
-     * Creates a new test case.
-     */
-    CQLTestCase() {
-        FF = DefaultFilterFactory.forFeatures();
-        GF = org.apache.sis.geometry.wrapper.jts.Factory.INSTANCE.factory(false);
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/ExpressionReadingTest.java b/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/ExpressionReadingTest.java
deleted file mode 100644
index bb3dd4c..0000000
--- a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/ExpressionReadingTest.java
+++ /dev/null
@@ -1,612 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.time.Instant;
-import java.time.Duration;
-import java.time.Period;
-import java.text.ParseException;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.TemporalAccessor;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryCollection;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.MultiLineString;
-import org.locationtech.jts.geom.MultiPoint;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-
-// Test dependencies
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-
-/**
- * Test reading CQL expressions.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class ExpressionReadingTest extends CQLTestCase {
-    /**
-     * Creates a new test case.
-     */
-    public ExpressionReadingTest() {
-    }
-
-    @Test
-    public void testValueReference1() throws CQLException {
-        final String cql = "geom";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof ValueReference);
-        final ValueReference expression = (ValueReference) obj;
-        assertEquals("geom", expression.getXPath());
-    }
-
-    @Test
-    public void testValueReference2() throws CQLException {
-        final String cql = "\"geom\"";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof ValueReference);
-        final ValueReference expression = (ValueReference) obj;
-        assertEquals("geom", expression.getXPath());
-    }
-
-    @Test
-    public void testValueReference3() throws CQLException {
-        final String cql = "ùthe_$uglY^_pr@perté";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof ValueReference);
-        final ValueReference expression = (ValueReference) obj;
-        assertEquals("ùthe_$uglY^_pr@perté", expression.getXPath());
-    }
-
-    @Test
-    public void testInteger() throws CQLException {
-        final String cql = "15";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Integer.valueOf(15), expression.getValue());
-    }
-
-    @Test
-    public void testNegativeInteger() throws CQLException {
-        final String cql = "-15";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Integer.valueOf(-15), expression.getValue());
-    }
-
-    @Test
-    public void testDecimal1() throws CQLException {
-        final String cql = "3.14";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(3.14), expression.getValue());
-    }
-
-    @Test
-    public void testDecimal2() throws CQLException {
-        final String cql = "9e-1";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(9e-1), expression.getValue());
-    }
-
-    @Test
-    public void testNegativeDecimal() throws CQLException {
-        final String cql = "-3.14";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(-3.14), expression.getValue());
-    }
-
-    @Test
-    public void testText() throws CQLException {
-        final String cql = "'hello world'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("hello world", expression.getValue());
-    }
-
-    @Test
-    public void testText2() throws CQLException {
-        final String cql = "'Valle d\\'Aosta'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("Valle d'Aosta", expression.getValue());
-    }
-
-    @Test
-    public void testText3() throws CQLException {
-        final String cql = "'Valle d\\'Aosta/Vallée d\\'Aoste'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("Valle d'Aosta/Vallée d'Aoste", expression.getValue());
-    }
-
-    @Test
-    public void testDate() throws CQLException, ParseException{
-        //dates are expected to be formated in ISO 8601 : yyyy-MM-dd'T'HH:mm:ss'Z'
-        final String cql = "2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        Object value = expression.getValue();
-        assertTrue(value instanceof TemporalAccessor);
-        TemporalAccessor acc = (TemporalAccessor) value;
-        assertEquals(Instant.parse("2012-03-21T05:42:36Z"), Instant.from(acc));
-    }
-
-    @Test
-    public void testDuration() throws CQLException, ParseException{
-        final String cql = "P7Y6M5D";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertTrue(expression.getValue() instanceof Period);
-        final Period period = (Period) expression.getValue();
-        assertEquals(7, period.getYears());
-        assertEquals(6, period.getMonths());
-        assertEquals(5, period.getDays());
-    }
-
-    @Test
-    public void testDuration2() throws CQLException, ParseException{
-        final String cql = "PT4H3M2S";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertTrue(expression.getValue() instanceof Duration);
-        final Duration duration = (Duration) expression.getValue();
-        assertEquals(14582, duration.get(ChronoUnit.SECONDS));
-    }
-
-    @Test
-    public void testAddition() throws CQLException {
-        final String cql = "3 + 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.add(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    @Disabled("String cannot be cast to Number.")
-    public void testAddition2() throws CQLException {
-        final String cql = "'test' + '23'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        Object res = expression.apply(null);
-        assertEquals("test23", res);
-    }
-
-    @Test
-    public void testSubstract() throws CQLException {
-        final String cql = "3 - 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.subtract(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    public void testMultiply() throws CQLException {
-        final String cql = "3 * 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.multiply(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    public void testDivide() throws CQLException {
-        final String cql = "3 / 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.divide(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    @Disabled("Function `max` not yet supported.")
-    public void testFunction1() throws CQLException {
-        final String cql = "max(\"att\",15)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression<Feature,?> expression = (Expression<Feature,?>) obj;
-        assertEquals(FF.function("max",FF.property("att"), FF.literal(15)), expression);
-    }
-
-    @Test
-    @Disabled("Function `min` not yet supported.")
-    public void testFunction2() throws CQLException {
-        final String cql = "min(\"att\",cos(3.14))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression<Feature,?> expression = (Expression<Feature,?>) obj;
-        assertEquals(FF.function("min",FF.property("att"), FF.function("cos",FF.literal(3.14d))), expression);
-    }
-
-    @Test
-    public void testGeometryPoint() throws CQLException {
-        final String cql = "POINT(15 30)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPoint(new Coordinate(15, 30));
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryPointEmpty() throws CQLException {
-        final String cql = "POINT EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Point);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMPoint() throws CQLException {
-        final String cql = "MULTIPOINT(15 30, 45 60)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createMultiPoint(
-                new Coordinate[]{
-                    new Coordinate(15, 30),
-                    new Coordinate(45, 60)
-                });
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMPointEmpty() throws CQLException {
-        final String cql = "MULTIPOINT EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiPoint);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryLineString() throws CQLException {
-        final String cql = "LINESTRING(10 20, 30 40, 50 60)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryLineStringEmpty() throws CQLException {
-        final String cql = "LINESTRING EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof LineString);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMLineString() throws CQLException {
-        final String cql = "MULTILINESTRING((10 20, 30 40, 50 60),(70 80, 90 100, 110 120))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createMultiLineString(
-                new LineString[]{
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(10, 20),
-                            new Coordinate(30, 40),
-                            new Coordinate(50, 60)
-                        }),
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120)
-                        })
-                    }
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMLineStringEmpty() throws CQLException {
-        final String cql = "MULTILINESTRING EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiLineString);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryPolygon() throws CQLException {
-        final String cql = "POLYGON((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryPolygonEmpty() throws CQLException {
-        final String cql = "POLYGON EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Polygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMPolygon() throws CQLException {
-        final String cql = "MULTIPOLYGON("
-                + "((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80)),"
-                + "((11 21, 31 41, 51 61, 11 21), (71 81, 91 101, 111 121, 71 81))"
-                + ")";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Polygon geom1 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Polygon geom2 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(11, 21),
-                        new Coordinate(31, 41),
-                        new Coordinate(51, 61),
-                        new Coordinate(11, 21)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(71, 81),
-                            new Coordinate(91, 101),
-                            new Coordinate(111, 121),
-                            new Coordinate(71, 81)
-                        })
-                    }
-                );
-        final Geometry geom = GF.createMultiPolygon(new Polygon[]{geom1,geom2});
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMPolygonEmpty() throws CQLException {
-        final String cql = "MULTIPOLYGON EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiPolygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryCollection() throws CQLException {
-        final String cql = "GEOMETRYCOLLECTION( POINT(15 30), LINESTRING(10 20, 30 40, 50 60) )";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom1 =  GF.createPoint(new Coordinate(15, 30));
-        final Geometry geom2 =  GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        final GeometryCollection geom =  GF.createGeometryCollection(new Geometry[]{geom1,geom2});
-        final GeometryCollection returned = (GeometryCollection)expression.getValue();
-        assertEquals(geom.getNumGeometries(), returned.getNumGeometries());
-        assertEquals(geom.getGeometryN(0), returned.getGeometryN(0));
-        assertEquals(geom.getGeometryN(1), returned.getGeometryN(1));
-    }
-
-    @Test
-    public void testGeometryCollectionEmpty() throws CQLException {
-        final String cql = "GEOMETRYCOLLECTION EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof GeometryCollection);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryEnvelope() throws CQLException {
-        final String cql = "ENVELOPE(10, 20, 40, 30)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 40),
-                        new Coordinate(20, 40),
-                        new Coordinate(20, 30),
-                        new Coordinate(10, 30),
-                        new Coordinate(10, 40)
-                    }),
-                new LinearRing[0]
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryEnvelopeEmpty() throws CQLException {
-        final String cql = "ENVELOPE EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Polygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        final String cql = "((3*1)+(2-6))/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(
-                FF.divide(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.subtract(FF.literal(2), FF.literal(6))
-                        ),
-                    FF.literal(4))
-                , expression);
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        final String cql = "3*1+2/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression rootAdd = (Expression) obj;
-        assertEquals(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-    }
-
-    @Test
-    @Disabled("Function `max` not yet supported.")
-    public void testCombine3() throws CQLException {
-        final String cql = "3*max(val,15)+2/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression rootAdd = (Expression) obj;
-        assertEquals(
-                    FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val"), FF.literal(15)).toValueType(Number.class)
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-    }
-
-    @Test
-    @Disabled("Function `max` not yet supported.")
-    public void testCombine4() throws CQLException {
-        final String cql = "3 * max ( val , 15 ) + 2 / 4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression rootAdd = (Expression) obj;
-        assertEquals(
-                    FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val", Number.class), FF.literal(15)).toValueType(Number.class)
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-    }
-
-    @Test
-    @Disabled("Difference in the class argument of `property(…)`.")
-    public void testCombine5() throws CQLException {
-        final String cql = "(\"NB-Curistes\"*50)/12000";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression result = (Expression) obj;
-        assertEquals(
-                FF.divide(
-                        FF.multiply(
-                                FF.property("NB-Curistes", Number.class),
-                                FF.literal(50)
-                        ),
-                        FF.literal(12000)
-                )
-                , result);
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/ExpressionWritingTest.java b/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/ExpressionWritingTest.java
deleted file mode 100644
index c72b134..0000000
--- a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/ExpressionWritingTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.time.Instant;
-import java.text.ParseException;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.Expression;
-import org.opengis.feature.Feature;
-
-// Test dependencies
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-
-/**
- * Test writing in CQL expressions.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class ExpressionWritingTest extends CQLTestCase {
-    /**
-     * Creates a new test case.
-     */
-    public ExpressionWritingTest() {
-    }
-
-    @Test
-    public void testValueReference1() throws CQLException {
-        final Expression<Feature,?> exp = FF.property("geom");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("geom", cql);
-    }
-
-    @Test
-    public void testValueReference2() throws CQLException {
-        final Expression<Feature,?> exp = FF.property("the geom");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("\"the geom\"", cql);
-    }
-
-    @Test
-    public void testInteger() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(15);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("15", cql);
-    }
-
-    @Test
-    public void testNegativeInteger() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(-15);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("-15", cql);
-    }
-
-    @Test
-    public void testDecimal1() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(3.14);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3.14", cql);
-    }
-
-    @Test
-    public void testDecimal2() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(9.0E-21);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("9.0E-21", cql);
-    }
-
-    @Test
-    @Disabled("Unsupported temporal field: Year")
-    public void testDate() throws CQLException, ParseException{
-        final Expression<Feature,?> exp = FF.literal(Instant.parse("2012-03-21T05:42:36Z"));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("2012-03-21T05:42:36Z", cql);
-    }
-
-
-    @Test
-    public void testNegativeDecimal() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(-3.14);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("-3.14", cql);
-    }
-
-    @Test
-    public void testText() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal("hello world");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("'hello world'", cql);
-    }
-
-    @Test
-    public void testAdd() throws CQLException {
-        final Expression<Feature,?> exp = FF.add(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 + 2", cql);
-    }
-
-    @Test
-    public void testSubtract() throws CQLException {
-        final Expression<Feature,?> exp = FF.subtract(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 - 2", cql);
-    }
-
-    @Test
-    public void testMultiply() throws CQLException {
-        final Expression<Feature,?> exp = FF.multiply(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 2", cql);
-    }
-
-    @Test
-    public void testDivide() throws CQLException {
-        final Expression<Feature,?> exp = FF.divide(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 / 2", cql);
-    }
-
-    @Test
-    @Disabled("Function `max` not yet supported.")
-    public void testFunction1() throws CQLException {
-        final Expression<Feature,?> exp = FF.function("max", FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("max(att , 15)", cql);
-    }
-
-    @Test
-    @Disabled("Function `min` not yet supported.")
-    public void testFunction2() throws CQLException {
-        final Expression<Feature,?> exp = FF.function("min",FF.property("att"), FF.function("cos", FF.literal(3.14d)));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("min(att , cos(3.14))", cql);
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        final Expression<Feature,?> exp =
-                FF.divide(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.subtract(FF.literal(2), FF.literal(6))
-                        ),
-                    FF.literal(4));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 1 + 2 - 6 / 4", cql);
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        final Expression<Feature,?> exp =
-                FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        );
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 1 + 2 / 4", cql);
-
-    }
-
-    @Test
-    @Disabled("Function `max` not yet supported.")
-    public void testCombine3() throws CQLException {
-        final Expression<Feature,?> exp =
-                FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val"), FF.literal(15)).toValueType(Number.class)
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        );
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * max(val , 15) + 2 / 4", cql);
-    }
-
-    @Test
-    public void testPoint() throws CQLException {
-        final Geometry geom = GF.createPoint(new Coordinate(15, 30));
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("POINT (15 30)", cql);
-    }
-
-    @Test
-    public void testMPoint() throws CQLException {
-        final Geometry geom = GF.createMultiPoint(
-                new Coordinate[]{
-                    new Coordinate(15, 30),
-                    new Coordinate(45, 60)
-                });
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTIPOINT ((15 30), (45 60))", cql);
-    }
-
-    @Test
-    public void testLineString() throws CQLException {
-        final Geometry geom = GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("LINESTRING (10 20, 30 40, 50 60)", cql);
-    }
-
-    @Test
-    public void testMLineString() throws CQLException {
-        final Geometry geom = GF.createMultiLineString(
-                new LineString[]{
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(10, 20),
-                            new Coordinate(30, 40),
-                            new Coordinate(50, 60)
-                        }),
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120)
-                        })
-                    }
-                );
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTILINESTRING ((10 20, 30 40, 50 60), (70 80, 90 100, 110 120))", cql);
-    }
-
-    @Test
-    public void testPolygon() throws CQLException {
-        final Geometry geom = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("POLYGON ((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80))", cql);
-    }
-
-    @Test
-    public void testMPolygon() throws CQLException {
-        final Polygon geom1 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Polygon geom2 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(11, 21),
-                        new Coordinate(31, 41),
-                        new Coordinate(51, 61),
-                        new Coordinate(11, 21)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(71, 81),
-                            new Coordinate(91, 101),
-                            new Coordinate(111, 121),
-                            new Coordinate(71, 81)
-                        })
-                    }
-                );
-        final Geometry geom = GF.createMultiPolygon(new Polygon[]{geom1,geom2});
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTIPOLYGON (((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80)), ((11 21, 31 41, 51 61, 11 21), (71 81, 91 101, 111 121, 71 81)))", cql);
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/FilterReadingTest.java b/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/FilterReadingTest.java
deleted file mode 100644
index fcedc82..0000000
--- a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/FilterReadingTest.java
+++ /dev/null
@@ -1,448 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Iterator;
-import java.time.Instant;
-import java.time.temporal.TemporalAccessor;
-import java.text.ParseException;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
-import javax.measure.Quantity;
-import javax.measure.quantity.Length;
-import org.opengis.geometry.Envelope;
-import org.opengis.util.CodeList;
-import org.opengis.feature.Feature;
-import org.opengis.filter.*;
-import org.apache.sis.measure.Units;
-import org.apache.sis.measure.Quantities;
-import org.apache.sis.geometry.AbstractEnvelope;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.geometry.Envelope2D;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.util.privy.UnmodifiableArrayList;
-
-// Test dependencies
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-
-/**
- * Test reading CQL filters.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class FilterReadingTest extends CQLTestCase {
-
-    private static final double DELTA = 0.00000001;
-
-    private final Geometry baseGeometry;
-
-    private final Geometry baseGeometryPoint;
-
-    /**
-     * Creates a new test case.
-     */
-    public FilterReadingTest() {
-        baseGeometry = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[0]);
-
-        baseGeometryPoint = GF.createPoint(
-                new Coordinate(12.1, 28.9));
-    }
-
-    @Test
-    public void testNullFilter() throws CQLException {
-        //this is not true cql but is since in commun use cases.
-        String cql = "";
-        Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(Filter.include(), filter);
-
-        cql = "*";
-        filter = CQL.parseFilter(cql);
-        assertEquals(Filter.include(), filter);
-    }
-
-    @Test
-    public void testAnd() throws CQLException {
-        final String cql = "att1 = 15 AND att2 = 30 AND att3 = 50";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.and(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                })),
-                filter);
-    }
-
-    @Test
-    public void testOr() throws CQLException {
-        final String cql = "att1 = 15 OR att2 = 30 OR att3 = 50";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.or(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                })),
-                filter);
-    }
-
-    @Test
-    public void testOrAnd1() throws CQLException {
-        final String cql = "Title = 'VMAI' OR (Title ILIKE '!$Pa_tt%ern?' AND DWITHIN(BoundingBox, POINT(12.1 28.9), 10, 'meter'))";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.or(
-                    FF.equal(FF.property("Title"), FF.literal("VMAI")),
-                    FF.and(
-                        FF.like(FF.property("Title"), "!$Pa_tt%ern?", '%', '_', '\\', false),
-                        FF.within(FF.property("BoundingBox"), FF.literal(baseGeometryPoint), Quantities.create(10, Units.METRE))
-                        )
-                ),
-                filter);
-    }
-
-    @Test
-    public void testOrAnd2() throws CQLException {
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 40),
-                        new Coordinate(20, 40),
-                        new Coordinate(20, 30),
-                        new Coordinate(10, 30),
-                        new Coordinate(10, 40)
-                    }),
-                new LinearRing[0]
-                );
-
-        final String cql = "NOT (INTERSECTS(BoundingBox, ENVELOPE(10, 20, 40, 30)) "
-                + "OR CONTAINS(BoundingBox, POINT(12.1 28.9))) AND BBOX(BoundingBox, 10,20,30,40)";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.and(
-                    FF.not(
-                        FF.or(
-                            FF.intersects(FF.property("BoundingBox"), FF.literal(geom)),
-                            FF.contains(FF.property("BoundingBox"), FF.literal(baseGeometryPoint))
-                            )
-                    ),
-                    FF.bbox(FF.property("BoundingBox"), new GeneralEnvelope(new Envelope2D(null, 10, 20, 30-10, 40-20)))
-                ),
-                filter);
-    }
-
-    @Test
-    public void testNot() throws CQLException {
-        final String cql = "NOT att = 15";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(FF.not(FF.equal(FF.property("att"), FF.literal(15))), filter);
-    }
-
-    @Test
-    public void testPropertyIsBetween() throws CQLException {
-        final String cql = "att BETWEEN 15 AND 30";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(FF.between(FF.property("att"), FF.literal(15), FF.literal(30)), filter);
-    }
-
-    @Test
-    public void testIn() throws CQLException {
-        verifyIn((LogicalOperator<?>) CQL.parseFilter("att IN ( 15, 30, 'hello')"));
-    }
-
-    @Test
-    public void testNotIn() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att NOT IN ( 15, 30, 'hello')");
-        assertEquals(LogicalOperatorName.NOT, filter.getOperatorType());
-        LogicalOperator<?> logical = assertInstanceOf(LogicalOperator.class, filter);
-        logical = assertInstanceOf(LogicalOperator.class, logical.getOperands().get(0));
-        verifyIn(logical);
-    }
-
-    private void verifyIn(final LogicalOperator<?> filter) {
-        assertInstanceOf(LogicalOperator.class, filter);
-        assertEquals(LogicalOperatorName.OR, filter.getOperatorType());
-        final Iterator<?> expected = Arrays.asList(15, 30, "hello").iterator();
-        for (final Filter<?> operand : filter.getOperands()) {
-            assertEquals(FF.equal(FF.property("att"), FF.literal(expected.next())), operand);
-        }
-        assertFalse(expected.hasNext());
-    }
-
-    @Test
-    public void testPropertyIsEqualTo1() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att=15");
-        assertEquals(FF.equal(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsEqualTo2() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att = 15");
-        assertEquals(FF.equal(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att <> 15");
-        assertEquals(FF.notEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo2() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att <>'15'");
-        assertEquals(FF.notEqual(FF.property("att"), FF.literal("15")), filter);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThan() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att > 15");
-        assertEquals(FF.greater(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThanOrEqualTo() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att >= 15");
-        assertEquals(FF.greaterOrEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLessThan() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att < 15");
-        assertEquals(FF.less(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLessThanOrEqualTo() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att <= 15");
-        assertEquals(FF.lessOrEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLike() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att LIKE '%hello_'");
-        assertEquals(FF.like(FF.property("att"), "%hello_", '%', '_', '\\', true), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotLike() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att NOT LIKE '%hello_'");
-        assertEquals(FF.not(FF.like(FF.property("att"), "%hello_", '%', '_', '\\', true)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLikeInsensitive() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att ILIKE '%hello_'");
-        assertEquals(FF.like(FF.property("att"),"%hello_", '%', '_', '\\', false), filter);
-    }
-
-    @Test
-    public void testPropertyIsNull() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att IS NULL");
-        assertEquals(FF.isNull(FF.property("att")), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotNull() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att IS NOT NULL");
-        assertEquals(FF.not(FF.isNull(FF.property("att"))), filter);
-    }
-
-    @Test
-    public void testBBOX1() throws CQLException {
-        final Envelope2D env = new Envelope2D(null, 10, 20, 30-10, 40-20);
-        testBBOX("BBOX(\"att\", 10, 20, 30, 40)", "att", env);
-    }
-
-    @Test
-    public void testBBOX2() throws CQLException {
-        final Envelope2D env = new Envelope2D(CommonCRS.WGS84.normalizedGeographic(), 10, 20, 30-10, 40-20);
-        testBBOX("BBOX(\"att\", 10, 20, 30, 40, 'CRS:84')", "att", env);
-    }
-
-    @Test
-    public void testBBOX3() throws CQLException {
-        final Envelope2D env = new Envelope2D(CommonCRS.WGS84.normalizedGeographic(), 10, 20, 30-10, 40-20);
-        testBBOX("BBOX(att, 10, 20, 30, 40, 'CRS:84')", "att", env);
-    }
-
-    @Test
-    public void testBBOX4() throws CQLException {
-        final Envelope2D env = new Envelope2D(null, -10, -20, 10 - -10, 20 - -20);
-        testBBOX("BBOX(geometry,-10,-20,10,20)", "geometry", env);
-    }
-
-    private void testBBOX(final String cql, final String att, final Envelope env) throws CQLException {
-        final Filter<?> filter = CQL.parseFilter(cql);
-        final BinarySpatialOperator<?> bsp = assertInstanceOf(BinarySpatialOperator.class, filter);
-        assertEquals(SpatialOperatorName.BBOX, filter.getOperatorType());
-        assertEquals(FF.property(att), bsp.getOperand1());
-        final Literal<?,?> literal = assertInstanceOf(Literal.class, bsp.getOperand2());
-        final Envelope value = assertInstanceOf(Envelope.class, literal.getValue());
-        assertTrue(AbstractEnvelope.castOrCopy(value).equals(env, 1e-2, false));
-    }
-
-    /**
-     * Tests {@code DWITHIN}.
-     */
-    @Test
-    public void testDWithin() throws CQLException {
-        final String cql = "DWITHIN(BoundingBox, POINT(12.1 28.9), 10, 'meters')";
-        final DistanceOperator<?> filter = assertInstanceOf(DistanceOperator.class, CQL.parseFilter(cql));
-        assertEquals(DistanceOperatorName.WITHIN, filter.getOperatorType());
-
-        final var expressions = filter.getExpressions();
-        assertEquals(FF.property("BoundingBox"), expressions.get(0));
-        final Quantity<Length> distance = filter.getDistance();
-        assertEquals(10.0, distance.getValue().doubleValue(), DELTA);
-        assertEquals(Units.METRE, distance.getUnit());
-
-        final Literal<?,?> literal = assertInstanceOf(Literal.class, expressions.get(1));
-        final Geometry value = assertInstanceOf(Geometry.class, literal.getValue());
-        assertTrue(baseGeometryPoint.equalsExact(value));
-    }
-
-    @Test
-    public void testBinarySpatialOperators() throws CQLException {
-        for (final SpatialOperatorName operator : SpatialOperatorName.values()) {
-            if (operator == SpatialOperatorName.BBOX) continue;
-            testSpatialOperators(operator, "");
-            if (operator == SpatialOperatorName.OVERLAPS) break;
-        }
-    }
-
-    @Test
-    public void testDistanceOperators() throws CQLException {
-        for (final DistanceOperatorName operator : DistanceOperatorName.values()) {
-            final DistanceOperator<?> filter = (DistanceOperator<?>) testSpatialOperators(operator, ", 10, 'meters'");
-            final Quantity<Length> distance = filter.getDistance();
-            assertEquals(10.0, distance.getValue().doubleValue(), DELTA);
-            assertEquals(Units.METRE, distance.getUnit());
-            if (operator == DistanceOperatorName.WITHIN) break;
-        }
-    }
-
-    private Filter<?> testSpatialOperators(final CodeList<?> operator, final String suffix) throws CQLException {
-        final String name = operator.identifier().toUpperCase(Locale.US);
-        final String cql = name + "(\"att\", POLYGON((10 20, 30 40, 50 60, 10 20))" + suffix + ')';
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf(SpatialOperator.class, filter, name);
-        assertEquals(operator, filter.getOperatorType(), name);
-
-        final var expressions = filter.getExpressions();
-        assertEquals(FF.property("att"), expressions.get(0));
-        final Literal<?,?> literal = assertInstanceOf(Literal.class, expressions.get(1));
-        final Geometry value = assertInstanceOf(Geometry.class, literal.getValue());
-        assertTrue(baseGeometry.equalsExact(value));
-        return filter;
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        testCombine("NOT att = 15 OR att BETWEEN 15 AND 30");
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        testCombine("(NOT att = 15) OR (att BETWEEN 15 AND 30)");
-    }
-
-    private void testCombine(final String cql) throws CQLException {
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf(LogicalOperator.class, filter);
-        assertEquals(LogicalOperatorName.OR, filter.getOperatorType());
-        assertEquals(
-                FF.or(
-                    FF.not(FF.equal(FF.property("att"), FF.literal(15))),
-                    FF.between(FF.property("att"), FF.literal(15), FF.literal(30))
-                ),
-                filter
-                );
-    }
-
-    @Test
-    public void testCombine3() throws CQLException {
-        final String cql = "(NOT att1 = 15) AND (att2 = 15 OR att3 BETWEEN 15 AND 30) AND (att4 BETWEEN 1 AND 2)";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf(LogicalOperator.class, filter);
-        assertEquals(LogicalOperatorName.AND, filter.getOperatorType());
-        assertEquals(
-                FF.and(
-                    UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                        FF.not(FF.equal(FF.property("att1"), FF.literal(15))),
-                        FF.or(
-                            FF.equal(FF.property("att2"), FF.literal(15)),
-                            FF.between(FF.property("att3"), FF.literal(15), FF.literal(30))
-                        ),
-                        FF.between(FF.property("att4"), FF.literal(1), FF.literal(2))
-                    })
-                ),
-                filter
-                );
-    }
-
-    @Test
-    @Disabled("Mismatched type in `property(…, type)`.")
-    public void testCombine4() throws CQLException {
-        final String cql = "(x+7) <= (y-9)";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf(BinaryComparisonOperator.class, filter);
-        assertEquals(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO, filter.getOperatorType());
-        assertEquals(
-                FF.lessOrEqual(
-                    FF.add(FF.property("x", Number.class), FF.literal(7)),
-                    FF.subtract(FF.property("y", Number.class), FF.literal(9))
-                ),
-                filter
-                );
-    }
-
-    @Test
-    public void testTemporalOperators() throws CQLException, ParseException {
-        for (final TemporalOperatorName operator : TemporalOperatorName.values()) {
-            final String name = operator.identifier().toUpperCase(Locale.US);
-            final String cql  = "att " + name + " 2012-03-21T05:42:36Z";
-            final Filter<?> filter = CQL.parseFilter(cql);
-            assertInstanceOf(TemporalOperator.class, filter, name);
-            assertEquals(operator, filter.getOperatorType(),name);
-            final var expressions = filter.getExpressions();
-
-            assertEquals(FF.property("att"), expressions.get(0), name);
-            final Literal<?,?> literal = assertInstanceOf(Literal.class, expressions.get(1));
-            final TemporalAccessor time = assertInstanceOf(TemporalAccessor.class, literal.getValue());
-            assertEquals(Instant.parse("2012-03-21T05:42:36Z"), Instant.from(time), name);
-
-            // Skip any non-standard operators added by user.
-            if (operator == TemporalOperatorName.ANY_INTERACTS) break;
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/FilterWritingTest.java b/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/FilterWritingTest.java
deleted file mode 100644
index ee75c69..0000000
--- a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/FilterWritingTest.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.time.Instant;
-import java.text.ParseException;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
-import org.opengis.filter.Filter;
-import org.opengis.feature.Feature;
-import org.apache.sis.geometry.Envelope2D;
-import org.apache.sis.util.privy.UnmodifiableArrayList;
-import org.apache.sis.measure.Quantities;
-import org.apache.sis.measure.Units;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Disabled;
-import static org.junit.jupiter.api.Assertions.*;
-
-
-/**
- * Test writing in CQL filters.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class FilterWritingTest extends CQLTestCase {
-
-    private final Geometry baseGeometry;
-
-    /**
-     * Creates a new test case.
-     */
-    public FilterWritingTest() {
-        baseGeometry = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[0]);
-    }
-
-    @Test
-    public void testExcludeFilter() throws CQLException {
-        final Filter filter = Filter.exclude();
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("1=0", cql);
-    }
-
-    @Test
-    public void testIncludeFilter() throws CQLException {
-        final Filter filter = Filter.include();
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("1=1", cql);
-    }
-
-    @Test
-    public void testAnd() throws CQLException {
-        final Filter filter = FF.and(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                }));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("(\"att1\" = 15 AND \"att2\" = 30 AND \"att3\" = 50)", cql);
-    }
-
-    @Test
-    public void testOr() throws CQLException {
-        final Filter filter = FF.or(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                }));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("(\"att1\" = 15 OR \"att2\" = 30 OR \"att3\" = 50)", cql);
-    }
-
-    @Test
-    public void testId() throws CQLException {
-        final Filter filter = FF.resourceId("test-1");
-        try {
-            CQL.write(filter);
-            fail("ID filter does not exist in CQL");
-        } catch (UnsupportedOperationException ex) {
-            assertTrue(ex.getMessage().contains("ID"));
-        }
-    }
-
-    @Test
-    public void testNot() throws CQLException {
-        final Filter filter = FF.not(FF.equal(FF.property("att"), FF.literal(15)));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("NOT att = 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsBetween() throws CQLException {
-        final Filter filter = FF.between(FF.property("att"), FF.literal(15), FF.literal(30));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BETWEEN 15 AND 30", cql);
-    }
-
-    @Test
-    public void testPropertyIsEqualTo() throws CQLException {
-        final Filter filter = FF.equal(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att = 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo() throws CQLException {
-        final Filter filter = FF.notEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att <> 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThan() throws CQLException {
-        final Filter filter = FF.greater(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att > 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThanOrEqualTo() throws CQLException {
-        final Filter filter = FF.greaterOrEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att >= 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsLessThan() throws CQLException {
-        final Filter filter = FF.less(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att < 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsLessThanOrEqualTo() throws CQLException {
-        final Filter filter = FF.lessOrEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att <= 15", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testPropertyIsLike() throws CQLException {
-        final Filter filter = FF.like(FF.property("att"),"%hello");
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ILIKE '%hello'", cql);
-    }
-
-    @Test
-    public void testPropertyIsNull() throws CQLException {
-        final Filter filter = FF.isNull(FF.property("att"));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att IS NULL", cql);
-    }
-
-    @Test
-    public void testBBOX() throws CQLException {
-        final Filter filter = FF.bbox(FF.property("att"), new Envelope2D(null, 10, 20, 30-10, 40-20));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("BBOX(att, 10.0, 30.0, 20.0, 40.0)", cql);
-    }
-
-    @Test
-    public void testBeyond() throws CQLException {
-        final Filter filter = FF.beyond(FF.property("att"), FF.literal(baseGeometry), Quantities.create(3, Units.METRE));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("BEYOND(att, POLYGON ((10 20, 30 40, 50 60, 10 20)), 3.0, 'meter')", cql);
-    }
-
-    @Test
-    public void testContains() throws CQLException {
-        final Filter filter = FF.contains(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("CONTAINS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testCrosses() throws CQLException {
-        final Filter filter = FF.crosses(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("CROSSES(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testDisjoint() throws CQLException {
-        final Filter filter = FF.disjoint(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("DISJOINT(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testDWithin() throws CQLException {
-        final Filter filter = FF.within(FF.property("att"), FF.literal(baseGeometry), Quantities.create(2, Units.METRE));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("DWITHIN(att, POLYGON ((10 20, 30 40, 50 60, 10 20)), 2.0, 'meter')", cql);
-    }
-
-    @Test
-    public void testEquals() throws CQLException {
-        final Filter filter = FF.equals(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("EQUALS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testIntersects() throws CQLException {
-        final Filter filter = FF.intersects(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("INTERSECTS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testOverlaps() throws CQLException {
-        final Filter filter = FF.overlaps(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("OVERLAPS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testTouches() throws CQLException {
-        final Filter filter = FF.touches(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("TOUCHES(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testWithin() throws CQLException {
-        final Filter filter = FF.within(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("WITHIN(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testAfter() throws CQLException, ParseException {
-        final Filter filter = FF.after(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att AFTER 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testAnyInteracts() throws CQLException, ParseException {
-        final Filter filter = FF.anyInteracts(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ANYINTERACTS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testBefore() throws CQLException, ParseException {
-        final Filter filter = FF.before(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEFORE 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testBegins() throws CQLException, ParseException {
-        final Filter filter = FF.begins(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEGINS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testBegunBy() throws CQLException, ParseException {
-        final Filter filter = FF.begunBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEGUNBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testDuring() throws CQLException, ParseException {
-        final Filter filter = FF.during(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att DURING 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testEndedBy() throws CQLException, ParseException {
-        final Filter filter = FF.endedBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ENDEDBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testEnds() throws CQLException, ParseException {
-        final Filter filter = FF.ends(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ENDS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testMeets() throws CQLException, ParseException {
-        final Filter filter = FF.meets(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att MEETS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testMetBy() throws CQLException, ParseException {
-        final Filter filter = FF.metBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att METBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testOverlappedBy() throws CQLException, ParseException {
-        final Filter filter = FF.overlappedBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att OVERLAPPEDBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testTcontains() throws CQLException, ParseException {
-        final Filter filter = FF.tcontains(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TCONTAINS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testTequals() throws CQLException, ParseException {
-        final Filter filter = FF.tequals(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TEQUALS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Disabled
-    @Test
-    public void testToverlaps() throws CQLException, ParseException {
-        final Filter filter = FF.toverlaps(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TOVERLAPS 2012-03-21T05:42:36Z", cql);
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/QueryReadingTest.java b/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/QueryReadingTest.java
deleted file mode 100644
index 57c0026..0000000
--- a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/QueryReadingTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.util.Arrays;
-import org.opengis.filter.SortOrder;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class QueryReadingTest extends CQLTestCase {
-    /**
-     * Creates a new test case.
-     */
-    public QueryReadingTest() {
-    }
-
-    @Test
-    public void testEmpty() throws CQLException {
-        String cql = "SELECT *";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(), query);
-    }
-
-    @Test
-    public void testProjections() throws CQLException {
-        String cql = "SELECT \"name\", 4 as 'col1'";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(Arrays.asList(new Query.Projection(FF.property("name"), null), new Query.Projection(FF.literal(4),"col1")), null, null, null, null), query);
-    }
-
-    @Test
-    public void testWhere() throws CQLException {
-        String cql = "SELECT * WHERE \"id\" = 'a'";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, FF.equal(FF.property("id"), FF.literal("a")), null, null, null), query);
-    }
-
-    @Test
-    public void testOffset() throws CQLException {
-        String cql = "SELECT * OFFSET 5";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, null, 5, null), query);
-    }
-
-    @Test
-    public void testLimit() throws CQLException {
-        String cql = "SELECT * LIMIT 10";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, null, null, 10), query);
-    }
-
-    @Test
-    public void testOrderBy() throws CQLException {
-        String cql = "SELECT * ORDER BY \"name\" ASC, \"age\" DESC";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, Arrays.asList(
-                FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                FF.sort(FF.property("age"), SortOrder.DESCENDING)),
-                null, null), query);
-    }
-
-    @Test
-    public void testOrderByDefault() throws CQLException {
-        String cql = "SELECT * ORDER BY \"name\", \"age\"";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, Arrays.asList(
-                FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                FF.sort(FF.property("age"), SortOrder.ASCENDING)),
-                null, null), query);
-    }
-
-    @Test
-    public void testComplete() throws CQLException {
-        String cql = "SELECT \"name\", 4 as 'col1' WHERE \"id\" = 'a' ORDER BY \"name\" ASC, \"age\" DESC OFFSET 5 LIMIT 10";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(
-                new Query(
-                        Arrays.asList(new Query.Projection(FF.property("name"), null), new Query.Projection(FF.literal(4),"col1")),
-                        FF.equal(FF.property("id"), FF.literal("a")),
-                        Arrays.asList(
-                            FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                            FF.sort(FF.property("age"), SortOrder.DESCENDING)),
-                        5,
-                        10),
-                query);
-    }
-}
diff --git a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/QueryWritingTest.java b/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/QueryWritingTest.java
deleted file mode 100644
index 464d4e9..0000000
--- a/incubator/src/org.apache.sis.cql/test/org/apache/sis/cql/QueryWritingTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.cql;
-
-import java.util.Arrays;
-import org.opengis.filter.SortOrder;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class QueryWritingTest extends CQLTestCase {
-    /**
-     * Creates a new test case.
-     */
-    public QueryWritingTest() {
-    }
-
-    @Test
-    public void testWrite() throws CQLException {
-        final Query query = new Query(
-                Arrays.asList(new Query.Projection(FF.property("name"), null), new Query.Projection(FF.literal(4),"col1")),
-                FF.equal(FF.property("id"), FF.literal("a")),
-                Arrays.asList(
-                    FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                    FF.sort(FF.property("age"), SortOrder.DESCENDING)),
-                5,
-                10);
-
-        String cql = CQL.write(query);
-        assertEquals("SELECT name, 4 AS 'col1' WHERE id = 'a' ORDER BY name ASC, age DESC OFFSET 5 LIMIT 10", cql);
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/module-info.java b/incubator/src/org.apache.sis.portrayal.map/main/module-info.java
deleted file mode 100644
index 128b11c..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/module-info.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Raster imagery and geometry features.
- *
- * @author  Johann Sorel (Geomatys)
- */
-module org.apache.sis.portrayal.map {
-    requires transitive org.apache.sis.portrayal;
-    requires static org.locationtech.jts;
-
-    exports org.apache.sis.map;
-    exports org.apache.sis.map.service;
-
-    uses org.apache.sis.map.service.StylePainter;
-    uses org.apache.sis.map.service.se1.SymbolizerToScene2D.Spi;
-
-    provides org.apache.sis.map.service.StylePainter
-            with org.apache.sis.map.service.se1.SEPainter;
-    provides org.apache.sis.map.service.se1.SymbolizerToScene2D.Spi
-            with org.apache.sis.map.service.se1.PointToScene2D.Spi,
-                 org.apache.sis.map.service.se1.LineToScene2D.Spi,
-                 org.apache.sis.map.service.se1.PolygonToScene2D.Spi,
-                 org.apache.sis.map.service.se1.TextToScene2D.Spi,
-                 org.apache.sis.map.service.se1.RasterToScene2D.Spi;
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ExceptionPresentation.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ExceptionPresentation.java
deleted file mode 100644
index dbaebca..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ExceptionPresentation.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.Objects;
-import org.opengis.feature.Feature;
-import org.apache.sis.storage.Resource;
-
-
-/**
- * Produced by the portrayal engines when an exception occurred.
- * Exception presentations are placed in the Stream of presentation leaving
- * the user the choice to log, ignore or stop rendering as needed.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- */
-public class ExceptionPresentation extends Presentation {
-
-    private final Exception exception;
-
-    /**
-     * @param exception not null.
-     */
-    public ExceptionPresentation(Exception exception) {
-        this.exception = Objects.requireNonNull(exception);
-    }
-
-    /**
-     * @param exception not null.
-     */
-    public ExceptionPresentation(MapLayer layer, Resource resource, Feature candidate, Exception exception) {
-        super(layer, resource, candidate);
-        this.exception = Objects.requireNonNull(exception);
-    }
-
-    /**
-     * @return exception, never null
-     */
-    public Exception getException() {
-        return exception;
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ListChangeEvent.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ListChangeEvent.java
deleted file mode 100644
index 41a5554..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ListChangeEvent.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.List;
-import java.beans.PropertyChangeEvent;
-import org.apache.sis.measure.NumberRange;
-
-
-/**
- * Event generated by modified list properties.
- *
- * @author Johann Sorel (Geomatys)
- */
-@SuppressWarnings("serial")     // TODO
-public final class ListChangeEvent<T> extends PropertyChangeEvent {
-
-    public enum Type {
-        ADDED,
-        REMOVED,
-        CHANGED
-    }
-
-    private final NumberRange<Integer> range;
-    private final Type type;
-    private final List<T> items;
-
-    private ListChangeEvent(final Object source, String propertyName, List<T> originalList, final List<? extends T> items, final NumberRange<Integer> range, final Type type){
-        super(source, propertyName, originalList, originalList);
-        this.range = range;
-        this.type = type;
-        this.items = (items != null) ? List.copyOf(items) : null;
-    }
-
-    /**
-     * Returns the range index of the affected items.
-     * If the event type is Added, the range correspond to the index range after insertion.
-     * If the event type is Removed, the range correspond to the index before deletion.
-     * @return NumberRange added or removed range.
-     */
-    public NumberRange<Integer> getRange(){
-        return range;
-    }
-
-    /**
-     * Returns event type.
-     */
-    public Type getType(){
-        return type;
-    }
-
-    /**
-     * Returns the affected items of this event.
-     * This property is set if event is of type added or removed.
-     *
-     * @return List
-     */
-    public List<T> getItems(){
-        return items;
-    }
-
-    public static <T> ListChangeEvent<T> added(Object source, String propertyName, List<T> originalList, T newItem, final int index) {
-        return added(source, propertyName, originalList, List.of(newItem),
-                NumberRange.create(index, true, index, true));
-    }
-
-    public static <T> ListChangeEvent<T> added(Object source, String propertyName, List<T> originalList, List<T> newItems, final NumberRange<Integer> range) {
-        return new ListChangeEvent<>(source, propertyName, originalList,  newItems, range, Type.ADDED);
-    }
-
-    public static <T> ListChangeEvent<T> removed(Object source, String propertyName, List<T> originalList, T newItem, final int index) {
-        return removed(source, propertyName, originalList, List.of(newItem),
-                NumberRange.create(index, true, index, true));
-    }
-
-    public static <T> ListChangeEvent<T> removed(Object source, String propertyName, List<T> originalList, List<T> oldItems, final NumberRange<Integer> range) {
-        return new ListChangeEvent<>(source, propertyName, originalList, oldItems, range, Type.REMOVED);
-    }
-
-    public static <T> ListChangeEvent<T> changed(Object source, String propertyName, List<T> originalList) {
-        return new ListChangeEvent<>(source, propertyName, originalList, null, null, Type.CHANGED);
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapItem.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapItem.java
deleted file mode 100644
index 2ba0cd4..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapItem.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.beans.PropertyChangeListener;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import org.opengis.geometry.Envelope;
-import org.opengis.util.InternationalString;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.portrayal.Observable;
-
-
-/**
- * Base class of map layer or group of map layers. This base class does not represent graphical elements.
- * Instead, it contains information (data and style) for creating a tree of portrayal objects.
- * A {@code MapItem} contains the following properties:
- *
- * <ul>
- *   <li>An {@linkplain #getIdentifier() identifier}, which can be any {@link String} at developer choice.</li>
- *   <li>A human-readable {@linkplain #getTitle() title} for pick lists, for example in GUI.</li>
- *   <li>A {@linkplain #getAbstract() narrative description} providing more details.</li>
- * </ul>
- *
- * Additional information can be added in a map of {@linkplain #getUserProperties() user properties}.
- * The actual feature or coverage data, together with styling information, are provided by subclasses.
- *
- * <h2>Synchronization</h2>
- * {@code MapItem} instances are not thread-safe. Synchronization, if desired, is caller responsibility.
- *
- * @todo Rename as {@code LayerNode}? "Item" suggests an element in a list, while {@link MapLayers} actually
- *       creates a tree. Furthermore, having {@code Layer} in the name would add emphasis that this is a tree
- *       of layers and not a tree of arbitrary objects.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public abstract class MapItem extends Observable {
-    /**
-     * The {@value} property name, used for notifications about changes in map item identifier.
-     * The identifier (or name) can be used to reference the item externally.
-     * Associated values are instances of {@link String}.
-     *
-     * @see #getIdentifier()
-     * @see #setIdentifier(String)
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     *
-     * @todo This property seems to be named {@code "se:Name"} in SLD specification. Should we rename?
-     */
-    public static final String IDENTIFIER_PROPERTY = "identifier";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item title.
-     * The title is a short description for item that might be displayed in a GUI pick list.
-     * Associated values are instances of {@link String} or {@link InternationalString}.
-     *
-     * @see #getTitle()
-     * @see #setTitle(CharSequence)
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     */
-    public static final String TITLE_PROPERTY = "title";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item description.
-     * The abstract is a narrative description providing additional information.
-     * It is more detailed than the {@value #TITLE_PROPERTY} property and may be a few paragraphs long.
-     * Associated values are instances of {@link String} or {@link InternationalString}.
-     *
-     * @see #getAbstract()
-     * @see #setAbstract(CharSequence)
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     */
-    public static final String ABSTRACT_PROPERTY = "abstract";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item visibility state.
-     * Associated values are instances of {@link Boolean}.
-     */
-    public static final String VISIBLE_PROPERTY = "visible";
-
-    /**
-     * Identifier of this map item.
-     *
-     * @see #IDENTIFIER_PROPERTY
-     * @see #getIdentifier()
-     */
-    private String identifier;
-
-    /**
-     * The title of this map item, for display to the user.
-     *
-     * @see #TITLE_PROPERTY
-     * @see #getTitle()
-     */
-    private CharSequence title;
-
-    /**
-     * A description of this map item, for display to the user. The property name is
-     * {@value #ABSTRACT_PROPERTY} but we use a different field name because "abstract"
-     * is a reserved keyword.
-     *
-     * @see #ABSTRACT_PROPERTY
-     * @see #getAbstract()
-     */
-    private CharSequence description;
-
-    /**
-     * Whether this item should be shown on the map.
-     *
-     * @see #VISIBLE_PROPERTY
-     * @see #isVisible()
-     */
-    private boolean visible;
-
-    /**
-     * Additional user defined properties, created when first requested.
-     *
-     * @see #getUserProperties()
-     */
-    private Map<String,Object> userMap;
-
-    /**
-     * Only used by classes in this package.
-     */
-    MapItem() {
-        visible = true;
-    }
-
-    /**
-     * Returns the identifier of this map item. The identifier can be any character string at developer choice;
-     * there is currently no restriction on identifier syntax and no restriction about identifier uniqueness.
-     * That identifier is currently not used by Apache SIS; it is made available as a user convenience for
-     * referencing {@code MapItem} instances externally.
-     *
-     * <p>NOTE: restriction about identifier syntax and uniqueness may be added in a future version.</p>
-     *
-     * @return identifier, or {@code null} if none.
-     *
-     * @see #IDENTIFIER_PROPERTY
-     */
-    public String getIdentifier() {
-        return identifier;
-    }
-
-    /**
-     * Sets a new identifier for this map item. If this method is never invoked, the default value is {@code null}.
-     * If the given value is different than the previous value, then a change event is sent to all listeners
-     * registered for the {@value #IDENTIFIER_PROPERTY} property.
-     *
-     * @param  newValue  the new identifier, or {@code null} if none.
-     */
-    public void setIdentifier(final String newValue) {
-        final String oldValue = identifier;
-        if (!Objects.equals(oldValue, newValue)) {
-            identifier = newValue;
-            firePropertyChange(IDENTIFIER_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns a human-readable short description for pick lists.
-     * This title should be user friendly and may be a {@link String} or {@link InternationalString} instance.
-     * It shall not be used as an identifier.
-     *
-     * @return a short description to be shown to the user, or {@code null} if none.
-     *
-     * @see #TITLE_PROPERTY
-     */
-    public CharSequence getTitle() {
-        return title;
-    }
-
-    /**
-     * Sets a new human-readable short description for pick lists. If this method is never invoked,
-     * the default value is {@code null}. If the given value is different than the previous value,
-     * then a change event is sent to all listeners registered for the {@value #TITLE_PROPERTY} property.
-     *
-     * @param  newValue  a short description to be shown to the user, or {@code null} if none.
-     */
-    public void setTitle(final CharSequence newValue) {
-        final CharSequence oldValue = title;
-        if (!Objects.equals(oldValue, newValue)) {
-            title = newValue;
-            firePropertyChange(TITLE_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns a narrative description providing additional information.
-     * The abstract is more detailed than the {@linkplain #getTitle() title} property and may be a few paragraphs long.
-     * This abstract should be user friendly and may be a {@link String} or {@link InternationalString} instance.
-     *
-     * @return narrative description to be shown to the user, or {@code null} if none.
-     *
-     * @see #ABSTRACT_PROPERTY
-     */
-    public CharSequence getAbstract() {
-        return description;
-    }
-
-    /**
-     * Sets a new a narrative description providing additional information. If this method is never invoked,
-     * the default value is {@code null}. If the given value is different than the previous value, then
-     * a change event is sent to all listeners registered for the {@value #ABSTRACT_PROPERTY} property.
-     *
-     * @param  newValue  a narrative description to be shown to the user, or {@code null} if none.
-     */
-    public void setAbstract(final CharSequence newValue) {
-        final CharSequence oldValue = description;
-        if (!Objects.equals(oldValue, newValue)) {
-            description = newValue;
-            firePropertyChange(ABSTRACT_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns whether this item should be shown on the map. If this item is a {@code MapGroup},
-     * then a {@code false} visibility status implies that all group components are also hidden.
-     *
-     * @return {@code true} if this item is visible.
-     *
-     * @see #VISIBLE_PROPERTY
-     */
-    public boolean isVisible() {
-        return visible;
-    }
-
-    /**
-     * Sets whether this item should be shown on the map.
-     * If this method is never invoked, the default value is {@code true}.
-     * If the given value is different than the previous value, then a change event
-     * is sent to all listeners registered for the {@value #VISIBLE_PROPERTY} property.
-     *
-     * <p>If this item is a {@code MapLayers}, then hiding this group should hide all components in this group,
-     * but without changing the individual {@value #VISIBLE_PROPERTY} property of those components.
-     * Consequently, making the group visible again restore each component to the visibility state
-     * it has before the group was hidden (assuming those states have not been changed in other ways).</p>
-     *
-     * @param  newValue  {@code false} to hide this item and all it's components.
-     */
-    public void setVisible(final boolean newValue) {
-        final boolean oldValue = visible;
-        if (oldValue != newValue) {
-            visible = newValue;
-            firePropertyChange(VISIBLE_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the envelope of this {@code MapItem}.
-     * If this instance is a {@code MapLayers} the envelope is the concatenation of all it's components,
-     * in case of multiple CRS for each MapLayer, the resulting envelope CRS is unpredictable.
-     * If this instance is a {@code MapLayer} the envelope is the resource data envelope.
-     *
-     * @return the spatiotemporal extent. May be absent if none or too costly to compute.
-     * @throws DataStoreException if an error occurred while reading or computing the envelope.
-     */
-    public Optional<Envelope> getEnvelope() throws DataStoreException {
-        return Optional.empty();
-    }
-
-    /**
-     * Returns a modifiable map of user properties.
-     * The content of this map is left to users; Apache SIS does not use it in any way.
-     * This map is not thread-safe; synchronization if desired is user responsibility.
-     *
-     * @return map of user properties. This map is live: changes in this map
-     *         are immediately reflected in this {@code MapItem}.
-     */
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Map<String,Object> getUserProperties() {
-        if (userMap == null) {
-            userMap = new HashMap<>();
-        }
-        return userMap;
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapLayer.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapLayer.java
deleted file mode 100644
index c99f33c..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapLayer.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.Objects;
-import java.util.Optional;
-import org.opengis.coverage.Coverage;
-import org.opengis.feature.Feature;
-import org.opengis.geometry.Envelope;
-import org.apache.sis.storage.Aggregate;
-import org.apache.sis.storage.DataSet;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.Query;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.style.Style;
-
-
-/**
- * Data (resource) associated to rules for visual representation (symbology).
- * Layers are the key elements of a map: they link data (given by {@link Resource}s) or a subset of
- * those data (filtered by {@link Query}) to their visual representation (defined by {@link Style}s).
- * The visual appearance of a layer should be similar with any rendering engine.
- * Some details may very because of different rendering strategies for label placements, 2D or 3D,
- * but the fundamentals aspect of each {@link Feature} or {@link Coverage} should be unchanged.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- */
-public class MapLayer extends MapItem {
-    /**
-     * The {@value} property name, used for notifications about changes in map layer resource.
-     * The data resource provides the digital data to be rendered. Note that not all kinds of resources
-     * are digital data. For example, a resource may be a citation of facts or figures printed on paper,
-     * photographic material, or other media (see all {@link org.opengis.metadata.citation.PresentationForm}
-     * values having the {@code _HARDCOPY} suffix in their name).
-     * Associated values should be instances of {@link DataSet} or {@link Aggregate}.
-     *
-     * @see #getData()
-     * @see #setData(Resource)
-     */
-    public static final String DATA_PROPERTY = "data";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map layer query.
-     * The query can filter resource data for rendering only a subset of available data.
-     * Associated values are instances of {@link Query}.
-     *
-     * @see #getQuery()
-     * @see #setQuery(Query)
-     */
-    public static final String QUERY_PROPERTY = "query";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map layer style.
-     * The style specifies the appearance of the filtered data to be rendered.
-     * Associated values are instances of {@link Style}.
-     *
-     * @see #getStyle()
-     * @see #setStyle(Style)
-     */
-    public static final String STYLE_PROPERTY = "style";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map layer opacity.
-     * The opacity specifies the gloabal opacity of the data to be rendered.
-     *
-     * @see #getOpacity()
-     * @see #setOpacity(double)
-     */
-    public static final String OPACITY_PROPERTY = "opacity";
-
-    /**
-     * Data to be rendered, or {@code null} if unavailable.
-     *
-     * @see #DATA_PROPERTY
-     * @see #getData()
-     */
-    private Resource resource;
-
-    /**
-     * Filter for rendering a subset of available data, or {@code null} if none.
-     *
-     * @see #QUERY_PROPERTY
-     * @see #getQuery()
-     */
-    private Query query;
-
-    /**
-     * Visual representation of data, or {@code null} if none.
-     *
-     * @see #STYLE_PROPERTY
-     * @see #getStyle()
-     */
-    private Style style;
-
-    /**
-     * Visual transparency of data, or {@code null} if none.
-     *
-     * @see #OPACITY_PROPERTY
-     * @see #getOpacity()
-     */
-    private double opacity = 1.0;
-
-    /**
-     * Constructs an initially empty map layer.
-     *
-     * @todo Expect {@code Resource} and {@code Style} in argument, for discouraging
-     *       the use of {@code MapLayer} with null resource and null style?
-     */
-    public MapLayer() {
-    }
-
-    /**
-     * Returns the data (resource) represented by this layer.
-     * The resource should be a {@link DataSet}, but {@link Aggregate} is also accepted.
-     * The behavior in aggregate case depends on the rendering engine.
-     *
-     * @return data to be rendered, or {@code null} is unavailable.
-     *
-     * @see #DATA_PROPERTY
-     */
-    public Resource getData() {
-        return resource;
-    }
-
-    /**
-     * Sets the data (resource) to be rendered.
-     * The resource should never be null, still the null case is tolerated to indicate
-     * that the layer should have existed but is unavailable for an unspecified reason.
-     * This case may happen with processing or distant services resources.
-     *
-     * <p>The given resource should be a {@link DataSet} or an {@link Aggregate} of data sets.
-     * However, this base class does not enforce those types. Subclasses may restrict the set
-     * of resource types accepted by this method.</p>
-     *
-     * <p>Note that not all kinds of resources are digital data. For example, a resource may be an organization,
-     * or citation of facts, tables and figures printed on paper, photographic material, or other media
-     * (see all {@link org.opengis.metadata.citation.PresentationForm} values having the {@code _HARDCOPY}
-     * suffix in their name). The kind of resources in {@code MapLayer} shall be one of those representing
-     * digital data.</p>
-     *
-     * @param  newValue  the new data, or {@code null} if unavailable.
-     */
-    public void setData(final Resource newValue) {
-        final Resource oldValue = resource;
-        if (!Objects.equals(oldValue, newValue)) {
-            resource = newValue;
-            firePropertyChange(DATA_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the filter for reducing the amount of data to render. Query filters can be
-     * specified for rendering a smaller amount of data than what the resource can provide.
-     * If the query is undefined, then all data will be rendered.
-     *
-     * @return filter for reducing data, or {@code null} for rendering all data.
-     *
-     * @see #QUERY_PROPERTY
-     */
-    public Query getQuery() {
-        return query;
-    }
-
-    /**
-     * Sets a filter for reducing the amount of data to render. If this method is never invoked, the default value
-     * is {@code null}. If the given value is different than the previous value, then a change event is sent to all
-     * listeners registered for the {@value #QUERY_PROPERTY} property.
-     *
-     * @param  newValue  filter for reducing data, or {@code null} for rendering all data.
-     */
-    public void setQuery(final Query newValue) {
-        final Query oldValue = query;
-        if (!Objects.equals(oldValue, newValue)) {
-            query = newValue;
-            firePropertyChange(QUERY_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the visual appearance of the data.
-     * If the style is undefined, the behavior is left to the rendering engine.
-     * It is expected that a default style should be used.
-     *
-     * @return description of data visual appearance, or {@code null} if unspecified.
-     */
-    public Style getStyle() {
-        return style;
-    }
-
-    /**
-     * Sets the visual appearance of the data. If this method is never invoked, the default value is {@code null}.
-     * If the given value is different than the previous value, then a change event is sent to all listeners
-     * registered for the {@value #STYLE_PROPERTY} property.
-     *
-     * @param  newValue  description of data visual appearance, or {@code null} if unspecified.
-     */
-    public void setStyle(final Style newValue) {
-        final Style oldValue = style;
-        if (!Objects.equals(oldValue, newValue)) {
-            style = newValue;
-            firePropertyChange(STYLE_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the global opacity of this layer.
-     * Based on the rendering context this property may be impossible to implement,
-     * it is therefor recommended to modify the style symbolizer opacity properties.
-     *
-     * @return opacity between 0.0 and 1.0
-     */
-    public double getOpacity() {
-        return opacity;
-    }
-
-    /**
-     * Sets the global rendering opacity of this layer.
-     *
-     * @param opacity must be betwen 0.0 and 1.0
-     */
-    public void setOpacity(double opacity) {
-        ArgumentChecks.ensureBetween(OPACITY_PROPERTY, 0.0, 1.0, opacity);
-        if (this.opacity != opacity) {
-            double old = this.opacity;
-            this.opacity = opacity;
-            firePropertyChange(OPACITY_PROPERTY, old, opacity);
-        }
-    }
-
-    /**
-     * Returns the envelope of this {@code MapLayer}.
-     * The envelope is the resource data envelope.
-     *
-     * @return the spatiotemporal extent. May be absent if none or too costly to compute.
-     * @throws DataStoreException if an error occurred while reading or computing the envelope.
-     */
-    @Override
-    public Optional<Envelope> getEnvelope() throws DataStoreException {
-        Resource data = getData();
-        if (data instanceof DataSet) {
-            return ((DataSet) data).getEnvelope();
-        }
-        return Optional.empty();
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapLayers.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapLayers.java
deleted file mode 100644
index 0a6fe87..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/MapLayers.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import org.opengis.geometry.Envelope;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.measure.NumberRange;
-import org.apache.sis.storage.DataSet;
-import org.apache.sis.storage.DataStoreException;
-
-
-/**
- * A group of layers to display together.
- * {@code MapLayers} can be used for grouping related layers under a same node.
- * This allows global actions, like {@linkplain #setVisible(boolean) hiding} background layers in one call.
- * A {@code MapLayers} can also contain nested {@code MapLayers}, thus forming a tree.
- * Since {@link MapLayer} and {@code MapLayers} are the only {@link MapItem} subclasses,
- * all leaves in this tree can only be {@link MapLayer} instances (assuming no {@code MapLayers} is empty).
- *
- * <p>A {@code MapLayers} is the root node of the tree of all layers to draw on the map,
- * unless there is only one layer to draw.
- * The {@link MapItem} children are listed by {@link #getComponents()} in <var>z</var> order.
- * In addition, {@code MapLayers} may define an {@linkplain #getAreaOfInterest() area of interest}
- * which should be zoomed by default when the map is rendered.</p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- */
-public class MapLayers extends MapItem {
-    /**
-     * The {@value} property name, used for notifications about changes in area of interest.
-     * Associated values are instances of {@link Envelope}.
-     *
-     * @see #getAreaOfInterest()
-     * @see #setAreaOfInterest(Envelope)
-     */
-    public static final String AREA_OF_INTEREST_PROPERTY = "areaOfInterest";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item components.
-     *
-     * @see #getComponents()
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     */
-    public static final String COMPONENTS_PROPERTY = "components";
-
-    /**
-     * The components in this group, or an empty list if none.
-     *
-     * @todo Should be an observable list with event sent when an element is added/removed/modified.
-     */
-    private final List<MapItem> components = new NotifiedList<MapItem>() {
-        @Override
-        protected void notifyAdd(MapItem item, int index) {
-            firePropertyChange(ListChangeEvent.added(MapLayers.this, COMPONENTS_PROPERTY, components, item, index));
-        }
-
-        @Override
-        protected void notifyAdd(List<MapItem> items, NumberRange<Integer> range) {
-            firePropertyChange(ListChangeEvent.added(MapLayers.this, COMPONENTS_PROPERTY, components, items, range));
-        }
-
-        @Override
-        protected void notifyRemove(MapItem item, int index) {
-            firePropertyChange(ListChangeEvent.removed(MapLayers.this, COMPONENTS_PROPERTY, components, item, index));
-        }
-
-        @Override
-        protected void notifyRemove(List<MapItem> items, NumberRange<Integer> range) {
-            firePropertyChange(ListChangeEvent.removed(MapLayers.this, COMPONENTS_PROPERTY, components, items, range));
-        }
-
-        @Override
-        protected void notifyReplace(MapItem olditem, MapItem newitem, int index) {
-            firePropertyChange(ListChangeEvent.changed(MapLayers.this, COMPONENTS_PROPERTY, components));
-        }
-    };
-
-    /**
-     * The area of interest, or {@code null} if unspecified.
-     */
-    private ImmutableEnvelope areaOfInterest;
-
-    /**
-     * Creates an initially empty group of layers.
-     */
-    public MapLayers() {
-    }
-
-    /**
-     * Gets the modifiable list of children contained in this group.
-     * The elements in the list are sorted in rendering order.
-     * This means that the first rendered element, which will be below
-     * all other elements on the rendered map, is located at index zero.
-     *
-     * <p>The returned list is modifiable: changes in the returned list will
-     * be immediately reflected in this {@code MapLayers}, and conversely.</p>
-     *
-     * @return modifiable list of children in this group of layers.
-     */
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public List<MapItem> getComponents() {
-        return components;
-    }
-
-    /**
-     * Returns the map area to show by default.
-     * This is not necessarily the {@linkplain DataSet#getEnvelope() envelope of data}
-     * since one may want to zoom in a different spatiotemporal area.
-     *
-     * <p>The {@linkplain org.apache.sis.geometry.GeneralEnvelope#getCoordinateReferenceSystem() envelope CRS}
-     * provides the reference system to use by default for rendering the map. It may be different than the CRS
-     * of data. The returned envelope may have {@linkplain org.apache.sis.geometry.GeneralEnvelope#isAllNaN()
-     * all its coordinates set to NaN} if only the {@link CoordinateReferenceSystem} is specified.</p>
-     *
-     * @return map area to show by default, or {@code null} if unspecified.
-     *
-     * @see DataSet#getEnvelope()
-     */
-    public Envelope getAreaOfInterest() {
-        return areaOfInterest;
-    }
-
-    /**
-     * Sets the map area to show by default.
-     * The given envelope is not necessarily related to the data contained in this group.
-     * It may be wider, or smaller, and in a different {@link CoordinateReferenceSystem}.
-     *
-     * @param  newValue  new map area to show by default, or {@code null} if unspecified.
-     */
-    public void setAreaOfInterest(final Envelope newValue) {
-        final ImmutableEnvelope imenv = ImmutableEnvelope.castOrCopy(newValue);
-        final Envelope oldValue = areaOfInterest;
-        if (!Objects.equals(oldValue, imenv)) {
-            areaOfInterest = imenv;
-            firePropertyChange(AREA_OF_INTEREST_PROPERTY, oldValue, imenv);
-        }
-    }
-
-    /**
-     * Returns the envelope of this {@code MapItem}.
-     * If this instance is a {@code MapLayers} the envelope is the concatenation of all it's components,
-     * in case of multiple CRS for each MapLayer, the resulting envelope CRS is unpredictable.
-     * If this instance is a {@code MapLayer} the envelope is the resource data envelope.
-     *
-     * @return the spatiotemporal extent. May be absent if none or too costly to compute.
-     * @throws DataStoreException if an error occurred while reading or computing the envelope.
-     */
-    @Override
-    public Optional<Envelope> getEnvelope() throws DataStoreException {
-        List<Envelope> envelopes = new ArrayList<>();
-        for (MapItem i : components) {
-            i.getEnvelope().ifPresent(envelopes::add);
-        }
-        switch (envelopes.size()) {
-            case 0 : return Optional.empty();
-            case 1 : return Optional.of(envelopes.get(0));
-            default : {
-                try {
-                    return Optional.ofNullable(Envelopes.union(envelopes.toArray(Envelope[]::new)));
-                } catch (TransformException ex) {
-                    throw new DataStoreException(ex.getMessage(), ex);
-                }
-            }
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/NotifiedList.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/NotifiedList.java
deleted file mode 100644
index e6ff28f..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/NotifiedList.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.AbstractList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import org.apache.sis.measure.NumberRange;
-
-
-/**
- * Decorate a CopyOnWriteArrayList and notify changes when elements are added or removed.
- *
- * @author Johann Sorel (Geomatys)
- */
-public abstract class NotifiedList<T> extends AbstractList<T> {
-
-    private final CopyOnWriteArrayList<T> parent = new CopyOnWriteArrayList<>();
-
-    public NotifiedList() {
-    }
-
-    @Override
-    public T get(int index) {
-        return parent.get(index);
-    }
-
-    @Override
-    public int size() {
-        return parent.size();
-    }
-
-    @Override
-    public T set(int index, T element) {
-        final T old = parent.set(index, element);
-        notifyReplace(old, element, index);
-        return old;
-    }
-
-    @Override
-    public void add(int index, T element) {
-        parent.add(index, element);
-        notifyAdd(element, index);
-    }
-
-    @Override
-    public T remove(int index) {
-        final T old = parent.remove(index);
-        notifyRemove(old, index);
-        return old;
-    }
-
-    protected abstract void notifyAdd(final T item, int index);
-
-    protected abstract void notifyAdd(final List<T> items, NumberRange<Integer> range);
-
-    protected abstract void notifyRemove(final T item, int index);
-
-    protected abstract void notifyRemove(final List<T> items, NumberRange<Integer> range);
-
-    protected abstract void notifyReplace(final T olditem, final T newitem, int index);
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/Presentation.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/Presentation.java
deleted file mode 100644
index 040173e..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/Presentation.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.Objects;
-import org.opengis.feature.Feature;
-import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.Resource;
-
-
-/**
- * Parent class of all elements having a graphical representation on the map.
- * {@code Presentation} instances are organized in a tree closely related to the {@link MapLayer} tree.
- * The {@link MapLayer} tree specifies data and styles in a device-independent way and for all zoom levels.
- * The {@code Presentation} tree can be seen as {@link MapLayer} information filtered for the current rendering
- * context (map projection, zoom level, window size, <i>etc.</i>) and converted to data structures more directly
- * exploitable by the display device. In particular a {@code Presentation} object must encapsulate data without
- * costly evaluation, processing or loading work remaining to do: the {@link Feature} or the {@link GridCoverage}
- * (for instance) should have been read in advance from the {@link DataStore}.
- * The preparation of a {@link Presentation} tree before displaying may be done in a background thread.
- *
- * <p>Note that multiple presentations may be generated for the same feature.
- * Consequently, many {@code Presentation} instances may encapsulate the same {@link Feature} instance.</p>
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @todo Consider renaming as {@code Graphic} for emphasis that this is a graphical representation of something.
- *       This would be consistent with legacy GO-1 specification (even if retired, it still have worthy material).
- * @todo Consider renaming as {@code Instruction} for emphasis that this is a rendering operation.
- *       IHO S-52 and S-100 specification. S-100 has been build guided by ISO-19117.
- *
- * @author  Johann Sorel (Geomatys)
- */
-public abstract class Presentation {
-
-    private MapLayer layer;
-    private Resource resource;
-    private Feature candidate;
-
-    public Presentation() {
-    }
-
-    public Presentation(MapLayer layer, Resource resource, Feature candidate) {
-        this.layer = layer;
-        this.resource = resource;
-        this.candidate = candidate;
-    }
-
-    /**
-     * Returns the original map layer the feature comes from.
-     *
-     * @return MapLayer can be null if the presentation is not associated to a layer.
-     */
-    public MapLayer getLayer() {
-        return layer;
-    }
-
-    /**
-     * Set map layer this presentation comes from.
-     *
-     * @param layer may be null
-     */
-    public void setLayer(MapLayer layer) {
-        this.layer = layer;
-    }
-
-    public Resource getResource() {
-        return resource;
-    }
-
-    public void setResource(Resource resource) {
-        this.resource = resource;
-    }
-
-    /**
-     * Returns the original candidate having this presentation.
-     * This is often a Coverage or a Feature.
-     *
-     * @return can be null if the presentation is not associated to any identifiable object.
-     */
-    public Feature getCandidate() {
-        return candidate;
-    }
-
-    /**
-     * Set feature this presentation comes from.
-     *
-     * @param feature may be null
-     */
-    public void setCandidate(Feature feature) {
-        this.candidate = feature;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 7;
-        hash = 89 * hash + Objects.hashCode(this.layer);
-        hash = 89 * hash + Objects.hashCode(this.resource);
-        hash = 89 * hash + Objects.hashCode(this.candidate);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final Presentation other = (Presentation) obj;
-        if (!Objects.equals(this.layer, other.layer)) {
-            return false;
-        }
-        if (!Objects.equals(this.resource, other.resource)) {
-            return false;
-        }
-        if (!Objects.equals(this.candidate, other.candidate)) {
-            return false;
-        }
-        return true;
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/PropertyNameCollector.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/PropertyNameCollector.java
deleted file mode 100644
index faa7813..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/PropertyNameCollector.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.Set;
-import java.util.HashSet;
-import org.opengis.filter.ValueReference;
-
-
-/**
- * Collects all properties used in style elements.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- */
-final class PropertyNameCollector extends SymbologyVisitor {
-    /**
-     * All value references found.
-     *
-     * @see ValueReference#getXPath()
-     */
-    final Set<String> references;
-
-    /**
-     * Creates a new collector.
-     */
-    PropertyNameCollector() {
-        references = new HashSet<>();
-    }
-
-    /**
-     * Invoked for each value reference found.
-     */
-    @Override
-    protected void visitProperty(final ValueReference<?,?> expression) {
-        if (expression != null) {
-            references.add(expression.getXPath());
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ResourceSymbolizer.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ResourceSymbolizer.java
deleted file mode 100644
index 62a3453..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/ResourceSymbolizer.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import org.apache.sis.style.se1.StyleFactory;
-import org.apache.sis.style.se1.Symbolizer;
-
-
-/**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- *
- * @param <R>  the type of data to style, such as {@code Feature} or {@code Coverage}.
- */
-public abstract class ResourceSymbolizer<R> extends Symbolizer<R> {
-    /**
-     * Constructs a new symbolozer.
-     *
-     * @param  context  context (features or coverages) in which this style element will be used.
-     */
-    public ResourceSymbolizer(final StyleFactory<R> context) {
-        super(context);
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SEPortrayer.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SEPortrayer.java
deleted file mode 100644
index e4a042f..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SEPortrayer.java
+++ /dev/null
@@ -1,750 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.awt.geom.AffineTransform;
-import java.awt.geom.NoninvertibleTransformException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.BiFunction;
-import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.MultiLineString;
-import org.locationtech.jts.geom.MultiPoint;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.util.GenericName;
-import org.opengis.geometry.Envelope;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.apache.sis.coverage.grid.PixelInCell;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.feature.PropertyType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Expression;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.feature.Features;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.feature.privy.AttributeConvention;
-import org.apache.sis.filter.privy.XPath;
-import org.apache.sis.storage.FeatureQuery;
-import org.apache.sis.storage.Aggregate;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.GridCoverageResource;
-import org.apache.sis.storage.Query;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
-import org.apache.sis.style.se1.FeatureTypeStyle;
-import org.apache.sis.style.se1.Rule;
-import org.apache.sis.style.se1.Symbolizer;
-import org.apache.sis.style.se1.SemanticType;
-import org.apache.sis.style.se1.Symbology;
-
-
-/**
- * Generation a Stream of Presentation for a map.
- *
- * <p>
- * NOTE: this class is experimental and subject to modifications.
- * </p>
- *
- * <p>
- * Style properties ignored :
- * </p>
- * <ul>
- *   <li>Style : isDefault : behavior from ISO 19117 which can be replaced by OGC SE Rule.isElseRule</li>
- *   <li>Style : defaultSpecification : behavior from ISO 19117 which can be replaced by OGC SE Rule.isElseRule</li>
- *   <li>FeatureTypeStyle : instance ids : behavior from ISO 19117 which can be replaced by OGC SE Rule.filter</li>
- * </ul>
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class SEPortrayer {
-    /**
-     * Rule scale tolerance.
-     */
-    private static final double SE_EPSILON = 1e-6;
-
-    /**
-     * Used in SLD/SE to calculate scale for degree CRSs.
-     */
-    private static final double SE_DEGREE_TO_METERS = 6378137.0 * 2.0 * Math.PI / 360.0;
-    private static final double DEFAULT_DPI = 90.0; // ~ 0.28 * 0.28mm
-    private static final double PIXEL_SIZE = 0.0254;
-
-    /**
-     * A test to know if a given property is an SIS convention or not. Return true if
-     * the property is NOT marked as an SIS convention, false otherwise.
-     */
-    private static final Predicate<IdentifiedType> IS_NOT_CONVENTION = p -> !AttributeConvention.contains(p.getName());
-
-    private final FilterFactory<Feature,Object,Object> filterFactory;
-
-    /**
-     * Hint to avoid decimating feature properties because they may be used
-     * later for other purposes.
-     */
-    private boolean preserveProperties;
-
-    private BiFunction<GridGeometry, Symbolizer<?>, Double> marginSolver;
-
-    public SEPortrayer() {
-        filterFactory = DefaultFilterFactory.forFeatures();
-        marginSolver  = (GridGeometry t, Symbolizer<?> u) -> 30.0;
-    }
-
-    /**
-     * Hint to avoid decimating feature properties because they may be used
-     * later for other purposes.
-     * Default value is false.
-     *
-     * @return true if all feature properties are preserved in Presentation instances.
-     */
-    public boolean isPreserveProperties() {
-        return preserveProperties;
-    }
-
-    /**
-     * Hint to avoid decimating feature properties because they may be used
-     * later for other purposes.
-     * Default value is false.
-     *
-     * @param preserveProperties set to true to preserve all feature properties in Presentation instances.
-     */
-    public void setPreserveProperties(boolean preserveProperties) {
-        this.preserveProperties = preserveProperties;
-    }
-
-    /**
-     * Replace default margin solver.
-     * The margin solver try to guess the expected symbolizer size to expand the query bounding box.
-     * @param marginSolver
-     */
-    public void setMarginSolver(BiFunction<GridGeometry, Symbolizer<?>, Double> marginSolver) {
-        this.marginSolver = Objects.requireNonNull(marginSolver);
-    }
-
-    /**
-     * Generate presentations for the given map item.
-     */
-    public Stream<Presentation> present(GridGeometry canvas, final MapItem mapitem) {
-        Stream<Presentation> stream = Stream.empty();
-        if (mapitem.isVisible()) {
-            if (mapitem instanceof MapLayer) {
-                final MapLayer layer = (MapLayer) mapitem;
-                stream = Stream.concat(stream, present(canvas, layer, layer.getData()));
-            } else if (mapitem instanceof MapLayers) {
-                final MapLayers layers = (MapLayers) mapitem;
-                for (MapItem item : layers.getComponents()) {
-                    stream = Stream.concat(stream, present(canvas, item));
-                }
-            }
-        }
-        return stream;
-    }
-
-    private Stream<Presentation> present(GridGeometry canvas, MapLayer layer, Resource resource) {
-        final Resource refResource = resource;
-        Stream<Presentation> stream = Stream.empty();
-        final FeatureType type;
-        if (resource instanceof FeatureSet) {
-            // Apply user query if defined.
-            final Query basequery = layer.getQuery();
-            if (basequery != null) try {
-                resource = ((FeatureSet) resource).subset(basequery);
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                return stream;
-            }
-            try {
-                type = ((FeatureSet) resource).getType();
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                return stream;
-            }
-        } else if (resource instanceof GridCoverageResource) {
-
-            // Apply user query if defined.
-            final Query basequery = layer.getQuery();
-            if (basequery != null) try {
-                resource = ((GridCoverageResource) resource).subset(basequery);
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                return stream;
-            }
-
-            type = null;
-        } else if (resource instanceof Aggregate) {
-            try {
-                // Combine each component resource in the stream.
-                for (final Resource r : ((Aggregate) resource).components()) {
-                    stream = Stream.concat(stream, present(canvas, layer, r));
-                }
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-            }
-            return stream;
-        } else {
-            // Unknown type.
-            return Stream.empty();
-        }
-        final MathTransform   gridToCRS = canvas.getGridToCRS(PixelInCell.CELL_CENTER);
-        final AffineTransform dispToObj;
-        final AffineTransform objToDisp;
-        try {
-            dispToObj = AffineTransforms2D.castOrCopy(gridToCRS);
-            objToDisp = dispToObj.createInverse();
-        } catch (IllegalArgumentException | NoninvertibleTransformException ex) {
-            stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-            return stream;
-        }
-        final double seScale = getSEScale(canvas, objToDisp);
-        final Symbology style = (Symbology) layer.getStyle();     // TODO: we do not yet support other implementations.
-        for (FeatureTypeStyle fts : style.featureTypeStyles()) {
-            final List<Rule<Feature>> rules = getValidRules(fts, seScale, type);
-            if (rules.isEmpty()) continue;
-
-            // Prepare the renderers.
-            final int elseRuleIndex = sortByElseRule(rules);
-            {   //special case for resource symbolizers
-                //resource symbolizers must be alone in a FTS
-                ResourceSymbolizer<?> resourceSymbolizer = null;
-                int count = 0;
-                for (final Rule<Feature> r : rules) {
-                    for (final Symbolizer<?> s : r.symbolizers()) {
-                        count++;
-                        if (s instanceof ResourceSymbolizer) {
-                            resourceSymbolizer = (ResourceSymbolizer) s;
-                        }
-                    }
-                }
-                if (resourceSymbolizer != null) {
-                    if (count > 1) {
-                        Exception ex = new IllegalArgumentException("A resource symbolizer must be alone in a FeatureTypeStyle element." );
-                        final ExceptionPresentation presentation = new ExceptionPresentation(ex);
-                        presentation.setLayer(layer);
-                        presentation.setResource(resource);
-                        stream = Stream.concat(stream, Stream.of(presentation));
-                    } else {
-                        final SEPresentation presentation = new SEPresentation(layer, resource, null, resourceSymbolizer);
-                        stream = Stream.concat(stream, Stream.of(presentation));
-                    }
-                    continue;
-                }
-            }
-            // Extract the used names.
-            Set<String> names;
-            if (preserveProperties) {
-                names = null;
-            } else {
-                names = propertiesNames(rules);
-                if (names.contains("*")) {
-                    // We need all properties.
-                    names = null;
-                }
-            }
-            if (resource instanceof GridCoverageResource) {
-                boolean painted = false;
-                for (int i = 0; i < elseRuleIndex; i++) {
-                    final Stream<Presentation> subStream = present(rules.get(i), layer, resource, resource, null);
-                    if (subStream != null) {
-                        painted = true;
-                        stream = Stream.concat(stream, subStream);
-                    }
-                }
-                // The data hasn't been painted, paint it with the 'else' rules.
-                if (!painted) {
-                    for (int i = elseRuleIndex, n = rules.size(); i < n; i++) {
-                        final Stream<Presentation> subStream = present(rules.get(i), layer, resource, resource, null);
-                        if (subStream != null) {
-                            stream = Stream.concat(stream, subStream);
-                        }
-                    }
-                }
-            } else if (resource instanceof FeatureSet) {
-                final FeatureSet fs = (FeatureSet) resource;
-                // Calculate max symbol size, to expand search envelope.
-                double symbolsMargin = 0.0;
-                for (Rule<Feature> rule : rules) {
-                    for (Symbolizer<?> symbolizer : rule.symbolizers()) {
-                        symbolsMargin = Math.max(symbolsMargin, marginSolver.apply(canvas, symbolizer));
-                    }
-                }
-                if (Double.isNaN(symbolsMargin) || Double.isInfinite(symbolsMargin)) {
-                    // Symbol margin cannot be pre calculated, expect a max of 300pixels.
-                    symbolsMargin = 300f;
-                }
-                if (symbolsMargin > 0) {
-                    symbolsMargin *= AffineTransforms2D.getScale(dispToObj);
-                }
-                try {
-                    // Optimize query.
-                    final Query query = prepareQuery(canvas, fs, names, rules, symbolsMargin);
-                    final Stream<Presentation> s = fs.subset(query)
-                            .features(false)
-                            .flatMap((Feature feature) ->
-                    {
-                        Stream<Presentation> stream1 = Stream.empty();
-                        boolean painted = false;
-                        for (int i = 0; i < elseRuleIndex; i++) {
-                            final Stream<Presentation> subStream = present(rules.get(i), layer, fs, refResource, feature);
-                            if (subStream != null) {
-                                painted = true;
-                                stream1 = Stream.concat(stream1, subStream);
-                            }
-                        }
-                        // The feature hasn't been painted, paint it with the 'else' rules.
-                        if (!painted) {
-                            for (int i = elseRuleIndex, n = rules.size(); i < n; i++) {
-                                final Stream<Presentation> subStream = present(rules.get(i), layer, fs, refResource, feature);
-                                if (subStream != null) {
-                                    stream1 = Stream.concat(stream1, subStream);
-                                }
-                            }
-                        }
-                        return stream1;
-                    });
-                    stream = Stream.concat(stream, s);
-                } catch (DataStoreException | TransformException ex) {
-                    stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                }
-            }
-        }
-        return stream;
-    }
-
-    private static Stream<Presentation> present(Rule<Feature> rule, MapLayer layer,
-            Resource resource, Resource refResource, Feature feature)
-    {
-        final Filter<Feature> ruleFilter = rule.getFilter();
-        //test if the rule is valid for this resource/feature
-        if (rule.isElseFilter() || ((Filter) ruleFilter).test(feature == null ? resource : feature)) {       // TODO: unsafe cast.
-            Stream<Presentation> stream = Stream.empty();
-            for (final Symbolizer<?> symbolizer : rule.symbolizers()) {
-                final SEPresentation presentation = new SEPresentation(layer, refResource, feature, symbolizer);
-                stream = Stream.concat(stream, Stream.of(presentation));
-            }
-            return stream;
-        }
-        return null;
-    }
-
-    /**
-     * Creates an optimal query to send to the Featureset, knowing which properties are knowned and
-     * the appropriate bounding box to filter.
-     */
-    private FeatureQuery prepareQuery(GridGeometry canvas, FeatureSet fs, Set<String> requiredProperties,
-            List<Rule<Feature>> rules, double symbolsMargin) throws DataStoreException, TransformException
-    {
-        final FeatureQuery query = new FeatureQuery();
-        final FeatureType schema = fs.getType();
-        /*
-         * Check if some used properties are not part of the type.
-         * This means the FeatureSet may contain sub types.
-         * We cannot optimize the query.
-         */
-        if (requiredProperties != null) {
-            for (String pn : requiredProperties) {
-                try {
-                    schema.getProperty(pn);
-                } catch (PropertyNotFoundException e) {
-                    return query;
-                }
-            }
-        }
-        // Search all geometry expression used in the symbols.
-        boolean allDefined = true;
-        final Set<Expression<Feature,?>> geomProperties = new HashSet<>();
-        if (rules != null) {
-            for (final Rule<Feature> r : rules) {
-                for (final Symbolizer<Feature> s : r.symbolizers()) {
-                    final Expression<Feature,?> expGeom = s.getGeometry();
-                    if (expGeom != null) {
-                        geomProperties.add(expGeom );
-                    } else {
-                        allDefined = false;
-                    }
-                }
-            }
-        } else {
-            allDefined = false;
-        }
-        if (!allDefined) {
-            // Add the default geometry property.
-            try {
-                PropertyType geomDesc = getDefaultGeometry(schema);
-                geomProperties.add(filterFactory.property(geomDesc.getName().toString()));
-            } catch (PropertyNotFoundException | IllegalStateException ex) {
-                // Do nothing.
-            }
-        }
-        if (geomProperties.isEmpty()) {
-            // No geometry selected for rendering.
-            query.setSelection(Filter.exclude());
-            return query;
-        }
-        final Envelope bbox = optimizeBBox(canvas, symbolsMargin);
-        Filter<Feature> filter;
-        // Make a bbox filter.
-        if (geomProperties.size() == 1) {
-            final Expression<Feature,?> geomExp = geomProperties.iterator().next();
-            filter = filterFactory.bbox(geomExp, bbox);
-        } else {
-            // Make an OR filter with all geometries.
-            final List<Filter<?>> geomFilters = new ArrayList<>();
-            for (final Expression<Feature,?> geomExp : geomProperties) {
-                geomFilters.add(filterFactory.bbox(geomExp, bbox));
-            }
-            filter = filterFactory.or((List) geomFilters);      // TODO
-        }
-        /*
-         * Combine the filter with rule filters.
-         */
-        ruleOpti:
-        if (rules != null) {
-            final List<Filter<Feature>> rulefilters = new ArrayList<>();
-            for (final Rule<Feature> rule : rules) {
-                if (rule.isElseFilter()) {
-                    // We cannot append styling filters, an else rule match all features.
-                    break ruleOpti;
-                } else {
-                    final Filter<Feature> rf = rule.getFilter();
-                    if (rf == Filter.<Feature>include()) {
-                        // We cannot append styling filters, this rule matchs all features.
-                        break ruleOpti;
-                    }
-                    rulefilters.add(rf);
-                }
-            }
-            final Filter<Feature> combined;
-            if (rulefilters.size() == 1) {
-//                //TODO need a stylefactory in SIS
-//                //special case, only one rule and we passed the filter to the query
-//                //we can remove it from the rule
-//                final Rule original = rules.get(0);
-//                Rule rule = styleFactory.rule(null, null, null,
-//                        original.getMinScaleDenominator(),
-//                        original.getMaxScaleDenominator(),
-//                        new ArrayList(original.symbolizers()),
-//                        Filter.include());
-//                rules.set(0, rule);
-                combined = rulefilters.get(0);
-            } else {
-                combined = filterFactory.or(rulefilters);
-            }
-            if (filter != Filter.<Feature>include()) {
-                filter = filterFactory.and(filter, combined);
-            } else {
-                filter = combined;
-            }
-        }
-        query.setSelection(filter);
-        /*
-         * Reduce requiered attributes.
-         */
-        if (requiredProperties == null) {
-            // All properties are required.
-        } else {
-            final Set<String> copy = new HashSet<>();
-            // Add used properties.
-            for (final String str : requiredProperties) {
-                copy.add(stripXpath(str));
-            }
-            // Add properties used as geometry.
-            for (Expression<?,?> exp : geomProperties) {
-                final PropertyNameCollector collector = new PropertyNameCollector();
-                collector.visit(exp);
-                collector.references.stream()
-                        .map(SEPortrayer::stripXpath)
-                        .forEach(copy::add);
-            }
-            try {
-                // Always include the identifier if it exist.
-                schema.getProperty(AttributeConvention.IDENTIFIER);
-                copy.add(AttributeConvention.IDENTIFIER);
-            } catch (PropertyNotFoundException ex) {
-                // No id, ignore it.
-            }
-            final List<FeatureQuery.NamedExpression> columns = new ArrayList<>();
-            for (String propName : copy) {
-                columns.add(new FeatureQuery.NamedExpression(filterFactory.property(propName), propName));
-            }
-            query.setProjection(columns.toArray(FeatureQuery.NamedExpression[]::new));
-        }
-        //TODO optimize filter
-        //TODO add linear resolution
-        return query;
-    }
-
-    /**
-     * Geographic scale calculated using OGC Symbology Encoding specification.
-     * This is not the scale Objective to Display.
-     * This is not an accurate geographic scale.
-     * This is a fake average scale unproper for correct rendering.
-     * It is used only to filter SE rules.
-     */
-    private static double getSEScale(final GridGeometry canvas, final AffineTransform objToDisp) {
-        final Envelope envelope = canvas.getEnvelope();
-        final CoordinateReferenceSystem objCRS = envelope.getCoordinateReferenceSystem();
-        final long width = canvas.getExtent().getSize(0);
-        if (AffineTransforms2D.getRotation(objToDisp) != 0.0) {
-            final double scale = AffineTransforms2D.getScale(objToDisp);
-            if (objCRS instanceof GeographicCRS) {
-                return (SE_DEGREE_TO_METERS * DEFAULT_DPI) / (scale*PIXEL_SIZE);
-            } else {
-                return DEFAULT_DPI / (scale *PIXEL_SIZE);
-            }
-        } else {
-            if (objCRS instanceof GeographicCRS) {
-                return (envelope.getSpan(0) * SE_DEGREE_TO_METERS) / (width / DEFAULT_DPI * PIXEL_SIZE);
-            } else {
-                return envelope.getSpan(0) / (width / DEFAULT_DPI * PIXEL_SIZE);
-            }
-        }
-    }
-
-    /**
-     * List the valid rules for current scale and type.
-     */
-    private static List<Rule<Feature>> getValidRules(final FeatureTypeStyle fts, final double scale, final FeatureType type) {
-        final Optional<GenericName> name = fts.getFeatureTypeName();
-        if (name.isPresent()) {
-            // TODO: should we check parent types?
-            if (!name.get().equals(type.getName())) {
-                return List.of();
-            }
-        }
-        // Check semantic, only if we have a feature type.
-        if (type != null) {
-            final Collection<SemanticType> semantics = fts.semanticTypeIdentifiers();
-            if (!semantics.isEmpty()) {
-                Class<?> ctype;
-                try {
-                    ctype = Features.toAttribute(getDefaultGeometry(type))
-                            .map(AttributeType::getValueClass)
-                            .orElse(null);
-                } catch (PropertyNotFoundException e) {
-                      ctype = null;
-                }
-                boolean valid = false;
-                for (SemanticType semantic : semantics) {
-                    if (semantic == SemanticType.ANY) {
-                        valid = true;
-                        break;
-                    } else if (semantic == SemanticType.LINE) {
-                        if (ctype == LineString.class || ctype == MultiLineString.class || ctype == Geometry.class) {
-                            valid = true;
-                            break;
-                        }
-                    } else if (semantic == SemanticType.POINT) {
-                        if (ctype == Point.class || ctype == MultiPoint.class || ctype == Geometry.class) {
-                            valid = true;
-                            break;
-                        }
-                    } else if (semantic == SemanticType.POLYGON) {
-                        if (ctype == Polygon.class || ctype == MultiPolygon.class || ctype == Geometry.class) {
-                            valid = true;
-                            break;
-                        }
-                    } else if (semantic == SemanticType.RASTER) {
-                        // Cannot test this on feature datas.
-                    } else if (semantic == SemanticType.TEXT) {
-                        // Cannot define a `text` type with current API.
-                    }
-                }
-                if (!valid) return List.of();
-            }
-        }
-
-        //TODO filter correctly possibilities
-        //test if the featutetype is valid
-        //we move to next feature  type if not valid
-        //if (typeName != null && !(typeName.equalsIgnoreCase(fts.getFeatureTypeName())) ) continue;
-
-        final List<? extends Rule<Feature>> rules = fts.rules();
-        final List<Rule<Feature>> validRules = new ArrayList<>();
-        for (final Rule<Feature> rule : rules) {
-            //test if the scale is valid for this rule
-            if (rule.getMinScaleDenominator() - SE_EPSILON <= scale && rule.getMaxScaleDenominator() + SE_EPSILON > scale) {
-                validRules.add(rule);
-            }
-        }
-        return validRules;
-    }
-
-    /**
-     * Lists all properties used in given rules.
-     */
-    private static Set<String> propertiesNames(final Collection<? extends Rule<Feature>> rules) {
-        final PropertyNameCollector collector = new PropertyNameCollector();
-        for (final Rule<Feature> r : rules) {
-            collector.visit(r);
-            collector.visit(r.getFilter());
-        }
-        return collector.references;
-    }
-
-    /**
-     * Sorts the rules, isolate the else rules, they must be handle differently
-     *
-     * @return index of starting else rules.
-     */
-    private static int sortByElseRule(final List<Rule<Feature>> sortedRules){
-        int elseRuleIndex = sortedRules.size();
-        for (int i = 0; i < elseRuleIndex; i++) {
-            final Rule<Feature> r = sortedRules.get(i);
-            if (r.isElseFilter()) {
-                elseRuleIndex--;
-                // Move the rule at the end
-                sortedRules.remove(i);
-                sortedRules.add(r);
-            }
-        }
-        return elseRuleIndex;
-    }
-
-    /**
-     * Search for the main geometric property in the given type. We'll search
-     * for an SIS convention first (see
-     * {@link AttributeConvention#GEOMETRY_PROPERTY}. If no convention is set on
-     * the input type, we'll check if it contains a single geometric property.
-     * If it's the case, we return it. Otherwise (no or multiple geometries), we
-     * throw an exception.
-     *
-     * @param  type  the data type to search into.
-     * @return the main geometric property we've found.
-     * @throws PropertyNotFoundException if no geometric property is available in the given type.
-     * @throws IllegalStateException if no convention is set (see {@link AttributeConvention#GEOMETRY_PROPERTY}),
-     *         and we have found more than one geometry.
-     */
-    private static PropertyType getDefaultGeometry(final FeatureType type) throws PropertyNotFoundException, IllegalStateException {
-        PropertyType geometry;
-        try {
-            geometry = type.getProperty(AttributeConvention.GEOMETRY_PROPERTY.toString());
-        } catch (PropertyNotFoundException e) {
-            try {
-                geometry = searchForGeometry(type);
-            } catch (RuntimeException e2) {
-                e2.addSuppressed(e);
-                throw e2;
-            }
-        }
-        return geometry;
-    }
-
-    /**
-     * Searches for a geometric attribute outside SIS conventions. More accurately,
-     * we expect the given type to have a single geometry attribute. If many are
-     * found, an exception is thrown.
-     *
-     * @param  type  the data type to search into.
-     * @return the only geometric property we've found.
-     * @throws PropertyNotFoundException if no geometric property is available in the given type.
-     * @throws IllegalStateException if we have found more than one geometry.
-     */
-    private static PropertyType searchForGeometry(final FeatureType type) throws PropertyNotFoundException, IllegalStateException {
-        final List<? extends PropertyType> geometries = type.getProperties(true).stream()
-                .filter(IS_NOT_CONVENTION)
-                .filter(AttributeConvention::isGeometryAttribute)
-                .collect(Collectors.toList());
-
-        if (geometries.size() < 1) {
-            throw new PropertyNotFoundException("No geometric property can be found outside of sis convention.");
-        } else if (geometries.size() > 1) {
-            throw new IllegalStateException("Multiple geometries found. We don't know which one to select.");
-        } else {
-            return geometries.get(0);
-        }
-    }
-
-    /**
-     * Removes any xpath elements, keep only the root property name.
-     */
-    private static String stripXpath(String attName) {
-        int index = attName.indexOf(XPath.SEPARATOR);
-        if (index == 0) {
-            attName = attName.substring(1);             // Remove first slash
-            final Pattern pattern = Pattern.compile("(\\{[^\\{\\}]*\\})|(\\[[^\\[\\]]*\\])|/{1}");
-            final Matcher matcher = pattern.matcher(attName);
-            final StringBuilder sb = new StringBuilder();
-            int position = 0;
-matches:    while (matcher.find()) {
-                final String match = matcher.group();
-                sb.append(attName.substring(position, matcher.start()));
-                position = matcher.end();
-                switch (match.charAt(0)) {
-                    case XPath.SEPARATOR: {
-                        // We do not query precisely sub elements.
-                        position = attName.length();
-                        break matches;
-                    }
-                    case '{': {
-                        sb.append(match);
-                        break;
-                    }
-                    case '[': {
-                        // Strip indexes or xpath searches.
-                        break;
-                    }
-                }
-            }
-            sb.append(attName.substring(position));
-            attName = sb.toString();
-        }
-        return attName;
-    }
-
-    /**
-     * Extracts envelope and expand it's horizontal component by given margin.
-     */
-    private static Envelope optimizeBBox(GridGeometry canvas, double symbolsMargin) throws TransformException {
-        Envelope env = canvas.getEnvelope();
-        //keep only horizontal component
-        env = Envelopes.transform(env, CRS.getHorizontalComponent(env.getCoordinateReferenceSystem()));
-
-        //expand the search area by given margin
-        if (symbolsMargin > 0) {
-            final GeneralEnvelope e = new GeneralEnvelope(env);
-            e.setRange(0, e.getMinimum(0) - symbolsMargin, e.getMaximum(0) + symbolsMargin);
-            e.setRange(1, e.getMinimum(1) - symbolsMargin, e.getMaximum(1) + symbolsMargin);
-            env = e;
-        }
-        return env;
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SEPresentation.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SEPresentation.java
deleted file mode 100644
index f7fe12c..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SEPresentation.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.Objects;
-import org.opengis.feature.Feature;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.style.se1.Symbolizer;
-
-
-/**
- * A presentation build with a standard Symbology Encoding Symbolizer.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class SEPresentation extends Presentation {
-
-    private Symbolizer<?> symbolizer;
-
-    public SEPresentation() {
-    }
-
-    public SEPresentation(MapLayer layer, Resource resource, Feature candidate, Symbolizer<?> symbolizer) {
-        super(layer, resource, candidate);
-        this.symbolizer = symbolizer;
-    }
-
-    /**
-     * @return Symbogy Encoding symbolizer
-     */
-    public Symbolizer<?> getSymbolizer() {
-        return symbolizer;
-    }
-
-    public void setSymbolizer(Symbolizer<?> symbolizer) {
-        this.symbolizer = symbolizer;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 7;
-        hash = 71 * hash + Objects.hashCode(this.symbolizer);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final SEPresentation other = (SEPresentation) obj;
-        if (!Objects.equals(this.symbolizer, other.symbolizer)) {
-            return false;
-        }
-        return super.equals(obj);
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SymbologyVisitor.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SymbologyVisitor.java
deleted file mode 100644
index ee9cd39..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/SymbologyVisitor.java
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.apache.sis.style.Style;
-import org.apache.sis.style.se1.*;
-import static org.apache.sis.util.privy.CollectionsExt.nonNull;
-
-
-/**
- * Loops on all objects contained in a style.
- * Sub classes are expected to override interested methods to fill their objectives.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- */
-abstract class SymbologyVisitor {
-    protected SymbologyVisitor() {
-    }
-
-    protected void visit(final Style candidate) {
-        if (candidate instanceof Symbology) {
-            visit((Symbology) candidate);
-        }
-    }
-
-    protected void visit(final Symbology candidate) {
-        if (candidate != null) {
-            nonNull(candidate.featureTypeStyles()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final FeatureTypeStyle candidate) {
-        if (candidate != null) {
-            nonNull(candidate.rules()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final Rule<?> candidate) {
-        if (candidate != null) {
-            nonNull(candidate.symbolizers()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final Symbolizer<?> candidate) {
-        if (candidate instanceof PointSymbolizer) {
-            visit((PointSymbolizer) candidate);
-        } else if (candidate instanceof LineSymbolizer) {
-            visit((LineSymbolizer) candidate);
-        } else if (candidate instanceof PolygonSymbolizer) {
-            visit((PolygonSymbolizer) candidate);
-        } else if (candidate instanceof TextSymbolizer) {
-            visit((TextSymbolizer) candidate);
-        } else if (candidate instanceof RasterSymbolizer) {
-            visit((RasterSymbolizer) candidate);
-        } else if (candidate != null) {
-            throw new IllegalArgumentException("Unexpected symbolizer " + candidate);
-        }
-    }
-
-    protected void visit(final PointSymbolizer<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getGraphic());
-        }
-    }
-
-    protected void visit(final LineSymbolizer<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getPerpendicularOffset());
-            visit(candidate.getStroke());
-        }
-    }
-
-    protected void visit(final PolygonSymbolizer<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getPerpendicularOffset());
-            visit(candidate.getDisplacement());
-            candidate.getFill().ifPresent(this::visit);
-            candidate.getStroke().ifPresent(this::visit);
-        }
-    }
-
-    protected void visit(final TextSymbolizer<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getLabel());
-            visit(candidate.getFill());
-            visit(candidate.getFont());
-            candidate.getHalo().ifPresent(this::visit);
-            visit(candidate.getLabelPlacement());
-        }
-    }
-
-    protected void visit(final RasterSymbolizer<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getOpacity());
-            candidate.getChannelSelection().ifPresent(this::visit);
-            candidate.getColorMap().ifPresent(this::visit);
-            candidate.getContrastEnhancement().ifPresent(this::visit);
-            candidate.getImageOutline().ifPresent(this::visit);
-            candidate.getShadedRelief().ifPresent(this::visit);
-        }
-    }
-
-    protected void visit(final GraphicalElement<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGraphic());
-        }
-    }
-
-    protected void visit(final Graphic<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getOpacity());
-            visit(candidate.getRotation());
-            visit(candidate.getSize());
-            visit(candidate.getAnchorPoint());
-            visit(candidate.getDisplacement());
-            nonNull(candidate.graphicalSymbols()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final GraphicalSymbol<?> candidate) {
-        if (candidate instanceof Mark) {
-            visit((Mark) candidate);
-        } else if (candidate instanceof ExternalGraphic) {
-            visit((ExternalGraphic) candidate);
-        } else if (candidate != null) {
-            throw new IllegalArgumentException("Unexpected GraphicalSymbol " + candidate);
-        }
-    }
-
-    protected void visit(final Mark<?> candidate) {
-        if (candidate != null) {
-            candidate.getFill().ifPresent(this::visit);
-            candidate.getStroke().ifPresent(this::visit);
-            visit(candidate.getWellKnownName());
-        }
-    }
-
-    protected void visit(final ExternalGraphic<?> candidate) {
-        nonNull(candidate.colorReplacements()).forEach(this::visit);
-    }
-
-    protected void visit(final Stroke<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getColor());
-            visit(candidate.getDashOffset());
-            candidate.getGraphicFill().ifPresent(this::visit);
-            candidate.getGraphicStroke().ifPresent(this::visit);
-            visit(candidate.getLineCap());
-            visit(candidate.getLineJoin());
-            visit(candidate.getOpacity());
-            visit(candidate.getWidth());
-        }
-    }
-
-    protected void visit(final Description<?> candidate) {
-    }
-
-    protected void visit(final Displacement<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getDisplacementX());
-            visit(candidate.getDisplacementY());
-        }
-    }
-
-    protected void visit(final Fill<?> candidate) {
-        if (candidate != null) {
-            candidate.getGraphicFill().ifPresent(this::visit);
-            visit(candidate.getColor());
-            visit(candidate.getOpacity());
-        }
-    }
-
-    protected void visit(final Font<?> candidate) {
-        if (candidate != null) {
-            candidate.family().forEach(this::visit);
-            visit(candidate.getSize());
-            visit(candidate.getStyle());
-            visit(candidate.getWeight());
-        }
-    }
-
-    protected void visit(final GraphicFill<?> candidate) {
-        visit((GraphicalElement) candidate);
-    }
-
-    protected void visit(final GraphicStroke<?> candidate) {
-        if (candidate != null) {
-            visit((GraphicalElement) candidate);
-            visit(candidate.getGap());
-            visit(candidate.getInitialGap());
-        }
-    }
-
-    protected void visit(final LabelPlacement<?> candidate) {
-        if (candidate instanceof PointPlacement) {
-            visit((PointPlacement) candidate);
-        } else if (candidate instanceof LinePlacement) {
-            visit((LinePlacement) candidate);
-        } else if (candidate != null) {
-            throw new IllegalArgumentException("Unexpected Placement " + candidate);
-        }
-    }
-
-    protected void visit(final PointPlacement<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getAnchorPoint());
-            visit(candidate.getDisplacement());
-            visit(candidate.getRotation());
-        }
-    }
-
-    protected void visit(final AnchorPoint<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getAnchorPointX());
-            visit(candidate.getAnchorPointY());
-        }
-    }
-
-    protected void visit(final LinePlacement<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGap());
-            visit(candidate.getInitialGap());
-            visit(candidate.getPerpendicularOffset());
-        }
-    }
-
-    protected void visit(final LegendGraphic<?> candidate) {
-        visit((GraphicalElement) candidate);
-    }
-
-    protected void visit(final Halo<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getFill());
-            visit(candidate.getRadius());
-        }
-    }
-
-    protected void visit(final ColorMap<?> candidate) {
-    }
-
-    protected void visit(final ColorReplacement<?> candidate) {
-    }
-
-    protected void visit(final ContrastEnhancement<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getGammaValue());
-        }
-    }
-
-    protected void visit(final ChannelSelection<?> candidate) {
-        if (candidate != null) {
-            SelectedChannel<?>[] channels = candidate.getChannels();
-            if (channels != null) {
-                for (final SelectedChannel<?> sct : channels) {
-                    visit(sct);
-                }
-            }
-        }
-    }
-
-    protected void visit(final SelectedChannel<?> candidate) {
-        if (candidate != null) {
-            candidate.getContrastEnhancement().ifPresent(this::visit);
-        }
-    }
-
-    protected void visit(final ShadedRelief<?> candidate) {
-        if (candidate != null) {
-            visit(candidate.getReliefFactor());
-        }
-    }
-
-    /**
-     * Find all value references and literal in the given expression and its parameters.
-     * This method invokes itself recursively.
-     *
-     * @param  candidate  the filter to examine, or {@code null} if none.
-     */
-    protected void visit(final Filter<?> candidate) {
-        if (candidate != null) {
-            if (candidate instanceof LogicalOperator<?>) {
-                ((LogicalOperator<?>) candidate).getOperands().forEach(this::visit);
-            } else {
-                candidate.getExpressions().forEach(this::visit);
-            }
-        }
-    }
-
-    /**
-     * Find all value references and literal in the given expression and its parameters.
-     * This method invokes itself recursively.
-     *
-     * @param  candidate  the expression to examine, or {@code null} if none.
-     */
-    protected void visit(final Expression<?,?> candidate) {
-        if (candidate != null) {
-            if (candidate instanceof ValueReference<?,?>) {
-                visitProperty((ValueReference<?,?>) candidate);
-            } else if (candidate instanceof Literal<?,?>) {
-                visitLiteral((Literal<?,?>) candidate);
-            } else {
-                candidate.getParameters().forEach(this::visit);
-            }
-        }
-    }
-
-    /**
-     * Invoked by {@link #visit(Expression)} for each value reference.
-     *
-     * @param  expression  a value reference found in a chain of expressions.
-     */
-    protected void visitProperty(ValueReference<?,?> expression) {
-    }
-
-    /**
-     * Invoked by {@link #visit(Expression)} for each literal.
-     *
-     * @param  expression  a literal found in a chain of expressions.
-     */
-    protected void visitLiteral(Literal<?,?> expression) {
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/package-info.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/package-info.java
deleted file mode 100644
index 414e2de..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/package-info.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-/**
- * Symbology and map representations, together with a rendering engine for display.
- *
- * <p><b>WARNING:</b> this package is work in progress and is not yet part of public API.
- * Some classes in this package will move to public API after we gained enough confidence
- * about their stability.</p>
- *
- * <h2>Synchronization</h2>
- * Unless otherwise specified, classes in this package are not thread-safe.
- * Synchronization, if desired, must be done by the caller.
- *
- * @author  Johann Sorel (Geomatys)
- */
-package org.apache.sis.map;
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/GraphicsPortrayer.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/GraphicsPortrayer.java
deleted file mode 100644
index 4ed1e56..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/GraphicsPortrayer.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service;
-
-import java.awt.Graphics2D;
-import java.awt.Shape;
-import java.awt.image.BufferedImage;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.ServiceLoader;
-import java.util.stream.Stream;
-import org.apache.sis.coverage.grid.GridCoverage2D;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.map.MapItem;
-import org.apache.sis.map.MapLayer;
-import org.apache.sis.map.MapLayers;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.style.Style;
-
-
-/**
- * Produce rendered image of styled resources.
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class GraphicsPortrayer {
-
-    /**
-     * Reference all painters.
-     */
-    private static final Map<Class<? extends Style>,StylePainter> PAINTERS = new HashMap<>();
-    static {
-        final ServiceLoader<StylePainter> loader = ServiceLoader.load(StylePainter.class, StylePainter.class.getClassLoader());
-        for (StylePainter painter : loader) {
-            PAINTERS.put(painter.getStyleClass(), painter);
-        }
-    }
-
-    private Graphics2D graphics;
-    private GridGeometry domain;
-    private BufferedImage image;
-
-    public GraphicsPortrayer(){}
-
-    /**
-     * Set the output image to render into.
-     *
-     * @param image not null
-     * @return this portrayer
-     */
-    public GraphicsPortrayer setCanvas(BufferedImage image) {
-        this.image = Objects.requireNonNull(image);
-        this.graphics = image.createGraphics();
-        return this;
-    }
-
-    /**
-     * Set the output Graphics2D to render into.
-     *
-     * @param graphics not null
-     * @return this portrayer
-     */
-    public GraphicsPortrayer setCanvas(Graphics2D graphics) {
-        this.graphics = Objects.requireNonNull(graphics);
-        return this;
-    }
-
-    /**
-     * Set the GridGeometry which is rendered.
-     *
-     * @param domain not null, lower extent coordinates must be on 0.
-     */
-    public GraphicsPortrayer setDomain(GridGeometry domain) {
-        // Implicit null check. As of Java 14, exception message is informative.
-        long[] low = domain.getExtent().getLow().getCoordinateValues();
-        for (long l : low) {
-            Objects.checkIndex((int) l, 1);
-        }
-
-        this.domain = domain;
-        return this;
-    }
-
-    /**
-     *
-     * @return created image, may be null
-     */
-    public BufferedImage getImage() {
-        return image;
-    }
-
-    /**
-     * Get the rendering image as a coverage.
-     * @return coverage, never null
-     */
-    public GridCoverage2D toCoverage() {
-        return new GridCoverage2D(domain, null, getImage());
-    }
-
-    /**
-     * Validate parameters and create image if needed.
-     */
-    private Scene2D init() {
-        Objects.requireNonNull(domain, "domain");       // Not an argument.
-        if (graphics == null) {
-            setCanvas(new BufferedImage(
-                    (int) domain.getExtent().getSize(0),
-                    (int) domain.getExtent().getSize(1),
-                    BufferedImage.TYPE_INT_ARGB));
-        }
-
-        return new Scene2D(domain, graphics);
-    }
-
-    /**
-     * Paint given map.
-     *
-     * An image is created if not defined.
-     *
-     * @param map to paint, not null
-     * @throws IllegalArgumentException if canvas is not property configured
-     * @throws RenderingException if a rendering procedure fails.
-     */
-    public synchronized GraphicsPortrayer portray(MapItem map) throws RenderingException {
-        portray(init(), map);
-        return  this;
-    }
-
-    private void portray(Scene2D scene, MapItem map) throws RenderingException {
-        if (map == null || !map.isVisible()) return;
-        if (map instanceof MapLayer) {
-            portray(scene, (MapLayer) map);
-        } else if (map instanceof MapLayers) {
-            final MapLayers layers = (MapLayers) map;
-            for (MapItem item : layers.getComponents()) {
-                portray(scene, item);
-            }
-        }
-    }
-
-    private void portray(Scene2D scene, MapLayer layer) throws RenderingException {
-        final Style style = layer.getStyle();
-        if (style == null) return;
-        final StylePainter painter = PAINTERS.get(style.getClass());
-        if (painter == null) return;
-        painter.paint(scene, layer);
-    }
-
-    /**
-     * Present given map.
-     *
-     * @param map to present, not null
-     * @throws IllegalArgumentException if canvas is not property configured
-     * @throws RenderingException if a rendering procedure fails.
-     */
-    public synchronized Stream<Presentation> present(MapItem map) throws RenderingException {
-        return present(init(), map);
-    }
-
-    private Stream<Presentation> present(Scene2D scene, MapItem map) throws RenderingException {
-        if (map == null || !map.isVisible()) return Stream.empty();
-        if (map instanceof MapLayer) {
-            return present(scene, (MapLayer) map);
-        } else if (map instanceof MapLayers) {
-            final MapLayers layers = (MapLayers) map;
-            Stream<Presentation> stream = Stream.empty();
-            for (MapItem item : layers.getComponents()) {
-                stream = Stream.concat(stream, present(scene, item));
-            }
-            return stream;
-        } else {
-            return Stream.empty();
-        }
-    }
-
-    private Stream<Presentation> present(Scene2D scene, MapLayer layer) throws RenderingException {
-        final Style style = layer.getStyle();
-        if (style == null) return Stream.empty();
-        final StylePainter painter = PAINTERS.get(style.getClass());
-        if (painter == null) return Stream.empty();
-        return painter.present(scene, layer);
-    }
-
-    /**
-     * Compute visual intersection of given map.
-     *
-     * @param mapItem to be processed, not null.
-     * @param mask intersection mask, not null.
-     * @return intersecting stream of presentations.
-     * @throws IllegalArgumentException if canvas is not property configured
-     * @throws RenderingException if a rendering procedure fails.
-     */
-    public Stream<Presentation> intersects(MapItem mapItem, Shape mask) throws RenderingException {
-        return intersects(init(), mapItem, mask);
-    }
-
-    private Stream<Presentation> intersects(Scene2D scene, MapItem map, Shape mask) throws RenderingException{
-        Stream<Presentation> results = Stream.empty();
-        if (map == null || !map.isVisible()) return results;
-        if (map instanceof MapLayer) {
-            results = Stream.concat(results, intersects(scene, (MapLayer) map, mask));
-        } else if (map instanceof MapLayers) {
-            final MapLayers layers = (MapLayers) map;
-            for (MapItem item : layers.getComponents()) {
-                results = Stream.concat(results, intersects(scene, item, mask));
-            }
-        }
-        return results;
-    }
-
-    private Stream<Presentation> intersects(Scene2D scene, MapLayer layer, Shape mask) throws RenderingException {
-        final Style style = layer.getStyle();
-        if (style == null) return Stream.empty();
-        final StylePainter painter = PAINTERS.get(style.getClass());
-        if (painter == null) return Stream.empty();
-        return painter.intersects(scene, layer, mask);
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/Scene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/Scene2D.java
deleted file mode 100644
index d58a599..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/Scene2D.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service;
-
-import java.awt.Graphics2D;
-import java.util.Objects;
-import java.util.logging.Logger;
-import javax.measure.Unit;
-import javax.measure.quantity.Length;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.measure.Units;
-
-
-/**
- * Holds the rendering properties.
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class Scene2D {
-
-    public static final Logger LOGGER = Logger.getLogger("org.apache.sis.internal.renderer");
-
-    /**
-     * The rendering grid geometry.
-     */
-    public final GridGeometry grid;
-    /**
-     * Graphics to render into.
-     * When modified by renderers, it must be reset accordingly.
-     */
-    public final Graphics2D graphics;
-
-    /**
-     * Definition from OGC SLD/SE :
-     * The portrayal unit “pixel” is the default unit of measure.
-     * If available, the pixel size depends on the viewer client resolution, otherwise it is equal to 0.28mm * 0.28mm (~ 90 DPI).
-     *
-     * In facts, all displays have there own DPI, but the common is around 96dpi (old 72dpi x 4/3).
-     * This dpi is the default on windows and replicated on different tools such as Apache Batik user agents.
-     *
-     * TODO : should we use a transform as in GraphicsConfiguration.getNormalizingTransform() ?
-     */
-    private double dpi = 96;
-
-    /**
-     * @param grid scene domain
-     * @param graphics to paint with
-     */
-    public Scene2D(GridGeometry grid, Graphics2D graphics) {
-        this.grid = Objects.requireNonNull(grid);
-        this.graphics = Objects.requireNonNull(graphics);
-    }
-
-    /**
-     * Graphics2D to use for painting data.
-     * This instance should be left untouched.
-     *
-     * @return Graphics2D
-     */
-    public Graphics2D getGraphics() {
-        return graphics;
-    }
-
-    /**
-     * Set current rendering DPI.
-     * Default is 96.
-     *
-     * @param dpi new DPI
-     */
-    public void setDpi(double dpi) {
-        this.dpi = dpi;
-    }
-
-    /**
-     * @return current DPI.
-     */
-    public double getDpi() {
-        return dpi;
-    }
-
-    /**
-     * Convert given distance to pixels.
-     *
-     * @param distance to convert
-     * @param unit distance unit, not null
-     * @return distance in pixels
-     */
-    public double toPixels(double distance, Unit<Length> unit) {
-        return unit.getConverterTo(Units.INCH).convert(distance) * dpi;
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/StylePainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/StylePainter.java
deleted file mode 100644
index 29da097..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/StylePainter.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service;
-
-import java.awt.Shape;
-import java.util.stream.Stream;
-import org.apache.sis.map.MapLayer;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.style.Style;
-
-
-/**
- * A Painter is responsible for portraying and querying resource on a scene.
- *
- * @author Johann Sorel (Geomatys)
- */
-public interface StylePainter {
-
-    /**
-     * Get the supported style implementation this painter can portray.
-     *
-     * @return supported style class.
-     */
-    Class<? extends Style> getStyleClass();
-
-    /**
-     * Stateless portraying of the given map layer.
-     *
-     * @param scene parameters for rendering
-     * @param layer having supported style class.
-     * @throws RenderingException if an error occured while rendering
-     */
-    void paint(Scene2D scene, MapLayer layer) throws RenderingException;
-
-    /**
-     * Statefull portraying of the given map layer.
-     * <p>
-     * Any exception should be returned in the stream as ExceptionPresentation, this allows
-     * to still have some results even if a data caused an error.
-     * <p>
-     * The nature of the Presentation instance should be related to the style API used.
-     *
-     * @param scene parameters for rendering
-     * @param layer having supported style class.
-     * @return stream of presentation objects.
-     */
-    Stream<Presentation> present(Scene2D scene, MapLayer layer);
-
-    /**
-     * Search for elements in the scene which intersect the given area.
-     * <p>
-     * Any exception should be returned in the stream as ExceptionPresentation, this allows
-     * to still have some results even if a data caused an error.
-     *
-     * @param scene parameters of the scene
-     * @param layer to seach in
-     * @param mask to search for intersection
-     * @return a stream of presentation instances that intersect the searched area.
-     */
-    Stream<Presentation> intersects(Scene2D scene, MapLayer layer, Shape mask);
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/LineToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/LineToScene2D.java
deleted file mode 100644
index eb713c2..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/LineToScene2D.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Shape;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.locationtech.jts.geom.Geometry;
-import org.opengis.feature.Feature;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.coverage.grid.PixelInCell;
-import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.util.FactoryException;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.RenderingException;
-import org.apache.sis.feature.privy.AttributeConvention;
-import org.apache.sis.geometry.wrapper.Geometries;
-import org.apache.sis.geometry.wrapper.GeometryWrapper;
-import org.apache.sis.geometry.wrapper.jts.JTS;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
-import org.apache.sis.style.se1.LineSymbolizer;
-
-
-/**
- * Support for LineSymbolizer rendering.
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class LineToScene2D extends SymbolizerToScene2D<LineSymbolizer<?>> {
-
-    private LineToScene2D(Scene2D state, LineSymbolizer<?> symbolizer) {
-        super(state, symbolizer);
-    }
-
-    @Override
-    public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            visual.paint(state.getGraphics());
-        }
-    }
-
-    @Override
-    public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            return visual.intersects(mask);
-        }
-        return false;
-    }
-
-    private RenderedShape createVisual(SEPresentation presentation) throws RenderingException {
-        final Feature feature = presentation.getCandidate();
-        Object geometry = feature.getPropertyValue(AttributeConvention.GEOMETRY);
-
-        if (geometry instanceof Geometry) {
-
-            final MathTransform gridToCRS = state.grid.getGridToCRS(PixelInCell.CELL_CENTER);
-
-            final GeometryWrapper geomWrap = Geometries.wrap(geometry).get();
-            final CoordinateReferenceSystem geomCrs = geomWrap.getCoordinateReferenceSystem();
-
-            final Geometry jts;
-            try {
-                final CoordinateOperation coop = CRS.findOperation(geomCrs, state.grid.getCoordinateReferenceSystem(), null);
-                final MathTransform geomToGrid = MathTransforms.concatenate(coop.getMathTransform(), gridToCRS.inverse());
-
-                jts = JTS.transform((Geometry) geometry, geomToGrid);
-            } catch (FactoryException | TransformException ex) {
-                throw new RenderingException(ex);
-            }
-
-            Shape shape = JTS.asShape(jts);
-
-            //TODO geometry world wrap and styling
-
-            RenderedShape rs = new RenderedShape();
-            rs.shape = shape;
-            rs.stroke = new BasicStroke(1);
-            rs.strokePaint = Color.BLACK;
-
-            return rs;
-        }
-
-        return null;
-    }
-
-    public static final class Spi implements SymbolizerToScene2D.Spi<LineSymbolizer> {
-
-        @Override
-        public Class<LineSymbolizer> getSymbolizerType() {
-            return LineSymbolizer.class;
-        }
-
-        @Override
-        public SymbolizerToScene2D create(Scene2D state, LineSymbolizer symbolizer) throws RenderingException {
-            return new LineToScene2D(state, symbolizer);
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PointToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PointToScene2D.java
deleted file mode 100644
index f5863c1..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PointToScene2D.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.Shape;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.RenderingException;
-import org.apache.sis.style.se1.PointSymbolizer;
-
-
-/**
- * Support for PointSymbolizer rendering.
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class PointToScene2D extends SymbolizerToScene2D<PointSymbolizer<?>> {
-
-    private PointToScene2D(Scene2D state, PointSymbolizer<?> symbolizer) {
-        super(state, symbolizer);
-    }
-
-    @Override
-    public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            visual.paint(state.getGraphics());
-        }
-    }
-
-    @Override
-    public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            return visual.intersects(mask);
-        }
-        return false;
-    }
-
-    private RenderedShape createVisual(SEPresentation presentation) throws RenderingException {
-        //todo
-        return null;
-    }
-
-    public static final class Spi implements SymbolizerToScene2D.Spi<PointSymbolizer> {
-
-        @Override
-        public Class<PointSymbolizer> getSymbolizerType() {
-            return PointSymbolizer.class;
-        }
-
-        @Override
-        public SymbolizerToScene2D create(Scene2D state, PointSymbolizer symbolizer) throws RenderingException {
-            return new PointToScene2D(state, symbolizer);
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PolygonToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PolygonToScene2D.java
deleted file mode 100644
index e617e52..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PolygonToScene2D.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.Shape;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.RenderingException;
-import org.apache.sis.style.se1.PolygonSymbolizer;
-
-
-/**
- * Support for PointSymbolizer rendering.
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class PolygonToScene2D extends SymbolizerToScene2D<PolygonSymbolizer<?>> {
-
-    private PolygonToScene2D(Scene2D state, PolygonSymbolizer<?> symbolizer) {
-        super(state, symbolizer);
-    }
-
-    @Override
-    public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            visual.paint(state.getGraphics());
-        }
-    }
-
-    @Override
-    public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            return visual.intersects(mask);
-        }
-        return false;
-    }
-
-    private RenderedShape createVisual(SEPresentation presentation) throws RenderingException {
-        //todo
-        return null;
-    }
-
-    public static final class Spi implements SymbolizerToScene2D.Spi<PolygonSymbolizer> {
-
-        @Override
-        public Class<PolygonSymbolizer> getSymbolizerType() {
-            return PolygonSymbolizer.class;
-        }
-
-        @Override
-        public SymbolizerToScene2D create(Scene2D state, PolygonSymbolizer symbolizer) throws RenderingException {
-            return new PolygonToScene2D(state, symbolizer);
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PresentationToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PresentationToScene2D.java
deleted file mode 100644
index 44d5233..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PresentationToScene2D.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.Graphics2D;
-import java.awt.Shape;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.logging.Level;
-import java.util.stream.Stream;
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.referencing.operation.NoninvertibleTransformException;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.util.FactoryException;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.RenderingException;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.style.se1.Symbolizer;
-
-
-/**
- * Generate a 2D scene from canvas and graphic presentations.
- *
- * @author Johann Sorel (Geomatys)
- */
-final class PresentationToScene2D {
-
-    /**
-     * Flag instance for missing symbolizers converters.
-     */
-    private static final SymbolizerToScene2D<?> NONE = new SymbolizerToScene2D<Symbolizer<?>>(null, null){
-        @Override
-        public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException {
-            throw new UnsupportedOperationException("Should not called.");
-        }
-    };
-    private static final SymbolizerCache NO_CACHE = new SymbolizerCache(){};
-
-    private final Scene2D state;
-
-    private final Map<Symbolizer<?>,SymbolizerToScene2D<?>> cache = new HashMap<>();
-    private Map<Symbolizer<?>,SymbolizerCache> symbolizerCaches;
-
-    /**
-     * Prepare parameters for scene creation.The grid geometry will provide the base coordinate system and scale informations.
-     * All further presentations must have been build with given grid as base.
-     *
-     * @param grid, not null
-     */
-    public PresentationToScene2D(GridGeometry grid, Graphics2D graphics) throws FactoryException, MismatchedDimensionException, TransformException {
-        // Null values are verified by the Scene2D constuctor.
-        state = new Scene2D(grid, graphics);
-    }
-
-    /**
-     * Create converter from an existing scene state.
-     *
-     * @param state, not null
-     */
-    public PresentationToScene2D(Scene2D state) {
-        this.state = Objects.requireNonNull(state);
-    }
-
-    /**
-     * Define global shared cache map instance.
-     *
-     * @param symbolizerCaches
-     */
-    public void setSymbolizerCaches(Map<Symbolizer<?>, SymbolizerCache> symbolizerCaches) {
-        this.symbolizerCaches = symbolizerCaches;
-    }
-
-    /**
-     * Convert and add given presentation to the scene.
-     * Exceptions will be logged.
-     *
-     * @param presentations, not null, will be closed.
-     */
-    public void render(Stream<Presentation> presentations) {
-        try {
-            presentations.parallel().forEach(new Consumer<Presentation>() {
-                @Override
-                public void accept(Presentation t) {
-                    try {
-                        render(t);
-                    } catch (Exception ex) {
-                        Scene2D.LOGGER.log(Level.INFO, ex.getMessage(), ex);
-                    }
-                }
-                });
-        } finally {
-            presentations.close();
-        }
-    }
-
-    private void render(Presentation presentation) throws RenderingException, IOException, NoninvertibleTransformException, TransformException, URISyntaxException, FactoryException, DataStoreException {
-        //standard presentation types
-        if (presentation instanceof SEPresentation) {
-            render((SEPresentation) presentation);
-        } else {
-            //unknown type
-        }
-    }
-
-    private void render(SEPresentation presentation) throws MismatchedDimensionException, TransformException, FactoryException, DataStoreException, IOException, RenderingException {
-        final SymbolizerToScene2D<?> sts = getRenderer(presentation.getSymbolizer());
-
-        if (sts != null) {
-            sts.paint(presentation, this::render);
-        } else {
-            Scene2D.LOGGER.log(Level.INFO, "Unnowned symbolizer {0}", presentation.getSymbolizer().getClass().getName());
-        }
-    }
-
-    /**
-     * Process given presentations and retain only thos who intersects the requested shape.
-     *
-     * @param presentations, not null.
-     */
-    public Stream<Presentation> intersects(Stream<Presentation> presentations, Shape mask) {
-
-        final Consumer<Stream<Presentation>> callback = (Stream<Presentation> t) -> {intersects(t, mask);};
-
-        return presentations.parallel().filter(new Predicate<Presentation>() {
-            @Override
-            public boolean test(Presentation t) {
-                try {
-                    return intersects(t, mask, callback);
-                } catch (RenderingException ex) {
-                    Scene2D.LOGGER.log(Level.WARNING, ex.getMessage(), ex);
-                }
-                return false;
-            }
-        });
-    }
-
-    private boolean intersects(Presentation presentation, Shape mask,  Consumer<Stream<Presentation>> callback) throws RenderingException {
-        //standard presentation types
-        if (presentation instanceof SEPresentation) {
-            return intersects((SEPresentation) presentation, mask, callback);
-        } else {
-            //unknown type
-            return false;
-        }
-    }
-
-    private boolean intersects(SEPresentation presentation, Shape mask,  Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final SymbolizerToScene2D<?> sts = getRenderer(presentation.getSymbolizer());
-
-        if (sts != null) {
-            return sts.intersects(presentation, mask, callback);
-        } else {
-            Scene2D.LOGGER.log(Level.INFO, "Unnowned symbolizer {0}", presentation.getSymbolizer().getClass().getName());
-            return false;
-        }
-    }
-
-    private SymbolizerToScene2D<?> getRenderer(Symbolizer<?> symbolizer) throws RenderingException {
-        SymbolizerToScene2D<?> sts;
-        synchronized (cache) {
-            sts = cache.get(symbolizer);
-            if (sts == NONE) {
-                sts = null;
-            } else if (sts == null) {
-                sts = SymbolizerToScene2D.create(state, symbolizer);
-                if (sts == null) sts = NONE;
-                cache.put(symbolizer, sts);
-                if (sts == NONE) {
-                    sts = null;
-                } else if (symbolizerCaches != null) {
-                    // get or create shared cache
-                    SymbolizerCache cache = symbolizerCaches.get(symbolizer);
-                    if (cache == null) {
-                        cache = SymbolizerToScene2D.createCache(symbolizer);
-                        if (cache == null) {
-                            cache = NO_CACHE;
-                        }
-                        symbolizerCaches.put(symbolizer, cache);
-                    }
-                    if (cache != NO_CACHE) {
-                        sts.sharedCache(cache);
-                    }
-                }
-            }
-        }
-        return sts;
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RasterToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RasterToScene2D.java
deleted file mode 100644
index c5af104..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RasterToScene2D.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.Shape;
-import java.awt.image.RenderedImage;
-import java.util.function.Consumer;
-import java.util.logging.Level;
-import java.util.stream.Stream;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.RenderingException;
-import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.coverage.grid.GridCoverageBuilder;
-import org.apache.sis.coverage.grid.GridCoverageProcessor;
-import org.apache.sis.coverage.grid.GridDerivation;
-import org.apache.sis.coverage.grid.GridExtent;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.GridCoverageResource;
-import org.apache.sis.storage.NoSuchDataException;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.style.se1.RasterSymbolizer;
-
-
-/**
- * Support for RasterSymbolizer rendering.
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class RasterToScene2D extends SymbolizerToScene2D<RasterSymbolizer<?>> {
-
-    private RasterToScene2D(Scene2D state, RasterSymbolizer<?> symbolizer) {
-        super(state, symbolizer);
-    }
-
-    @Override
-    public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        Resource resource = presentation.getResource();
-
-        if (resource instanceof GridCoverageResource) {
-            final GridCoverageResource gcr = (GridCoverageResource) resource;
-
-            try {
-                GridCoverage coverage = gcr.read(state.grid);
-
-                // naive inefficient implementation to be improved
-                // keep only a 2D slice for rendering
-                final GridGeometry gridGeometry = coverage.getGridGeometry();
-                final GridDerivation sliceGridBuilder = gridGeometry.derive().sliceByRatio(0.5, 0, 1);
-                final GridExtent intersection = sliceGridBuilder.getIntersection();
-                final GridGeometry sliceGrid = sliceGridBuilder.build().selectDimensions(0,1);
-                final RenderedImage image = coverage.render(intersection);
-
-                final GridCoverageBuilder gcb = new GridCoverageBuilder();
-                gcb.setValues(image);
-                gcb.setDomain(sliceGrid);
-                gcb.setRanges(coverage.getSampleDimensions());
-                final GridCoverage coverage2d = gcb.build();
-
-                final GridCoverageProcessor gcp = new GridCoverageProcessor();
-                final GridCoverage resampled = gcp.resample(coverage2d, state.grid);
-
-                //TODO handle raster symbolizer parameters.
-
-                state.graphics.drawRenderedImage(resampled.render(null), null);
-
-            } catch (NoSuchDataException ex) {
-                //do nothing
-            } catch (DataStoreException ex) {
-                LOGGER.log(Level.WARNING, ex.getMessage(), ex);
-            } catch (TransformException ex) {
-                LOGGER.log(Level.WARNING, ex.getMessage(), ex);
-            }
-
-        }
-    }
-
-    @Override
-    public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        return false;
-    }
-
-    public static final class Spi implements SymbolizerToScene2D.Spi<RasterSymbolizer> {
-
-        @Override
-        public Class<RasterSymbolizer> getSymbolizerType() {
-            return RasterSymbolizer.class;
-        }
-
-        @Override
-        public SymbolizerToScene2D create(Scene2D state, RasterSymbolizer symbolizer) throws RenderingException {
-            return new RasterToScene2D(state, symbolizer);
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RenderedShape.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RenderedShape.java
deleted file mode 100644
index 9f4cb84..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RenderedShape.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.AlphaComposite;
-import java.awt.Graphics2D;
-import java.awt.Paint;
-import java.awt.Shape;
-import java.awt.Stroke;
-import java.awt.geom.Area;
-
-
-/**
- * Combine an AWT Shape and it's rendering options.
- *
- * @author Johann Sorel (Geomatys)
- */
-final class RenderedShape {
-
-    public static final AlphaComposite ALPHA_COMPOSITE_0F = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
-    public static final AlphaComposite ALPHA_COMPOSITE_1F = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f);
-
-    public AlphaComposite fillComposite = ALPHA_COMPOSITE_1F;
-    public AlphaComposite strokeComposite = ALPHA_COMPOSITE_1F;
-
-    /**
-     * Shape to render.
-     * If null, paint and hit methods will always return false.
-     */
-    public Shape shape;
-    /**
-     * The fill paint of the shape.
-     * If null, no fill rendering is made.
-     */
-    public Paint fillPaint;
-    /**
-     * The stroke paint of the shape.
-     * If null, no stroke rendering is made.
-     */
-    public Paint strokePaint;
-    /**
-     * The stroke of the shape.
-     * If null, no stroke rendering is made.
-     */
-    public Stroke stroke;
-
-    /**
-     * Paint this shape with given Graphics2D.
-     *
-     * @param g2d not null
-     * @return true if something was painted.
-     */
-    public boolean paint(Graphics2D g2d) {
-        if (shape == null) return false;
-
-        boolean painted = false;
-        if (fillPaint != null) {
-            g2d.setComposite(fillComposite);
-            g2d.setPaint(fillPaint);
-            g2d.fill(shape);
-            painted = true;
-        }
-        if (stroke != null && strokePaint != null) {
-            g2d.setComposite(strokeComposite);
-            g2d.setPaint(strokePaint);
-            g2d.setStroke(stroke);
-            g2d.draw(shape);
-            painted = true;
-        }
-        return painted;
-    }
-
-    /**
-     * Test intersection of the shape and it's style options with the searched mask.
-     * @param mask not null.
-     * @return true if rendered shape intersects the mask.
-     */
-    public boolean intersects(Shape mask) {
-
-        //test intersection with fill if defined
-        if (fillPaint != null) {
-            final Area maskArea = new Area(mask);
-            final Area shapeArea = new Area(shape);
-            maskArea.intersect(shapeArea);
-            if (!maskArea.isEmpty()) {
-                return true;
-            }
-        }
-        //test intersection with stroke if defined
-        if (stroke != null && strokePaint != null) {
-            final Area maskArea = new Area(mask);
-            final Area shapeArea = new Area(stroke.createStrokedShape(shape));
-            maskArea.intersect(shapeArea);
-            return !maskArea.isEmpty();
-        }
-        return false;
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SEPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SEPainter.java
deleted file mode 100644
index d8f9e1d..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SEPainter.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.Shape;
-import java.util.stream.Stream;
-import org.apache.sis.map.MapLayer;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPortrayer;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.StylePainter;
-import org.apache.sis.style.Style;
-import org.apache.sis.style.se1.Symbology;
-
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class SEPainter implements StylePainter {
-
-
-    @Override
-    public Class<? extends Style> getStyleClass() {
-        return Symbology.class;
-    }
-
-    /**
-     * Render the given map using default SEPortrayer configuration.
-     *
-     * @param layer to be rendered, not null.
-     * @return this portrayer
-     */
-    @Override
-    public void paint(Scene2D scene, MapLayer layer) {
-        try (Stream<Presentation> stream = new SEPortrayer().present(scene.grid, layer)) {
-            paint(scene, stream);
-        }
-    }
-
-    /**
-     * {@inheritDoc }
-     */
-    @Override
-    public Stream<Presentation> present(Scene2D scene, MapLayer layer) {
-        return new SEPortrayer().present(scene.grid, layer);
-    }
-
-    /**
-     * Render the given stream of Presentations.
-     *
-     * @param presentations to be rendered, not null.
-     * @return this portrayer
-     */
-    public void paint(Scene2D scene, Stream<Presentation> presentations) {
-        final PresentationToScene2D pts = new PresentationToScene2D(scene);
-        pts.render(presentations);
-    }
-
-    /**
-     * Compute visual intersection of given map using default SEPortrayer configuration.
-     *
-     * @param layer to be processed, not null.
-     * @param mask intersection mask, not null.
-     * @return intersecting stream of presentations.
-     */
-    @Override
-    public Stream<Presentation> intersects(Scene2D scene, MapLayer layer, Shape mask) {
-        Stream<Presentation> stream = new SEPortrayer().present(scene.grid, layer);
-        return intersects(scene, stream, mask);
-    }
-
-    /**
-     * Compute visual intersection of given map using default SEPortrayer configuration.
-     *
-     * @param presentations to be processed, not null.
-     * @param mask intersection mask, not null.
-     * @return intersecting stream of presentations.
-     */
-    public Stream<Presentation> intersects(Scene2D scene, Stream<Presentation> presentations, Shape mask) {
-        final PresentationToScene2D pts = new PresentationToScene2D(scene);
-        return pts.intersects(presentations, mask);
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerCache.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerCache.java
deleted file mode 100644
index 0bddfc2..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerCache.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-
-/**
- * Subclassed by symbolizer who have resource caches.
- * This could contain images or models.
- * The cache instance may be shared by multiple {@link SymbolizerToScene2D} instances
- * using method {@link SymbolizerToScene2D#sharedCache(org.apache.sis.internal.renderer.SymbolizerCache) }.
- *
- * @author Johann Sorel (Geomatys)
- */
-public abstract class SymbolizerCache {
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerToScene2D.java
deleted file mode 100644
index 09b78ae..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerToScene2D.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.Shape;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-import java.util.function.Consumer;
-import java.util.logging.Logger;
-import java.util.stream.Stream;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.RenderingException;
-import org.apache.sis.style.se1.Symbolizer;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ObjectConverters;
-
-
-/**
- * Transforms a {@link Presentation} to Java2D graphics.
- *
- * @author Johann Sorel (Geomatys)
- */
-public abstract class SymbolizerToScene2D<S extends Symbolizer> {
-
-    private static final List<SymbolizerToScene2D.Spi<?>> SPIS;
-    private static final Map<Class<?>,SymbolizerToScene2D.Spi<?>> SPI_MAP = new HashMap<>();
-    static {
-        //collect all symbolizer SPI
-        List<SymbolizerToScene2D.Spi<?>> spis = new ArrayList<>();
-        ServiceLoader.load(SymbolizerToScene2D.Spi.class).iterator().forEachRemaining(spis::add);
-        SPIS = Collections.unmodifiableList(spis);
-        for (SymbolizerToScene2D.Spi<?> spi : SPIS) {
-            if (SPI_MAP.put(spi.getSymbolizerType(), spi) != null) {
-                throw new IllegalStateException("More then one SymbolizerToScene2D.Spi is registered for type " + spi.getSymbolizerType().getName());
-            }
-        }
-    }
-
-    protected static final Logger LOGGER = Logger.getLogger("com.examind.sdk.render");
-    protected final Scene2D state;
-    protected final S symbolizer;
-
-    protected SymbolizerToScene2D(Scene2D state, S symbolizer) {
-        this.state = state;
-        this.symbolizer = symbolizer;
-    }
-
-    /**
-     * Define a shared cache instance.
-     * Shared caches avoid multiple loading of a resource (example : images, models)
-     *
-     * @param cache not null
-     */
-    public void sharedCache(SymbolizerCache cache) {
-    }
-
-    /**
-     * Paint the given {@link SEPresentation}.
-     *
-     * @param presentation not null
-     * @param callback not null, can be used to create new presentation treated in the rendering loop.
-     * @throws RenderingException
-     */
-    public abstract void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException;
-
-    /**
-     * Test intersection of the given {@link SEPresentation}.
-     *
-     * @param presentation not null
-     * @param callback not null, can be used to test new presentations treated in the rendering loop.
-     * @throws RenderingException
-     */
-    public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        return false;
-    }
-
-    static <T> T evaluate(Feature feature, Expression<? super Feature,?> exp, Expression<? super Feature,?> fallback, Class<T> clazz) {
-        T value = null;
-        if (exp != null) {
-            value = ObjectConverters.convert(exp.apply(feature), clazz);
-        }
-        if (value == null && fallback != null) {
-            value = ObjectConverters.convert(fallback.apply(feature), clazz);
-        }
-        return value;
-    }
-
-    /**
-     * Create a symbolizer to scene processor.
-     *
-     * @param state not null
-     * @param symbolizer not null
-     * @return may be null if no Spi support this symbolizer.
-     * @throws RenderingException if the symbolizer is incorrectly defined or some assets cannot be resolved.
-     */
-    public static SymbolizerToScene2D<?> create(Scene2D state, Symbolizer<?> symbolizer) throws RenderingException {
-        ArgumentChecks.ensureNonNull("symbolizer", symbolizer);
-        final Spi<Symbolizer> spi = (Spi<Symbolizer>) getSpi(symbolizer.getClass());
-        return spi == null ? null : spi.create(state, symbolizer);
-    }
-
-    /**
-     * Create a symbolizer cache.
-     *
-     * @param symbolizer not null
-     * @return may be null if no Spi support this symbolizer.
-     * @throws RenderingException if the symbolizer is incorrectly defined or some assets cannot be resolved.
-     */
-    public static SymbolizerCache createCache(Symbolizer symbolizer) throws RenderingException {
-        for (SymbolizerToScene2D.Spi spi : SPIS) {
-            final SymbolizerCache sts = spi.createCache(symbolizer);
-            if (sts != null) {
-                return sts;
-            }
-        }
-        return null;
-    }
-    /**
-     * Get the Spi capable to handle given symbolizer.
-     *
-     * @return may be null if no Spi support this symbolizer.
-     */
-    public static synchronized <T extends Symbolizer> SymbolizerToScene2D.Spi<T> getSpi(Class<T> clazz) {
-        Spi<T> cdt = (Spi<T>) SPI_MAP.get(clazz);
-        if (cdt == null) {
-            for (SymbolizerToScene2D.Spi spi : SPIS) {
-                if (spi.getSymbolizerType().isAssignableFrom(clazz)) {
-                    SPI_MAP.put(clazz, spi);
-                    cdt = spi;
-                    break;
-                }
-            }
-        }
-        return cdt;
-    }
-
-    /**
-     * Factory to create new transformation instances.
-     *
-     * @param <T> symbolizer type supported
-     */
-    public interface Spi<T extends Symbolizer> {
-
-        /**
-         * Returns the support symbolizer class.
-         * @return supported symbolizer class, not null.
-         */
-        Class<T> getSymbolizerType();
-
-        /**
-         * Create a cache for given {@link Symbolizer}.
-         *
-         * @param symbolizer not null
-         * @return cache or null if symbolizer is not supported or no cache is needed.
-         * @throws RenderingException if symbolizer declaration contains errors.
-         */
-        default SymbolizerCache createCache(T symbolizer) throws RenderingException {
-            return null;
-        }
-
-        /**
-         * Create a transformation instance.
-         *
-         * @param state scene state, not null
-         * @param symbolizer not null
-         * @return instance or null if symbolizer is not supported
-         * @throws RenderingException if symbolizer declaration contains errors.
-         */
-        SymbolizerToScene2D create(Scene2D state, T symbolizer) throws RenderingException;
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/TextToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/TextToScene2D.java
deleted file mode 100644
index 8378bfd..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/TextToScene2D.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map.service.se1;
-
-import java.awt.Shape;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.map.service.Scene2D;
-import org.apache.sis.map.service.RenderingException;
-import org.apache.sis.style.se1.TextSymbolizer;
-
-
-/**
- * Support for PointSymbolizer rendering.
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class TextToScene2D extends SymbolizerToScene2D<TextSymbolizer<?>> {
-
-    private TextToScene2D(Scene2D state, TextSymbolizer<?> symbolizer) {
-        super(state, symbolizer);
-    }
-
-    @Override
-    public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            visual.paint(state.getGraphics());
-        }
-    }
-
-    @Override
-    public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException {
-        final RenderedShape visual = createVisual(presentation);
-        if (visual != null) {
-            return visual.intersects(mask);
-        }
-        return false;
-    }
-
-    private RenderedShape createVisual(SEPresentation presentation) throws RenderingException {
-        //todo
-        return null;
-    }
-
-    public static final class Spi implements SymbolizerToScene2D.Spi<TextSymbolizer> {
-
-        @Override
-        public Class<TextSymbolizer> getSymbolizerType() {
-            return TextSymbolizer.class;
-        }
-
-        @Override
-        public SymbolizerToScene2D create(Scene2D state, TextSymbolizer symbolizer) throws RenderingException {
-            return new TextToScene2D(state, symbolizer);
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/MapLayersTest.java b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/MapLayersTest.java
deleted file mode 100644
index 1b8f865..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/MapLayersTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.sis.measure.NumberRange;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-
-/**
- *
- * @author  Johann Sorel (Geomatys)
- */
-public final class MapLayersTest {
-    /**
-     * Creates a new test case.
-     */
-    public MapLayersTest() {
-    }
-
-    /**
-     * Test the maplayers components list events.
-     */
-    @Test
-    public void testListEvents() {
-
-        final MapLayers layers = new MapLayers();
-        final MapLayer layer1 = new MapLayer();
-        final MapLayer layer2 = new MapLayer();
-
-        final AtomicInteger eventNum = new AtomicInteger();
-        layers.addPropertyChangeListener(MapLayers.COMPONENTS_PROPERTY, new PropertyChangeListener() {
-            @Override
-            public void propertyChange(PropertyChangeEvent evt) {
-                assertTrue(evt instanceof ListChangeEvent<?>);
-                final ListChangeEvent<?> levt = (ListChangeEvent<?>) evt;
-                assertEquals(layers.getComponents(), levt.getOldValue());
-                assertEquals(layers.getComponents(), levt.getNewValue());
-                assertEquals(MapLayers.COMPONENTS_PROPERTY, levt.getPropertyName());
-                assertEquals(layers, levt.getSource());
-                int eventId = eventNum.incrementAndGet();
-                switch (eventId) {
-                    case 1 :
-                        assertEquals(ListChangeEvent.Type.ADDED, levt.getType());
-                        assertEquals(NumberRange.create(0, true, 0, true), levt.getRange());
-                        assertEquals(1, levt.getItems().size());
-                        assertEquals(layer1, levt.getItems().get(0));
-                        break;
-                    case 2 :
-                        assertEquals(ListChangeEvent.Type.ADDED, levt.getType());
-                        assertEquals(NumberRange.create(1, true, 1, true), levt.getRange());
-                        assertEquals(1, levt.getItems().size());
-                        assertEquals(layer2, levt.getItems().get(0));
-                        break;
-                    case 3 :
-                        assertEquals(ListChangeEvent.Type.REMOVED, levt.getType());
-                        assertEquals(NumberRange.create(1, true, 1, true), levt.getRange());
-                        assertEquals(1, levt.getItems().size());
-                        assertEquals(layer2, levt.getItems().get(0));
-                        break;
-                    case 4 :
-                        assertEquals(ListChangeEvent.Type.CHANGED, levt.getType());
-                        assertEquals(null, levt.getRange());
-                        assertEquals(null, levt.getItems());
-                        break;
-                }
-
-            }
-        });
-
-        layers.getComponents().add(layer1);
-        assertEquals(1, eventNum.get());
-
-        layers.getComponents().add(layer2);
-        assertEquals(2, eventNum.get());
-
-        layers.getComponents().remove(layer2);
-        assertEquals(3, eventNum.get());
-
-        layers.getComponents().set(0, layer2);
-        assertEquals(4, eventNum.get());
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
deleted file mode 100644
index dfd6b6e..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.map;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.locationtech.jts.geom.CoordinateXY;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.util.GenericName;
-import org.opengis.geometry.Envelope;
-import org.opengis.metadata.Metadata;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.MatchAction;
-import org.apache.sis.coverage.grid.GridExtent;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.coverage.grid.GridOrientation;
-import org.apache.sis.feature.builder.AttributeRole;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.feature.privy.AttributeConvention;
-import org.apache.sis.storage.FeatureQuery;
-import org.apache.sis.storage.Aggregate;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.base.MemoryFeatureSet;
-import org.apache.sis.style.se1.FeatureTypeStyle;
-import org.apache.sis.style.se1.Symbology;
-import org.apache.sis.style.se1.StyleFactory;
-import org.apache.sis.style.se1.Symbolizer;
-import org.apache.sis.style.se1.SemanticType;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.storage.event.StoreEvent;
-import org.apache.sis.storage.event.StoreListener;
-import org.apache.sis.util.iso.Names;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import static org.junit.jupiter.api.Assertions.*;
-import org.junit.jupiter.api.parallel.Execution;
-import org.junit.jupiter.api.parallel.ExecutionMode;
-
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-@Execution(ExecutionMode.CONCURRENT)
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-public class SEPortrayerTest {
-    /**
-     * The factory to use for creating style elements.
-     */
-    private final StyleFactory<Feature> factory = FeatureTypeStyle.FACTORY;
-
-    private final FilterFactory<Feature,Object,Object> filterFactory;
-    private final FeatureSet fishes;
-    private final FeatureSet boats;
-
-    /**
-     * Creates a new test case.
-     */
-    public SEPortrayerTest() {
-        filterFactory = DefaultFilterFactory.forFeatures();
-
-        final GeometryFactory gf = org.apache.sis.geometry.wrapper.jts.Factory.INSTANCE.factory(false);
-        final CoordinateReferenceSystem crs = CommonCRS.WGS84.normalizedGeographic();
-
-        final FeatureTypeBuilder fishbuilder = new FeatureTypeBuilder();
-        fishbuilder.setName("fish");
-        fishbuilder.addAttribute(String.class).setName("id").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        fishbuilder.addAttribute(Point.class).setCRS(crs).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY);
-        fishbuilder.addAttribute(String.class).setName("description");
-        final FeatureType fishType = fishbuilder.build();
-
-        final Point point1 = gf.createPoint(new CoordinateXY(0, 0));
-        point1.setUserData(crs);
-        final Feature fish1 = fishType.newInstance();
-        fish1.setPropertyValue("id", "1");
-        fish1.setPropertyValue("geom", point1);
-        fish1.setPropertyValue("description", "A red fish");
-
-        final Point point2 = gf.createPoint(new CoordinateXY(10, 20));
-        point2.setUserData(crs);
-        final Feature fish2 = fishType.newInstance();
-        fish2.setPropertyValue("id", "2");
-        fish2.setPropertyValue("geom", point2);
-        fish2.setPropertyValue("description", "A small blue fish");
-
-        //a special fish with a sub-type
-        final FeatureTypeBuilder sharkbuilder = new FeatureTypeBuilder();
-        sharkbuilder.setName("shark");
-        sharkbuilder.setSuperTypes(fishType);
-        sharkbuilder.addAttribute(String.class).setName("specie");
-        sharkbuilder.addAttribute(Double.class).setName("length");
-        final FeatureType sharkType = sharkbuilder.build();
-
-        final Point point3 = gf.createPoint(new CoordinateXY(30, 40));
-        point3.setUserData(crs);
-        final Feature shark1 = sharkType.newInstance();
-        shark1.setPropertyValue("id", "100");
-        shark1.setPropertyValue("geom", point3);
-        shark1.setPropertyValue("description", "dangerous fish");
-        shark1.setPropertyValue("specie", "White Shark");
-        shark1.setPropertyValue("length", 12.0);
-
-        fishes = new MemoryFeatureSet(null, sharkType, List.of(fish1, fish2, shark1));
-
-        final FeatureTypeBuilder boatbuilder = new FeatureTypeBuilder();
-        boatbuilder.setName("boat");
-        boatbuilder.addAttribute(String.class).setName("id").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        boatbuilder.addAttribute(Polygon.class).setCRS(crs).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY);
-        boatbuilder.addAttribute(String.class).setName("description");
-        final FeatureType boatType = boatbuilder.build();
-
-        final Polygon poly1 = gf.createPolygon(gf.createLinearRing(new CoordinateXY[] {
-            new CoordinateXY(0, 0), new CoordinateXY(0, 1), new CoordinateXY(1, 1), new CoordinateXY(0, 0)}));
-        poly1.setUserData(crs);
-        final Feature boat1 = boatType.newInstance();
-        boat1.setPropertyValue("id", "10");
-        boat1.setPropertyValue("geom", poly1);
-        boat1.setPropertyValue("description", "A fishing boat");
-
-        final Polygon poly2 = gf.createPolygon(gf.createLinearRing(new CoordinateXY[] {
-            new CoordinateXY(0, 0), new CoordinateXY(0, 1), new CoordinateXY(1, 1), new CoordinateXY(0, 0)}));
-        poly2.setUserData(crs);
-        final Feature boat2 = boatType.newInstance();
-        boat2.setPropertyValue("id", "20");
-        boat2.setPropertyValue("geom", poly2);
-        boat2.setPropertyValue("description", "A submarine");
-
-        boats = new MemoryFeatureSet(null, boatType, List.of(boat1, boat2));
-    }
-
-    private Set<Match> present(MapItem item) {
-        return present(item, CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()));
-    }
-
-    private Set<Match> present(MapItem item, Envelope env) {
-        final GridGeometry grid = new GridGeometry(new GridExtent(360, 180), env, GridOrientation.REFLECTION_Y);
-        final SEPortrayer portrayer = new SEPortrayer();
-        final Stream<Presentation> stream = portrayer.present(grid, item);
-        final List<Presentation> presentations = stream.collect(Collectors.toList());
-
-        final Set<Match> ids = new HashSet<>();
-        presentations.stream().forEach(new Consumer<Presentation>() {
-            @Override
-            public void accept(Presentation t) {
-                if (t instanceof SEPresentation se) {
-                    Feature Feature = se.getCandidate();
-                    ids.add(new Match(String.valueOf(Feature.getPropertyValue(AttributeConvention.IDENTIFIER)),
-                            se.getLayer(),
-                            se.getResource(),
-                            se.getSymbolizer()));
-                } else if (t instanceof ExceptionPresentation ep) {
-                    ids.add(new Match(ep.getException()));
-                }
-            }
-        });
-        return ids;
-    }
-
-    /**
-     * Portray using no filtering operations
-     */
-    @Test
-    public void testSanity() {
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        final var rule = factory.createRule();
-        final var symbolizer = factory.createLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("10", boatLayer, boats, symbolizer)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizer)));
-    }
-
-    /**
-     * Test portrayer includes the bounding box of the canvas while querying features.
-     * Only fish feature with identifier "2" matches in this test.
-     */
-    @Test
-    public void testCanvasBboxfilter() {
-        final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
-        env.setRange(0, 9, 11);
-        env.setRange(1, 19, 21);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        final var rule = factory.createRule();
-        final var symbolizer = factory.createLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers, env);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Test portrayer uses the user defined query when portraying.
-     * Only fish feature with identifier "1" and boat feature with identifier "20" matches in this test.
-     */
-    @Test
-    public void testUserQuery() {
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        final var rule = factory.createRule();
-        final var symbolizer = factory.createLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final Filter<Feature> filter = filterFactory.or(
-                filterFactory.resourceId("1"),
-                filterFactory.resourceId("20"));
-        final FeatureQuery query = new FeatureQuery();
-        query.setSelection(filter);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        fishLayer.setQuery(query);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        boatLayer.setQuery(query);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(2, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizer)));
-    }
-
-    /**
-     * Portray using defined type names.
-     * Test expect only boat type features to be rendered.
-     */
-    @Test
-    public void testFeatureTypeStyleTypeNames() {
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        fts.setFeatureTypeName(Names.createLocalName(null, null, "boat"));
-        final var rule = factory.createRule();
-        final var symbolizer = factory.createLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(2, presentations.size());
-        assertTrue(presentations.contains(new Match("10", boatLayer, boats, symbolizer)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizer)));
-    }
-
-    /**
-     * Portray using defined type names.
-     * Test expect only point geometric type to be rendered.
-     */
-    @Test
-    public void testFeatureTypeStyleSemanticType() {
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        fts.semanticTypeIdentifiers().add(SemanticType.POINT);
-        final var rule = factory.createRule();
-        final var symbolizer = factory.createLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(3, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Portray using defined rule filter
-     * Test expect only features with identifier equals "2" to match.
-     */
-    @Test
-    public void testRuleFilter() {
-        final Filter<Feature> filter = filterFactory.resourceId("2");
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        final var rule = factory.createRule();
-        rule.setFilter(filter);
-        final var symbolizer = factory.createLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Portray using defined rule scale filter.
-     * Test expect only matching scale rule symbolizer to be portrayed.
-     */
-    @Test
-    public void testRuleScale() {
-        final var symbolizerAbove = factory.createLineSymbolizer();
-        final var symbolizerUnder = factory.createLineSymbolizer();
-        final var symbolizerMatch = factory.createLineSymbolizer();
-
-        //Symbology rendering scale here is 3.944391406060875E8
-        final var ruleAbove = factory.createRule();
-        ruleAbove.symbolizers().add(symbolizerAbove);
-        ruleAbove.setMinScaleDenominator(4e8);
-        ruleAbove.setMaxScaleDenominator(Double.MAX_VALUE);
-        final var ruleUnder = factory.createRule();
-        ruleUnder.symbolizers().add(symbolizerUnder);
-        ruleUnder.setMinScaleDenominator(0.0);
-        ruleUnder.setMaxScaleDenominator(3e8);
-        final var ruleMatch = factory.createRule();
-        ruleMatch.symbolizers().add(symbolizerMatch);
-        ruleMatch.setMinScaleDenominator(3e8);
-        ruleMatch.setMaxScaleDenominator(4e8);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(ruleAbove);
-        fts.rules().add(ruleUnder);
-        fts.rules().add(ruleMatch);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match(  "1", fishLayer, fishes, symbolizerMatch)));
-        assertTrue(presentations.contains(new Match(  "2", fishLayer, fishes, symbolizerMatch)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizerMatch)));
-        assertTrue(presentations.contains(new Match( "10", boatLayer, boats,  symbolizerMatch)));
-        assertTrue(presentations.contains(new Match( "20", boatLayer, boats,  symbolizerMatch)));
-    }
-
-    /**
-     * Portray using defined rule filter.
-     * The rule uses a property only available on the shark sub type.
-     * Test expect only features with specy equals "White Shark" to match.
-     */
-    @Test
-    public void testRuleFilterOnSubType() {
-        final BinaryComparisonOperator<Feature> filter = filterFactory.equal(
-                filterFactory.property("specie", String.class),
-                filterFactory.literal("White Shark"),
-                true, MatchAction.ANY);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        final var rule = factory.createRule();
-        rule.setFilter(filter);
-        final var symbolizer = factory.createLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Portray using defined rule 'is else' property.
-     * Test expect only feature with identifier "10" to be rendered with the base rule
-     * and other features to rendered with the fallback rule.
-     */
-    @Test
-    public void testRuleElseCondition() {
-        final Filter<Feature> filter = filterFactory.resourceId("10");
-
-        final var symbolizerBase = factory.createLineSymbolizer();
-        final var symbolizerElse = factory.createLineSymbolizer();
-
-        final var ruleBase = factory.createRule();
-        ruleBase.symbolizers().add(symbolizerBase);
-        ruleBase.setFilter(filter);
-        final var ruleOther = factory.createRule();
-        ruleOther.setElseFilter(true);
-        ruleOther.symbolizers().add(symbolizerElse);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(ruleBase);
-        fts.rules().add(ruleOther);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match(  "1", fishLayer, fishes, symbolizerElse)));
-        assertTrue(presentations.contains(new Match(  "2", fishLayer, fishes, symbolizerElse)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizerElse)));
-        assertTrue(presentations.contains(new Match( "10", boatLayer, boats,  symbolizerBase)));
-        assertTrue(presentations.contains(new Match( "20", boatLayer, boats,  symbolizerElse)));
-    }
-
-    /**
-     * Portray using and aggregated resource.
-     * Test expect presentations to be correctly associated to each resource but on the same layer.
-     */
-    @Test
-    public void testAggregateResource() {
-        final var symbolizerBase = factory.createLineSymbolizer();
-
-        final var ruleBase = factory.createRule();
-        ruleBase.symbolizers().add(symbolizerBase);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(ruleBase);
-
-        final List<Resource> list = List.of(fishes, boats);
-        final Aggregate agg = new Aggregate() {
-            @Override
-            public Collection<? extends Resource> components() throws DataStoreException {
-                return list;
-            }
-
-            @Override
-            public Optional<GenericName> getIdentifier() throws DataStoreException {
-                return Optional.empty();
-            }
-
-            @Override
-            public Metadata getMetadata() throws DataStoreException {
-                return null;
-            }
-
-            @Override
-            public <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener) {}
-
-            @Override
-            public <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener) {}
-        };
-
-        final MapLayer aggLayer = new MapLayer();
-        aggLayer.setData(agg);
-        aggLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(aggLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match(  "1", aggLayer, fishes, symbolizerBase)));
-        assertTrue(presentations.contains(new Match(  "2", aggLayer, fishes, symbolizerBase)));
-        assertTrue(presentations.contains(new Match("100", aggLayer, fishes, symbolizerBase)));
-        assertTrue(presentations.contains(new Match( "10", aggLayer, boats,  symbolizerBase)));
-        assertTrue(presentations.contains(new Match( "20", aggLayer, boats,  symbolizerBase)));
-    }
-
-    /**
-     * Portray preserving all feature attributes test.
-     */
-    @Test
-    public void testPreserveProperties() {
-        final Filter<Feature> filter = filterFactory.resourceId("2");
-        final var symbolizer = factory.createLineSymbolizer();
-
-        final var rule = factory.createRule();
-        rule.symbolizers().add(symbolizer);
-        rule.setFilter(filter);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-
-        final GridGeometry grid = new GridGeometry(new GridExtent(360, 180), CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()), GridOrientation.REFLECTION_Y);
-        {
-            // test without preserve properties
-            // we expect only identifier and geometry to be available.
-            final SEPortrayer portrayer = new SEPortrayer();
-            portrayer.setPreserveProperties(false);
-            final Stream<Presentation> stream = portrayer.present(grid, layers);
-            final List<Presentation> presentations = stream.collect(Collectors.toList());
-            assertEquals(1, presentations.size());
-            final SEPresentation presentation = (SEPresentation) presentations.get(0);
-            final Feature feature = presentation.getCandidate();
-            final FeatureType type = feature.getType();
-            assertEquals(2, type.getProperties(true).size());
-            assertNotNull(type.getProperty(AttributeConvention.IDENTIFIER));
-            assertNotNull(type.getProperty(AttributeConvention.GEOMETRY));
-        }
-        {
-            // test with preserve properties
-            // we expect only identifier and geometry to be available.
-            final SEPortrayer portrayer = new SEPortrayer();
-            portrayer.setPreserveProperties(true);
-            final Stream<Presentation> stream = portrayer.present(grid, layers);
-            final List<Presentation> presentations = stream.collect(Collectors.toList());
-            assertEquals(1, presentations.size());
-            final SEPresentation presentation = (SEPresentation) presentations.get(0);
-            final Feature feature = presentation.getCandidate();
-            final FeatureType type = feature.getType();
-            assertEquals(6, type.getProperties(true).size());
-            assertNotNull(type.getProperty(AttributeConvention.IDENTIFIER));
-            assertNotNull(type.getProperty(AttributeConvention.GEOMETRY));
-            assertNotNull(type.getProperty(AttributeConvention.ENVELOPE));
-            assertNotNull(type.getProperty("id"));
-            assertNotNull(type.getProperty("geom"));
-            assertNotNull(type.getProperty("description"));
-        }
-    }
-
-    /**
-     * Test all properties used in the style are returned
-     * in the presentation features.
-     */
-    @Test
-    public void testStylePropertiesReturned() {
-        final BinaryComparisonOperator<Feature> filter = filterFactory.equal(
-                filterFactory.property("id", String.class),
-                filterFactory.literal("2"),
-                true, MatchAction.ANY);
-
-        final var symbolizer = factory.createLineSymbolizer();
-        symbolizer.setPerpendicularOffset(filterFactory.property("offset", Integer.class));
-
-        final var rule = factory.createRule();
-        rule.symbolizers().add(symbolizer);
-        rule.setFilter(filter);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-
-        final GridGeometry grid = new GridGeometry(new GridExtent(360, 180), CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()), GridOrientation.REFLECTION_Y);
-
-        // test without preserve properties
-        // we expect identifier, geometry, id(used by rule filter), description (used in symbolizer)
-        final SEPortrayer portrayer = new SEPortrayer();
-        portrayer.setPreserveProperties(false);
-        final Stream<Presentation> stream = portrayer.present(grid, layers);
-        final List<Presentation> presentations = stream.collect(Collectors.toList());
-        assertEquals(1, presentations.size());
-        final SEPresentation presentation = (SEPresentation) presentations.get(0);
-        final Feature feature = presentation.getCandidate();
-        final FeatureType type = feature.getType();
-        assertEquals(6, type.getProperties(true).size());
-        assertNotNull(type.getProperty(AttributeConvention.IDENTIFIER));
-        assertNotNull(type.getProperty(AttributeConvention.GEOMETRY));
-        assertNotNull(type.getProperty("id"));
-        assertNotNull(type.getProperty("description"));
-    }
-
-    /**
-     * Test a geometry expression do not affect portraying bbox filtering.
-     */
-    @Test
-    public void testGeometryExpression() {
-        final var symbolizer = factory.createLineSymbolizer();
-        symbolizer.setGeometry(filterFactory.function("ST_Centroid", filterFactory.property("geom")));
-
-        final var rule = factory.createRule();
-        rule.symbolizers().add(symbolizer);
-
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-
-        final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
-        env.setRange(0, 9, 11);
-        env.setRange(1, 19, 21);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers, env);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-    }
-
-    private static class Match {
-        private final String identifier;
-        private final MapLayer layer;
-        private final Resource resource;
-        private final Symbolizer<?> symbolizer;
-        private final Exception exception;
-
-        public Match(String identifier, MapLayer layer, Resource resource, Symbolizer<?> symbolizer) {
-            this.identifier = identifier;
-            this.layer = layer;
-            this.resource = resource;
-            this.symbolizer = symbolizer;
-            this.exception = null;
-        }
-
-        public Match(Exception e) {
-            this.identifier = null;
-            this.layer = null;
-            this.resource = null;
-            this.symbolizer = null;
-            this.exception = e;
-        }
-
-        @Override
-        public int hashCode() {
-            int hash = 7;
-            hash = 29 * hash + Objects.hashCode(this.identifier);
-            hash = 29 * hash + Objects.hashCode(this.layer);
-            hash = 29 * hash + Objects.hashCode(this.resource);
-            hash = 29 * hash + Objects.hashCode(this.symbolizer);
-            hash = 29 * hash + Objects.hashCode(this.exception);
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null) {
-                return false;
-            }
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final Match other = (Match) obj;
-            if (!Objects.equals(this.identifier, other.identifier)) {
-                return false;
-            }
-            if (!Objects.equals(this.layer, other.layer)) {
-                return false;
-            }
-            if (!Objects.equals(this.resource, other.resource)) {
-                return false;
-            }
-            if (!Objects.equals(this.symbolizer, other.symbolizer)) {
-                return false;
-            }
-            if (!Objects.equals(this.exception, other.exception)) {
-                return false;
-            }
-            return true;
-        }
-    }
-}
diff --git a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java
deleted file mode 100644
index 94bad9e..0000000
--- a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions andz
- * limitations under the License.
- */
-package org.apache.sis.map.service;
-
-import java.awt.Color;
-import java.awt.Rectangle;
-import java.awt.image.BufferedImage;
-import java.util.Arrays;
-import java.util.stream.Stream;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LineString;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.apache.sis.coverage.grid.GridExtent;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.coverage.grid.GridOrientation;
-import org.apache.sis.feature.builder.AttributeRole;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.privy.AttributeConvention;
-import org.apache.sis.map.MapLayer;
-import org.apache.sis.map.Presentation;
-import org.apache.sis.map.SEPresentation;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.base.MemoryFeatureSet;
-import org.apache.sis.style.se1.FeatureTypeStyle;
-import org.apache.sis.style.se1.LineSymbolizer;
-import org.apache.sis.style.se1.Rule;
-import org.apache.sis.style.se1.Symbolizer;
-import org.apache.sis.style.se1.Symbology;
-
-// Test dependencies
-import static org.junit.jupiter.api.Assertions.*;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.parallel.Execution;
-import org.junit.jupiter.api.parallel.ExecutionMode;
-
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-@Execution(ExecutionMode.CONCURRENT)
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-public class GraphicsPortrayerTest {
-
-    private final GridGeometry WORLD = new GridGeometry(
-            new GridExtent(360, 180),
-            CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()),
-            GridOrientation.REFLECTION_Y);
-
-    private final GeometryFactory GF = new GeometryFactory();
-
-    /**
-     * Creates a new test case.
-     */
-    public GraphicsPortrayerTest() {
-    }
-
-    /**
-     * Sanity test for rendering.
-     */
-    @Test
-    public void testPortray() throws RenderingException {
-
-        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
-        ftb.setName("test");
-        ftb.addAttribute(Geometry.class).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY);
-        final FeatureType ft = ftb.build();
-
-        final Feature feature = ft.newInstance();
-        LineString geom = GF.createLineString(new Coordinate[]{new Coordinate(0,0), new Coordinate(0,90)});
-        geom.setUserData(WORLD.getCoordinateReferenceSystem());
-        feature.setPropertyValue("geom", geom);
-
-        final FeatureSet featureSet = new MemoryFeatureSet(null, ft, Arrays.asList(feature));
-
-        final LineSymbolizer<Feature> symbolizer = new LineSymbolizer<>(FeatureTypeStyle.FACTORY);
-        final Symbology style = createStyle(symbolizer);
-
-        final MapLayer item = new MapLayer();
-        item.setData(featureSet);
-        item.setStyle(style);
-
-        BufferedImage image = new GraphicsPortrayer()
-                .setDomain(WORLD)
-                .portray(item)
-                .getImage();
-
-        int color1 = image.getRGB(180, 45);
-        int color2 = image.getRGB(179, 45);
-        assertEquals(color1, Color.BLACK.getRGB());
-        assertEquals(color2, new Color(0,0,0,0).getRGB());
-    }
-
-    /**
-     * Sanity test for presentation.
-     */
-    @Test
-    public void testPresent() throws RenderingException {
-
-        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
-        ftb.setName("test");
-        ftb.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER);
-        ftb.addAttribute(Geometry.class).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY);
-        final FeatureType ft = ftb.build();
-
-        final Feature feature = ft.newInstance();
-        LineString geom = GF.createLineString(new Coordinate[]{new Coordinate(0,0), new Coordinate(0,90)});
-        geom.setUserData(WORLD.getCoordinateReferenceSystem());
-        feature.setPropertyValue(AttributeConvention.IDENTIFIER, "test-1");
-        feature.setPropertyValue("geom", geom);
-
-        final FeatureSet featureSet = new MemoryFeatureSet(null, ft, Arrays.asList(feature));
-
-        final LineSymbolizer<Feature> symbolizer = new LineSymbolizer<>(FeatureTypeStyle.FACTORY);
-        final Symbology style = createStyle(symbolizer);
-
-        final MapLayer item = new MapLayer();
-        item.setData(featureSet);
-        item.setStyle(style);
-
-        try (Stream<Presentation> stream = new GraphicsPortrayer()
-                .setDomain(WORLD)
-                .present(item)) {
-            Object[] presentations = stream.toArray();
-            assertEquals(1, presentations.length);
-            assertTrue(presentations[0] instanceof SEPresentation);
-            final SEPresentation pe = (SEPresentation) presentations[0];
-            assertEquals(item, pe.getLayer());
-            assertEquals(featureSet, pe.getResource());
-            assertEquals("test-1", pe.getCandidate().getPropertyValue(AttributeConvention.IDENTIFIER));
-            assertEquals(symbolizer, pe.getSymbolizer());
-        }
-    }
-
-    /**
-     * Sanity test for intersection.
-     */
-    @Test
-    public void testIntersects() throws RenderingException {
-
-        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
-        ftb.setName("test");
-        ftb.addAttribute(Geometry.class).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY);
-        final FeatureType ft = ftb.build();
-
-        final Feature feature = ft.newInstance();
-        LineString geom = GF.createLineString(new Coordinate[]{new Coordinate(0,0), new Coordinate(0,90)});
-        geom.setUserData(WORLD.getCoordinateReferenceSystem());
-        feature.setPropertyValue("geom", geom);
-
-        final FeatureSet featureSet = new MemoryFeatureSet(null, ft, Arrays.asList(feature));
-
-        final LineSymbolizer<Feature> symbolizer = new LineSymbolizer<>(FeatureTypeStyle.FACTORY);
-        final Symbology style = createStyle(symbolizer);
-
-        final MapLayer item = new MapLayer();
-        item.setData(featureSet);
-        item.setStyle(style);
-
-        //rectangle outside, no intersection
-        try (Stream<Presentation> result = new GraphicsPortrayer()
-                .setDomain(WORLD)
-                .intersects(item, new Rectangle(7,50,4,4))) {
-            assertEquals(0, result.count());
-        }
-
-        //rectangle overlaps, intersects
-        try (Stream<Presentation> result = new GraphicsPortrayer()
-                .setDomain(WORLD)
-                .intersects(item, new Rectangle(178,50,4,4))) {
-            assertEquals(1, result.count());
-        }
-    }
-
-    private static Symbology createStyle(Symbolizer symbolizer) {
-        final Symbology style = new Symbology();
-        final FeatureTypeStyle fts = new FeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-
-        final Rule<Feature> rule = new Rule<>(FeatureTypeStyle.FACTORY);
-        rule.symbolizers().add(symbolizer);
-        fts.rules().add(rule);
-
-        return style;
-    }
-
-}
diff --git a/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageResource.java b/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageResource.java
index f1975d8..a20e14a 100644
--- a/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageResource.java
+++ b/incubator/src/org.apache.sis.storage.coveragejson/main/org/apache/sis/storage/coveragejson/CoverageResource.java
@@ -82,8 +82,8 @@
 import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.grid.SequenceType;
+// Specific to the main branch:
+import org.apache.sis.image.SequenceType;
 
 
 /**
@@ -511,7 +511,10 @@
         final MathTransform gridToCrs = gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER);
         final int dimension = gridGeometry.getDimension();
 
-        final long[] gridLow = extent.getLow().getCoordinateValues();
+        final long[] gridLow = new long[dimension];
+        for (int i=0; i<dimension; i++) {
+            gridLow[i] = extent.getLow(i);
+        }
         final int[] gridSize = new int[dimension];
         final List<Integer> gridToCrsIndex = new ArrayList<>(dimension);
         final double[] scales = new double[dimension];
diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ListingPropertyVisitor.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ListingPropertyVisitor.java
index bde71bc..51f71b3 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ListingPropertyVisitor.java
+++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ListingPropertyVisitor.java
@@ -20,15 +20,13 @@
 import org.apache.sis.filter.privy.FunctionNames;
 import org.apache.sis.filter.privy.Visitor;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.CodeList;
-import org.opengis.filter.BetweenComparisonOperator;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.LikeOperator;
-import org.opengis.filter.LogicalOperator;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
+import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
+import org.apache.sis.pending.geoapi.filter.LogicalOperator;
 
 
 /**
@@ -54,8 +52,7 @@
             visit(filter.getUpperBoundary(), names);
         });
         setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE), (f, names) -> {
-            final LikeOperator<Object> filter = (LikeOperator<Object>) f;
-            visit(filter.getExpressions().get(0), names);
+            visit(f.getExpressions().get(0), names);
         });
         setExpressionHandler(FunctionNames.ValueReference, (e, names) -> {
             final ValueReference<Object,?> expression = (ValueReference<Object,?>) e;
@@ -67,7 +64,7 @@
     }
 
     @Override
-    protected void typeNotFound(final CodeList<?> type, final Filter<Object> filter, final Collection<String> names) {
+    protected void typeNotFound(final Enum<?> type, final Filter<Object> filter, final Collection<String> names) {
         for (final Expression<? super Object, ?> f : filter.getExpressions()) {
             visit(f, names);
         }
diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
index 93c7731..6456807 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
+++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
@@ -53,13 +53,13 @@
 import org.locationtech.jts.geom.MultiPolygon;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
-import org.opengis.util.FactoryException;
-import org.opengis.util.GenericName;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.Metadata;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
+import org.opengis.util.GenericName;
 import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -106,19 +106,18 @@
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.BackingStoreException;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.CodeList;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.LogicalOperator;
+import org.apache.sis.pending.geoapi.filter.LogicalOperatorName;
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
 
 
 /**
@@ -205,7 +204,7 @@
      * {@inheritDoc }
      */
     @Override
-    public FeatureType getType() throws DataStoreException {
+    public DefaultFeatureType getType() throws DataStoreException {
         return featureSetView.getType();
     }
 
@@ -221,7 +220,7 @@
      * {@inheritDoc }
      */
     @Override
-    public Stream<Feature> features(boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(boolean parallel) throws DataStoreException {
         return featureSetView.features(parallel);
     }
 
@@ -237,7 +236,7 @@
      * {@inheritDoc }
      */
     @Override
-    public void updateType(FeatureType featureType) throws DataStoreException {
+    public void updateType(DefaultFeatureType featureType) throws DataStoreException {
         featureSetView.updateType(featureType);
     }
 
@@ -245,7 +244,7 @@
      * {@inheritDoc }
      */
     @Override
-    public void add(Iterator<? extends Feature> iterator) throws DataStoreException {
+    public void add(Iterator<? extends AbstractFeature> iterator) throws DataStoreException {
         featureSetView.add(iterator);
     }
 
@@ -253,7 +252,7 @@
      * {@inheritDoc }
      */
     @Override
-    public void removeIf(Predicate<? super Feature> predicate) throws DataStoreException {
+    public void removeIf(Predicate<? super AbstractFeature> predicate) throws DataStoreException {
         featureSetView.removeIf(predicate);
     }
 
@@ -261,7 +260,7 @@
      * {@inheritDoc }
      */
     @Override
-    public void replaceIf(Predicate<? super Feature> predicate, UnaryOperator<Feature> unaryOperator) throws DataStoreException {
+    public void replaceIf(Predicate<? super AbstractFeature> predicate, UnaryOperator<AbstractFeature> unaryOperator) throws DataStoreException {
         featureSetView.replaceIf(predicate, unaryOperator);
     }
 
@@ -291,7 +290,7 @@
          */
         private String idField;
         private CoordinateReferenceSystem crs;
-        private FeatureType type;
+        private DefaultFeatureType type;
 
         /**
          * @param filter optional shape filter, must be in data CRS
@@ -312,7 +311,7 @@
         }
 
         @Override
-        public synchronized FeatureType getType() throws DataStoreException {
+        public synchronized DefaultFeatureType getType() throws DataStoreException {
             if (type == null) {
                 if (!Files.isRegularFile(shpPath)) {
                     throw new DataStoreException("Shape files do not exist. Update FeatureType first to initialize this empty datastore");
@@ -434,8 +433,8 @@
         }
 
         @Override
-        public Stream<Feature> features(boolean parallel) throws DataStoreException {
-            final FeatureType type = getType();
+        public Stream<AbstractFeature> features(boolean parallel) throws DataStoreException {
+            final DefaultFeatureType type = getType();
             final ShapeReader shpreader;
             final DBFReader dbfreader;
             try {
@@ -460,7 +459,7 @@
                             long offset = (long)header.headerSize + ((long)(shpRecord.recordNumber-1)) * ((long)header.recordSize);
                             dbfreader.moveToOffset(offset);
                             final Object[] dbfRecord = dbfreader.next();
-                            final Feature next = type.newInstance();
+                            final AbstractFeature next = type.newInstance();
                             next.setPropertyValue(GEOMETRY_NAME, shpRecord.geometry);
                             for (int i = 0; i < dbfPropertiesIndex.length; i++) {
                                 next.setPropertyValue(header.fields[dbfPropertiesIndex[i]].fieldName, dbfRecord[i]);
@@ -480,7 +479,7 @@
                         try {
                             final ShapeRecord shpRecord = shpreader.next();
                             if (shpRecord == null) return false;
-                            final Feature next = type.newInstance();
+                            final AbstractFeature next = type.newInstance();
                             next.setPropertyValue(GEOMETRY_NAME, shpRecord.geometry);
                             action.accept(next);
                             return true;
@@ -498,7 +497,7 @@
                         try {
                             final Object[] dbfRecord = dbfreader.next();
                             if (dbfRecord == null) return false;
-                            final Feature next = type.newInstance();
+                            final AbstractFeature next = type.newInstance();
                             for (int i = 0; i < dbfPropertiesIndex.length; i++) {
                                 next.setPropertyValue(header.fields[dbfPropertiesIndex[i]].fieldName, dbfRecord[i]);
                             }
@@ -511,7 +510,7 @@
                 };
             }
 
-            final Stream<Feature> stream = StreamSupport.stream(spliterator, false);
+            final Stream<AbstractFeature> stream = StreamSupport.stream(spliterator, false);
             return stream.onClose(new Runnable() {
                 @Override
                 public void run() {
@@ -533,7 +532,7 @@
             if (query instanceof FeatureQuery) {
                 final FeatureQuery fq = (FeatureQuery) query;
                 FeatureQuery.NamedExpression[] projection = fq.getProjection();
-                Filter<? super Feature> selection = fq.getSelection();
+                Filter<? super AbstractFeature> selection = fq.getSelection();
 
                 if (selection == null && projection == null) {
                     //no optimisation
@@ -541,7 +540,7 @@
                 }
 
                 //force loading
-                final FeatureType type = getType();
+                final DefaultFeatureType type = getType();
 
                 //extract bbox
                 Envelope bbox = null;
@@ -610,10 +609,12 @@
                     needSubProcessing = true;
                     subQuery.setOffset(fq.getOffset());
                 }
+                /* Unsupported on the main branch.
                 if (fq.getSortBy() != null) {
                     needSubProcessing = true;
                     subQuery.setSortBy(fq.getSortBy());
                 }
+                */
                 if (selection != null) {
                     needSubProcessing = true;
                     subQuery.setSelection(selection);
@@ -634,7 +635,7 @@
         }
 
         @Override
-        public synchronized void updateType(FeatureType newType) throws DataStoreException {
+        public synchronized void updateType(DefaultFeatureType newType) throws DataStoreException {
 
             if (!isDefaultView()) throw new DataStoreException("Resource not writable in current filter state");
             if (Files.exists(shpPath)) {
@@ -651,9 +652,9 @@
                 final Charset charset = userDefinedCharSet == null ? StandardCharsets.UTF_8 : userDefinedCharSet;
                 CoordinateReferenceSystem crs = CommonCRS.WGS84.normalizedGeographic();
 
-                for (PropertyType pt : newType.getProperties(true)) {
-                    if (pt instanceof AttributeType) {
-                        final AttributeType at = (AttributeType) pt;
+                for (AbstractIdentifiedType pt : newType.getProperties(true)) {
+                    if (pt instanceof DefaultAttributeType) {
+                        final DefaultAttributeType at = (DefaultAttributeType) pt;
                         final Class valueClass = at.getValueClass();
                         final String attName = at.getName().tip().toString();
 
@@ -674,8 +675,8 @@
                             else throw new DataStoreException("Unsupported geometry type " + valueClass);
 
                             Object cdt = at.characteristics().get(AttributeConvention.CRS);
-                            if (cdt instanceof AttributeType) {
-                                Object defaultValue = ((AttributeType) cdt).getDefaultValue();
+                            if (cdt instanceof DefaultAttributeType) {
+                                Object defaultValue = ((DefaultAttributeType) cdt).getDefaultValue();
                                 if (defaultValue instanceof CoordinateReferenceSystem) {
                                     crs = (CoordinateReferenceSystem) defaultValue;
                                 }
@@ -752,15 +753,15 @@
         }
 
         @Override
-        public void add(Iterator<? extends Feature> features) throws DataStoreException {
+        public void add(Iterator<? extends AbstractFeature> features) throws DataStoreException {
             if (!isDefaultView()) throw new DataStoreException("Resource not writable in current filter state");
             if (!Files.exists(shpPath)) throw new DataStoreException("FeatureType do not exist, use updateType before modifying features.");
 
             final Writer writer = new Writer(charset);
             try {
                 //write existing features
-                try (Stream<Feature> stream = features(false)) {
-                    Iterator<Feature> iterator = stream.iterator();
+                try (Stream<AbstractFeature> stream = features(false)) {
+                    Iterator<AbstractFeature> iterator = stream.iterator();
                     while (iterator.hasNext()) {
                         writer.write(iterator.next());
                     }
@@ -783,15 +784,15 @@
         }
 
         @Override
-        public void removeIf(Predicate<? super Feature> filter) throws DataStoreException {
+        public void removeIf(Predicate<? super AbstractFeature> filter) throws DataStoreException {
             if (!isDefaultView()) throw new DataStoreException("Resource not writable in current filter state");
             if (!Files.exists(shpPath)) throw new DataStoreException("FeatureType do not exist, use updateType before modifying features.");
 
             final Writer writer = new Writer(charset);
             try {
                 //write existing features not matching filter
-                try (Stream<Feature> stream = features(false)) {
-                    Iterator<Feature> iterator = stream.filter(filter.negate()).iterator();
+                try (Stream<AbstractFeature> stream = features(false)) {
+                    Iterator<AbstractFeature> iterator = stream.filter(filter.negate()).iterator();
                     while (iterator.hasNext()) {
                         writer.write(iterator.next());
                     }
@@ -808,17 +809,17 @@
         }
 
         @Override
-        public void replaceIf(Predicate<? super Feature> filter, UnaryOperator<Feature> updater) throws DataStoreException {
+        public void replaceIf(Predicate<? super AbstractFeature> filter, UnaryOperator<AbstractFeature> updater) throws DataStoreException {
             if (!isDefaultView()) throw new DataStoreException("Resource not writable in current filter state");
             if (!Files.exists(shpPath)) throw new DataStoreException("FeatureType do not exist, use updateType before modifying features.");
 
             final Writer writer = new Writer(charset);
             try {
                 //write existing features applying modifications
-                try (Stream<Feature> stream = features(false)) {
-                    Iterator<Feature> iterator = stream.iterator();
+                try (Stream<AbstractFeature> stream = features(false)) {
+                    Iterator<AbstractFeature> iterator = stream.iterator();
                     while (iterator.hasNext()) {
-                        Feature feature = iterator.next();
+                        AbstractFeature feature = iterator.next();
                         if (filter.test(feature)) {
                             feature = updater.apply(feature);
                         }
@@ -1002,7 +1003,7 @@
      */
     private static Entry<Envelope,Filter> extractBbox(Filter<?> filter) {
 
-        final CodeList operatorType = filter.getOperatorType();
+        final Enum operatorType = filter.getOperatorType();
 
         if (operatorType == SpatialOperatorName.BBOX) {
             Envelope env = isDirectBbox(filter);
@@ -1136,16 +1137,16 @@
 
         }
 
-        private void write(Feature feature) throws IOException {
+        private void write(AbstractFeature feature) throws IOException {
             inc++; //number starts at 1
             final ShapeRecord shpRecord = new ShapeRecord();
             final long recordStartPosition = shpWriter.getSteamPosition();
 
             if (defaultGeomName == null) {
                 //search for the geometry name
-                for (PropertyType pt : feature.getType().getProperties(true)) {
-                    if (pt instanceof AttributeType) {
-                        final AttributeType at = (AttributeType) pt;
+                for (AbstractIdentifiedType pt : feature.getType().getProperties(true)) {
+                    if (pt instanceof DefaultAttributeType) {
+                        final DefaultAttributeType at = (DefaultAttributeType) pt;
                         final String attName = at.getName().toString();
                         if (Geometry.class.isAssignableFrom(at.getValueClass())) {
                             defaultGeomName = attName;
diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java
index 56f78b3..39322f7 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java
+++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java
@@ -200,10 +200,10 @@
         final double maxY = channel.readDouble();
         if (filter != null && maxY < filter.y) return false;
         shape.bbox = new GeneralEnvelope(getDimension());
-        shape.bbox.getLowerCorner().setCoordinate(0, minX);
-        shape.bbox.getLowerCorner().setCoordinate(1, minY);
-        shape.bbox.getUpperCorner().setCoordinate(0, maxX);
-        shape.bbox.getUpperCorner().setCoordinate(1, maxY);
+        shape.bbox.getLowerCorner().setOrdinate(0, minX);
+        shape.bbox.getLowerCorner().setOrdinate(1, minY);
+        shape.bbox.getUpperCorner().setOrdinate(0, maxX);
+        shape.bbox.getUpperCorner().setOrdinate(1, maxY);
         return true;
     }
 
diff --git a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
index ec293c9..c706cae 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
+++ b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
@@ -44,12 +44,11 @@
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.FilterFactory;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.filter.Filter;
 
 
 /**
@@ -66,18 +65,18 @@
         try (final ShapefileStore store = new ShapefileStore(Paths.get(url.toURI()))) {
 
             //check feature type
-            final FeatureType type = store.getType();
+            final DefaultFeatureType type = store.getType();
             assertEquals("point", type.getName().toString());
             assertEquals(9, type.getProperties(true).size());
             assertNotNull(type.getProperty("sis:identifier"));
             assertNotNull(type.getProperty("sis:envelope"));
             assertNotNull(type.getProperty("sis:geometry"));
-            final var geomProp    = (AttributeType) type.getProperty("geometry");
-            final var idProp      = (AttributeType) type.getProperty("id");
-            final var textProp    = (AttributeType) type.getProperty("text");
-            final var integerProp = (AttributeType) type.getProperty("integer");
-            final var floatProp   = (AttributeType) type.getProperty("float");
-            final var dateProp    = (AttributeType) type.getProperty("date");
+            final var geomProp    = (DefaultAttributeType) type.getProperty("geometry");
+            final var idProp      = (DefaultAttributeType) type.getProperty("id");
+            final var textProp    = (DefaultAttributeType) type.getProperty("text");
+            final var integerProp = (DefaultAttributeType) type.getProperty("integer");
+            final var floatProp   = (DefaultAttributeType) type.getProperty("float");
+            final var dateProp    = (DefaultAttributeType) type.getProperty("date");
             assertEquals(Point.class, geomProp.getValueClass());
             assertEquals(Long.class, idProp.getValueClass());
             assertEquals(String.class, textProp.getValueClass());
@@ -85,10 +84,10 @@
             assertEquals(Double.class, floatProp.getValueClass());
             assertEquals(LocalDate.class, dateProp.getValueClass());
 
-            try (Stream<Feature> stream = store.features(false)) {
-                Iterator<Feature> iterator = stream.iterator();
+            try (Stream<AbstractFeature> stream = store.features(false)) {
+                Iterator<AbstractFeature> iterator = stream.iterator();
                 assertTrue(iterator.hasNext());
-                Feature feature1 = iterator.next();
+                AbstractFeature feature1 = iterator.next();
                 assertEquals(1L, feature1.getPropertyValue("id"));
                 assertEquals("text1", feature1.getPropertyValue("text"));
                 assertEquals(10L, feature1.getPropertyValue("integer"));
@@ -97,7 +96,7 @@
                 Point pt1 = (Point) feature1.getPropertyValue("geometry");
 
                 assertTrue(iterator.hasNext());
-                Feature feature2 = iterator.next();
+                AbstractFeature feature2 = iterator.next();
                 assertEquals(2L, feature2.getPropertyValue("id"));
                 assertEquals("text2", feature2.getPropertyValue("text"));
                 assertEquals(40L, feature2.getPropertyValue("integer"));
@@ -118,7 +117,7 @@
         final URL url = ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
         try (final ShapefileStore store = new ShapefileStore(Paths.get(url.toURI()))) {
 
-            final FilterFactory<Feature, Object, Object> ff = DefaultFilterFactory.forFeatures();
+            final DefaultFilterFactory<AbstractFeature, Object, Object> ff = DefaultFilterFactory.forFeatures();
 
             final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
             env.setRange(0, 2, 3);
@@ -130,10 +129,10 @@
             //ensure we obtained an optimized version
             assertEquals("org.apache.sis.storage.shapefile.ShapefileStore$AsFeatureSet", featureset.getClass().getName());
 
-            try (Stream<Feature> stream = featureset.features(false)) {
-                Iterator<Feature> iterator = stream.iterator();
+            try (Stream<AbstractFeature> stream = featureset.features(false)) {
+                Iterator<AbstractFeature> iterator = stream.iterator();
                 assertTrue(iterator.hasNext());
-                Feature feature = iterator.next();
+                AbstractFeature feature = iterator.next();
                 assertEquals(2L, feature.getPropertyValue("id"));
                 assertEquals("text2", feature.getPropertyValue("text"));
                 assertEquals(40L, feature.getPropertyValue("integer"));
@@ -159,15 +158,15 @@
             //ensure we obtained an optimized version
             assertEquals("org.apache.sis.storage.shapefile.ShapefileStore$AsFeatureSet", featureset.getClass().getName());
 
-            try (Stream<Feature> stream = featureset.features(false)) {
-                Iterator<Feature> iterator = stream.iterator();
+            try (Stream<AbstractFeature> stream = featureset.features(false)) {
+                Iterator<AbstractFeature> iterator = stream.iterator();
                 assertTrue(iterator.hasNext());
-                Feature feature1 = iterator.next();
+                AbstractFeature feature1 = iterator.next();
                 assertEquals("text1", feature1.getPropertyValue("text"));
                 assertEquals(20.0, feature1.getPropertyValue("float"));
 
                 assertTrue(iterator.hasNext());
-                Feature feature2 = iterator.next();
+                AbstractFeature feature2 = iterator.next();
                 assertEquals("text2", feature2.getPropertyValue("text"));
                 assertEquals(60.0, feature2.getPropertyValue("float"));
 
@@ -202,7 +201,7 @@
             assertEquals(0, componentFiles.length);
 
             {//create type
-                final FeatureType type = createType();
+                final DefaultFeatureType type = createType();
                 store.updateType(type);
             }
 
@@ -217,19 +216,19 @@
             }
 
             {// check created type
-                FeatureType type = store.getType();
+                DefaultFeatureType type = store.getType();
                 assertEquals(name, type.getName().toString());
                 assertEquals(9, type.getProperties(true).size());
                 assertNotNull(type.getProperty("sis:identifier"));
                 assertNotNull(type.getProperty("sis:envelope"));
                 assertNotNull(type.getProperty("sis:geometry"));
-                final var geomProp = (AttributeType) type.getProperty("geometry");
-                final var idProp = (AttributeType) type.getProperty("id");
-                final var textProp = (AttributeType) type.getProperty("text");
-                final var integerProp = (AttributeType) type.getProperty("integer");
-                final var floatProp = (AttributeType) type.getProperty("float");
-                final var dateProp = (AttributeType) type.getProperty("date");
-                final AttributeType crsChar = (AttributeType) geomProp.characteristics().get(AttributeConvention.CRS);
+                final var geomProp = (DefaultAttributeType) type.getProperty("geometry");
+                final var idProp = (DefaultAttributeType) type.getProperty("id");
+                final var textProp = (DefaultAttributeType) type.getProperty("text");
+                final var integerProp = (DefaultAttributeType) type.getProperty("integer");
+                final var floatProp = (DefaultAttributeType) type.getProperty("float");
+                final var dateProp = (DefaultAttributeType) type.getProperty("date");
+                final DefaultAttributeType crsChar = (DefaultAttributeType) geomProp.characteristics().get(AttributeConvention.CRS);
                 assertTrue(Utilities.equalsIgnoreMetadata(CommonCRS.WGS84.geographic(),crsChar.getDefaultValue()));
                 assertEquals(Point.class, geomProp.getValueClass());
                 assertEquals(Integer.class, idProp.getValueClass());
@@ -248,12 +247,12 @@
     public void testAddFeatures(@TempDir final Path folder) throws URISyntaxException, DataStoreException, IOException {
         final Path temp = folder.resolve("test.shp");
         try (final ShapefileStore store = new ShapefileStore(temp)) {
-            FeatureType type = createType();
+            DefaultFeatureType type = createType();
             store.updateType(type);
             type = store.getType();
 
-            Feature feature1 = createFeature1(type);
-            Feature feature2 = createFeature2(type);
+            AbstractFeature feature1 = createFeature1(type);
+            AbstractFeature feature2 = createFeature2(type);
             store.add(List.of(feature1, feature2).iterator());
 
             Object[] result = store.features(false).toArray();
@@ -270,16 +269,16 @@
     public void testRemoveFeatures(@TempDir final Path folder) throws DataStoreException, IOException {
         final Path temp = folder.resolve("test.shp");
         try (final ShapefileStore store = new ShapefileStore(temp)) {
-            FeatureType type = createType();
+            DefaultFeatureType type = createType();
             store.updateType(type);
             type = store.getType();
-            Feature feature1 = createFeature1(type);
-            Feature feature2 = createFeature2(type);
+            AbstractFeature feature1 = createFeature1(type);
+            AbstractFeature feature2 = createFeature2(type);
             store.add(List.of(feature1, feature2).iterator());
 
             //remove first feature
-            final FilterFactory<Feature, Object, Object> ff = DefaultFilterFactory.forFeatures();
-            final BinaryComparisonOperator<Feature> filter = ff.equal(ff.property("id"), ff.literal(1));
+            final DefaultFilterFactory<AbstractFeature, Object, Object> ff = DefaultFilterFactory.forFeatures();
+            final Filter<AbstractFeature> filter = ff.equal(ff.property("id"), ff.literal(1));
             store.removeIf(filter);
 
             Object[] result = store.features(false).toArray();
@@ -295,19 +294,19 @@
     public void testReplaceFeatures(@TempDir final Path folder) throws DataStoreException, IOException {
         final Path temp = folder.resolve("test.shp");
         try (final ShapefileStore store = new ShapefileStore(temp)) {
-            FeatureType type = createType();
+            DefaultFeatureType type = createType();
             store.updateType(type);
             type = store.getType();
-            Feature feature1 = createFeature1(type);
-            Feature feature2 = createFeature2(type);
+            AbstractFeature feature1 = createFeature1(type);
+            AbstractFeature feature2 = createFeature2(type);
             store.add(List.of(feature1, feature2).iterator());
 
             //remove first feature
-            final FilterFactory<Feature, Object, Object> ff = DefaultFilterFactory.forFeatures();
-            final BinaryComparisonOperator<Feature> filter = ff.equal(ff.property("id"), ff.literal(1));
-            store.replaceIf(filter, new UnaryOperator<Feature>() {
+            final DefaultFilterFactory<AbstractFeature, Object, Object> ff = DefaultFilterFactory.forFeatures();
+            final Filter<AbstractFeature> filter = ff.equal(ff.property("id"), ff.literal(1));
+            store.replaceIf(filter, new UnaryOperator<AbstractFeature>() {
                 @Override
-                public Feature apply(Feature feature) {
+                public AbstractFeature apply(AbstractFeature feature) {
                     feature.setPropertyValue("id",45);
                     return feature;
                 }
@@ -315,13 +314,13 @@
 
             Object[] result = store.features(false).toArray();
             assertEquals(2, result.length);
-            Feature f1 = (Feature) result[0];
+            AbstractFeature f1 = (AbstractFeature) result[0];
             assertEquals(45, f1.getPropertyValue("id"));
             assertEquals(feature2, result[1]);
         }
     }
 
-    private static FeatureType createType() {
+    private static DefaultFeatureType createType() {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.setName("test");
         ftb.addAttribute(Integer.class).setName("id");
@@ -333,8 +332,8 @@
         return ftb.build();
     }
 
-    private static Feature createFeature1(FeatureType type) {
-        Feature feature = type.newInstance();
+    private static AbstractFeature createFeature1(DefaultFeatureType type) {
+        AbstractFeature feature = type.newInstance();
         feature.setPropertyValue("geometry", GF.createPoint(new Coordinate(10,20)));
         feature.setPropertyValue("id", 1);
         feature.setPropertyValue("text", "some text 1");
@@ -344,8 +343,8 @@
         return feature;
     }
 
-    private static Feature createFeature2(FeatureType type) {
-        Feature feature = type.newInstance();
+    private static AbstractFeature createFeature2(DefaultFeatureType type) {
+        AbstractFeature feature = type.newInstance();
         feature.setPropertyValue("geometry", GF.createPoint(new Coordinate(30,40)));
         feature.setPropertyValue("id", 2);
         feature.setPropertyValue("text", "some text 2");
diff --git a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
index ef5acb8..f76cb70 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
+++ b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
@@ -30,11 +30,10 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureQuery;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.BinarySpatialOperator;
-import org.opengis.filter.FilterFactory;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Filter;
 
 
 /**
@@ -52,7 +51,7 @@
             System.out.println(store.getType());
 
             //print all features
-            try (Stream<Feature> features = store.features(false)) {
+            try (Stream<AbstractFeature> features = store.features(false)) {
                 features.forEach(System.out::println);
             }
 
@@ -60,15 +59,15 @@
             GeneralEnvelope bbox = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
             bbox.setRange(0, -10, 30);
             bbox.setRange(1, 45, 55);
-            FilterFactory<Feature, Object, Object> ff = DefaultFilterFactory.forFeatures();
-            BinarySpatialOperator<Feature> bboxFilter = ff.bbox(ff.property("geometry"), bbox);
+            DefaultFilterFactory<AbstractFeature, Object, Object> ff = DefaultFilterFactory.forFeatures();
+            Filter<AbstractFeature> bboxFilter = ff.bbox(ff.property("geometry"), bbox);
 
             FeatureQuery query = new FeatureQuery();
             query.setProjection("att1", "att4", "att5");
             query.setSelection(bboxFilter);
 
             //print selected features
-            try (Stream<Feature> features = store.subset(query).features(false)) {
+            try (Stream<AbstractFeature> features = store.subset(query).features(false)) {
                 features.forEach(System.out::println);
             }
 
@@ -88,14 +87,14 @@
             ftb.addAttribute(Integer.class).setName("id");
             ftb.addAttribute(String.class).setName("text");
             ftb.addAttribute(Point.class).setName("geometry").setCRS(CommonCRS.WGS84.geographic());
-            FeatureType type = ftb.build();
+            DefaultFeatureType type = ftb.build();
 
             store.updateType(type);
             type = store.getType();
 
             //create features
             GeometryFactory gf = new GeometryFactory();
-            Feature feature = type.newInstance();
+            AbstractFeature feature = type.newInstance();
             feature.setPropertyValue("geometry", gf.createPoint(new Coordinate(10,20)));
             feature.setPropertyValue("id", 1);
             feature.setPropertyValue("text", "some text 1");
diff --git a/netbeans-project/build.xml b/netbeans-project/build.xml
index 47443fb..44a7f93 100644
--- a/netbeans-project/build.xml
+++ b/netbeans-project/build.xml
@@ -33,10 +33,6 @@
     <target name="-pre-compile" depends="dir.check" unless="dir.exists">
         <ivy:settings file="ivy-settings.xml"/>
         <ivy:retrieve pattern="build/dependencies/[artifact].[ext]" symlink="true" sync="true"/>
-        <symlink link="${basedir}/build/dependencies/geoapi-pending.jar"
-                 resource="../../../geoapi/snapshot/geoapi-pending/target/geoapi-pending-3.1-SNAPSHOT.jar"/>
-        <symlink link="${basedir}/build/dependencies/geoapi-conformance.jar"
-                 resource="../../../geoapi/snapshot/geoapi-conformance/target/geoapi-conformance-3.1-SNAPSHOT.jar"/>
     </target>
     <!--
         Called after compilation. Copies the "*.utf" resources files created by Gradle.
diff --git a/netbeans-project/ivy.xml b/netbeans-project/ivy.xml
index 0e79664..9532a5f 100644
--- a/netbeans-project/ivy.xml
+++ b/netbeans-project/ivy.xml
@@ -11,6 +11,8 @@
 <ivy-module version="2.0">
     <info organisation="org.apache" module="sis"/>
     <dependencies defaultconf="default">
+        <dependency org="org.opengis"            name="geoapi"                  rev="3.0.2"/>
+        <dependency org="org.opengis"            name="geoapi-conformance"      rev="3.0.2"/>
         <dependency org="javax.measure"          name="unit-api"                rev="2.1.3"/>
         <dependency org="org.glassfish.jaxb"     name="jaxb-runtime"            rev="4.0.4"/>
         <dependency org="org.eclipse"            name="yasson"                  rev="3.0.3"/>
diff --git a/netbeans-project/nbproject/build-impl.xml b/netbeans-project/nbproject/build-impl.xml
index c68a938..51639b4 100644
--- a/netbeans-project/nbproject/build-impl.xml
+++ b/netbeans-project/nbproject/build-impl.xml
@@ -19,7 +19,7 @@
   - cleanup
 
         -->
-<project xmlns:if="ant:if" xmlns:unless="ant:unless" basedir=".." default="default" name="Apache_SIS_on_GeoAPI_3.1-impl">
+<project xmlns:if="ant:if" xmlns:unless="ant:unless" basedir=".." default="default" name="Apache_SIS_on_GeoAPI_3.0-impl">
     <fail message="Please build using Ant 1.9.7 or higher.">
         <condition>
             <not>
@@ -778,7 +778,7 @@
                     </fileset>
                 </union>
                 <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
-                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="Apache_SIS_on_GeoAPI_3.1" testname="TestNG tests" workingDir="${work.dir}">
+                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="Apache_SIS_on_GeoAPI_3.0" testname="TestNG tests" workingDir="${work.dir}">
                     <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
                     <propertyset>
                         <propertyref prefix="test-sys-prop."/>
@@ -875,7 +875,7 @@
                 <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
                     <isset property="test.method"/>
                 </condition>
-                <condition else="-suitename Apache_SIS_on_GeoAPI_3.1 -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
+                <condition else="-suitename Apache_SIS_on_GeoAPI_3.0 -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
                     <matches pattern=".*\.xml" string="@{testClass}"/>
                 </condition>
                 <delete dir="${build.test.results.dir}" quiet="true"/>
@@ -1118,7 +1118,7 @@
         <delete file="${built-jar.properties}" quiet="true"/>
     </target>
     <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
-        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.0 was already built"/>
     </target>
     <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
         <mkdir dir="${build.dir}"/>
@@ -1909,7 +1909,7 @@
         <delete file="${built-clean.properties}" quiet="true"/>
     </target>
     <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
-        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.0 was already built"/>
     </target>
     <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
         <mkdir dir="${build.dir}"/>
diff --git a/netbeans-project/nbproject/genfiles.properties b/netbeans-project/nbproject/genfiles.properties
index b5d9e58..f2d42fe 100644
--- a/netbeans-project/nbproject/genfiles.properties
+++ b/netbeans-project/nbproject/genfiles.properties
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=d82237a1
 build.xml.script.CRC32=9a509f0a
 build.xml.stylesheet.CRC32=32069288@1.22
-nbproject/build-impl.xml.data.CRC32=e365e8d0
-nbproject/build-impl.xml.script.CRC32=3c96dd2f
+nbproject/build-impl.xml.data.CRC32=0e40efdb
+nbproject/build-impl.xml.script.CRC32=c509535a
 nbproject/build-impl.xml.stylesheet.CRC32=d1ebcf0f@1.22
diff --git a/netbeans-project/nbproject/project.properties b/netbeans-project/nbproject/project.properties
index afb84f5..7cd2d06 100644
--- a/netbeans-project/nbproject/project.properties
+++ b/netbeans-project/nbproject/project.properties
@@ -111,8 +111,7 @@
                --add-exports org.apache.sis.metadata/org.apache.sis.xml.bind.gcx=org.apache.sis.referencing \
                --add-exports org.apache.sis.metadata/org.apache.sis.metadata.privy=org.apache.sis.referencing.gazetteer \
                --add-exports org.apache.sis.feature/org.apache.sis.feature.privy=org.apache.sis.storage.sql \
-               --add-exports org.apache.sis.feature/org.apache.sis.geometry.wrapper.jts=org.apache.sis.storage.sql,org.apache.sis.portrayal.map \
-               --add-exports org.apache.sis.storage/org.apache.sis.storage.base=org.apache.sis.portrayal.map \
+               --add-exports org.apache.sis.feature/org.apache.sis.geometry.wrapper.jts=org.apache.sis.storage.sql \
                --add-exports org.apache.sis.storage/org.apache.sis.storage.test=${modules.list}
 
 #
diff --git a/netbeans-project/nbproject/project.xml b/netbeans-project/nbproject/project.xml
index 57af576..2396a86 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -15,7 +15,7 @@
     <type>org.netbeans.modules.java.j2semodule</type>
     <configuration>
         <data xmlns="http://www.netbeans.org/ns/j2se-modular-project/1">
-            <name>Apache SIS on GeoAPI 3.1</name>
+            <name>Apache SIS on GeoAPI 3.0</name>
             <source-roots>
                 <root id="src.dir" pathref="src.dir.path"/>
                 <root id="optional.main.dir" pathref="optional.main.dir.path"/>
diff --git a/optional/src/org.apache.sis.gui/bundle/conf/imports.jsh b/optional/src/org.apache.sis.gui/bundle/conf/imports.jsh
index fd9764f..c3ad133 100644
--- a/optional/src/org.apache.sis.gui/bundle/conf/imports.jsh
+++ b/optional/src/org.apache.sis.gui/bundle/conf/imports.jsh
@@ -203,6 +203,7 @@
 import org.opengis.referencing.crs.DerivedCRS;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.EngineeringCRS;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.SingleCRS;
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
index 114e3aa..b1151b1 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -141,7 +141,7 @@
      * when data are ready.
      *
      * <p>Current implementation is restricted to {@link GridCoverage} instances, but a future
-     * implementation may generalize to {@link org.opengis.coverage.Coverage} instances.</p>
+     * implementation may generalize to {@code org.opengis.coverage.Coverage} instances.</p>
      *
      * @see #getCoverage()
      * @see #setCoverage(GridCoverage)
@@ -1220,7 +1220,7 @@
                              + "POI: %, 16.4f  %, 16.4f%n"
                              + "Min: %, 16.4f  %, 16.4f%n",
                              aoi.getMaxX(),        aoi.getMaxY(),
-                             poi.getCoordinate(0), poi.getCoordinate(1),
+                             poi.getOrdinate(0),   poi.getOrdinate(1),
                              aoi.getMinX(),        aoi.getMinY()))
                      .appendHorizontalSeparator();
             }
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
index 8e6f1b9..a6cc599 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -69,7 +69,7 @@
  *
  * <h2>Limitations</h2>
  * Current implementation is restricted to {@link GridCoverage} instances, but a future
- * implementation may generalize to {@link org.opengis.coverage.Coverage} instances.
+ * implementation may generalize to {@code org.opengis.coverage.Coverage} instances.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.5
@@ -173,7 +173,7 @@
      * when data are ready.
      *
      * <p>Current implementation is restricted to {@link GridCoverage} instances, but a future
-     * implementation may generalize to {@link org.opengis.coverage.Coverage} instances.</p>
+     * implementation may generalize to {@code org.opengis.coverage.Coverage} instances.</p>
      *
      * <h4>Relationship with view properties</h4>
      * This property is "weakly bound" to {@link CoverageCanvas#coverageProperty}:
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandableList.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandableList.java
index a2476f2..396a4c5 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandableList.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandableList.java
@@ -34,21 +34,20 @@
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 import org.apache.sis.gui.internal.Styles;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
  * Wraps a {@link FeatureList} with the capability to expand the multi-valued properties of
- * a selected {@link Feature}. The expansion appears as additional rows below the feature.
+ * a selected {@code Feature}. The expansion appears as additional rows below the feature.
  * This view is used only if the feature type contains at least one property type with a
  * maximum number of occurrence greater than 1.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class ExpandableList extends TransformationList<Feature,Feature>
-        implements Callback<TableColumn<Feature,Feature>, TableCell<Feature,Feature>>,
+final class ExpandableList extends TransformationList<AbstractFeature,AbstractFeature>
+        implements Callback<TableColumn<AbstractFeature,AbstractFeature>, TableCell<AbstractFeature,AbstractFeature>>,
                    EventHandler<MouseEvent>
 {
     /**
@@ -106,7 +105,7 @@
 
     /**
      * Specifies the names of properties that may be multi-valued. This method needs to be invoked
-     * only if the {@link FeatureType} changed. This method shall not be invoked if there is any
+     * only if the {@code FeatureType} changed. This method shall not be invoked if there is any
      * {@link #expansion} rows. Normally this list will be empty at invocation time.
      *
      * @param  columnNames  names of properties that may contain multi-values.
@@ -133,8 +132,8 @@
      *
      * @return the removed rows, or {@code null} if none.
      */
-    private List<Feature> shrink() {
-        final List<Feature> removed = (expansion == null) ? null
+    private List<AbstractFeature> shrink() {
+        final List<AbstractFeature> removed = (expansion == null) ? null
                                     : UnmodifiableArrayList.wrap(expansion, 1, expansion.length);
         expansion       = null;
         indexOfExpanded = Integer.MAX_VALUE;
@@ -149,7 +148,7 @@
     @Override
     public void clear() {
         final int removeAfter = indexOfExpanded;
-        final List<Feature> removed = shrink();
+        final List<AbstractFeature> removed = shrink();
         if (removed != null) {
             beginChange();
             nextUpdate(removeAfter);
@@ -173,7 +172,7 @@
         final IconCell cell = (IconCell) event.getSource();
         final int index = getSourceIndex(cell.getIndex());      // Must be invoked before `shrink()`.
         final int removeAfter = indexOfExpanded;
-        final List<Feature> removed = shrink();
+        final List<AbstractFeature> removed = shrink();
 //      index = getViewIndex(index);                // Not needed for current single-selection model.
         /*
          * If a new row is selected, extract now all properties. We need at least the number
@@ -215,7 +214,7 @@
     /**
      * Returns {@code true} if the given feature contains more than one row.
      */
-    private boolean isExpandable(final Feature feature) {
+    private boolean isExpandable(final AbstractFeature feature) {
         if (feature != null) {
             for (final String name : nameToIndex.keySet()) {
                 final Object value = feature.getPropertyValue(name);
@@ -247,7 +246,7 @@
      * except if the given index is for an expanded row.
      */
     @Override
-    public Feature get(int index) {
+    public AbstractFeature get(int index) {
         final int i = index - indexOfExpanded;
         if (i >= 0) {
             final int n = expansion.length;     // A NullPointerException here would be an ExpandableList bug.
@@ -260,7 +259,7 @@
     /**
      * Given an index in this expanded list, returns the index of corresponding element in the feature list.
      * All indices from {@link #indexOfExpanded} inclusive to <code>{@linkplain #indexOfExpanded} +
-     * {@linkplain #expansion}.length</code> exclusive map to the same {@link Feature} instance.
+     * {@linkplain #expansion}.length</code> exclusive map to the same {@link AbstractFeature} instance.
      *
      * @param  index  index in this expandable list.
      * @return index of the corresponding element in {@link FeatureList}.
@@ -297,8 +296,8 @@
      * {@link FeatureList} and converts source indices to indices of this expandable list.
      */
     @Override
-    protected void sourceChanged(final ListChangeListener.Change<? extends Feature> c) {
-        fireChange(new ListChangeListener.Change<Feature>(this) {
+    protected void sourceChanged(final ListChangeListener.Change<? extends AbstractFeature> c) {
+        fireChange(new ListChangeListener.Change<AbstractFeature>(this) {
             @Override public void     reset()               {c.reset();}
             @Override public boolean  next()                {return c.next();}
             @Override public boolean  wasAdded()            {return c.wasAdded();}
@@ -325,8 +324,8 @@
 
             @Override
             @SuppressWarnings("unchecked")
-            public List<Feature> getRemoved() {
-                return (List<Feature>) expandRemoved(c.getFrom(), c.getRemoved());
+            public List<AbstractFeature> getRemoved() {
+                return (List<AbstractFeature>) expandRemoved(c.getFrom(), c.getRemoved());
             }
         });
     }
@@ -347,13 +346,13 @@
      * @param  removed     the removed elements provided by the {@link FeatureList}.
      * @return the removed elements as seen by this {@code ExpandableList}.
      */
-    private List<? extends Feature> expandRemoved(final int sourceFrom, final List<? extends Feature> removed) {
+    private List<? extends AbstractFeature> expandRemoved(final int sourceFrom, final List<? extends AbstractFeature> removed) {
         if (!overlapExpanded(sourceFrom, removed.size())) {
             return removed;
         }
         final int s = indexOfExpanded;
         final int n = expansion.length;         // A NullPointerException here would be an ExpandableList bug.
-        final Feature[] features = removed.toArray(new Feature[removed.size() + (n - 1)]);
+        final AbstractFeature[] features = removed.toArray(new AbstractFeature[removed.size() + (n - 1)]);
         System.arraycopy(features,  s+1, features, s + n, features.length - (s+1));
         System.arraycopy(expansion, 0,   features, s,  n);
         return Arrays.asList(features);
@@ -367,7 +366,7 @@
      * @param  column  the column where the cell will be shown.
      */
     @Override
-    public TableCell<Feature,Feature> call(final TableColumn<Feature,Feature> column) {
+    public TableCell<AbstractFeature,AbstractFeature> call(final TableColumn<AbstractFeature,AbstractFeature> column) {
         return new IconCell();
     }
 
@@ -375,7 +374,7 @@
      * The cell which represents whether a row is expandable or expanded.
      * If visible, this is the first column in the table.
      */
-    private final class IconCell extends TableCell<Feature,Feature>  {
+    private final class IconCell extends TableCell<AbstractFeature,AbstractFeature>  {
         /**
          * Whether this cell is listening to mouse click events.
          */
@@ -394,7 +393,7 @@
          * The call will have a listener only if it has an icon.
          */
         @Override
-        protected void updateItem(final Feature value, final boolean empty) {
+        protected void updateItem(final AbstractFeature value, final boolean empty) {
             super.updateItem(value, empty);
             Background b = null;
             String  text = null;
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java
index a67208b..9bb03e0 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java
@@ -21,10 +21,9 @@
 import java.util.Collection;
 import java.util.List;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Property;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -36,7 +35,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class ExpandedFeature implements Feature {
+final class ExpandedFeature extends AbstractFeature {
     /**
      * The array for properties having no value.
      * This is the fill value for {@link #values} array.
@@ -48,12 +47,12 @@
      * This feature may contain a mix of single-valued and multi-valued properties.
      * Values of multi-valued properties are copied in the {@link #values} array.
      */
-    private final Feature source;
+    private final AbstractFeature source;
 
     /**
      * Mapping from property names to index in the {@link #values} array.
      * <strong>Do not modify,</strong> because all {@link ExpandedFeature} instances created
-     * after a call to {@link FeatureTable#setFeatureType(FeatureType)} share the same map.
+     * after a call to {@link FeatureTable#setFeatureType(DefaultFeatureType)} share the same map.
      */
     private final Map<String,Integer> nameToIndex;
 
@@ -75,9 +74,10 @@
     /**
      * Creates a new feature wrapping the given source.
      */
-    private ExpandedFeature(final Feature source, final Map<String,Integer> nameToIndex,
+    private ExpandedFeature(final AbstractFeature source, final Map<String,Integer> nameToIndex,
                             final Object[][] values, final int index)
     {
+        super(source.getType());
         this.source      = source;
         this.nameToIndex = nameToIndex;
         this.values      = values;
@@ -94,7 +94,7 @@
      * @param  nameToIndex  mapping from property names to index in the {@code values} array.
      * @return pseudo-features for property elements at all indices, or {@code null} if none.
      */
-    static ExpandedFeature[] create(final Feature source, final Map<String,Integer> nameToIndex) {
+    static ExpandedFeature[] create(final AbstractFeature source, final Map<String,Integer> nameToIndex) {
         if (source != null) {
             final Object[][] values = new Object[nameToIndex.size()][];
             Arrays.fill(values, EMPTY);
@@ -130,7 +130,7 @@
      * Returns the source feature type verbatim.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return source.getType();
     }
 
@@ -139,7 +139,7 @@
      * This method is not used by {@link FeatureTable} so we just delegate to the source.
      */
     @Override
-    public Property getProperty(final String name) {
+    public Object getProperty(final String name) {
         return source.getProperty(name);
     }
 
@@ -148,7 +148,7 @@
      * This method is not used by {@link FeatureTable} so we just forward to the source.
      */
     @Override
-    public void setProperty(final Property property) {
+    public void setProperty(final Object property) {
         source.setProperty(property);
     }
 
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureList.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureList.java
index f5dc70e..e1957dd 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureList.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureList.java
@@ -28,8 +28,8 @@
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -53,7 +53,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class FeatureList extends ObservableListBase<Feature> {
+final class FeatureList extends ObservableListBase<AbstractFeature> {
     /**
      * Number of empty rows to show in the bottom of the table when we don't know how many rows still
      * need to be read. Those rows do not stay empty for long since they will become valid as soon as
@@ -69,12 +69,12 @@
     /**
      * The {@link #elements} value when this list is empty.
      */
-    private static final Feature[] EMPTY = new Feature[0];
+    private static final AbstractFeature[] EMPTY = new AbstractFeature[0];
 
     /**
      * The elements in this list, never {@code null}.
      */
-    private Feature[] elements;
+    private AbstractFeature[] elements;
 
     /**
      * Number of valid elements in {@link #elements}.
@@ -114,7 +114,7 @@
     /**
      * Returns the currently valid elements.
      */
-    private List<Feature> validElements() {
+    private List<AbstractFeature> validElements() {
         return UnmodifiableArrayList.wrap(elements, 0, validCount);
     }
 
@@ -126,7 +126,7 @@
      */
     @Override
     public void clear() {
-        final List<Feature> removed = validElements();
+        final List<AbstractFeature> removed = validElements();
         elements = EMPTY;
         estimatedSize = 0;
         validCount    = 0;
@@ -164,7 +164,7 @@
     /**
      * Invoked by {@link FeatureLoader} for replacing the current content by a new list of features.
      * The list size after this method invocation will be {@code expectedSize}, not {@code count}.
-     * The missing elements will be implicitly null until {@link #addFeatures(Feature[], int, boolean)}
+     * The missing elements will be implicitly null until {@link #addFeatures(AbstractFeature[], int, boolean)}
      * is invoked. If the expected size is unknown (i.e. its value is {@link Long#MAX_VALUE}),
      * then an arbitrary size is computed from {@code count}.
      *
@@ -176,15 +176,15 @@
      */
     @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
     final void setFeatures(long remainingCount, int characteristics,
-                           final Feature[] features, final int count, final boolean hasMore)
+                           final AbstractFeature[] features, final int count, final boolean hasMore)
     {
         assert Platform.isFxApplicationThread();
         int newValidCount = 0;
         for (int i=0; i<count; i++) {
-            final Feature f = features[i];
+            final AbstractFeature f = features[i];
             if (f != null) features[newValidCount++] = f;       // Exclude null elements.
         }
-        final List<Feature> removed = validElements();          // Want this call outside {beginChange … endChange}.
+        final List<AbstractFeature> removed = validElements();  // Want this call outside {beginChange … endChange}.
         if (remainingCount == Long.MAX_VALUE) {
             remainingCount  = count + NUM_PENDING_ROWS;         // Arbitrary additional amount.
             characteristics = 0;
@@ -212,7 +212,7 @@
      * @param  hasMore   if the stream may have more features.
      * @throws ArithmeticException if the number of elements exceeds this list capacity.
      */
-    final void addFeatures(final Feature[] features, final int count, final boolean hasMore) {
+    final void addFeatures(final AbstractFeature[] features, final int count, final boolean hasMore) {
         assert Platform.isFxApplicationThread();
         if (count > 0) {
             int newValidCount = Math.addExact(validCount, count);
@@ -222,7 +222,7 @@
             }
             newValidCount = validCount;         // Recompute `validCount + count` but excluding null elements.
             for (int i=0; i<count; i++) {
-                final Feature f = features[i];
+                final AbstractFeature f = features[i];
                 if (f != null) elements[newValidCount++] = f;
             }
             /*
@@ -230,7 +230,7 @@
              * Only if the new size exceeds the previously expected size, we send a notification about addition.
              */
             final int replaceTo = Math.min(newValidCount, estimatedSize);
-            final List<Feature> removed = Collections.nCopies(replaceTo - validCount, null);
+            final List<AbstractFeature> removed = Collections.nCopies(replaceTo - validCount, null);
             if (newValidCount >= estimatedSize) {
                 estimatedSize = newValidCount;              // Update before we send events.
                 if (hasMore) {
@@ -274,7 +274,7 @@
         if (next == null) {
             final int n = estimatedSize - validCount;
             if (n != 0) {
-                final List<Feature> removed = Collections.nCopies(n, null);
+                final List<AbstractFeature> removed = Collections.nCopies(n, null);
                 estimatedSize = validCount;
                 beginChange();
                 nextRemove(validCount, removed);
@@ -307,7 +307,7 @@
      * but has not yet been loaded, returns {@code null}.
      */
     @Override
-    public Feature get(final int index) {
+    public AbstractFeature get(final int index) {
         assert Platform.isFxApplicationThread();
         if (index < validCount) {
             return elements[index];
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureLoader.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureLoader.java
index 2152b13..c566b57 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureLoader.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureLoader.java
@@ -29,9 +29,9 @@
 import org.apache.sis.gui.internal.Resources;
 import org.apache.sis.system.Configuration;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -41,7 +41,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class FeatureLoader extends Task<Boolean> implements Consumer<Feature> {
+final class FeatureLoader extends Task<Boolean> implements Consumer<AbstractFeature> {
     /**
      * Maximum number of features to load in a background task.
      * If there is more features to load, we will use many tasks.
@@ -68,18 +68,18 @@
      * The stream to close after we finished to iterate over features.
      * This stream should not be used for any other purpose.
      */
-    private Stream<Feature> toClose;
+    private Stream<AbstractFeature> toClose;
 
     /**
      * If the reading process is not finished, the iterator for reading more feature instances.
      */
-    private Spliterator<Feature> iterator;
+    private Spliterator<AbstractFeature> iterator;
 
     /**
      * The features loaded by this task. This array is created in a background thread,
      * then added to {@link #table} in the JavaFX thread.
      */
-    private Feature[] loaded;
+    private AbstractFeature[] loaded;
 
     /**
      * Number of features loaded by this task.
@@ -111,7 +111,7 @@
      * defined for {@link #call()} internal purpose only.
      */
     @Override
-    public void accept(final Feature feature) {
+    public void accept(final AbstractFeature feature) {
         loaded[count++] = feature;
     }
 
@@ -142,7 +142,7 @@
          */
         final long remaining = iterator.estimateSize();
         final int stopAt = (remaining > PAGE_SIZE) ? PAGE_SIZE : 1 + (int) remaining;
-        loaded = new Feature[stopAt];
+        loaded = new AbstractFeature[stopAt];
         try {
             while (iterator.tryAdvance(this)) {
                 if (count >= stopAt) {
@@ -174,7 +174,7 @@
      */
     private void close() throws DataStoreException {
         iterator = null;
-        final Stream<Feature> c = toClose;
+        final Stream<AbstractFeature> c = toClose;
         if (c != null) try {
             toClose = null;                             // Clear now in case an exception happens below.
             c.close();
@@ -305,7 +305,7 @@
 
     /**
      * Invoked when the feature type may have been found. If the given type is non-null,
-     * then this method delegates to {@link FeatureTable#setFeatureType(FeatureType)} in
+     * then this method delegates to {@link FeatureTable#setFeatureType(DefaultFeatureType)} in
      * the JavaFX thread. This will erase the previous content and prepare new columns.
      *
      * <p>This method is invoked, directly or indirectly, only from the {@link #call()}
@@ -315,7 +315,7 @@
      * @param  type  the feature type, or {@code null}.
      * @return whether the given type was non-null.
      */
-    private boolean setType(final FeatureType type) {
+    private boolean setType(final DefaultFeatureType type) {
         if (type != null) {
             Platform.runLater(() -> table.setFeatureType(type));
             return true;
@@ -333,7 +333,7 @@
     private void setMissingType(final boolean isTypeKnown) throws DataStoreException {
         if (!isTypeKnown) {
             for (int i=0; i<count; i++) {
-                final Feature f = loaded[i];
+                final AbstractFeature f = loaded[i];
                 if (f != null && setType(f.getType())) {
                     return;
                 }
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java
index 29314d5..1eabb02 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java
@@ -46,12 +46,11 @@
 import org.apache.sis.gui.internal.ExceptionReporter;
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -71,7 +70,7 @@
  *   <li>The list returned by {@link #getItems()} should be considered read-only.</li>
  * </ul>
  *
- * @todo This class does not yet handle {@link FeatureAssociationRole}. We could handle them with
+ * @todo This class does not yet handle {@code FeatureAssociationRole}. We could handle them with
  *       {@link javafx.scene.control.SplitPane} with the main feature table in the upper part and
  *       the feature table of selected cell in the bottom part. Bottom part could put tables in a
  *       {@link javafx.scene.control.Accordion} since there is possibly different tables to show
@@ -84,7 +83,7 @@
  * @since   1.1
  */
 @DefaultProperty("features")
-public class FeatureTable extends TableView<Feature> {
+public class FeatureTable extends TableView<AbstractFeature> {
     /**
      * The locale to use for texts. This is usually {@link Locale#getDefault()}.
      * This value is given to {@link InternationalString#toString(Locale)} calls.
@@ -95,9 +94,9 @@
      * The type of features, or {@code null} if not yet determined.
      * This type determines the columns that will be shown.
      *
-     * @see #setFeatureType(FeatureType)
+     * @see #setFeatureType(DefaultFeatureType)
      */
-    private FeatureType featureType;
+    private DefaultFeatureType featureType;
 
     /**
      * The data shown in this table. Note that setting this property to a non-null value
@@ -111,7 +110,7 @@
 
     /**
      * Whether the {@link #getItems()} list may be shared by another {@link FeatureTable} instance.
-     * In such case, {@link #setFeatureType(FeatureType)} should create a new list instead of invoking
+     * In such case, {@link #setFeatureType(DefaultFeatureType)} should create a new list instead of invoking
      * {@link FeatureList#clear()} on the existing list.
      */
     private boolean isSharingList;
@@ -177,7 +176,7 @@
      * All methods on the returned list shall be invoked from JavaFX thread.
      */
     final FeatureList getFeatureList() {
-        final ObservableList<Feature> items = getItems();
+        final ObservableList<AbstractFeature> items = getItems();
         if (items instanceof FeatureList) {
             return (FeatureList) items;
         } else {
@@ -190,7 +189,7 @@
      * This method wraps the {@link FeatureList} into an {@link ExpandableList} if needed.
      */
     private ExpandableList getExpandableList() {
-        final ObservableList<Feature> items = getItems();
+        final ObservableList<AbstractFeature> items = getItems();
         if (items instanceof ExpandableList) {
             return (ExpandableList) items;
         } else {
@@ -254,7 +253,7 @@
      * This method clears all rows and replaces all columns by new columns
      * determined from the given type.
      */
-    final void setFeatureType(final FeatureType type) {
+    final void setFeatureType(final DefaultFeatureType type) {
         setPlaceholder(null);
         getItems().clear();
         final boolean update = (type != null) && !type.equals(featureType);
@@ -272,10 +271,10 @@
      * Creates table columns for the current {@link #featureType}.
      */
     private void createColumns() {
-        final Collection<? extends PropertyType> properties = featureType.getProperties(true);
-        final List<TableColumn<Feature,?>> columns = new ArrayList<>(properties.size());
+        final Collection<? extends AbstractIdentifiedType> properties = featureType.getProperties(true);
+        final List<TableColumn<AbstractFeature,?>> columns = new ArrayList<>(properties.size());
         final List<String> multiValued = new ArrayList<>(columns.size());
-        for (final PropertyType pt : properties) {
+        for (final AbstractIdentifiedType pt : properties) {
             /*
              * Get localized text to show in column header. Also remember
              * the plain property name; it will be needed for ValueGetter.
@@ -295,8 +294,8 @@
              *       See comment in class javadoc.
              */
             boolean isMultiValued = false;
-            if (pt instanceof AttributeType<?>) {
-                isMultiValued = ((AttributeType<?>) pt).getMaximumOccurs() > 1;
+            if (pt instanceof DefaultAttributeType<?>) {
+                isMultiValued = ((DefaultAttributeType<?>) pt).getMaximumOccurs() > 1;
             }
             if (isMultiValued) {
                 multiValued.add(name);
@@ -306,7 +305,7 @@
              * gives the whole collection. Fetching a particular element in that collection will
              * be ElementCell's work.
              */
-            final TableColumn<Feature,Object> column = new TableColumn<>(title);
+            final TableColumn<AbstractFeature,Object> column = new TableColumn<>(title);
             column.setCellValueFactory(new ValueGetter(name));
             column.setCellFactory(isMultiValued ? ElementCell::new : ValueCell::new);
             if (AttributeConvention.contains(qualifiedName)) {
@@ -323,7 +322,7 @@
         } else {
             final ExpandableList list = getExpandableList();
             list.setMultivaluedColumns(multiValued);
-            final TableColumn<Feature,Feature> column = new TableColumn<>("▤");
+            final TableColumn<AbstractFeature,AbstractFeature> column = new TableColumn<>("▤");
             column.setCellValueFactory(IdentityValueFactory.instance());
             column.setCellFactory(list);
             column.setReorderable(false);
@@ -341,12 +340,12 @@
 
 
     /**
-     * Given a {@link Feature}, returns the value of the property having the name specified at construction time.
+     * Given a {@code Feature}, returns the value of the property having the name specified at construction time.
      * Note that if the property is multi-valued, then this getter returns the whole collection since we have no
      * easy way to know the current row number. Fetching a particular element in that collection will be done by
      * {@link ExpandedFeature}.
      */
-    private static final class ValueGetter implements Callback<TableColumn.CellDataFeatures<Feature,Object>, ObservableValue<Object>> {
+    private static final class ValueGetter implements Callback<TableColumn.CellDataFeatures<AbstractFeature,Object>, ObservableValue<Object>> {
         /**
          * The name of the feature property for which to fetch values.
          */
@@ -366,9 +365,9 @@
          * This method is invoked by JavaFX when a cell needs to be rendered with a new value.
          */
         @Override
-        public ObservableValue<Object> call(final TableColumn.CellDataFeatures<Feature, Object> cell) {
+        public ObservableValue<Object> call(final TableColumn.CellDataFeatures<AbstractFeature, Object> cell) {
             Object value = null;
-            final Feature feature = cell.getValue();
+            final AbstractFeature feature = cell.getValue();
             if (feature != null) {
                 value = feature.getPropertyValue(name);
             }
@@ -380,13 +379,13 @@
      * A cell displaying a value in {@link FeatureTable}. This base class expects single values.
      * If the property values are collections, then {@link ElementCell} should be used instead.
      */
-    private static class ValueCell extends TableCell<Feature,Object> {
+    private static class ValueCell extends TableCell<AbstractFeature,Object> {
         /**
          * Creates a new cell for feature property value.
          *
          * @param  column  the column where the cell will be shown.
          */
-        ValueCell(final TableColumn<Feature,Object> column) {
+        ValueCell(final TableColumn<AbstractFeature,Object> column) {
             // Column not used at this time, but we need it in method signature.
         }
 
@@ -428,7 +427,7 @@
          *
          * @param  column  the column where the cell will be shown.
          */
-        ElementCell(final TableColumn<Feature,Object> column) {
+        ElementCell(final TableColumn<AbstractFeature,Object> column) {
             super(column);
         }
 
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
index 92b0135..fce63c7 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
@@ -91,8 +91,8 @@
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
 import static org.apache.sis.util.privy.Constants.NANOS_PER_MILLISECOND;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
index 7b261b7..eff59d0 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
@@ -99,8 +99,8 @@
 import org.apache.sis.referencing.gazetteer.ReferencingByIdentifiers;
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main branch:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -702,7 +702,6 @@
         position.setText(null);
         if (geometry != null) {
             final int dimension = geometry.getDimension();
-            ArgumentChecks.ensureDimensionMatches("sliceExtent", dimension, sliceExtent);
             ArgumentChecks.ensureBetween("xdim", 0,      dimension-1, xdim);
             ArgumentChecks.ensureBetween("ydim", xdim+1, dimension-1, ydim);
             xDimension = xdim;
@@ -998,7 +997,7 @@
             final double y = lastY;
             lastX = lastY = Double.NaN;
             if (!Double.isNaN(x) && !Double.isNaN(y)) {
-                if (current == null || current.getCoordinate(0) != x || current.getCoordinate(1) != y) {
+                if (current == null || current.getOrdinate(0) != x || current.getOrdinate(1) != y) {
                     setLocalCoordinates(x, y);
                 }
             }
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java
index 7eb697a..97872dc 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java
@@ -45,8 +45,8 @@
 import org.apache.sis.util.privy.Numerics;
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
+// Specific to the main branch:
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
@@ -366,7 +366,7 @@
          * that position should be given as a non-null {@code slice} argument.
          */
         Position(final DirectPosition position, final GridExtent slice) {
-            coordinates = position.getCoordinates();
+            coordinates = position.getCoordinate();
             crs         = position.getCoordinateReferenceSystem();
             newSlice    = slice;
         }
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java
index 08a6632..e5617a1 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java
@@ -59,6 +59,9 @@
 import org.apache.sis.util.resources.Vocabulary;
 import static org.apache.sis.util.privy.CollectionsExt.nonNull;
 
+// Specific to the main branch:
+import org.opengis.metadata.identification.DataIdentification;
+
 
 /**
  * The pane where to show the values of {@link Identification} objects.
@@ -203,8 +206,8 @@
                         if (isCancelled()) break;
                         if (metadata != null) {
                             for (final Identification id : nonNull(metadata.getIdentificationInfo())) {
-                                if (id != null) {
-                                    for (final Extent extent : id.getExtents()) {
+                                if (id instanceof DataIdentification) {
+                                    for (final Extent extent : ((DataIdentification) id).getExtents()) {
                                         final GeographicBoundingBox b = Extents.getGeographicBoundingBox(extent);
                                         if (b != null) boxes.add(b);
                                     }
@@ -310,24 +313,19 @@
         /*
          * Topic category.
          */
-        addLine(Vocabulary.Keys.TopicCategory, owner.string(nonNull(info.getTopicCategories())));
-        /*
-         * Type of resource: vector, grid, table, tin, video, etc. It gives a slight overview
-         * of the next section, "Spatial representation". For that reason we put it close to
-         * that next section, i.e. last in this section but just before the map.
-         */
-        addLine(Vocabulary.Keys.TypeOfResource, owner.string(nonNull(info.getSpatialRepresentationTypes())));
+        final DataIdentification dataInfo = (info instanceof DataIdentification) ? (DataIdentification) info : null;
+        if (dataInfo != null) {
+            addLine(Vocabulary.Keys.TopicCategory, owner.string(nonNull(dataInfo.getTopicCategories())));
+            addLine(Vocabulary.Keys.TypeOfResource, owner.string(nonNull(dataInfo.getSpatialRepresentationTypes())));
+        }
         /*
          * Resource format. Current implementation shows only the first format found.
          */
         for (final Format format : nonNull(info.getResourceFormats())) {
-            final Citation c = format.getFormatSpecificationCitation();
-            if (c != null) {
-                text = owner.string(c.getTitle());
-                if (text != null) {
-                    addLine(Vocabulary.Keys.Format, text);
-                    break;
-                }
+            text = owner.string(format.getSpecification());
+            if (text != null) {
+                addLine(Vocabulary.Keys.Format, text);
+                break;
             }
         }
         /*
@@ -342,7 +340,7 @@
                 final Date cd = c.getDate();
                 if (cd != null) {
                     final DateType type = c.getDateType();
-                    if (type == DateType.PUBLICATION || type == DateType.RELEASED) {
+                    if (type == DateType.PUBLICATION || type == DateType.valueOf("RELEASED")) {
                         label = Vocabulary.Keys.PublicationDate;
                         date  = cd;
                         break;                      // Take the first publication or release date.
@@ -366,7 +364,7 @@
         Identifier identifier = null;
         Range<Date> timeRange = null;
         Range<Double> heights = null;
-        for (final Extent extent : nonNull(info.getExtents())) {
+        for (final Extent extent : nonNull(dataInfo != null ? dataInfo.getExtents() : null)) {
             if (extent != null) {
                 if (text == null) {
                     text = owner.string(extent.getDescription());
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
index b621d92..a8bad7d 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
@@ -31,8 +31,8 @@
 import javafx.scene.image.Image;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.VBox;
-import org.opengis.metadata.Metadata;
 import org.opengis.util.InternationalString;
+import org.opengis.metadata.Metadata;
 import org.apache.sis.gui.Widget;
 import org.apache.sis.gui.internal.BackgroundThreads;
 import org.apache.sis.gui.internal.ExceptionReporter;
@@ -45,8 +45,8 @@
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.iso.Types;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.ControlledVocabulary;
+// Specific to the main branch:
+import org.opengis.util.CodeList;
 
 
 /**
@@ -350,9 +350,9 @@
     /**
      * Returns all code lists in a comma-separated list.
      */
-    final String string(final Collection<? extends ControlledVocabulary> codes) {
+    final String string(final Collection<? extends CodeList<?>> codes) {
         final StringJoiner buffer = new StringJoiner(", ");
-        for (final ControlledVocabulary c : codes) {
+        for (final CodeList<?> c : codes) {
             final String text = string(Types.getCodeTitle(c));
             if (text != null) buffer.add(text);
         }
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
index 7830144..45565a8 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -392,7 +392,7 @@
             for (final Code code : snapshot) {
                 String text;
                 try {
-                    var i18n = factory.getDescriptionText(CoordinateReferenceSystem.class, code.code).orElse(null);
+                    var i18n = factory.getDescriptionText(code.code);
                     text = Strings.trimOrNull(Types.toString(i18n, locale));
                     if (text == null) {
                         text = Vocabulary.forLocale(locale).getString(Vocabulary.Keys.Unnamed);
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
index 9a11b70..e5f2dac 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
@@ -71,9 +71,6 @@
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-
 
 /**
  * A list of Coordinate Reference Systems (CRS) from which the user can select.
@@ -351,9 +348,8 @@
      */
     private void setDomainOfValidity(final CoordinateReferenceSystem crs, final Locale locale) {
         String extent = null;
-        for (ObjectDomain c : crs.getDomains()) {
-            extent = Extents.getDescription(c.getDomainOfValidity(), locale);
-            if (extent != null) break;
+        if (crs != null) {
+            extent = Extents.getDescription(crs.getDomainOfValidity(), locale);
         }
         String tip   = extent;
         Color  color = Styles.NORMAL_TEXT;
@@ -370,7 +366,6 @@
     /**
      * Returns the text to show of right of the "type" label.
      */
-    @SuppressWarnings("deprecation")
     private static String typeOf(CoordinateReferenceSystem crs, final Locale locale) {
         while (crs instanceof CompoundCRS) {
             crs = ((CompoundCRS) crs).getComponents().get(0);
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java
index 6336eb1..16981e0 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java
@@ -37,6 +37,10 @@
 import org.apache.sis.util.logging.Logging;
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
 
+// Specific to the main branch:
+import java.util.List;
+import java.util.ArrayList;
+
 
 /**
  * Provider of map projections centered on a point of interest.
@@ -49,6 +53,12 @@
 @SuppressWarnings("serial")         // We do not guarantee serialization compatibility.
 public abstract class PositionableProjection extends CodeList<PositionableProjection> {
     /**
+     * List of all enumerations of this type.
+     * Must be declared before any enum declaration.
+     */
+    private static final List<PositionableProjection> VALUES = new ArrayList<>(1);
+
+    /**
      * Provides <cite>Orthographic</cite> projection centered on a point of interest.
      */
     public static final PositionableProjection ORTHOGRAPHIC =
@@ -137,7 +147,7 @@
      * @param nameKey  the projection name as a {@link Resources} keys.
      */
     PositionableProjection(final String name, final short nameKey) {
-        super(name);
+        super(name, VALUES);
         this.nameKey = nameKey;
     }
 
@@ -147,7 +157,9 @@
      * @return the list of codes declared in the current JVM.
      */
     public static PositionableProjection[] values() {
-        return values(PositionableProjection.class);
+        synchronized (VALUES) {
+            return VALUES.toArray(PositionableProjection[]::new);
+        }
     }
 
     /**
@@ -163,6 +175,16 @@
     }
 
     /**
+     * Disables the search for UML identifiers because we do not export this package to GeoAPI.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public String identifier() {
+        return null;
+    }
+
+    /**
      * Returns a name for this enumeration which can be used in a user interface.
      *
      * @return a human-readable name for the projection created by this enumeration.
@@ -198,8 +220,8 @@
             center = CRS.findOperation(inherit, normalizedCRS, null).getMathTransform().transform(center, null);
         }
         return createProjectedCRS(normalizedCRS,
-                Latitude .clamp    (center.getCoordinate(0)),
-                Longitude.normalize(center.getCoordinate(1)));
+                Latitude .clamp    (center.getOrdinate(0)),
+                Longitude.normalize(center.getOrdinate(1)));
     }
 
     /**
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java
index ecf7cea..f11cc31 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/Utils.java
@@ -32,9 +32,6 @@
 import org.apache.sis.referencing.CRS;
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.referencing.ObjectDomain;
-
 
 /**
  * Utility methods shared by classes in this package only.
@@ -86,8 +83,8 @@
     static boolean intersects(final ImmutableEnvelope areaOfInterest, final ReferenceSystem crs) {
         boolean conservative = true;
         if (areaOfInterest != null) {
-            for (final ObjectDomain domain : crs.getDomains()) {
-                final GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(domain.getDomainOfValidity());
+            if (crs != null) {
+                final GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(crs.getDomainOfValidity());
                 if (bbox != null) {
                     if (areaOfInterest.intersects(new ImmutableEnvelope(bbox))) {
                         return true;
diff --git a/parent/pom.xml b/parent/pom.xml
index 000fb61..211b661 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -38,7 +38,7 @@
        ============================================================== -->
   <groupId>org.apache.sis</groupId>
   <artifactId>parent</artifactId>
-  <version>1.x-SNAPSHOT</version>
+  <version>1.5-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>Apache SIS</name>
diff --git a/settings.gradle.kts b/settings.gradle.kts
index d951c0e..f312fd2 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,14 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-rootProject.name  = "Apache SIS on GeoAPI 3.1"
-val geoapiVersion = "3.1-SNAPSHOT"
+rootProject.name  = "Apache SIS on GeoAPI 3.0"
+val geoapiVersion = "3.0.2"
 
 /*
  * The sub-projects to include in the build.
  * They are directory names relative to this file.
  */
-include("geoapi")
 include("parent")
 include("endorsed")
 include("incubator")
@@ -40,7 +39,6 @@
      */
     repositories {
         mavenCentral()
-        mavenLocal()        // For GeoAPI SNAPSHOT only, which are built locally.
         maven {
             name = "UCAR"
             url = uri("https://artifacts.unidata.ucar.edu/repository/unidata-releases")
@@ -59,7 +57,7 @@
      */
     versionCatalogs {
         create("libs") {
-            library("geoapi",        "org.opengis",            "geoapi-pending")      .version {strictly(geoapiVersion)}
+            library("geoapi",        "org.opengis",            "geoapi")              .version {strictly(geoapiVersion)}
             library("units",         "javax.measure",          "unit-api")            .version {strictly("[2.1, 3.0[");  prefer("2.1.3")}
             library("jaxb.api",      "jakarta.xml.bind",       "jakarta.xml.bind-api").version {strictly("[4.0, 5.0[");  prefer("4.0.1")}
             library("jaxb.impl",     "org.glassfish.jaxb",     "jaxb-runtime")        .version {strictly("[4.0, 5.0[");  prefer("4.0.4")}